鳥なき里のマイコン屋(92) Longan nano、外部割込み

JosephHalfmoon

前回は、Longan nanoのADコンバータを動かしてみましたが、なるべくまだ触っていないDMAとか割り込みとかには触れないようにソフトトリガの単発変換でした。今回は、積み残しの機能からまず割り込みを使ってみたいと思います。でもね、ちゃんと読んでいないのだRISC-Vの割り込みの仕組み。

正直に申告すると、マイコンとしてのGigaDevice社GD32VF103のUser Manualの必要なページを参照しているだけで、CPU本体について、RISC-Vのマニュアルなど読んじゃいません。割り込みどころか、RISC-VのCPU自体、雲をつかむような話だ。。。 まあ、でもね、SDKなどというものは良くしたもので、適当に必要そうな関数を使っていれば、動いてしまう(本当か?ちょくちょく失敗しているが。。。)

まずは、割り込みを使うにあたり、割り込みの結果として起動される割り込みハンドラ相当の機能を実装したい。何かAPIでコールバック関数など設定すれば、「本物の割り込みハンドラ」から個別の処理用の関数を呼び出してくれるんじゃないかなどと、勝手に想像しました。

しかし予想は大外れでしたね。調べてみると割り込みベクタ・テーブルは、スタートアップ用のアセンブラソースファイル start.S の中にありました。

vector_base

というラベルの元、固定の名前(ラベル)でキメウチされていました。しかし、ラベルの値そのものはweak属性が与えられているので、同名の関数をどこかで定義すれば、リンカはそちらをリンクしてくれる筈。ま、オーバライドするような感じでしょうか。しかし、割り込み時のレジスタの退避、復旧、割り込みからのリターンなど割り込み特有の始末は、どうも別のところでやられてから「オーバライド」ハンドラを呼び出す仕組みのようです。ま、コールバック風ではあるか。普通、割り込みハンドラをCで書くときはいろいろ属性とかつけてメンドイですが、とりあえず名前さえ押さえておけば、普通に書けばよいみたい。この辺の割り込みと割り込みコントローラの仕組みは後でちゃんと調べておかないと。

さて、今回は端子から割り込み信号を入れようと考えています。外部割込み。固定名の割り込みハンドラは、何を使ったらよいのでしょうか。先ほどのテーブルを見ると、外部割込み用のハンドラは以下の7個でした。

EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI5_9_IRQHandler
EXTI10_15_IRQHandler

妙な番号付けだなと思ってちと調べてみると以下のことが分かりました。

  • AからEまでの全てのポートの全端子に割り込みを割り当て可能。
  • 各ポートには0番から15番の16本の端子がある
  • AからEまでの0番端子が1本の外部割込み線を共有。以下同文で16本。

Longan nano搭載のチップは小ピンなのでAからEまで5群x16本の全てが端子として出ているわけではないです。実際に使えそうな端子として、今回は、

PB11

を割り込みに使うことにいたしました。すると11番の割り込み線はというと

EXTI10_15_IRQHandler

で受けるのがお作法のようです。0番から4番までは独立のハンドラが割り当ててあるのに、後ろの方は何本かでシェアせよ、という節約型。なお、ECLIC(Enhancement Core-Local Interrupt Controller)は、0から15番までの外部端子16本に加えて以下の3要因にもサービスしているらしいです。

  • 16番はLVD
  • 17番はRTCアラーム
  • 18番はUSB Wakeup

他にもUSARTとかDMAとかいろいろあるけどどうなのよ、というと、それぞれに割り込みハンドラ名の割当があります。外部端子とはまた違った経路で割り込みがサービスされるようです。またこれもしらべなきゃ。

さて、例によって端子の設定、PB11を割り込み用に。こんな感じ。

gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_11); //PB11--EXT-INT

例によって、Examplesを参考にしながら、割り込みを設定。PB11には、10kΩでプルアップをつけて3.3Vにつり、スイッチで0Vに落ちるようにしてみました。スイッチを倒したときのフォーリングエッジをひっかける設定。

void key_exti_init() {
    rcu_periph_clock_enable(RCU_AF);
    eclic_global_interrupt_enable();
    eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
    eclic_irq_enable(EXTI10_15_IRQn, 1, 1);
    gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_11);
    exti_init(EXTI_11, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
    exti_interrupt_flag_clear(EXTI_11);
}

そして、「割り込みハンドラ」。何の属性もつけず、main()関数のすぐとなりに書き入れました。やることは赤色LEDの点灯。contLEDは大分前の回で作成済の赤、青、緑LEDを制御できるだけのユーティリティ関数です。なお、割り込みハンドラは赤点灯するだけです。ゆるゆると約9秒周期で回っているmain()関数内の「処理ループ」、前回、前々回でDAとかADとかやっているやつ、のループ先頭でLEDを全消灯するようにしてみました。割り込みが入ると赤くなり、ループの先頭まで割り込みのタイミング次第で9秒から0秒の間維持されるという趣向。

void EXTI10_15_IRQHandler(void) {
    if (RESET != exti_interrupt_flag_get(GPIO_PIN_11)) {
        exti_interrupt_flag_clear(GPIO_PIN_11);
        contLED(LED_RED, (LED_GREEN | LED_BLUE));
    }
}

実際に動作させてみたブレッドボードの様子はアイキャッチ画像の通り。まあ、こんなんで一応動きました。

鳥なき里のマイコン屋(91) Longan nano、ADC単発変換 へ戻る

鳥なき里のマイコン屋(93) Longan nano、アセンブラ関数を呼び出す へ進む