鳥なき里のマイコン屋(119) M5Stack、FreeRTOS機能使って周波数カウンタ

Joseph Halfmoon

今回、M5Stack Grayを使って、外部から入力される信号の周波数(せいぜい数kHzくらい)を測定したかったのであります。しかし、Arduino環境で以前使ったことがある周波数カウンタのライブラリ、AVR用でした。M5Stackでは使えません。M5Stack用のタイマライブラリで自力更生とも考えましたが、別シリーズでFreeRTOSの「タイマ」使ったのを思い出しました。今回はFreeRTOS機能を使ってみます。

(今回使用の Arduino環境用 .ino 型式のファイル全文は末尾に)

まずはArduino環境で良く使われると思われる周波数測定用のライブラリへのリンクを掲げておきます。

Arduino-IoT/libraries/FreqCounter

使いやすいライブラリだと思いますが、AVRマイコンのハードウエア(Timer2)依存です。なおライセンスはLGPLです。

今回ターゲットはESP32マイコン搭載のM5Stack Grayとしたので、残念ながらAVRマイコン依存のライブラリは使えません。今回やりたいことは、所謂「ワンショットタイマ」的な動作です。M5Stackで、ワンショットタイマを利用するためには以下のライブラリが使えるのではないかと思います。

Ticker

上記使って実装するかとも思ったのですが、最近別シリーズでFreeRTOSのソフトウエア・タイマAPIというのを使ってみて、これがなかなか「楽ちん」だったのです。「ソフトウエア」タイマといいつつ、くるくる回っている間、他に何もできないような単純なループではありません。RTOSです。メインの仕事の裏側で「手配」をしてくれる優れものです。

モダンOSのお砂場(22) FreeRTOS、ソフトウエアタイマAPIでワンショット

上の記事で使用したボードはM5Stackではなく、ESP32 DevKitCであったのですが、同じESP32コアです。互換性は非常に高いです。

そこで以下のように周波数カウンタを実装してみることにいたしました。

  1. 入力信号は立ち上がりエッジを端子の割り込みで検出し、その回数を数える
  2. 割り込み回数を数えるのは一定期間(ここでは1000msとした)とする
  3. 上の一定期間を制御するのにFreeRTOSのソフトウエアタイマAPIをワンショットで使う

細かいところを補足すると

  • 入力信号はM5Stackの上側面の19番ピンで受ける
  • 周波数カウント開始のトリガは23番ピンに接続したスイッチで行う
  • 「動作中」が外部で分かるように18番ピン接続のLEDでLチカする

です。実際に動作させたソースコードを末尾に掲げました。動作検証に使ったハードウエアは冒頭のアイキャッチ画像に掲げた通りです。テスト用の入力信号としては例によって Digilent社 Analog Discovery2 の波形ジェネレータで方形波を生成して19番ピンに与えました。こんな感じ。TEST_WAVEFORM

実際に Analog Discovery2で設定した入力信号の周波数と、M5Stack上のソフトウエアで測定した周波数の測定結果を下に示します。

入力信号[Hz] 測定結果[Hz]
10 10
20 20
50 50
100 100
200 199
500 500
1000 999
2000 1999
5000 4999
10000 9998
20000 19996

ま、だいだいね、まあ良さそうな感じでとれてるね。使い道次第だけれども。

ESP32機では裏でFreeRTOSが最初から動いているので、「何も特別な準備もせず」FreeRTOSのAPIを使えます。何気に使いやすいんでないかい。

鳥なき里のマイコン屋(118) M5ATOM Lite、UART、I2C接続 へ戻る

鳥なき里のマイコン屋(119) RP2040搭載、Raspberry Pi Pico、到着予定 へ進む

FreeRTOS ソフトウエアタイマAPIで作った周波数カウンタ(M5Stack用)
const byte eventPin = 19;
const byte swPin = 23;
const byte ledPin = 18;

static TimerHandle_t oneShot = NULL;
boolean okFlag;

volatile int nEvent;
volatile bool countOK;
volatile byte ledSTAT;

void oneShotCallback(TimerHandle_t xTimer) {
  countOK = false;
  detachInterrupt(digitalPinToInterrupt(eventPin));
}

void eventCounter() {
  if (countOK) {
    nEvent++;
  }
}

void setup() {
  Serial.begin(115200);
  while(!Serial) {};

  pinMode(ledPin, OUTPUT);
  ledSTAT = LOW;

  countOK = false;
  nEvent = 0;
  oneShot = xTimerCreate("one-shot", 1000, pdFALSE, (void *)0, oneShotCallback);
  if (oneShot == NULL) {
    Serial.println("SOFTWARE TIMER CREATION FAIL.");
    okFlag = false;
  } else {
    okFlag = true;
    pinMode(eventPin, INPUT);
  }
}

void loop() {
  digitalWrite(ledPin, ledSTAT);
  ledSTAT = (ledSTAT == LOW) ? HIGH : LOW;
  Serial.println(nEvent);
  if (okFlag && (!countOK) && (digitalRead(swPin) == 0)) {
    nEvent = 0;
    countOK = true;
    attachInterrupt(digitalPinToInterrupt(eventPin), eventCounter, RISING);
    xTimerStart(oneShot, portMAX_DELAY);
  }
  delay(1000);
}