hardware_timerの用途と主要なメソッド

raspberry pi pico

用途

タイマー機能を扱うためのライブラリ

一定時間後に処理を実行したり、
決まった周期で同じ処理を繰り返したいときに使用する

LEDの点滅、フレーム制御、定期的なセンサー読み取りなどに使えるよ

主要なメソッド

add_repeating_timer_ms(delay, callback, user_data, out)
指定した周期(ミリ秒)でコールバック関数を繰り返し実行するタイマーを追加

add_repeating_timer_us(delay, callback, user_data, out)
指定した周期(マイクロ秒)で処理を繰り返すタイマーを追加

add_alarm_in_ms(delay, callback, user_data, fire_if_past)
指定した時間(ミリ秒)後に1回だけ
第2引数(callback)に指定した関数を実行する

add_alarm_in_us(delay, callback, user_data, fire_if_past)
指定した時間(マイクロ秒)後に1回だけ
第2引数(callback)に指定した関数を実行する

cancel_repeating_timer(timer)
登録済みの繰り返しタイマーを停止する
※再生停止やモード切り替え時によく使う

time_reached(time)
指定した絶対時刻に到達したかどうかを判定する

hardware_timerの基本的な考え方

hardware_timer は「一定時間待ってから何かをする」ための仕組み

sleep_ms() のように処理を止めるのではなく、
CPUを動かしたまま、指定時間後に関数を呼んでくれるのが特徴

そのため、ループを止めずに定期処理を行いたい場合に向いている

繰り返しタイマーについて

add_repeating_timer_ms()を使うと、
指定した周期ごとにコールバック関数が自動で呼ばれる

例:
・1000ms → 1秒ごとに処理を実行
・16ms → 約60fps相当の周期処理

コールバック関数はtrueを返すと継続、
falseを返すとタイマーが停止する

単発タイマー(アラーム)について

add_alarm_in_ms() / add_alarm_in_us() は
「指定時間後に1回だけ処理したい」場合に使う

・一定時間後にLEDを消す
・起動後しばらくして処理を開始する
とか。

初学者向けの補足

タイマーのコールバックは割り込みコンテキストで実行される
「main()で何かしてる間にも、タイマーで呼び出してる関数ではまた別の処理をできる。」みたいな感じ
※ただし並列処理ではない。(脳みそが2個になったわけではないということ。)

タイマーで呼び出す関数内で重い処理をすると周期が狂う
タイマーの周期よりも時間がかかるような処理とか
printf()みたいな重めの処理をするのは避ける

グローバル変数を触るときは注意
main()と同時にアクセスすると競合が起きる可能性がある
必要ならmutexやcritical_sectionを使う

sleep_ms()の代わりに使えるわけではない
hardware_timerは「非同期で処理したいとき」向け
単純に待ちたいだけならsleep_ms() の方が分かりやすい

精密なリアルタイム制御には限界がある
add_repeating_timer_us()で一応usオーダーの指定ができるけど、
実際のところは割り込みとか処理の遅延で少しはズレが起きちゃう

そもそも、add_repeating_timer_us()の第1引数には整数しか渡せなくて、
「10.5us間隔で何かを処理し続ける」みたいな指示はできない
us以上の緻密さが必要だったらPIOやPWMみたいなハード寄りの仕組みで対応したほうがいいっぽい。

実装の例

自作のソースの残骸みたいなものだから変数名とか関数名は気にしないでね
↓62usごとにaudio_timer_cb()というコールバック関数を呼ぶ処理

#include "hardware/timer.h"

int main(){
    // タイマー本体
    repeating_timer_t timer;
    // 1つの変数に複数のスレッドからアクセスしてしまわないように必要なロック
    critical_section_t adpcm_lock;

    // ここで繰り返し処理がスタート
    // ※ 第1引数がなぜ負の数なのかは後述
    add_repeating_timer_us(-62, audio_timer_cb, NULL, &timer);

   while (true); // タイマーで繰り返し処理をずっと動かしとくなら必要な記述
}


// コールバック関数から毎回呼ばれる関数
// 関数内でグローバルな変数(pcm_tailとか)にアクセスしてるから
// critical_section_tでロックをしている
// ==========================
// PCMデータ取得
// ==========================
static inline bool pcm_pop(int16_t *s) {
    // もしもmain()とaudio_timer_cb()両方からアクセスしたい変数を扱うなら、
    // ここでこんな風にロックしておく↓
    critical_section_enter_blocking(&adpcm_lock);
    if (pcm_tail == pcm_head) {
        printf("PCMバッファ空\r\n");
        return false; // empty
    } 
    *s = pcm_ring[pcm_tail];
    pcm_tail = (pcm_tail + 1) % PCM_BUF_SIZE;

    // メソッドを抜ける前にロックを解除しておく
    critical_section_exit(&adpcm_lock);
    return true;
}

// タイマーから繰り返し呼ばれるコールバック関数
// ==========================
// PWM 出力 (16kHz タイマー)
// ==========================
bool audio_timer_cb(repeating_timer_t *rt) {
    int16_t s;
    if (pcm_pop(&s)) {
        int32_t u = ((int32_t)s + 32768) >> 4;
        if (u < 0) u = 0;
        if (u > 4095) u = 4095;
        pwm_set_gpio_level(16, u);
        prev_duty = u;
    } else {
        printf("PCMデータ不足\r\n"); //もしもこっちの分岐に入ってprintfが走ったら周期が狂う
        pwm_set_gpio_level(16, 0);
    }
    audio_play_counter++;
    return true;
}

※ add_repeating_timer_us()の第1引数(delay)に負の値を指定すると
 「前回の実行時刻基準」で周期が計算される。
 → つまり処理時間の影響を受けにくくなる

タイトルとURLをコピーしました