SSブログ

Arduinoでの時間管理 [Arduino]

Arduinoにおける時間の扱いを Arduino IDE 1.0.5-r2 (Windows版) で調べてみました。
time.png
C:\Program Files (x86)\Arduino\hardware\arduino\cores\arduino にある wiring.c を覗いてみます。

ArduinoはTimer0という8ビットのタイマ/カウンタを使用して時間の管理をしています。
CPUクロックを64分周して0から255までカウントしていきオーバーフローで割り込んで時間を計算していっているようです。

64分周でさらに256回に1回割り込むと、割り込みがかかるのにかかるクロック数は、64 x 256 clkです。
16MHzだと、割り込みがかかるのにかかる時間は、(64 x 256) / 16000000 sec = 1024 μsec となります。
割り込みのたびに、この 1024 を 1000で割った商(=1)をミリ秒のところに足していきます。
また余り(=24)を別に足していき、1000をになったらミリ秒に1を足す、、と思いきや、、余りを3ビット右シフト(=8で割る)したもの(=3)が、1000を3ビット右シフト(=8で割る)したもの(=125)になったらミリ秒に1を足していく。(3ビット右にシフト(=8で割る)しても整数であることと、8ビットでの処理ができるのでこのようにしたのだと思います。)
こんな感じで、mills()はできています。

micros() は、Timer0がオーバーフローした回数と、Timer0のカウンタを使ってできています。
CPUクロックの64分周ごと、つまり (1/16000000) x 64 sec = 4μsecごとにカウントアップしていくわけで、
micros()関数が呼ばれたときにはトータルで「(オーバーフローした回数 x 256) + 現在のカウンタ」回カウントされているので、これに4μsecをかけると、経過したμ秒が得られる。これが micros() の正体のようです。

さて、delay() はどうなっているのかというと、呼び出された時間と現在の時間をμ秒単位で(micros()を呼び出して)比較し、1000以上になったらカウントダウンしていき0になるまでループするというあんばいです。

問題は、delayMicroseconds() です。
CPUクロックが 20MHz, 16MHz, 8MHzの場合しかまともに動作しません。
これは、1サイクルで4クロック消費するループ(つまり、20MHzなら 1/5 μsec, 16MHzなら 1/4 μsec, 8MHzなら 1/2 μsecを消費するループ)をそれぞれ、必要とするμ秒数の5倍、4倍、2倍回すことでできています。
ですから ATtiny45 の 1MHz など 16MHz未満のものはすべて 8MHz として動作当然するようになっていうるので、うまいくはずがありません。
関数の呼び出しなどに伴うオーバーヘッドなども考慮された作りにはなっています。

同じような時間を止める関数がArduinoのシステムとは別にAVRのライブラリにあります。
C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\util にある delay.h , dalay_basic.h を覗いてみます。

dalay_basic.h には、_delay_loop_1() と _delay_loop_2() という2つの関数が定義されています。

_delay_loop_1() は、パラメータに8ビットの符号なし整数型でループ回数を指定、1ループあたり3クロック消費。
_delay_loop_2() は、パラメータに16ビットの符号なし整数型でループ回数を指定、1ループあたり4クロック消費。
オーバーヘッドは、1MHz動作のCPUだとそれぞれ、768μsec, 262.1msecに達するとコメントにありました。

dalay.h には、_delay_us() と _delay_ms() という2つの関数が定義されています。

_delay_us() は、F_CPU を 3000000 で割ったものに、μ秒をかけたものが、1未満なら _delay_loop_1() を1回ループ。255より大きければ(8ビットに収まらないならば)精度をミリ秒に落として _delay_ms() で処理。それ以外なら、_delay_loop_1() で処理するというもの。
つまり、16MHz の場合、256 x 3 / 16 = 48 μ秒以上だとミリ秒単位に制度がおちて、さらにオーバーヘッドについては無考慮です。
ArduinoのdelayMicroseconds()だと使えるクロックが限定されますが、よっぽど正確です。

_delay_ms() は、F_CPU を 4000 で割ったものに、ミリ秒をかけたものが、1未満なら(パラメータが1以上でF_CPUが4kHz以上ならあり得ませんが、、) _delay_loop_2() を1回ループ。65535より大きければ(16ビットに収まらないならば)F_CPUを40000で割ったものを _delay_loop_2() でループさせ(=1/10msec消費する)、それをさらにμ秒の10倍でループするという処理。それ以外なら、_delay_loop_2() でそのまま処理するというもの。_delay_loop_2() には、16bit(=65535)までしかパラメータに渡せないので、16MHzの場合、65536 x 40000 / 16000000 = 16.384 秒未満しか使えないようです。

あと、時間に関係する関数としては、pulseIn() があります。
C:\Program Files (x86)\Arduino\hardware\arduino\cores\arduino にある wiring_pulse.c を覗いてみます。
こちらも信号が変化するまでループをします。ループの回数を測定しそのループにかかるクロック数をかけることでパルスの長さを得ることができます。

消費クロック数から時間を測る方法は、割り込みによる影響で誤差がでるので注意が必要なようです。

タグ:時間 Delay
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。