avr-libcには、<util/atomic.h>
というものがあります。最初の反応は、AVRは単一コアなのになぜatomicが必要なのかということですので、説明を見てみました。
このヘッダーファイルのマクロは、アトミックまたは非アトミックに実行されることが保証されたコードブロックに対応しています。この文脈での「アトミック」という用語は、対応するコードが割り込みを受けることができないことを指します。
マイクロコントローラでも、さまざまな割り込みを使用することができるため、プログラムの実行は途中で中断される可能性があります。atomic
を使用することで、その中に含まれるコードセグメントが割り込みに影響を受けないように保証することができます(一時的に割り込みを停止します)。
ATOMIC_BLOCK(ATOMIC_FORCEON) {
// 重要な処理を行う
}
直感的には、cli()
とsei()
の組み合わせに似ていますが、ドキュメントの説明によると、さらに多くのことが行われているようです。内部の実装は次のようになっています:
#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
__ToDo ; __ToDo = 0 )
type
を引数として受け取ることができ、ATOMIC_RESTORESTATE
とATOMIC_FORCEON
の2つの引数を渡すことができます。
#define ATOMIC_RESTORESTATE uint8_t sreg_save \
__attribute__((__cleanup__(__iRestore))) = SREG
これはsei
の効果に似ていますが、より興味深いのは__attribute__
の使用です。GCCでは、変数や関数がどのように保存されるべきかを決定するために__attribute__
を使用することができます。これはlibc
の実装で定義されている動作であり、AVRの場合は次のように書くことができます:
#include <avr/pgmspace.h>
const int my_var[2] PROGMEM = { 1, 2 };
ここでのPROGMEM
は実際には__attribute__
です:
#ifndef __ATTR_PROGMEM__
#define __ATTR_PROGMEM__ __attribute__((__progmem__))
#endif
通常、変数はメモリに格納されますが、マイクロコントローラではメモリが非常に制限されています。ただし、一般的なコンピュータとは異なり、通常、マイクロコントローラのコードは事前に書かれ、コンパイル後にプログラムメモリ(フラッシュメモリ)に直接書き込まれます。プログラムコードが少ない場合、フラッシュメモリに余分なスペースが残ることがあり、その場合はPROGMEM
を使用して変数をフラッシュメモリに格納し、メモリ使用量を減らすことができます。読み取り時には、pgm_read_word
を使用して変数を読み取ることができます。
ATOMIC_BLOCK
に戻ると、展開すると次のようなコードになります:
for (uint8_t sreg_save __attribute__((__cleanup__(__iRestore))) = 0; __ToDo = __iCliRetVal(); __ToDo ; __ToDo = 0) {
// 重要な処理を行う
}
このようにして、割り込みの影響を受けずにコードの実行が保証されます。forループ
を使用するこのテクニックは、驚くべきものであり、このように応用することは全く考えていませんでした。