🌊

ZephyrRTOSでSTM32マイコン再入門~タイマ割り込みでLED点滅制御~

に公開

はじめに

これまでWindows上にZephyrRTOSの開発環境を構築し、NucleoF103RBボードを用いて学習を進めてきた。
https://zenn.dev/gotoooo/articles/894213f95372bd

今回は、タイマ割り込みを使ってNucleoF103RBのLEDを点滅制御するサンプルを作成し、タイマ動作の仕組みの理解に取り組みたい。

コード

今回はmain.cの実装に加えて、zephyr/prj.confの編集、そしてzephyr/boards/nucleo_f103rb.overlay のデバイスツリー定義が必要である。

main.cのコード全体
main.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/counter.h>

#define LED_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED_NODE, gpios);

#define TIMER_NODE DT_NODELABEL(counter3)
const struct device *counter_dev = DEVICE_DT_GET(TIMER_NODE);

static bool led_on = false;
static bool flag_tim = false;

static void timer_callback(const struct device *dev, uint8_t chan_id,
                           uint32_t ticks, void *user_data)
{
    ARG_UNUSED(dev);
    ARG_UNUSED(chan_id);
    ARG_UNUSED(ticks);
    ARG_UNUSED(user_data);

    flag_tim = true;

    struct counter_alarm_cfg alarm_cfg = {
        .flags = 0,
        .ticks = counter_us_to_ticks(counter_dev, 500 * 1000), // 500ms
        .callback = timer_callback,
        .user_data = NULL,
    };
    counter_set_channel_alarm(counter_dev, 0, &alarm_cfg);
}

int main(void)
{
    if (!device_is_ready(counter_dev)) {
        printk("Counter device not ready\n");
        return -1;
    }

    if (!gpio_is_ready_dt(&led)) {
        printk("LED device not ready\n");
        return -1;
    }

    printk("Debug start\n");
    gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);

    counter_start(counter_dev);

    struct counter_alarm_cfg alarm_cfg = {
        .flags = 0,
        .ticks = counter_us_to_ticks(counter_dev, 500 * 1000), // 500ms
        .callback = timer_callback,
        .user_data = NULL,
    };
    counter_set_channel_alarm(counter_dev, 0, &alarm_cfg);

    while (1) {
        if (flag_tim == true){
            flag_tim = false;

            led_on = ! led_on;
            gpio_pin_set_dt(&led, led_on);
        }

        k_sleep(K_MSEC(10));
    }

    return 0;
}

zephyr/prj.conf
CONFIG_COUNTER=y
zephyr/boards/nucleo_f103rb.overlay
&timers3 {
    status = "okay";
    st,prescaler = <10000>;

    counter3: counter {
        compatible = "st,stm32-counter";
        status = "okay";
    };
};

zephyrで用意されるデバイスツリーに対して独自の定義を追加する場合は[ボード名].overlayという名前のファイルを作成し、そこに追記する。そしてzephyr/boardsディレクトリに配置することでビルド時に自動的に読み込まれる。
詳細は以下のドキュメントを参照されたい。

https://docs.zephyrproject.org/latest/build/dts/howtos.html#set-devicetree-overlays

デバイスツリーでのタイマ設定

#define TIMER_NODE DT_NODELABEL(counter3)
const struct device *counter_dev = DEVICE_DT_GET(TIMER_NODE);

nucleo_f103rb.overlayで定義したcounter3ノードを参照している。
プリスケーラを10000に設定しているため、タイマのクロック周波数は72MHz/10000+1≒7.2kHzとなる。

タイマ割り込みコールバック関数

static bool flag_tim = false;

static void timer_callback(const struct device *dev, uint8_t chan_id,
                           uint32_t ticks, void *user_data)
{
    ARG_UNUSED(dev);
    ARG_UNUSED(chan_id);
    ARG_UNUSED(ticks);
    ARG_UNUSED(user_data);

    flag_tim = true;

    struct counter_alarm_cfg alarm_cfg = {
        .flags = 0,
        .ticks = counter_us_to_ticks(counter_dev, 500 * 1000), // 500ms
        .callback = timer_callback,
        .user_data = NULL,
    };
    counter_set_channel_alarm(counter_dev, 0, &alarm_cfg);
}

タイマー割り込みが発生すると呼び出されるコールバック関数を定義している。
割り込みハンドラ内では極力処理を軽くするため、フラグを立てるだけにしている。
また、今回のプログラムでは繰り返し割り込みを発生させたいので、コールバック関数内で再度カウンタのアラームを設定している。

メイン関数内での初期化処理


    counter_start(counter_dev);

    struct counter_alarm_cfg alarm_cfg = {
        .flags = 0,
        .ticks = counter_us_to_ticks(counter_dev, 500 * 1000), // 500ms
        .callback = timer_callback,
        .user_data = NULL,
    };
    counter_set_channel_alarm(counter_dev, 0, &alarm_cfg);

メイン関数内では、まずカウンタをスタートさせている。
そしてタイマー割り込みを発生させるためのアラームを設定している。

フラグのポーリング

    while (1) {
        if (flag_tim == true){
            flag_tim = false;

            led_on = ! led_on;
            gpio_pin_set_dt(&led, led_on);
        }

        k_sleep(K_MSEC(10));
    }

メインループ内でタイマー割り込みコールバック関数で立てられたフラグをポーリングしている。
フラグが立っていたらLEDの点灯/消灯を切り替えている。

おわりに

以上でZephyrRTOSを用いたSTM32マイコンのタイマ割り込みによるLED点滅制御の解説を終わる。
タイマ割り込みの仕組みを理解する一助となれば幸いである。

Discussion