Arduinoの内部で勝手に実行されているタイマー割り込み、
(タイマー0のオーバーフローが使われている)
その実行時間を調べておきました。
いわゆるシステム割り込みで、ユーザープログラムが走り
始める時、その裏でこれが勝手に走り始めます。
これが、millis()関数などのタイマーを管理しています。
メイン側で、タイミングにクリチカルな処理を行う時は、
この割り込みが裏で動いていることを自覚しておかなくて
はなりません。
これがいやなら、処理の前に割り込み禁止命令を入れてお
きます。
ただし、1ミリ秒以内に割り込みを有効に戻しておかないと
タイマーが遅れます。
●確認方法1
「loop」内で出力ポートにパルスを出し、そのパルスの
変動をオシロスコープで観察します。
スケッチはこんな具合。
void setup() {
pinMode(8, OUTPUT);
}
void loop() {
digitalWrite(8, HIGH); // pin8にH/Lパルス
digitalWrite(8, LOW);
}
これの観察結果(クリックで拡大↓)
割り込み処理が入ると、パルス出力の間隔が変わり
ます。
●確認方法2
digitalWriteを使うと、パルス幅が長くなるので、
「SBI CBI」命令を使います。
#include "wiring_private.h" // sbi,cbi命令用
#define D8_H (sbi(PORTB,PORTB0)) // digital pin8
#define D8_L (cbi(PORTB,PORTB0))
void setup() {
pinMode(8, OUTPUT);
}
void loop() {
while(1){ // 永久実行で
D8_H; // pin8にH/Lパルス
D8_L;
}
}
すると、こうなります。(クリックで拡大↓)
むちゃ出力パルスが早くなりました。
この処理時間(6.5マイクロ秒ほど)が約1msに1回、
入るのです。
●割り込み周期を見る
約1msに1回と記しましたが、割り込み周期は正確な1msでは
ありません。
(タイマー0のオーバーフローということで、ハードでは
微調整できないのです)
それを確認してみます。
こんなスケッチです。
#include "wiring_private.h" // sbi,cbi命令用
#define D8_H (sbi(PORTB,PORTB0)) // digital pin8
#define D8_L (cbi(PORTB,PORTB0))
#define D9_H (sbi(PORTB,PORTB1)) // digital pin9
#define D9_L (cbi(PORTB,PORTB1))
unsigned long tm1; // ミリ秒タイマー
void setup() {
pinMode(8, OUTPUT); // pin8はサイクル実行
pinMode(9, OUTPUT); // pin9は1ms経過で
}
void loop() {
while(1){
D8_H; // pin 8 H,Lパルス出力
D8_L;
if(tm1 != millis()){ // タイマー変化あり
D9_H; // pin 9 H,Lパルス出力
D9_L;
tm1 = millis();
}
}
}
こんな波形が出てきます。
まずこれ。 1msの変化検出したところ。
(クリックで拡大↓)
このpin9パルスの周期をオシロで読むと1msを超えて
いることがわかります。
(クリックで拡大↓)
タイマー値の変化を検出して出しているパルスが1ms
ちょいの間隔で見えています。
正確には、システムクロック周波数16MHzの1/64の1/256で
「1.024ms」。
(タイマー割り込み内では端数をうまく処理して
millis関数での誤差が出ないようにしています)
※追記
せっかくなんで、このタイマー割り込み処理の機械語命令
を拾っておきました。(avr-objdumpを使ってで出てくる)
まずこれがwiring.c内のCで記述されたタイマー割り込み
プログラム。
volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;
#if defined(__AVR_ATtiny24__) ||
defined(__AVR_ATtiny44__) ||
defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
unsigned long m = timer0_millis;
unsigned char f = timer0_fract;
m += MILLIS_INC;
f += FRACT_INC;
if (f >= FRACT_MAX) {
f -= FRACT_MAX;
m += 1;
}
timer0_fract = f;
timer0_millis = m;
timer0_overflow_count++;
}
コンパイルされて出てきた機械語がこれ。
00000174 <__vector_16>:
174: 1f 92 push r1
176: 0f 92 push r0
178: 0f b6 in r0, 0x3f ; 63
17a: 0f 92 push r0
17c: 11 24 eor r1, r1
17e: 2f 93 push r18
180: 3f 93 push r19
182: 8f 93 push r24
184: 9f 93 push r25
186: af 93 push r26
188: bf 93 push r27
18a: 80 91 01 01 lds r24, 0x0101
18e: 90 91 02 01 lds r25, 0x0102
192: a0 91 03 01 lds r26, 0x0103
196: b0 91 04 01 lds r27, 0x0104
19a: 30 91 00 01 lds r19, 0x0100
19e: 23 e0 ldi r18, 0x03 ; 3
1a0: 23 0f add r18, r19
1a2: 2d 37 cpi r18, 0x7D ; 125
1a4: 20 f4 brcc .+8 ; 0x1ae
1a6: 01 96 adiw r24, 0x01 ; 1
1a8: a1 1d adc r26, r1
1aa: b1 1d adc r27, r1
1ac: 05 c0 rjmp .+10 ; 0x1b8
1ae: 26 e8 ldi r18, 0x86 ; 134
1b0: 23 0f add r18, r19
1b2: 02 96 adiw r24, 0x02 ; 2
1b4: a1 1d adc r26, r1
1b6: b1 1d adc r27, r1
1b8: 20 93 00 01 sts 0x0100, r18
1bc: 80 93 01 01 sts 0x0101, r24
1c0: 90 93 02 01 sts 0x0102, r25
1c4: a0 93 03 01 sts 0x0103, r26
1c8: b0 93 04 01 sts 0x0104, r27
1cc: 80 91 05 01 lds r24, 0x0105
1d0: 90 91 06 01 lds r25, 0x0106
1d4: a0 91 07 01 lds r26, 0x0107
1d8: b0 91 08 01 lds r27, 0x0108
1dc: 01 96 adiw r24, 0x01 ; 1
1de: a1 1d adc r26, r1
1e0: b1 1d adc r27, r1
1e2: 80 93 05 01 sts 0x0105, r24
1e6: 90 93 06 01 sts 0x0106, r25
1ea: a0 93 07 01 sts 0x0107, r26
1ee: b0 93 08 01 sts 0x0108, r27
1f2: bf 91 pop r27
1f4: af 91 pop r26
1f6: 9f 91 pop r25
1f8: 8f 91 pop r24
1fa: 3f 91 pop r19
1fc: 2f 91 pop r18
1fe: 0f 90 pop r0
200: 0f be out 0x3f, r0 ; 63
202: 0f 90 pop r0
204: 1f 90 pop r1
206: 18 95 reti
「unsigned long」の加算や比較処理の実態が見えています。
※関連
・ラジオペンチ ArduinoのMStimer2の精度の改善
・Arduinoでの時間管理:放課後マイコンクラブ
・Arduinoの入力速度を調べた
|
この記事がきっかけで、下記の記事を書かせていただいたのでご報告。
http://radiopench.blog96.fc2.com/blog-entry-643.html