LPCXpressoとLPC800-MAXでマイコンを学ぶ(その6:タイマーを使う)
今回はBlinkyのサンプルに戻って、残っているタイマーの動作を確認したいと思います。
LPC800シリーズのタイマー
LPC800シリーズにはタイマーが2種類あります。
- Cortex-M0+内にあるSystick timer, 24bit 1チャンネル
- LPCシリーズとしてのMRT(Multi-rate Timer), 31bit 4チャンネル
BlinkyではMRTを使ってLEDを点滅させています。サンプルの書き方としては「?」なところもあるのですが、とりあえず見ていきます。
全体の動作
LEDを点滅させている部分は以下の箇所です。
extern uint32_t mrt_counter; : init_mrt(0x8000); while (1) /* Loop forever */ { /* I/O configuration and LED setting pending. */ if ( (mrt_counter > 0) && (mrt_counter <= 200) ) { GPIOSetBitValue( 0, 7, 0 ); } if ( (mrt_counter > 200) && (mrt_counter <= 400) ) { GPIOSetBitValue( 0, 7, 1 ); } if ( (mrt_counter > 400) && (mrt_counter <= 600) ) { GPIOSetBitValue( 0, 16, 0 ); } if ( (mrt_counter > 600) && (mrt_counter <= 800) ) { GPIOSetBitValue( 0, 16, 1 ); } if ( (mrt_counter > 800) && (mrt_counter <= 1000) ) { GPIOSetBitValue( 0, 17, 0 ); } if ( (mrt_counter > 1000) && (mrt_counter <= 1200) ) { GPIOSetBitValue( 0, 17, 1 ); } else if ( mrt_counter > 1200 ) { mrt_counter = 0; } }
init_mrt関数でMRTを初期化して、mrt_counterの値をチェックして点滅間隔を調整していることがわかります。この時点でmrt_counterが当たり前のようにextern変数ってどうなのよ?って感じはしますが気にしないことにします(^^;
MRT(Multi-rate Timer)の初期化
init_mrt関数を見ていきましょう。
(lpc8xx_mrt.c) void init_mrt(uint32_t TimerInterval) { /* Enable clock to MRT and reset the MRT peripheral */ LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<10); LPC_SYSCON->PRESETCTRL &= ~(0x1<<7); LPC_SYSCON->PRESETCTRL |= (0x1<<7); mrt_counter = 0; LPC_MRT->Channel[0].INTVAL = TimerInterval; LPC_MRT->Channel[0].INTVAL |= 0x1UL<<31; LPC_MRT->Channel[0].CTRL = MRT_REPEATED_MODE|MRT_INT_ENA; /* Enable the MRT Interrupt */ NVIC_EnableIRQ(MRT_IRQn); return; }
まず最初にSYSAHBCLKCTRLレジスタの10bit目に1を書込み、MRTブロックへのクロック供給を行っています。
LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<10);
次の2行でPeripheral reset control registerでMRTブロックのリセットを行っています。ユーザーズマニュアルp28にあります。
0を書き込んでから、1を書き込んでブロックにリセットを掛けています。
LPC_SYSCON->PRESETCTRL &= ~(0x1<<7); LPC_SYSCON->PRESETCTRL |= (0x1<<7);
グローバル変数mrt_counterを初期化して、タイマー値を設定しています。ここでMRTは4チャンネルあるはずなのですが、なぜか0チャンネル固定になっています。MRTはユーザーズマニュアルp162から
なお、マニュアルにはちゃんと4チャンネル分の記述があります。Time interval registerにタイマーの設定値を書込み、31bit目にタイマーを即時開始するように、LOADビットを書き込んでいます。ユーザーズマニュアルp163
mrt_counter = 0; LPC_MRT->Channel[0].INTVAL = TimerInterval; LPC_MRT->Channel[0].INTVAL |= 0x1UL<<31;
次にControl registerにリピートモードに設定し、割り込みを有効にしています。
LPC_MRT->Channel[0].CTRL = MRT_REPEATED_MODE|MRT_INT_ENA;
最後にMRTの割り込みを有効にして、タイマー割り込みが入るようにしています。
/* Enable the MRT Interrupt */ NVIC_EnableIRQ(MRT_IRQn);
このNVIC_EnableIRQ関数は何をやっているのでしょう。IRT_IRQnを調べてみると
typedef enum IRQn { /****** Cortex-M0 Processor Exceptions Numbers ***************************************************/ Reset_IRQn = -15, /*!< 1 Reset Vector, invoked on Power up and warm reset*/ NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */ HardFault_IRQn = -13, /*!< 3 Cortex-M0 Hard Fault Interrupt */ SVCall_IRQn = -5, /*!< 11 Cortex-M0 SV Call Interrupt */ PendSV_IRQn = -2, /*!< 14 Cortex-M0 Pend SV Interrupt */ SysTick_IRQn = -1, /*!< 15 Cortex-M0 System Tick Interrupt */ /****** LPC8xx Specific Interrupt Numbers ********************************************************/ SPI0_IRQn = 0, /*!< SPI0 */ SPI1_IRQn = 1, /*!< SPI1 */ Reserved0_IRQn = 2, /*!< Reserved Interrupt */ UART0_IRQn = 3, /*!< USART0 */ UART1_IRQn = 4, /*!< USART1 */ UART2_IRQn = 5, /*!< USART2 */ Reserved1_IRQn = 6, /*!< Reserved Interrupt */ Reserved2_IRQn = 7, /*!< Reserved Interrupt */ I2C_IRQn = 8, /*!< I2C */ SCT_IRQn = 9, /*!< SCT */ MRT_IRQn = 10, /*!< MRT */
割り込み番号として、MRTは10番に割り当てられていることがわかります。さてNVIC_EnableIRQの中は、
__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn) { NVIC->ISER[0] = (1 << ((uint32_t)(IRQn) & 0x1F)); }
引数として割り込み番号をNVICのISER[0]に設定をしています。NVICレジスタはユーザーズマニュアルp14、ISERはp15にあります。
つまり、NVICのInterrupt Set Enable registerに、MRTの割り込み許可を行っています。NVICとは「Nested Vector Interrupt Controller」で要は「割り込みコントローラ」です。ここではそれ以上はいいでしょう。
これで、タイマーを設定し、タイマー値がカウントダウンして0になった時に、MRTの割り込みが動作することになります。
割り込みハンドラ
次に割り込みが起こった時の動作を確認します。割り込みハンドラの実装は
(lpc8xx_mrt.c) void MRT_IRQHandler(void) { if ( LPC_MRT->Channel[0].STAT & MRT_STAT_IRQ_FLAG ) { LPC_MRT->Channel[0].STAT = MRT_STAT_IRQ_FLAG; /* clear interrupt flag */ mrt_counter++; } return; }
タイマー値がカウントダウンされ0になると、この割り込みハンドラが呼ばれます。全体としてはグローバル変数のmrt_counterをインクリメントしているだけです。さて詳細に見て行きましょう。
まずは、MRTの0チャンネルのStatus registerに割り込みフラグが立っているかチェックしています。
if ( LPC_MRT->Channel[0].STAT & MRT_STAT_IRQ_FLAG )
割り込みフラグが立っていたら、割り込みフラグをクリアします。INTFLAGに1を書き込むことでクリアになります。クリア後にカウンタを+1して終わりです。
LPC_MRT->Channel[0].STAT = MRT_STAT_IRQ_FLAG; /* clear interrupt flag */ mrt_counter++;
LEDの点滅動作
MRTの動作がわかりました。init_mrt関数で設定した値がカウンタとして働き、カウントダウンされ、0になるとmrt_counterが+1されていることになります。したがって、サンプルプログラムの動作は0x8000(32768) × 200のタイミングで点滅をするようになっていて、mrt_counterが1200を超えると0にリセットされ、繰り返すことになります。
注意:mrt_counterがグローバルで初期化がinit_mrt内で行われるのに、メインプログラムで0にクリアするとか、サンプルとして良くない例です。こういうプログラムは書かないようにしましょう(^^;
タイミングを変えてみる
それではタイマーのタイミングを変更してみましょう。こんな感じです。
// init_mrt(0x8000); init_mrt(SystemCoreClock / 100);
mrt_counterが200で点滅することになるので、これだと2秒おきに点滅することになります。??よくわかりませんね。解説します。タイマーは結局、クロック周波数に同期して動いています。さてSystemCoreClock = 12MHzでした。これは1秒間の振動数です。つまり120000を設定すると、1秒のタイマーになります。ということは100で割っているので10msのタイマーを設定していることになります。点滅のカウントは200ですので、10ms × 200 = 2s ということになります。実際のタイマーはこのように10msや1msといった単位で作成しますので、このような設定の仕方になります。
次回は、MRTではなくSystickを使ったタイマーと、今回説明できなかったハンドラの補足をしようと思います。