鳥なき里のマイコン屋(130) VS CodeでラズパイPico、GPIOで割り込み

Joseph Halfmoon

先週からRaspberry Pi Pico C/C++ SDKをボチボチと試用しております。前回は高水準なTimer関数(インターバルタイマ)を勉強。まずは基礎からということで今回はGPIOです。ただ読み書きするだけでは面白くないので、端子からの割り込みをトリガにワンショットタイマも動作させてみたいと思います。

(使用したCソース全文は末尾に)

今回やってみる課題

自分で自分への課題、あるいは実験ですが以下のように設定しました。

  • 先週に引き続きGPIO15に外部LEDが接続してある
  • 今回はGPIO14にプッシュスイッチを追加する

古い人間なので、LED点灯するときはマイコン端子は電流引く側がしっくりくるという理由で、LEDはLow出力で点灯です。また、プッシュスイッチもスイッチを押さない状態ではHigh、スイッチ押すとLowです(適当に抵抗いれてあります。)

さてこの回路で

  1. プッシュスイッチを押したら即座にLEDを2秒間点灯させる
  2. きっかり2秒後に消灯。LEDの点灯期間中にはさらにスイッチ押しても何もおこらない。
  3. 上記のLED点灯動作とはまったく関係なく、ソフトウエアループの中で1777m秒毎にプッシュスイッチの現在値(Low=0, High=1)を読み取ってUSBシリアルに報告する

という設定です。内部動作としては

  1. GPIO14のフォーリング・エッジでGPIO割り込みをかける
  2. 上記の割り込みのコールバック関数内でLEDを点灯し、2秒間のワンショットタイマをしかける
  3. ワンショットタイマの2秒が過ぎたらワンショットのコールバック関数内でLEDを消灯し、GPIOの割り込みを再び受付るようにする
  4. これらとは別にsleepで時間をつぶすソフトウエアループの中でGPIO14を読み取っては値を標準IO(USBシリアルに向けている)に出力
GPIO関係の資料

さてC/C++から(SDK経由で)ラズパイPicoのGPIOを使用する場合に使用する関数やらenumやらを説明してくれている資料へのリンクは以下です。

hardware_gpio

これ読めば分かるはずではありますが、イマイチ素っ気ないっす。今回割り込みを設定するのに使用しているのは以下の関数です。

gpio_set_irq_enabled_with_callback()

上記の資料を読んでいたら1か所注意点ありでした。Noteから一文引用させていただきます。

Currently the GPIO parameter is ignored, and this callback will be called for any enabled GPIO IRQ on any pin.

上記の関数の定義を見ると、特定の端子からの割り込みをトリガとしてコールバック関数を呼び出せる雰囲気なのですが、実は、端子番号を指定しても無視されると。ともかくGPIOからの割り込みにすべて反応してしまうみたいです。

今回、割り込みに使っているのは1端子だけなのでとりあえず問題ないので、ま、いいか。しかし、複数端子を使う場合にはコールバック関数側で端子の識別の必要ありのようです。また、コールバック関数の実行は、上記の関数でコールバック関数を設定したときのプロセッサで行なわれるようです。今回2個目のコアは使っていないのでこれまた問題ないけれど。

プロジェクトの作成

前回に引き続きプロジェクト・ディレクトリと各種ファイルの生成はお手軽なGUIに頼り切ってしまっています。設定画面はこんな感じ。

gpioTST0_PROJGUIでプロジェクトを作ってから、PC上のVS Codeでラズパイ4にリモート接続し作成済のプロジェクトディレクトリを開いています。

またラズパイPicoのstdioはUSBシリアルに向けているので、リモート接続したラズパイ4のターミナル画面からminicomで /dev/ttyACM0に接続してprintfの結果を眺めています。

作成したソースコード

短いサンプルコードですが、VS Codeでの記述は楽です。当然といえば当然ですが、適当に入力していけば enum とか列挙してくれるので、ドキュメントを探し回る必要はありません。このコールバック関数の引数どうなってんの?みたいな疑問でも、右クリック一発で定義に飛べるので楽。

(ソース全文は末尾に)

動作

実動作は、アイキャッチ画像に掲げたブレッドボード写真の黄色いプッシュスイッチを押すととなりの青LEDが2秒光るというだけのものです。その間、ほぼ1.777s毎にプッシュボタンの状態もUSBシリアルに表示されます。USBシリアルに表示する方は、GPIOの値をポーリングしているので、たまたま読み取りに行ったタイミングでボタンが押されていない限り 0 というステータスは返ってきません。それに対して割り込み受けの方はフォーリング・エッジ検出なので、LED非点灯時にボタンを押すと取りこぼしなく即座に反応します。また一度反応するとLEDが点灯している2秒間は次のボタン押しを検出しなくなります。

次は「出来合いのインタフェース」ですかね。お楽しみのPIOはまた後で(MicroPythonではPIOにおんぶにだっこだけれども)

鳥なき里のマイコン屋(129) VS CodeでラズパイPico、SDKのTimerを使う へ戻る

鳥なき里のマイコン屋(131) ラズパイPico、PWM、外部クロック、周波数カウンタ へ進む

実験に使ったソースコードの全文がこちら
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/divider.h"

//External circuit
#define EXT_SWITCH 14
#define EXT_LED 15

volatile bool ledOff;

int64_t alarm_callback(alarm_id_t id, void *user_data) {
    ledOff = true;
    gpio_put(EXT_LED, ledOff);
    gpio_set_irq_enabled(EXT_SWITCH, GPIO_IRQ_EDGE_FALL, true);
}

gpio_irq_callback_t gpio_callback(uint id, uint32_t event) {
    if (ledOff) {
        ledOff = false;
        gpio_put(EXT_LED, ledOff);
        add_alarm_in_ms(2000, alarm_callback, NULL, false);
    }
}

int main()
{
    int counter = 0;
    stdio_init_all();

    gpio_init(EXT_SWITCH);
    gpio_init(EXT_LED);
    gpio_set_dir(EXT_LED, GPIO_OUT);
    gpio_set_dir(EXT_SWITCH, GPIO_IN);
    ledOff = true;
    gpio_put(EXT_LED, ledOff);
    gpio_set_irq_enabled_with_callback(EXT_SWITCH, GPIO_IRQ_EDGE_FALL, true, gpio_callback);

    while (counter < 100) {
        printf("%d : %d\r\n", counter, gpio_get(EXT_SWITCH));
        sleep_ms(1777);
        counter++;
    }

    puts("End of Execution.\r\n");

    return 0;
}