説明
volatileは、変数修飾子と呼ばれるキーワードで、通常、変数のデータ型の前に使用され、コンパイラや後続のプログラムがその変数をどのように扱うかを変更するために使用されます。
変数をvolatileと宣言することは、コンパイラへの指示となります。コンパイラは、C/C++コードをArduinoのAtmegaチップの実際の命令であるマシンコードに変換するソフトウェアです。
具体的には、変数をレジスタからではなく、RAMからロードするようにコンパイラに指示します。レジスタとは、プログラムの変数を保存したり操作したりするための一時的なメモリの場所です。特定の条件下では、レジスタに格納された変数の値が不正確になることがあります。
同時に実行されているスレッドなど、その変数が登場するコードセクションの制御を超えた何かによって変数の値が変更される可能性がある場合は、揮発性を宣言する必要があります。Arduinoでは、このような事態が発生する可能性があるのは、割り込みサービスルーチンと呼ばれる、割り込みに関連するコードセクションだけです。
intまたはlongの揮発性変数
揮発性変数が1バイトよりも大きい場合(16ビットのintや32ビットのlongなど)、マイコンは8ビットのマイコンであるため、一度に読み取ることができません。つまり、メインのコードセクション(ループなど)が変数の最初の8ビットを読み込んでいる間に、割り込みによってすでに2番目の8ビットが変更されている可能性があるということです。これにより、変数の値がランダムになってしまいます。
対策としては
変数が読み込まれている間は、割り込みを無効にして、ビットを変更できないようにする必要があります。これを行うにはいくつかの方法があります。
LANGUAGE noInterrupts
ATOMIC_BLOCKマクロを使用する。ATOMIC_BLOCKマクロを使用します。アトミック演算は単一のMCU演算であり、可能な限り最小の単位です。
コード例
volatile修飾子は、state変数の変更がloop()内ですぐに確認できるようにします。volatile修飾子がないと、関数に入るときに状態変数がレジスタにロードされ、関数が終了するまで更新されなくなる可能性があります。
// Flashes the LED for 1 s if the input has changed // in the previous second. volatile byte changed = 0; void setup() { pinMode(LED_BUILTIN, OUTPUT); attachInterrupt(digitalPinToInterrupt(2), toggle, CHANGE); } void loop() { if (changed == 1) { // toggle() has been called from interrupts! // Reset changed to 0 changed = 0; // Blink LED for 200 ms digitalWrite(LED_BUILTIN, HIGH); delay(200); digitalWrite(LED_BUILTIN, LOW); } } void toggle() { changed = 1; }
マイコンの8ビットデータバスを超えるサイズの変数にアクセスするには、マクロ「ATOMIC_BLOCK」を使用します。ATOMIC_BLOCKマクロは、アトミックな操作で変数が読み込まれることを保証するもので、読み出し中に内容が変更されることはありません。
#include <util/atomic.h> // this library includes the ATOMIC_BLOCK macro. volatile int input_from_interrupt; // Somewhere in the code, e.g. inside loop() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { // code with interrupts blocked (consecutive atomic operations will not get interrupted) int result = input_from_interrupt; }