前回の記事 で、tone()delay() による楽譜再生の課題と FspTimer クラス や既存ライブラリの調査結果について書きました。

今回は、それらを元に作成したタイマライブラリ CallbackTimerR4 を解説します。本ライブラリと、バックグラウンドで tone() を鳴らすライブラリ BackgroundMusicR4 を組み合わせれば、「Lチカしながら音楽再生」が出来るようになります。

CallbackTimerR4

楽譜中には全音符以外にも、数秒間同じ音を繋げて鳴らす記号 タイ が度々現れます。例えば BackgroundMusicR4 に添付の イングランド民謡 Greensleeves には最長 2571msec の音符が現れます。この時間をきっちり計り、かつ CPU リソースの使用を小さく抑えたのがこのライブラリの特徴です。

仕組みの大雑把な話は「自作のタイマ系ライブラリと BGM 再生ライブラリ」の章を見てもらうとして、ここでは使い方とインストール方法、および正確性の確認方法を説明します。

スケッチのサンプル

Arduino UNO R4 の LED をフワッとさせながら Minima は TX、RX を、WiFi は TX を Lチカさせるサンプルです。

#include "Arduino.h"
#include "CBTimer.h"

void callback_func(void) {
  static int n = 0;

#if defined(ARDUINO_MINIMA)

  if (n++ % 2) {
    digitalWrite(LED_TX, HIGH);
    digitalWrite(LED_RX,  LOW);
  } else {
    digitalWrite(LED_TX,  LOW);
    digitalWrite(LED_RX, HIGH);
  }

#elif defined(ARDUINO_UNOWIFIR4)

  // P109 Port Output Data
  if (n++ % 2) {
    R_BSP_PinWrite(BSP_IO_PORT_01_PIN_09, BSP_IO_LEVEL_HIGH);
  } else {
    R_BSP_PinWrite(BSP_IO_PORT_01_PIN_09, BSP_IO_LEVEL_LOW);
  }

#endif
}

void setup() {
  // put your setup code here, to run once:
#if defined(ARDUINO_MINIMA)

  pinMode(LED_TX, OUTPUT);
  pinMode(LED_RX, OUTPUT);

#elif defined(ARDUINO_UNOWIFIR4)

  // setup P109 functions as GPIO output pin (PDR:1, PMR:0) for TX LED
  R_BSP_PinWrite(BSP_IO_PORT_01_PIN_09, BSP_IO_LEVEL_LOW);

#endif

  static CBTimer timer;
  timer.begin(350 /* msec cycle */, callback_func);
}

void loop() {
  // put your main code here, to run repeatedly:
  for (int i = 0; i < 256; i++) {
    analogWrite(LED_BUILTIN, i);
    delay(2);
  }

  for (int i = 255; i >= 0; i--) {
    analogWrite(LED_BUILTIN, i);
    delay(2);
  }

  delay(100);
}

メンバ関数の説明

コンストラクタ/デストラクタ

new は使わず、クラス名 CBTimer 用いてインスタンス化するのが Arduino 的かと思います。

CBTimer 変数名;

コンストラクタは特に何もしていません。デストラクタは begin()FspTimer クラス のインスタンスが確保したタイマを解放し、再利用可能な状態にします。

またそのインスタンス自体は CBTimer クラス内で静的に保持されているため、残念ながら(というか、敢えて)次のように複数を宣言しても1つしか動作しません。

CBTimer 変数名1, 変数名2; // 変数名1と変数名2でFspTimerのインスタンスが共通となってしまう

初期化

bool begin(int period_ms, void (*callback)(void), bool start = true);
  • int period_ms
    タイマによるイベントの発火周期をミリ秒単位で指定します。

  • void (*callback)(void)
    タイマイベントの発火周期ごとに呼び出すコールバック関数を指定します。

  • bool start
    すぐにタイマのカウントを開始する場合は true(デフォルト、省略可能)を指定します。

  • 戻り値
    初期化に成功し、FspTimer が確保できれば true が、出来なければ false を返します。

また次の様に timer_mode を指定することもできます。

bool begin(timer_mode_t timer_mode, int period_ms, void (*callback)(void), bool start = true);
  • timer_mode_t
    タイマイベントを周期的に発火させるには列挙定数の TIMER_MODE_PERIODIC を、1回限りの場合は TIMER_MODE_ONE_SHOT を指定します。列挙定数 timer_mode_t は階層の深いところ(UNO R4 MinimaUNO R4 WiFi)で定義されています。

その他のパラメータと戻り値は前項と同じです。

開始、停止、終了

bool start(void);
bool stop(void);
void end(void);

初期化関数 begin()startfalse を指定した場合は start() でタイマのカウントを開始できます。また stop() はカウントの停止、end()FspTimer クラス 中の GPT もしくは AGT インスタンスを解放し、再利用可能な状態にします。

コールバック関数

タイマイベントの発火と共に呼び出すコールバック関数ですが、「割り込みハンドラの怪」にも書いた通り、少なくとも UNO R4 Minima と UNO R4 WiFi では millis()micros() も使えちゃいます。

void callback_func(void) {
  uint32_t time = millis();
  ...
}

インストール方法

CallbackTimerR4-main.zipのダウンロード スケッチ → ライブラリをインクルード → .ZIP形式のライブラリをインストール…

まず Github の CallbackTimerR4 リポジトリ から画面右側にある Releases をたどり、移動先のページから最新版の zip ファイルSource code (zip) をダウンロードします。

続いて Arduino IDE メニューの「スケッチライブラリをインクルード.ZIP形式のライブラリをインストール…」から、先にダウンロードした .zip ファイルを読み込ませれば、ライブラリフォルダにインストールされます。

Examples の説明

ライブラリのインストール後は、メニューの「ファイルスケッチ例カスタムライブラリのスケッチ例」から例題のスケッチを参照することができます。

例題 dimming_and_blinking は上記 スケッチのサンプル と同じなので、ここではもう1つの例題 check-timer-accuracy について説明します。

check-timer-accuracy

タイマイベント発火周期の正確性(精度)をソフトウェアで観測するスケッチです。ライブラリがちゃんと働いているかを確認するために作ったスケッチで、正確性の基準はあくまで millis()micros() ですので、参考程度のものです。

実行時はシリアルモニタを開き、ボーレートを 115200bps に設定してください。

スケッチ中の下記定義で、観測の基準を millis()micros() かを切り替えます。

#if 1
#define TIME_FUNCTION millis  // Check in milliseconds
#define TIME_SCALE    1
#else
#define TIME_FUNCTION micros  // Check in microseconds
#define TIME_SCALE    1000
#endif

また TIME_PERIOD_MS でタイマ周期を、TIME_MEASUREMENT で観測時間を設定します。

#define TIME_PERIOD_MS    10 // 10, 50, 100, 500, 1000, 1500, 2000, 3000, ...[msec]
#define TIME_MEASUREMENT  (60000 * TIME_SCALE)  // measurement for 1 minute

実行結果はシリアルモニタに出力されます。下の例は、TIME_PERIOD_MS を 10ms に、TIME_MEASUREMENT を1分に設定した時の結果で、タイマの開始から user_callback で観測された 経過時間周期、および 割り込み回数の総計 を出力します。

...
15:12:38.076 -> 59970 (10)
15:12:38.076 -> 59980 (10)
15:12:38.076 -> 59990 (10)
15:12:38.076 -> 60000 (10)
15:12:38.076 -> Number of interrupts = 6000

またこのスケッチ単体では GPT と AGT のうち前者が割り当てられるハズですが、下記のようにライブラリフォルダにある CBTimer.cpp#include "CBTimer.h 直前に #deifne CBTIMER_FORCE_AGT を挿入すれば、強制的に AGT を割り当てて観測することが可能です。

#define CBTIMER_FORCE_AGT
#include "CBTimer.h"

あとがき(という名の言い訳…)

今回作成したライブラリは単に FspTimer クラス をラップしているに過ぎませんが、長周期のタイマを少ない割り込みのフットプリントで実行させられるのが特徴です(もっとも、数時間おきに何かさせたいならリアルタイムクロック(RTC)を使うべきですが…)

ただしインスタンスは1個限りという制約があります。僕の目的からすればこれで十分というのが理由ですが、通常はタイマ1つで複数のイベントを発火させることができるので、これを使わずして貴重なタイマ資源を消費するプログラムを作るのをためらったというのがもう一つの理由です。

複数のタスクをキックしたいなら「ArduinoでTimerを使わずに割り込み処理をする」で紹介されている方法が、直感的かつシンプルで良いと思います。

むしろ次に紹介予定の BackgroundMusicR4 のように、何かと組み合わせて内部に隠蔽してしまうのが適した使い方でしょう。

ということで、次回はその BackgroundMusicR4 を紹介したいと思います :herb: