RepeatTimer

はじめに

ESP32のハードウェアタイマーを利用して、1秒ごとに割り込みハンドラを起動します。また、0番ピンをLOWにするとタイマを停止します。

プログラム

定義等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/*
 Repeat timer example

 This example shows how to use hardware timer in ESP32. The timer calls onTimer
 function every second. The timer can be stopped with button attached to PIN 0
 (IO0).

 This example code is in the public domain.
 */

// Stop button is attached to PIN 0 (IO0)
#define BTN_STOP_ALARM    0

hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

volatile uint32_t isrCounter = 0;
volatile uint32_t lastIsrAt = 0;
 

BTN_STOP_ALARMは、タイマーを停止するためのスイッチを接続するピン番号(ここでは0)です。

timerは、hw_timer_t型へのポインタで、タイマーを表す変数です。

timerSemaphoreは、タイマー割り込みが発生したかどうかを表すセマフォです。割り込みハンドラの中で操作するグローバル変数なので、volatileを付与して、他のコンテキストで操作される可能性があることをコンパイラに伝えます(volatileは排他制御を実現する機構ではありません)。

timerMuxは、排他制御を行うための変数です。

isrCounterは、タイマー割り込みが発生した回数を保存する変数で、lastIsrAtは、最後にタイマー割り込みが発生した時刻(millis()の値)を保存する変数です。

onTimer()

21
22
23
24
25
26
27
28
29
30
31
void ARDUINO_ISR_ATTR onTimer(){
  // Increment the counter and set the time of ISR
  portENTER_CRITICAL_ISR(&timerMux);
  isrCounter++;
  lastIsrAt = millis();
  portEXIT_CRITICAL_ISR(&timerMux);
  // Give a semaphore that we can check in the loop
  xSemaphoreGiveFromISR(timerSemaphore, NULL);
  // It is safe to use digitalRead/Write here if you want to toggle an output
}
 

この関数は、タイマー割り込みが発生したときに呼び出される関数です。ARDUINO_ISR_ATTR属性を付けることで、このコードがIRAM(Instruction RAM)に配置されるようにしています。esp32では、割り込みハンドラはIRAMに配置する必要があるようです。

ARDUINO_ISR_ATTRは、esp32-hal.hで、定義されています。

portEnter_CRITICAL_ISR()により、排他制御を行います。ここでは、isrCounterをインクリメントし、lastIsrAtにmillis()の値を代入します。その後、portEXIT_CRITICAL_ISR()を呼び出し、排他制御を終了します。

その後、xSemaphoreGiveFromISR()により、セマフォを解放します。これにより、xSemaphoreTake()を使って、セマフォを獲得することができるようになります。

xSemaphoreGiveFromISR()以外に、xSemaphoreGiveFrom()という関数も、セマフォを解放する関数ですが、割り込みハンドラの中では、xSemaphoreGiveFromISR()を使う必要があるとのことです。

setup()

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void setup() {
  Serial.begin(115200);

  // Set BTN_STOP_ALARM to input mode
  pinMode(BTN_STOP_ALARM, INPUT);

  // Create semaphore to inform us when the timer has fired
  timerSemaphore = xSemaphoreCreateBinary();

  // Use 1st timer of 4 (counted from zero).
  // Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more
  // info).
  timer = timerBegin(0, 80, true);

  // Attach onTimer function to our timer.
  timerAttachInterrupt(timer, &onTimer, true);

  // Set alarm to call onTimer function every second (value in microseconds).
  // Repeat the alarm (third parameter)
  timerAlarmWrite(timer, 1000000, true);

  // Start an alarm
  timerAlarmEnable(timer);
}
 

pinMode()によりBTN_STOP_ALARMを入力モードに切り替えます。

次に、割り込みハンドラが呼ばれた際の通信のために利用するセマフォ(バイナリセマフォ)の作成と、タイマーの設定を行います。

xSemaphoreCreateBinary()により、バイナリセマフォを作成します。このセマフォは、最初に、xSemaphoreGive()により開放しないと、獲得できません。実際には、onTimer()の中で、xSemaphoreGiveFromISR()を使って開放しています。

timerBegin()は、タイマーの初期設定を行います。タイマー番号0、分周比80を設定しています。

timerAttachInterrupt()は、タイマーに割り込みハンドラを設定します。

timerAlarmWrite()で、タイマーの設定値(割り込みのタイミング)を設定します。クロックの周波数は80MHzで、timerBegin()で、分周比を80に設定しているので、周波数は、80,000,000 / 80 = 1,000,000 Hz となるため、1チックあたり、1マイクロ秒となります。ここで、第2引数に1000000を設定しているので、タイマーは1秒ごとに起動されます。第3引数でtrueを設定すると、周期的にタイマーが起動されます。

最後に、timerAlarmEnable()で、タイマーを開始します。

loop()

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
void loop() {
  // If Timer has fired
  if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE){
    uint32_t isrCount = 0, isrTime = 0;
    // Read the interrupt count and time
    portENTER_CRITICAL(&timerMux);
    isrCount = isrCounter;
    isrTime = lastIsrAt;
    portEXIT_CRITICAL(&timerMux);
    // Print it
    Serial.print("onTimer no. ");
    Serial.print(isrCount);
    Serial.print(" at ");
    Serial.print(isrTime);
    Serial.println(" ms");
  }
  // If button is pressed
  if (digitalRead(BTN_STOP_ALARM) == LOW) {
    // If timer is still running
    if (timer) {
      // Stop and free timer
      timerEnd(timer);
      timer = NULL;
    }
  }
}

xSemaphoreTake()で、セマフォを取得します。取得に成功した場合(=割り込みハンドラであるonTimer()が呼ばれて、xSemaphoreGiveFromISR()でセマフォを解放した場合)、{}の中を実行します。

isrCounterとlastIsrAt割り込みハンドラ内で変更される可能性のある変数なので、排他制御を行い操作します。

digitalRead()でBTN_STOP_ALARMがLOWになっているのを検出すると、timerEnd()を呼び出し、タイマーを停止します。

その他

以下は、FreeRTOSのAPIです。

  • portEnter_CRITICAL_ISR()
  • portEXIT_CRITICAL_ISR()
  • xSemaphoreGiveFromISR()
  • xSemaphoreTake()

バージョン

Hardware:ESP-WROOM-32
Software:Arduino core for the ESP32 2.0.4

最終更新日

November 1, 2022

inserted by FC2 system