SNSへはこちら

ESP32で遊んでみる(4) - Timer

お次は純然たるタイマーです。

ESP32 は1つのグループ内にタイマーを2つ持ち、更にそのグループが2つあります。よって4つのタイマーを利用可能です。
プリスケーラは 16bit, そしてカウンタはなんと 64bit です。すごいですねぇ〜〜

ソースコード

取り敢えず先に載せておきます。若干独特。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/timer.h"
#include "driver/gpio.h"
#include "sdkconfig.h"

void timer_isr(void *parameters) {
    timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
    timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
    ets_printf("Hey\n");
}

void hwtimer_init(void) {
    // Timer Clock Source: APB(80MHz)
    timer_config_t config = {
        .alarm_en = TIMER_ALARM_EN,
        .counter_en = TIMER_PAUSE,
        .intr_type = TIMER_INTR_LEVEL,
        .counter_dir = TIMER_COUNT_UP,
        .auto_reload = TIMER_AUTORELOAD_EN,
        .divider = 80
    };
    timer_init(TIMER_GROUP_0, TIMER_0, &config);
    timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1000 * 1000);
    timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_isr, NULL, 0, NULL);
    timer_enable_intr(TIMER_GROUP_0, TIMER_0);
    timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
    timer_start(TIMER_GROUP_0, TIMER_0);
}

void app_main(void) {
    hwtimer_init();
    while(1) {
        vTaskDelay(1);
    }
}

設定

では簡単に述べます。そこまで難しくはないです。

構造体による初期化

例によって初期化には構造体を用います。それに続くコードを見ればお分かりでしょうが、案外それ以降の設定は関数を呼んでますね。独特なポイントです。

割り込みはコンペアマッチのみで、これを Alarm と呼んでいます。

    // Timer Clock Source: APB(80MHz)
    timer_config_t config = {
        .alarm_en = TIMER_ALARM_EN,
        .counter_en = TIMER_PAUSE,
        .intr_type = TIMER_INTR_LEVEL,
        .counter_dir = TIMER_COUNT_UP,
        .auto_reload = TIMER_AUTORELOAD_EN,
        .divider = 80
    };
    timer_init(TIMER_GROUP_0, TIMER_0, &config);

特にどうってこと無いコードですが、強いて言うならクロック源が 80MHz であることですかね。.divider を 80 とすることによって、カウンタに入力されるクロック周波数は 1MHz となります。
また、.counter_enTIMER_PAUSE にすることで、「まだ動くな」と指示しています。

    timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1000 * 1000);
    timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_isr, NULL, 0, NULL);
    timer_enable_intr(TIMER_GROUP_0, TIMER_0);
    timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
    timer_start(TIMER_GROUP_0, TIMER_0);

具体的な設定です。timer_set_alarm_value は割り込みを入れるコンペアマッチ値を指定します。
timer_isr は割り込み関数ですね。後はカウンタをリセットしてスタートするだけです。なんと楽ちん。

割り込み関数内で必要な処理

地味に独特で、ちょっとハマっていました。ってかこの処理 IDF 側がやってくれよ、と思わなくもない。

void timer_isr(void *parameters) {
    timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
    timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
    ets_printf("Hey\n");
}

まず、ステータスのフラグをクリアする必要があります。そこ手動なのかい。そして割り込み関数内だとコードのように timer_group_clr_intr_status_in_isr を呼ぶ必要があります。(ってかむしろ、割り込み関数外だとレジスタを手打ちする必要があるらしい。なんじゃそりゃ〜〜)

そしてお次は、タイマーの仕様で、アラーム割り込みが入ったら次のアラーム割り込みが自動的に無効化されるということに注意です。ですので、何処かで再度有効化する必要があります。こちらも割り込み関数内で有効化しました。

ということで、若干ハマりかけましたが割と素直なタイマーでした。
結構簡単に動いてくれましたね。