割込みが使えない時に気にすること
今年こそは記事を続けるぞ、と毎年思うのに、1つか2つ書いたらついついそれで満足してしまい、今回も着実に日が空いてしまっているな。いつもすみません、誰にだ。
前回の記事に出てきた「Cプログラムでいきなりマイコン制御」を読みながら、まずはマイコンのGPIOの使い方を覚え、GPIOはただの接点入出力だと思っていたのだけど、設定すればいろんなことに使えるんだ、ということを知る。オンボードのLEDはすぐにLチカできるようになり、適当に短絡して入力できるようにもなった。今取り組んでいる開発は、電圧値のアナログ入力を行って、それをモーターを制御するためのPWMに変えて出力する、ジャイロのデータも使って4つのPWMを微妙に制御する、みたいなことをやろうとしている。僕個人的には、小学生の頃好きだったFM音源のドライバをまた作りたいな。YAMAHAさまのIC(2608と2610、どちらも取扱超難らしい)とDACはもうたくさんあるので早くつなぎたい。
ちなみにボードは、例えば秋月電子であれば2019年2月現在2500円くらいで手に入る。開発環境はEclipseベースで、たくさんのサンプルとともにインストールされるので、たぶんすぐに使えるようになるだろう。でもちょっとだけハマったところがあるのでまとめておこうと思う。
自分がちょっと苦労したのは、開発環境上で、まっさらなプロジェクトを作る方法。既述の本にプロジェクトウィザードを使った流れで細かく書いてあって、その通りに作れば基本的には動くよ。でも、ADCとか、タイマーを使おうとして、いざ割込みを使おうとなったら一向に動かなくて悩んだ。ADCはポーリングで順番に各chを見に行くと動くけど、バーストモードを使おうと思うとダメ。USBは基本割込でみたいで、まったく使えない。デバッガで起動しても、ブート直後にダンマリとなり、強制的にBREAKかけると、IntDefaultHandlerにいらっしゃることが多い。SysTickだけを使うような定期的なLチカ点滅とかは問題無かったのだけど、他のペリフェラルを使おうとすると途端にダメになる感じ。なのに、サンプルのコードは問題無く動く。main関数の内容は説明に必要なものだけ書いたものが下記。
int main(void) { SystemCoreClockUpdate(); SysTick_Config(SystemCoreClock / 10000); Chip_GPIO_Init(LPC_GPIO_PORT); Chip_GPIO_SetPinDIROutput(LPC_GPIO_PORT, 0, 7); USBInit(); while (1) { Chip_GPIO_WritePortBit(LPC_GPIO_PORT, 0, 7, true); delay(1); Chip_GPIO_WritePortBit(LPC_GPIO_PORT, 0, 7, false); delay(1000); /** ここにUSBの処理いろいろ */ } }
ここで、8行目のUSBInit()を呼ぶとこの症状が出るが、呼ばないと出ない。もちろん呼ばないとUSBの処理いろいろは動かない。症状が出る状態だと、14行目のdeley()あたりで止まってしまう。delay()はSysTickを使ってウェイトをしている関数で、どうやらハンドラが全然飛ばなくなり、カウンタが進まなくなる。ちなみに、割込みテーブルみたいなものは、プロジェクトを作成した時にウィザードが自動的に作成し、cr_startup_lp13xx.cというファイルになる。ブレイクしたときも、このファイルのIntDefaultHandlerの中でwhileしている。いろいろ調べて行くと、ADCやUSBのサンプルコードのプロジェクトは動くのに、今回まっさらから作ったプロジェクトは動かない、ということに気がついた。「あれ、もしかして、割込みテーブルって既述違う?」
サンプルのプロジェクトにあるcr_startup_lp13xx.cに書かれている割込みテーブルって、チップごとにdefineでいろいろ定義がかかれている。でも、まっさらから作ったプロジェクトの割込みテーブルには、そういった既述が少なく、SysTick_Handler以降の並びも大きく違った。ちょっと引用長いかもしれないが、自分で忘れないように、の意味もこめて。
void (* const g_pfnVectors[])(void) = { &_vStackTop, // The initial stack pointer ResetISR, // The reset handler NMI_Handler, // The NMI handler HardFault_Handler, // The hard fault handler MemManage_Handler, // The MPU fault handler BusFault_Handler, // The bus fault handler UsageFault_Handler, // The usage fault handler __valid_user_code_checksum, // LPC MCU Checksum 0, // Reserved 0, // Reserved 0, // Reserved SVC_Handler, // SVCall handler DebugMon_Handler, // Debug monitor handler 0, // Reserved PendSV_Handler, // The PendSV handler SysTick_Handler, // The SysTick handler /** ここから */ WAKEUP_IRQHandler, // PIO0_0 Wakeup WAKEUP_IRQHandler, // PIO0_1 Wakeup : WAKEUP_IRQHandler, // PIO3_2 Wakeup WAKEUP_IRQHandler, // PIO3_3 Wakeup /** ここまでのIRQHandlerが凄く多い */ I2C_IRQHandler, // I2C0 TIMER16_0_IRQHandler, // CT16B0 (16-bit Timer 0) TIMER16_1_IRQHandler, // CT16B1 (16-bit Timer 1) TIMER32_0_IRQHandler, // CT32B0 (32-bit Timer 0) TIMER32_1_IRQHandler, // CT32B1 (32-bit Timer 1) (以下省略)
void (* const g_pfnVectors[])(void) = { &_vStackTop, // initial stack pointer ResetISR, // The reset handler NMI_Handler, // The NMI handler HardFault_Handler, // The hard fault handler MemManage_Handler, // The MPU fault handler BusFault_Handler, // The bus fault handler UsageFault_Handler, // The usage fault handler __valid_user_code_checksum, // LPC MCU Checksum 0, // Reserved 0, // Reserved 0, // Reserved SVC_Handler, // SVCall handler DebugMon_Handler, // Debug monitor handler 0, // Reserved PendSV_Handler, // The PendSV handler SysTick_Handler, // The SysTick handler /** ここから */ PIN_INT0_IRQHandler, // All GPIO pin can be routed ... PIN_INT1_IRQHandler, // ... to PIN_INTx PIN_INT2_IRQHandler, PIN_INT3_IRQHandler, PIN_INT4_IRQHandler, PIN_INT5_IRQHandler, PIN_INT6_IRQHandler, PIN_INT7_IRQHandler, GINT0_IRQHandler, GINT1_IRQHandler, // PIO0 (0:7) 0, 0, /** ここまでが圧倒的に少ないし、以降defineは省略したが有効なものの並びも大分異なる */ RIT_IRQHandler, 0, SSP1_IRQHandler, // SSP1 I2C_IRQHandler, // I2C TIMER16_0_IRQHandler, // 16-bit Counter-Timer 0 TIMER16_1_IRQHandler, // 16-bit Counter-Timer 1 TIMER32_0_IRQHandler, // 32-bit Counter-Timer 0 TIMER32_1_IRQHandler, // 32-bit Counter-Timer 1 (以下省略)
バージョンとか、いろいろあるのかもしれない。実は割込みテーブルも自分で書くモノ、とかかもしれない。でもリザーブとかも書かれてる位だから、何番目は何の割込みであること、って決まってるに違いないし、こんなに違っちゃいけない気がする。ユーザマニュアルのNVICの章をみたところ、同じような並びのテーブルがあった、正解はサンプルコードの方だ。そこで、サンプルコードの cr_startup_lpc13xx.c を拝借しまっさらなプロジェクトに上書きしたところ問題無く動作した。ウィザードでボードの型番とか使うライブラリとか指定するのに、ちゃんと正解のテーブルにならないのなんでだろう。ちなみに、プロジェクト作成の操作手順は冒頭の参考図書に書いてあり(ちょっと増えてる画面もあるけど)、その手順で行っているので、今やろうとしてて同じ状況の人は意外と居るかもしれない。
とりあえず割込み問題は解決できたようなので、先に進めることにしようと思う。