Skip to content

TimeoutTimer

askn37 edited this page Jan 24, 2024 · 3 revisions

TimeoutTimer ツールリファレンス

タイムアウトタイマー支援ツール。

reduceAVR は未対応

用例

このライブラリの最も主要な機能は、タイムアウトブロックである。

#include <TimeoutTimer.h>

TIMEOUT_BLOCK(1000) {
  while (true) {
    /* 無限ループ等 */
  }
}

TIMEOUT_BLOCKは指定ミリ秒を経過してなおブロック内のコードが実行中であるなら、 それを強制中断してブロックの外に処理を移す。 ブロック内で状態変数を管理し、自身でタイムアウト脱出を判定する必要はない。 従って単純な無限ループでも経過時間が過ぎれば処理を中断させることができ、 記述を単純化できる。

制約

1. TIMEOUT_BLOCK内での変数書換はブロック外へ伝播されずに破棄される可能性が高い。 これを避けるには変数にvolatile宣言を付加しておく。

2. タイムアウトはミリ秒で指定するが、計時器精度に伴う誤差がある。 丸めの方向によって最大時間にも解釈されるから最小 2 は指定しなければならない。 一方で指定可能な最大値は 63998 である。

16bit幅ミリ秒指定の最大有効値は__TIMEOUT_MILLIS_MAXマクロ定数で参照できる。

3. TIMEOUT_BLOCK内で全体割込を禁止すると、その間のタイムアウト割込は抑制される。 無限ループ内で割込禁止にすると抜け出せなくなるので注意。 また割込禁止のままブロックを抜けると以後の動作も保証できない。 従ってsei cliを直接使うのではなく、 割込禁止対象はATOMIC_BLOCKで囲むようにすべきだ。

4. TIMEOUT_BLOCKを再入すると、内側ブロック開始時に外側ブロックの計時は一時停止する。

これらの詳細と解説はTaskChangerサンプルを参照のこと。

<TimeoutTimer.h>

各関数は namespaceTimeoutTimer空間にある。

このライブラリはRTC周辺機能を使用し ISR(RTC_CNT_vect)割込ベクター/ハンドラを専有する。

依存性:<setjmp.h>

#define NOTUSED_INITIALIZE_TIMEOUTTIMER

このマクロを事前に宣言すると暗黙のTimeout::begin実行開始が抑止される。 setup 内で明示的にTimeout::beginが呼ばれるまで 計時動作は開始されず、他の機能も動作しない。

#define __TIMEOUT_CLKFREQ

RTC計時器の時間粒度を指定するマクロ定数。 既定値は 1024 (1024Hz)。

<TimeoutTimer.h>ライブラリをインクルードする前に これを再定義すると計測時間粒度を変更できる。 指定可能なのは 512、1024、2048 (単位は Hz)の三種。 通常は既定値のままでよいだろう。

  • 512Hzにすると約1953us粒度に精度が下がるが、8388608秒(約97日)までの計時が可能になる。
  • 2048Hzにすると約488us粒度に精度が上がるが、2097152秒(約24日)までしか計時ができない。
    • delay_timerや{interval_check_timer`の指定可能値も 31266(ms)まで低下する。

16bit幅ミリ秒指定の最大有効値は__TIMEOUT_MILLIS_MAXマクロ定数で参照できる。

define __TIMEOUT_MILLIS_MAX

16bit幅ミリ秒指定の最大有効値を示す。

void TimeoutTimer::abort (void)

現在のTIMEOUT_BLOCKを中断し、ブロック外に抜ける。

TIMEOUT_BLOCK(1000) {
  /* STUB */

  TimeoutTimer::abort(); /* 途中で中断 */

  /* STUB */
}

脱出できるTIMEOUT_BLOCKは1段だけであり、複数段を一回で抜けることは出来ない。

TIMEOUT_BLOCKを再入している場合、外側のタイムアウト計時が中断点から再開する。
TIMEOUT_BLOCKの外でこれを実行すると予測できない結果を招く。多くはリセットが掛かる。

uint16_t TimeoutTimer::time_left (void)

TIMEOUT_BLOCK 内で、タイムアウトまでの残数を取得できる。単位は 計時器精度。(約976us)

取得値をミリ秒に換算するには timeout_ticks_to_millisマクロを、 マイクロ秒に換算するには timeout_ticks_to_microsマクロを利用できる。

TIMEOUT_BLOCK(1000) {
  while (true) {
    uint16_t timeleft = TimeoutTimer::time_left();
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      Serial.println(timeleft, DEC);
    }
  }
}

取得時間は目安であって運用時の CPU動作クロックや周辺環境(気温等)によっても変動する。

uint32_t TimeoutTimer::ticks_left (void)

計時器の継続計数を取得する。 単位は 計時器精度(約976us)で 32bit幅の有効範囲を持つ。 従って 4194304秒(約48.5日)でこの計数は一巡する。 元期は MCUリセット直後からである。

取得値をミリ秒に換算するには timeout_ticks_to_millisマクロを、 マイクロ秒に換算するには timeout_ticks_to_microsマクロを利用できる。

uint32_t tc, ms, us;
tc = TimeoutTimer::ticks_left();
ms = timeout_ticks_to_millis(tc); // TimeoutTimer::millis_left() 取得結果に同じ
us = timeout_ticks_to_micros(tc); // TimeoutTimer::micros_left() 取得結果に同じ

uint32_t TimeoutTimer::millis_left (void)

計時器の継続計数をミリ秒換算で取得する。 計時開始から 4194304000 カウント経過(約48.5日)でゼロに丸められる。 いわゆる millis 関数の代替。

32bit幅いっぱいで溢れる前に丸めが発生することに注意。 ある時間差を求めたい場合は ticks 計数値を直接加減算し、比較判定の換算は最後にすべきである。

uint32_t TimeoutTimer::micros_left (void)

計時器の継続計数をマイクロ秒換算で取得する。 これはおおむね 268秒相当で丸められる。 いわゆる maicros 関数の代替。

便宜上用意されているが時間粒度が荒くかつ計数溢れと丸めが早いため目安にしかならないことに注意。

void TimeoutTimer::delay_ticks (uint16_t _ticks)

指定計数_ticksのあいだ処理進行を遅滞させる。いわゆるdelay関数の代替。 ミリ秒から指定計数値を得るにはtimeout_millis_to_ticksマクロが使用できる。 直接ミリ秒を指定可能なdelay_timerマクロも用意されている。

指定可能な最大値は 63998 (ms)である。(16bit幅)

TimeoutTimer::delay_ticks( timeout_millis_to_ticks(1000) );

// または短縮マクロで
delay_timer(1000);

// または(他のAPIを同時に組み込んでいなければ)
delay(1000);

ライブラリをインクルードした時点で従前の Macro API delay() は、このマクロに交換される。
delayMaicroseconds()を代替する機能はない。

なお遅延待機中は(TIMEOUT_BLOCKの外であれば)yield() 関数が継続して実行されるので、 これを用いると 協調的マルチタスク を実現できる。 詳細は <TaskChanger.h> を参照のこと。

void TimeoutTimer::sleep_cpu_ticks (uint32_t _ticks)

指定計数_ticksのあいだ CPUを休止状態に置く。 指定計数経過か、他の割込によって休止状態は解除される。 ミリ秒で直接指定するにはsleep_cpu_timerマクロが使用できる。

set_sleep_mode(SLEEP_MODE_STANDBY);
sleep_enable();

TimeoutTimer::sleep_cpu_ticks( timeout_millis_to_ticks(10000) );

// または
sleep_cpu_timer(10000);

この関数はTIMEOUT_BLOCKの中で使うことが出来ない。その機能を停止する。

休止中は計時が停止する。休止中の実経過時間を取得することはできない。
休止を終了させた条件を知ることは出来ない。これは他の割込ベクタで調べる必要がある。
休止時間は概ねの目安であって精度の保証はない。休止中の正確な経過時間を得るには外部RTCを使用のこと。

bool TimeoutTimer::interval_check_ticks (uint32_t &_ticks, uint16_t _interval = 0)

時間間隔_intervalが経過するたびに真を返し、指定の保持カウンタ変数_ticksを更新する。 時間間隔_intervalをミリ秒で直接指定するにはinterval_check_timerマクロが使用できる。

指定可能な最大値は 63998 (ms)である。

uint32_t _check1 = TimeoutTimer::ticks_left();
uint32_t _check2 = _check1;
while (true) {
  // if ( TimeoutTimer::interval_check_ticks( _check1, timeout_millis_to_ticks(30) ) )
  if ( interval_check_timer( _check1, 30 ) ) digitalWriteMacro(LED_BUILTIN, TOGGLE);
  if ( interval_check_timer( _check2, 31 ) ) digitalWriteMacro(LED_BUILTIN, TOGGLE);
}

殆どの周期的逐次処理はこの機能で記述できるだろう。

実際の確認周期より短い計数を指定すると、常に真を返す。(内部計数が溢れるまで)
保持変数の初期値が現在計時値より小さい場合、それに追いつくまで繰り返し真を返す。

マクロでない方のinterval_check_ticksの第2引数は 省略したなら 0 指定であると解釈される。 これは計数値が溢れるまで(符号付32bit変数比較なので最大約24日)いちど真になった状態を変化させない。 従って単発の時間経過を知るには、次のように書き下すことができる。

/* 10秒経過後から真となる計数比較 */
uint32_t _check = TimeoutTimer::ticks_left() + timeout_millis_to_ticks(10000);
while (!TimeoutTimer::interval_check_ticks(_check)) {
  TIMEOUT_BLOCK(1000) {
    while (true) {
      /* STUB */
    }
  }
}

void TimeoutTimer::begin (uint32_t _ticks = 0)

uint32_t TimeoutTimer::end (void)

RTC計時器を開始または停止する。 停止時には現在の計数値を返すので、 計時再開時にこれを_ticksで渡して以前の状態から継続することができる。 これらはsleep_cpu_ticks内で使用されている。

/* RTC計時器停止 */
uint32_t _save_ticks = TimeoutTimer::end();

/* RTC周辺機能を使用する他の処理... */

/* RTC計時器再開 */
TimeoutTimer::begin( _save_ticks );

停止中はTIMEOUT_BLOCK等を使用できない。その中でも使えない。
RTC_CNT_vect割込ベクタは開放されず再定義も出来ないことには注意。

その他の付加機能

PIT周期割込との併用

RTC計時器とその割込は使用するが PIT周期計時器とその割込は使用しないため、 利用者は自由に活用して構わない。 両者は独立して機能する。

#include <avr/io.h>

loop_until_bit_is_clear(RTC_PITSTATUS, RTC_CTRLBUSY_bp);
RTC_PITINTCTRL = RTC_PI_bm;
RTC_PITCTRLA = RTC_PITEN_bm | RTC_PERIOD_CYC32768_gc;

ISR(RTC_PIT_vect) {
  RTC_PITINTFLAGS = RTC_PI_bm;
}

RTC_CTRLA 等の PIT 周辺機能制御以外のレジスタには触らないこと。