前々回にZephyrのデバイスツリーが分かったかのようなことを書いてしまいましたが、あれは一時の気の迷いでしたね。全然分かっちゃおりませんでしたな。今回、スイッチから割り込みを受けようとしてまた迷宮に迷いこみました。そういう時は素直にサンプルソースなど真似してみるべしと。やってみました。
※「モダンOSのお砂場」投稿順Indexはこちら
今までやってきたFreeRTOSとか、Arm Mbed OSにくらべるとZephyrは抽象度が高いです。即物的ベアメタル派で直レジスタに書き込んでしまうような私メにとっては、結構辛いです。そのうえ各種構造体、enum、関数、マクロなどOS機能充実。充実は良いのですが、たとえばDで始まるそやつらだけを数えあげても579個もありました。多分覚えきれませぬ。
今回実験してみること
今回テーマは「割り込み」です。組み込みで割り込み受付できなければ何もはじまりません。とりあえず「簡単なところ」でGPIO端子からの割り込みの受付をやってみることにいたました。
GPIO端子につながっているスイッチを押したら「フォーリングエッジ」で割り込みを受け付けたい。
これだけです。
実験は例によって ST Microelectronics社の評価ボード Nucleo-F401REを使用です。STM32F401REマイコン、Arm Cortex-M4Fコア搭載であります。Zephyrのボードサポートページが以下に。
また、使用している開発環境は VScode + PlatformIOです。標準的なZephyrのビルト環境とはチト違うやに思われます。でもま、PlatformIOお楽。
ソースの作成
まずは VScode + PlatformIO 環境でProjectを作成します。PlatformIOのHomeをあけて、Project Wizardを起動し、プロジェクト名(今回はkeyIntrとしました)、ターゲットボード、そしてFrameworkとして Zephyr RTOSを指定すれば環境が出来上がります。
前回もやりましたが、自動生成される platformio.ini に以下1行書き加えておくと、後でPlatformIOのシリアルモニタを使って printk() 出力を観察しやすくなります。
monitor_speed = 115200
さて、今回はスクラッチから自力で書くのをあきらめてしまい、サンプルコードに寄りかかって「ともかく動作する」コードを手早くしつらえようという魂胆です。動いてからいろいろ考えると。泥縄パターン。参照させていただいたのは、PlatformIOの場合、packages > framework-zephyr の配下にある以下のフォルダのサンプルソースです。
samples > basic > button
上記そのままだとスイッチからの割り込みに関係ないコードも多数なので、割り込み受けとその確認(printk利用)に必要なコード以外は「バッサリ」させていただき、当方要求の「フォーリングエッジ」検出用にチョイ直しさせていただきました。なお、対象スイッチは、Nucleoボード上に搭載されているユーザボタン(青色のタクトスイッチ)です。GPIOのポートCの13番に接続されとります。
ちょい変ソースがこちら。
#include <zephyr.h> #include <device.h> #include <devicetree.h> #include <drivers/gpio.h> #include <sys/printk.h> #include <string.h> #define SLEEP_TIME_MS (1000) #define SW0_NODE DT_ALIAS(sw0) static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios,{0}); static struct gpio_callback button_cb_data; void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32()); } void main(void) { int ret = 0; if (!device_is_ready(button.port)) { return; } ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_FALLING); if (ret != 0) { return; } gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin)); gpio_add_callback(button.port, &button_cb_data); printk("Set up button at %s pin %d\n", button.port->name, button.pin); while (1) { k_msleep(SLEEP_TIME_MS); } }
基本GPIO端子を割り込み受けできるような状態に設定し、その端子からの割り込みが発生したら呼び出されるコールバック関数を設定する、という「どのOSでも似たような」手順なのですが、知らない構造体、関数、マクロが目白押し。こいつらを逐一理解していったらきっと自在にZephyrできるようになるのでしょうな。そいつらの理解に役立つページが以下に
ここから辿っていけば、関数でもマクロでも説明が読めます。しかし数が多いので辛い。
実機での実行結果
以下はUSBシリアルに接続した PlatformIOのシリアルターミナルのキャプチャです。printk()の出力はここに流れてきます。
最初に3回 Button pressed at xxx とあるところで青色の「ユーザ」ボタンを押しています。割り込みコールバックが動作し、そのときのk_cycle_get_32()値を表示してます。結構レゾリューションの高いタイムスタンプになりそうでないかい?ホントか。
*** Booting Zephyrのところで、黒色のRESETスイッチを押しています。再起動して、GPIOのCポート、13番ピンで動作していることが分かります。その後また割り込んでいるけれども、カウンタの値は継続しているように見えますな。電源ONのまま外部RESETかけた場合は初期化されないようですな。
サンプルソースの真似っこだから動作はカッコいいんだけれども、この境地に至るのは大変そう。まあ千里の道も1歩よりというし。でも月に2,3歩じゃ千里どころか1里もすすめんぞ、自分。