第1回目は処理タイミングのお話。
組み込みプログラミングではほぼ必須と言っていいぐらいタイミング処理が必要であり、delay()により何もしない期間を作るのが一般的かつ簡単な方法であるが、delay()を使うと無駄に時間を消費するだけでなく正確なタイミング処理を不可能にしてしまう。
1秒間隔でLEDを点滅するコードの例を参考に説明すると、
1 2 3 4 5 |
loop() { digitalWrite(LED_PIN, !digitalRead(LED_PIN)); delay(1000); } |
のようにするのが一般的ではないかと思うが、このコードでは正確なタイミング処理は無理である。
delay()自体に問題があるわけではなく、delay()以外の実行にわずかながらではあるが時間がかかるためである。delay()以外のコードの実行に10usの処理時間がかかると仮定した場合、1000回繰り替しただけで10us x 1000 = 10msもの遅延が生じてしまう。エアコン等の赤外線リモコンの発光処理では最大で300パルス弱も正確にLEDを点滅させなければならず、一回のパルス毎に10usの誤差が発生してしまった場合、最後のバルスは、10us x 300 = 3msもタイミングがズレてしまうことになる。これでは誤動作して当然というしかないが、公開されている赤外線リモコンのコードの多くが上記のような問題を抱えているという現実は残念というしかない。
組み込みプログラミングでは、他の処理も同時に処理するのが普通であるため、そもそも正確なタイミングで処理するなど至難の業とも言えるのだが、時間誤差を積み重ねないというだけなら簡単に実現可能だ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
uint32_t timestamp; // 8bit-CPUかつ65秒以内ならuint16_t型にしたほうが効率的 setup() { timestamp = millis(); // ここがスタートタイミングになる } loop() { if (millis() - timestamp >= 1000) { timestamp += 1000; // 次回の処理タイミングを計算する。 digitalWrite(LED_PIN, !digitalRead(LED_PIN)); } } |
最初のコードと同じく1秒間隔でLEDを点滅するコードであるが、このコードの場合、時間誤差の積み重ねは全く発生しない。1秒経過したという判断タイミングにパラツキが発生したとしても、次回の処理タイミングを決定するtimestamp変数の計算には誤差が生じないためである。この方法は、複数の処理タイミングを作ることも容易にできるのでお勧め。
少しでも皆さんの参考になれば幸いです。