LPCXpressoとLPC800-MAXでマイコンを学ぶ(その7:Systickタイマーを使う)

前々回はMRT(Multi-rate Timer)を使いましたが、今回はSystickタイマーのサンプルを例に動かしてみます。SystickタイマーはCortex-M0+内にあるタイマーで、1チャンネルしかありません。通常このタイマーはOSのタイマーに使われるという記述がどっかにありました。

Systickサンプルプログラム

Blinkyのサンプルをインポートした時と同様に、今度はSystickサンプルをインポートします。メインプログラムはsysticktest.cとなっています。まずはこれを動作させると、赤色のLEDが点滅するように動きます。早速全体を見てみましょう。

今までソースを見ていれば、大体想像は付きますね。LEDの点滅のところはGPIOInit関数でGPIOの初期化を行い、7pinを出力に切り替えて、whileループの中で出力をON/OFFしています。Systickについては、Systick_Config関数で初期化を行い、delaySysTick関数でSystickタイマーを使用しています。

(systicktest.c)

#define SYSTICK_DELAY       (SystemCoreClock/100)

:

int main (void) 
{
  SystemCoreClockUpdate();

  /* Called for system library in core_cmx.h(x=0 or 3). */
  SysTick_Config( SYSTICK_DELAY );
  
  GPIOInit();
  
  /* Set port 0_7 to output */
  GPIOSetDir( 0, 7, 1 );

  while (1)  /* Loop forever */
  {
    delaySysTick(10);
    GPIOSetBitValue( 0, 7, 0 );
    delaySysTick(10);
    GPIOSetBitValue( 0, 7, 1 );
  }
}

Systickタイマー

Systickタイマーの初期化

Systickタイマーの初期化はSystick_Config関数で行っています。引数はSYSTICK_DELAY = SystemCoreClock/100と前々回出てきたように、10msのタイマーを作成していると予想されます(というかそうなっています)Systick_Configの関数を見てみます。

(core_cm0plus.h)

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  return (1);      /* Reload value impossible */

  SysTick->LOAD  = ticks - 1;                                  /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;      /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

最初のマスクを一旦飛ばして、レジスタへの代入を見てみます。微妙に名前が違いますがここに出てくる対応は以下です。 ユーザーズマニュアルp180

  • Systick->LOAD : SYST_RVR
  • Systick->VAL : SYST_CVR
  • Systick->CTRL : SYST_CSR

f:id:tomo_watanabe:20131014234129p:plain

SYST_RVRレジスタにタイマーの設定値をセットします(あれ?なんで-1してんだろ?)SYST_CVRレジスタに値を書き込むことでSystickタイマのクリアが行われます。

f:id:tomo_watanabe:20131014235116p:plain

SYST_CSRにはCLKSOURCE, TICKINT, ENABLEに1を書き込むことで、クロックソースをシステムクロック、割り込み有効、タイマーを有効としています。

f:id:tomo_watanabe:20131014235452p:plain

NVIC_SetPriority関数は、このSystickの割り込みの優先度を設定しているようですが、ここを深堀りするとCortex-M0+の内容まで追うことになるので置いておきます。この辺のコードはARMが提供しているので、普通はいじる必要性がないものと思われます(もちろんカリカリチューニングするなら必要かも)

割り込みハンドラ

GPIOInit関数とGPIOの操作については、やってることはだいたいわかると思うので割愛します。Systickの初期設定ができたので、今度はタイマーの割り込み処理を見ると、TimeTickをカウントアップしてるだけでした。これはMRTと同じですね。違うのは割り込み判断のレジスタ操作をしていない(1チャンネルしかないので必要ない)ことです。これで、Systickタイマーがこのサンプルの場合、10msで割り込みが入りTimeTickがカウントアップされることがわかりました。

/* SysTick interrupt happens every 10 ms */
void SysTick_Handler(void)
{
  TimeTick++;
}

ウェイト関数

残る関数はdelaySysTick関数です。文字通りdelay(ウェイト)を行う関数です。処理の前後はSystickのタイマーの有効と無効を行っています。

void delaySysTick(uint32_t tick)
{
  uint32_t timetick;

  /* Clear SysTick Counter */
  SysTick->VAL = 0;
  /* Enable the SysTick Counter */
  SysTick->CTRL |= (0x1<<0);

  timetick = TimeTick;
  while ((TimeTick - timetick) < tick);
  
  /* Disable SysTick Counter */
  SysTick->CTRL &= ~(0x1<<0);
  /* Clear SysTick Counter */
  SysTick->VAL = 0;
  return;
}

ウェイトの部分は、timetickとTimeTickの差分だけwhileを回しています。10msの単位でTimeTickがカウントアップされるので設定された時間で抜けるという処理です(・・・これだとCPUブロックするけど、いいのかな本当に(^^; まぁSleepじゃなくてWaitだからヨシとしましょう)

weakシンボル

これでひと通りの動作がわかりました。もう一つweakシンボルという概念を知っておかないと、ちゃんとハンドラの設計ができません。weakシンボルはweakシンボルで解説されています。C++でのオーバーロードに近い機能で、weakと宣言された関数は、デフォルト動作を記述でき、もし同名の関数があった場合はそちらが優先されてリンクされるようです。このサンプルの場合「void SysTick_Handler(void)」がそれにあたります。デフォルトがどこになるかと言うと

(cr_startup_lpc8xx.c)

#define WEAK __attribute__ ((weak))
:
WEAK void SysTick_Handler(void);
:
__attribute__ ((section(".after_vectors")))
void SysTick_Handler(void)
{
    while(1)
    {
    }
}

このように宣言され、デフォルトでは永久ループに入るようになっています。試しにサンプルの方のSystick_Handler関数をコメントアウトしてビルドしてみてください。ビルドが通ります(動かすと永久ループに入ってデッドロックします)

今回は以上で、次はUARTをやります。やっと実践的な感じになってきました。しかし、どのサンプルも微妙な実装だなぁ・・・(;´Д`)