Intel Edison for Arduino の delayMicroseconds の精度が悪い件
Edisonで赤外線LEDをつかってリモコンを作っているが、どうにもうまく行かない。
調べた結果、delayMicroseconds
の精度が悪すぎて正しくリモコンの信号が出力できていなかったようだ。
一般的なリモコンの周波数はだいたい38kHz。この周波数にあわせてLEDを明滅すればリモコンとして機能する。いろいろフォーマットがあるが、NECのフォーマットが割と採用されている。このNECフォーマットでは、duty比は1/3のようだ。
この書籍のリモコンのコードを参考に、以下の様なコードで再現しようとしていた。
#define LED_PIN 15
digitalWrite(LED_PIN, HIGH);
delayMicroseconds(9);
digitalWrite(LED_PIN, LOW);
delayMicroseconds(13);
そもそも、NECフォーマットが38kHzなら、1周期が 約26us なのだが、ズレが有るように見えるが…。
digitalWriteが遅い?
最初はdigitalWriteが遅いのではないかと考えていた。
上記の書籍のコードでは、元々はbitSet
を利用してATmega上のメモリを直接操作することでIOの状態を操作してリモコンを実現していた。しかし、Intel EdisonのArduinoではbitSet
自体が定義されていないので、digitalWrite
を代わりに利用して実装したので、それが原因だと考えていた。
Qiitaにも以下のような投稿がありdigitalWriteが遅いのかなと疑った。
http://qiita.com/yoneken/items/7b5e3e35d483a27726e1p
実際は、ArduinoのdigitalWriteは高速に動作するようだ。
https://communities.intel.com/thread/56516
以下のようなコードを書いて計測してみた
void loop() {
Serial.println("Start");
signed long start_time = micros();
for(int i=0; i < 10000; i++) {
digitalWrite(16,HIGH);
digitalWrite(16,LOW);
}
signed long delay = micros() - start_time;
Serial.println(delay/10000.0);
Serial.println("End");
}
結果は、以下のような感じになった
Start
0.70
End
Start
0.70
End
Start
0.71
End
大体0.7us程度でon/off出来るようだ。
というわけで、digitalWrite
を疑ってみたが外れてしまった。
delayMicroseconds を疑ってみる
digitalWriteは十分に高速のようなので、じゃあ実は全体の速度は問題なくて赤外線LEDの特性とか出力とかの問題かなぁと考え始めた。
そのまえに、念のため1周期が正しく表現できてるかを確認してみることにした。
以下の様なコードで実験してみた。
void loop() {
Serial.println("Start");
signed long start_time = micros();
for(int i=0; i < 10000; i++) {
digitalWrite(16,HIGH);
delayMicroseconds(9);
digitalWrite(16,LOW);
delayMicroseconds(13);
}
signed long delay = micros() - start_time;
Serial.println(delay/10000.0);
Serial.println("End");
}
実行結果
Start
209.38
End
Start
211.08
End
Start
210.22
End
まさかのズレ。
26 us が 210 usとかになっているとは…。
void loop() {
Serial.println("Start");
signed long start_time = micros();
for(int i=0; i < 10000; i++) {
delayMicroseconds(1);
}
signed long delay = micros() - start_time;
Serial.println(delay/10000.0);
Serial.println("End");
}
こんなコードで測定してみると
Start
93.36
End
Start
93.39
End
Start
93.11
End
犯人はどうみても、delayMicroseconds
です。
全くあてにならない精度ですね。
自作のwait関数を作ってみる
スケッチは、Intel Edison上のLinux上の1プロセスとして動作しているのでdelayMicrosecondsが呼び出されるとコンテキストスイッチが発生して、大幅にずれが発生しているのだろう。
そこで、busyWaitを使った時間調整関数を適当に作ってみて、調整してみる。また、digitalWrite
が十分に速度があるので、時間の設定も変えてみて再度測定してみる。
void wait(signed long wait_time) {
signed long start_time = micros();
while ( micros() - start_time < wait_time) {};
}
void loop() {
Serial.println("Start");
signed long start_time = micros();
for(int i=0; i < 10000; i++) {
digitalWrite(16,HIGH);
digitalWrite(16,LOW);
}
signed long delay = micros() - start_time;
Serial.println(delay/10000.0);
Serial.println("End");
}
計測結果は
Start
26.32
End
Start
26.25
End
Start
26.27
End
かなり良い精度になった。
リモコンとしては?
タイミングの制御に上記のwait関数を利用するようにしたら、うまく動作するようになった。
結論
Intel Edisonで時間の制御が必要そうなものを作るときにはdelayMicroseconds
を使って制御してはならない。