Arduino-UNOでのdelayMicrosecondsの設定は16383までだ!
Arduinoでまたもや注意点を。
落とし穴にはまりました。
Arduinoでの時間待ち関数、あれこれ用意されていますが
・μ秒単位の設定で。
・ソフトのループで処理してる。
・割り込みを止める処理内でも使える。
という「delayMicroseconds」が便利かと。
delay()はタイマー0のオーバーフロー割り込みを使ってるんで、
ちょいキラいで、簡単なテスト的プログラムの他は私は使いません。
delayMicrosecondsの関数プロトタイプを見ると、
void delayMicroseconds(unsigned int us)
となっていて、「unsigned int」、16ビットのword値で設定できる
ことがわかります。
つまり、最大が「65535」で「65ミリ秒ちょい」が最大値だと思っちゃう
わけです。
ところが・・・
Arduino-UNOのような16MHzクロックで動いているAVRマイコンでは
この「us」値を4倍にしてループカウンタの値を得ているのです。
そしてそのループカウンタはwordのまま。
ということは「us」値として設定できる有効な最大値は16383。
ソース「wiring.c」を見ると、
#elif F_CPU >= 16000000L // クロック16MHz
:
us <<= 2; // x4 us, = 4 cycles // us値を4倍に
:
L1:sbiw us, 1 // 機械語で減算 4クロックでloop
brne L1 // 1loopが0.25us
:
こんな処理になっているのです。
関数のプロトタイプを見ただけでは「us」の最大が16383
だなんて気が付きません。
wordの最大65535に近い「delayMicroseconds(50000)」と
50mS待ちをやってみると
50000*4 = 2000000 → & 0x0FFFF → 3392
3392*0.25us = 0.848ms
と0.8msちょいになってしまい、望んだ50mSとはかけ離れた
待ち時間になってしまいます。
Arduinoの関数リファレンスを見れば、
・16383マイクロ秒以内の値を指定する
・数千マイクロ秒を超える場合はdelay関数を使え
と記されているんですが、こんな落とし穴があるとは
ついさっきまで気が付きませんでした。
※参考
・Arduinoから「タイマー0」を取り上げる(ユーザーが使う
※追記
delay()が32bit値で設定できるんで、delayMicrosecondsも
32bitで時間設定できるよう、こんな関数にしておけばいかがでしょうか。
/***** 1us delay *****/
// delayMicrosecondsの最大値16383(0x3FFF)で
// 処理を区分
void delay1us(uint32_t us)
{
uint32_t cnt, i;
uint16_t m;
if(us < 16384L){ // 16383までなら
delayMicroseconds((uint16_t)us); // そのまま
}
else{ // 16384以上
cnt = us >> 13; // 1/8192
m = (uint16_t)us & 0x1FFF; // あまり 0~8191
for(i = 0; i < cnt; i++){ // loop
delayMicroseconds(8192); // 8192usで
}
if(m != 0){ // 残り
delayMicroseconds(m);
}
}
}
cnt回ループするところでの遅れが積み重なるんで、遅れが気になる
なら delayMicroseconds(8192 - 10); などとちょい補正を。
※さらに追記
液晶表示制御ライブラリ「LiquidCrystal.cpp」の中、105行目に・・
// according to datasheet, we need at least 40ms after power rises
// above 2.7V before sending commands.
// Arduino can turn on way before 4.5V so we'll wait 50
delayMicroseconds(50000);
:
私と同じ50ms待ちを発見。 実害は無いでしょうけど。
| 固定リンク
« パナソニックeneloopスタンダード単3「BK-3MCC」60%(72分)放電で1600回目 | トップページ | 秋月の液晶表示器 ACM0802C-NLW-BBW-IIC、I2Cのプルアップ抵抗 »
「Arduino」カテゴリの記事
- Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMの例(2025.03.22)
- パルスジェネレータをI2C液晶で動かす(2025.01.28)
- EEPROMを使ったシリアル受信バッファ 512kバイトに増設(2024.12.26)
- 1/nカウント方式とDDS方式の2相パルス発生回路(2024.10.13)
- おっと。map関数の計算桁に注意(2024.10.06)
コメント
delayMicroseconds()の上限の約16msの半端な値はどういう理屈で決まっているんだろうと思っていたのですが、なるほど64kの1/4だったんですね。すっきりしました。
投稿: ラジオペンチ | 2021年7月 3日 (土) 11時05分
今回、「16383」までというのを初めて知った次第です。
ソースを見て、その理由も判明。
50ms遅延ができない理由が分かり、すっきりしました。
しかし、「どうにかしてよ」の想いが・・・
1us、16MHzだと16クロックループの繰り返しです。
ループカウンタを4倍せずに、減算+loop(4クロック)に加えて12クロック費やす命令を入れて時間つぶししていればこんなことにはならなかったのかと。
そしたら16bit目一杯の65535usまで遅延できたのに。
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年7月 4日 (日) 10時31分
delayMicroseconds(50000)をオシロで確認
http://igarage.cocolog-nifty.com/blog/2024/02/post-08c9b1.html
投稿: 居酒屋ガレージ店主(JH3DBO) | 2024年2月10日 (土) 10時30分