Intel Edison for Arduino の delayMicroseconds の精度が悪い件

投稿者: | 2015年7月28日

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を使って制御してはならない。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です