Arduino、IoTのおかげでファームウェアの活躍の場が増えています。
ファームウェアは時間に対してシビア。モーターの回転パルスを取得して速度検出、タイマー割り込みで一定間隔で発光など。
この外部信号処理の扱いを間違えると、機能追加でプログラムが大きくなった時に「あれ、信号、取得できてなくない?」という、時限爆弾になります。
パルス数の取得
目的
外部からのパルス数を数える(たとえば、モーター回転速度など)
NG1: メインルーチンで頑張る
割り込みを使わなくてもできる!というモチベーションで行うもの。
int pin = 13;
volatile int pin_state = LOW; // パルス入力ピンの状態保持
unsigned int cnt = 0; // パルスカウンター
void setup()
{
pinMode(pin, INPUT);
}
void loop()
{
/*
色々な処理
*/
int pin_now;
pin_now = digitalRead(pin); // パルス信号入力ピン読み込み
if ( pin_now != pin_state ){
// ピンの状態が遷移
pin_state = pin_now;
if ( pin_now == HIGH ){
cnt++; // ピンがLOWからHIGHになったので、カウントアップ
}
}
}
何が問題か
メインルーチン(上記の loop 関数)で処理を行う場合、色々な処理 が問題になる可能性があります。
作り始めはちゃんと機能していても、機能追加でメインルーチンはどんどん重くなる方向です。そしてある時、この色々な処理をしているうちに、パルスが立ち上がり、立下がる、という状況が起きます。認識できなくなります。
NG2: 割り込み内で色々な処理
「xx回に一回はこれをやる」といったとき、割り込み内でやるパターン
int pin = 13;
volatile int pin_state = LOW;
void pluse()
{
cnt++;
if ( cnt == 10 ){
/*
色々な処理
*/
}
}
void setup()
{
attachInterrupt(0, pluse, RISING);
}
void loop()
{
}
何が問題か
これも、色々な処理 が、次のパルスが来るまでに重くなれば間に合わなくなります。
特に、特にこのパターンは色々形を変えて、入り込んできます。油断すると、処理を書いてしまう場所です。
おすすめパターン
割り込み処理内はとにかくシンプルに。基本はフラグ処理。必要な変数はglobal変数に格納
boolean flag = false;
volatile int pin_state = LOW;
void pluse()
{
cnt++;
if ( cnt == 10 ){
flag = true;
}
}
void setup()
{
attachInterrupt(0, pluse, RISING);
}
void loop()
{
if ( flag )
{
/*
色々な処理
*/
flag = false;
}
}
まとめ
言いたいことは簡単ですが、
「プログラムはメインルーチンで。割り込み処理内は最低限の処理のみ。」
です。
この基本からずれたコードを仕事でも見かけます。後々問題を起こす危険のある時限爆弾は、未然に防ぎたいです。