割り込み関数まとめ概要Arduino環境ではTickerクラスを利用したタイマーと、より高精度の割り込みを利用したタイマー処理があり、割り込みを利用したタイマー処理を調べてみました。タイマーの概要ESP32には4つのタイマーがあり、自由に利用することができます。タイマーの原理としては、指定したクロック数が経過したらカウントして、特定カウントになったらタイマー割り込みをかけて、割り込み関数を呼び出すような動きになっています。そのため、クロック数とカウント数の掛け算でタイマーの間隔を制御しています。あとの例では80クロック×1000000カウント=80Mクロックでタイマー割り込みが発生しています。タイマーは通常80MHzで動いていますので、1秒間隔での実行になります。タイマーの動作周波数はCPU周波数に応じて変化します。CPU周波数と同一ではなく、ペリフェラル周波数と呼ばれる周辺機器の周波数です。10MHz上記の対応になっていて、ペリフェラル周波数の上限は80MHzで、それ以下の場合にはCPU周波数と同一です。1カウントの時間をわかりやすい1マイクロ秒に設定したい場合には、ペリフェラル周波数に合わせて変える必要があります。変更していない場合、80Mクロックで設定していたタイマーを、ペリフェラル周波数40MHzで動かすと2秒になります。{}最小構成のLチカです。このよう処理は実際にはTickerクラスを利用したほうが良いです。true);timerBegin()でタイマーを作成しています。最初の引数はタイマーのIDです。ESP32はタイマーが4つあり、0-3までのタイマーを利用できます。2つ目が何クロックでカウントをするかの数値になります。80の場合、80クロックで1カウントします。getApbFrequency()/1000000で、どのCPU周波数でも1マイクロ秒に固定化することができます。3つ目がカウンターをカウントアップする場合にはtrue。カウントダウンする場合にはfalseになります。通常はtrueのみしか使わないと思います。true);最初の引数は設定するタイマー。2つ目は割り込み時に呼ばれる割り込み関数。3つ目が割り込み検知方法です。{割り込み関数は上記のようにIRAM_ATTRが追加されています。Forumこのオプションは関数を高速なIRAM上に読み込みことを保証させるもののようです。おのオプションが無いと、フラッシュ上に配置される可能性があり、その場合には低速動作になり、結果的にクラッシュする可能性があるみたいです。単純なプログラムの場合にはおそらく指定しなくても動きますが、基本的には割り込み関数は高速である必要があり、IRAM_ATTRをつけてください。3つ目の割り込み検知方法は、trueの場合にはエッジトリガー、falseの場合にはレベルトリガーになります。タイマーの場合にはカウントアップしていって、指定カウントに変わったところを検知するので、エッジトリガーのtrueを指定します。true);割り込みが発生したときの、トリガー条件を設定します。最初の引数は設定するタイマー。3つ目がautoreloadで、trueの場合には定期実行、falseの場合には1ショットの実行になります。timerAlarmEnable(timer);上記でタイマーを有効化しています。timerAlarmDisable()という関数で停止させることもできます。}}実際のタイマー利用例です。マルチタスクや割り込みを使う場合には、通常のコールバックと違い意図しないタイミングで変数を更新される可能性などがあり、排他処理を行う必要があります。説明すると、他の人が使っていないのを確認してから、利用を宣言して、使い終わっていたら、開放する処理です。@hideakitai上記に詳しく書いてありますが、スケッチ例ではバイナリセマフォを利用しています。記事には書いていませんが、キューに似ているが、1つしか保存しない通知機能もあるのでどこかでマルチタスクでの注意点をまとめたいと思っています。このスケッチでは、1秒間隔でシリアルに時間を出力し、GPIOがLOWになるとタイマーを停止する動きになります。M5StickCの場合にはGPIO37がHOMEボタンなので、書き換えてから実行してみてください。この動き自体はGPIOにLOWになったら割り込み関数を実行したほうが適して、あまり良くないサンプルに見えます。xSemaphoreCreateBinary();バイナリセマフォとは、一人だけセマフォを取得できる排他制御です。デフォルトは誰もセマフォを取得できず、誰かが許可した場合にだけ取得可能です。timerAlarmEnable(timer);1マイクロ秒単位のタイマーを作成して、1000000カウント(1秒)したらonTimer()関数を割り込みで呼び出すタイマーを実行しています。ここは最小構成スケッチと同じです。pdTRUE){ここはセマフォの処理になります。0はタイムアウトの時間を表し、この場合にはセマフォを取得できなかった場合には即時終了します。portMAX_DELAYを指定すると、セマフォが取得できるまで、ブロッキングして待ちます。portEXIT_CRITICAL(&timerMux);セマフォが取得できた場合には、タイマー値を取得してからシリアルに出力しています。portENTER_CRITICAL()とportEXIT_CRITICAL()は割り込み禁止で、この関数に囲まれているクリティカルセクションと呼ばれる処理を実行中には、タイマー割り込みなどが発生しません。取得しようとしている変数が、タイマー割り込みで変更される可能性があるものなので、取得途中で変更されて困るものは、割り込み禁止を設定します。}ここは単純にGPIOがLOWの場合にはタイマーを終了しています。timerSemaphore;volatile宣言がついています。割り込みで利用する変数にはすべてこの宣言を利用する必要があります。この宣言があると、変数の置き場所がいろいろな場所から変更されても大丈夫な領域に確保されます。portMUX_INITIALIZER_UNLOCKED;割り込み禁止も、ミューテックスと呼ばれる排他制御です。初期値は常にportMUX_INITIALIZER_UNLOCKEDで排他制御されていない状態です。0;isrCounterが割り込み発生回数、lastIsrAtが最後に割り込みが発生したときの経過時間が入っている変数です。両方とも割り込みから変更される可能性があるのでvolatile宣言が付いています。output}割り込み関数では、通常と使える関数群が違いますので注意しましょう。_ISRが最後につく関数群がある場合には、そちらを利用しないと正しく動きません。単純な処理の場合には_ISRの割り込み中用の関数群ではなくても動いてしまうので、注意してください。複雑な割り込みが発生した場合など、意図しないタイミングでハングアップする可能性があります。portENTER_CRITICAL_ISR()とportEXIT_CRITICAL_ISR()で囲んだ間で、タイマー変数の更新を行っています。この処理であれば、おそらく割り込み禁止をしなくても大丈夫そうですが、割込み禁止にしているようです。NULL)で、タイマー用のバイナリセマフォに許可を与えています。NULLに指定しているところには、本来以下のように値をいれた変数にする必要があります。);この関数を実行後にxHigherPriorityTaskWokenの中身を確認し、pdTRUEに変更された場合には、セマフォ取得待ちでブロックだった優先度の高いタスクが実行されたのがわかります。ブロックしていると、かなりテクニカルな処理が必要になるので、なるべくノンブロックで動かすことをおすすめします。まとめ割り込みは非常に難しいです。今回の例ではありませんでしたが基本的にはマルチタスクを意識したコーディングが必要になります。使う関数群なども違いますので注意が必要です。このへんの処理はArduinoやESP32ではなく、内部で利用しているFreeRTOSの知識が必要になります。日本語の情報も少なく、体系的に勉強するのは難易度が高そうです。コメントをキャンセルメールアドレスが公開されることはありません。date*管理者承認後にページに追加されます。公開されたくない相談はその旨本文に記載するかTwitterなどでDM投げてください。スパム対策として、日本語が含まれない投稿は無視されますのでご注意ください。その1ESP32のGPIO入力について関連記事はこちらです。関連記事はこちらです。Themegraphy。