volatileの使い方②
volatileの使い方としてvolatileの使い方①で説明しました。ここではその続きとして他のパターンを見てみたいと思います。
Pattern 5
unsigned int iTimer;
#pragma interrupt
void Interrupt1msecTimer(void){
if(iTimer){
iTimer--;
}
}
void PortWait(void){
iTimer = 10; /*10msecの待ち処理*/
while(1){
if(!iTimer) break;
}
}
上記はソフトウェアウェイトのタイマ割り込みを使用したパターンです。割り込み関数の表現の仕方はマイコンによってさまざまありますが、ここではInterrup1msecTimerという関数が1msec毎に呼び出される事を想定しています。このPortWait関数内にてiTimerにレジスタが割りあたってしまった場合、割り込み関数でディクリメントされずにiTimer = 10で固定され、PortWait関数内で無限ループにはまってしまいます。こういった場合はvolatile unsigned iTimer; にしてください。
Pattern 6
unsigned int iTimer;
unsigned int iTask=1;
#pragma interrupt
void Interrupt1msecTimer(void){
if(iTimer){
iTimer--;
}
}
bool DeviceControlSequence(void){
bool bRtn = false;
switch(iTask){
case 1:
…
iTimer = 10;
iTask++;
break;
case 2:
if(!iTimer){
…
iTask++;
}
break;
case 3:
…
break;
default:
ASSERT();
break;
}
return(bRtn);
}
上記は他デバイスを制御するようなプログラムに良く見られる、シーケンス処理もしくはタスク処理などと呼ばれるものです。(もっとも一般的な呼び名はなんなのか良くわかりません) 他デバイスがあると、通信に次のコマンドを発行するためのWaitを持たせる事が良くあります。しかしDeviceControlSequence内のiTimerにレジスタが割りあたってしまった場合はどうなるでしょうか?ここでもiTimer=10固定となりcase 2以降の処理に進まなくなってしまいます。結局Pattern5もPattern6も同じ事なのですが、タイマ割り込みの関数で使用するグローバル変数はvolatileをつけるのを常習化させておくと良いかと思います。
さて、意図と違うRAMの使い方をされてしまう(レジスタが割りあたってしまう)プログラムを見てきましたが、こちらの意図と違うからと言って常にvolatileが有効なわけではありません。次のプログラムを見てください。
Pattern 7
struct StBuffer{
unsigned char byTxdBuffer[256];
unsigned char byRxdBuffer[256];
unsigned char byTransBuffer[512];
};
StBuffer stBuffer;
void initialize(void){
unsinged char *pbTemp = (unsigned char)&stBuffer;
unsigned short i;
for(i=0; i < sizeof(StBuffer); i++ ){
*pbTemp = 0;
}
}
巨大な構造体(組み込みの世界ではそこそこ巨大です。)で宣言された変数stBufferをinitialize関数内で0初期化しています。ここでpbTempポインタの指し示す空間にスタックが割り当てられてしまう場合があります(通常であればそれはグローバル変数stBufferの先頭アドレスのはずにも関わらず)。そうなった場合この関数に入った途端1kのスタックを消費する事になります、スタックの資源もカツカツですからこの事によりスタックオーバーフローが起こってしまう場合があるので望ましいコンパイル結果とは思えません。通常であればstBufferがグローバル変数なのでRAMのアドレスをpbTempに格納し4byteしかスタックを使いそうにないのですが、コンパイラによってはご丁寧にスタックを一回確保してから丸写しという処理をする場合があるようなのです。この場合最適化の影響ではなかった為、volatileをつけても意図した処理に変更する事ができませんでした。コンパイラの仕様によるコンパイル動作はvolatileで回避できません、もはや処理を変えるかスタックを増やすしかありません。
まとめ
・タイマ割り込み処理に使用する、時間計測のグローバル変数には無条件にvolatileをつける。
・コンパイラ仕様によるコンパイル結果にはvolatileは無効となる。