Arduino-UNO 割り込み処理のミスあれこれ:「cnt+n; n=0; 」での抜けを確かめる
Arduino-UNO 割り込み処理のミスあれこれ:ロータリーエンコーダー
の中で示した、重大なバグにつながる落とし穴の手順
~~~~~~~~~~~~~~~~~~~~~~~~~~
if(m_nValue != 0){ ←(1) up/downパルスあり?
R_count = R_count + m_nValue; ←(2)カウント値を+/-
m_nValue = 0; ←(3)up/downパルスをゼロに
}
もし、(2)と(3)の間にエンコーダカウント割り込みが入って
m_nValueの値が+/-されたとすると・・・
(3)でクリアされてしまって、パルス抜けが発生します。
(2)と(3)は割り込み禁止状態で実行しなければなりません。
short,、longデータを扱える16bit、32bitマイコンでも
発生します。
~~~~~~~~~~~~~~~~~~~~~~~~~~
この「パルス抜け」が発生する様子を確かめるスケッチを
書いてみました。
・・・ちょっと長いですが
/***** 割り込み処理ミスの検証 *****/
// cnt + n; n = 0; 処理間での割り込みとの競合をチェック
// 【1】に割り込みが入ると「n2」がミスする
// PD2 パルス入力 ↓エッジ
// PB1 10kHz方形波出力
// PB3 125Hz方形波出力
// 1秒サイクルでカウント値を出力
// CR入力でクリア
// cnt_up, cnt_add1は割り込み禁止で処理
// cnt_add2を割り込み有効のままで計数
/***** I/O MACRO *****/
#define PB0_H (PORTB |= (1 << PB0)) // (!!!)PB0 H/L
#define PB0_L (PORTB &= ~(1 << PB0))
#define PB2_H (PORTB |= (1 << PB2)) // (!!!)PB2 H/L
#define PB2_L (PORTB &= ~(1 << PB2))
#define PB5_H (PORTB |= (1 << PB5)) // (!!!)PB5 H/L
#define PB5_L (PORTB &= ~(1 << PB5))
/***** データ *****/
// 加算データ
volatile long cnt_up; // 割込み内でカウントアップ
volatile int8_t cnt_n1; // 同じだが1バイトでカウントアップ
volatile int8_t cnt_n2; // 割り込み禁止なし用
long cnt_add1; // loop内でcnt_n1,n2を加算
long cnt_add2; // n1は割り込み禁止で
// n2は割り込み有効のままで処理
// タイマーデータ
volatile byte tm_4ms; // 4msタイマー割り込みでダウンカウント
// 250で1秒
// シリアル出力バッファ
char tx_bff[64]; // sprintfで出力文字を設定
/***** INT0(PD2) ↓エッジパルス割り込み *****/
ISR(INT0_vect){
PB0_H; // (!!!)
if(cnt_up < 99999999) cnt_up++; // 8桁maxで+1
cnt_n1++; // loop内でcnt_add1に加算
cnt_n2++; // cnt_add2に加算
PB0_L; // (!!!)
}
/***** タイマー2コンペアマッチA割込み *****/
// 250Hz 4mサイクル
ISR(TIMER2_COMPA_vect){
if(tm_4ms) tm_4ms--; // 4msタイマーダウンカウント
}
/***** カウント値シリアル出力 *****/
// cnt_up, add1, add2 ,差 の4データを出力
// 割り込み禁止でカウント値をコピー
void txcnt(void){
long d0, d1, d2;
PB5_H; // (!!!)
cli(); // ★いったん割込禁止に
d0 = cnt_up; // カウント値longデータ
cnt_add1 += (long)cnt_n1; // n1の残値をadd1に加算
cnt_n1 = 0; // 次に備えてゼロクリア
cnt_add2 += (long)cnt_n2; // n2をadd2に加算
cnt_n2 = 0; // ゼロクリア
d1 = cnt_add1; // 割込禁止でのカウントアップ
d2 = cnt_add2; // 割込禁止しないで
sei(); // ★割込再開
sprintf_P(tx_bff, PSTR("%8ld %8ld %8ld %8ld"), // 8桁で表示
d0, d1, d2, d1 - d2);
Serial.println(tx_bff); // 1行シリアル出力
PB5_L; // (!!!)
}
/***** セットアップ *****/
// ATmega328Pのレジスタを直接制御
void setup(){
cli(); // 割込禁止
// I/Oイニシャル
PORTB = 0b00000000; // data/pull up
DDRB = 0b00111111; // port指定
// |||||+---- PB0 IO8 out TEST 割り込みでパルス
// ||||+----- PB1 IO9 out OC1A出力 10kHz
// |||+------ PB2 IO10 out TEST カウントでパルス
// ||+------- PB3 IO11 out OC2A出力 125Hz
// |+-------- PB4 IO12 out
// +--------- PB5 IO13 out (LED) シリアル送信タイミング
PORTD = 0b00000111;
DDRD = 0b11111010;
// |||||||+---- PD0 RXD in
// ||||||+----- PD1 TXD out
// |||||+------ PD2 IO2 in INT0↓エッジをカウント
// ||||+------- PD3 IO3 out
// |||+-------- PD4 IO4 out
// ||+--------- PD5 IO5 out
// |+---------- PD6 IO6 out
// +----------- PD7 IO7 out
// INT0 ↓エッジ割り込み (PD2)
EICRA = 0b00000010; // 外部割り込み
// ||++---- ISC0 INT0 立ち下がりエッジ
// ++------ ISC1 INT1 Lレベル
EIMSK = 0b00000001;
// |+---- INT0 割り込み有効
// +----- INT1 割り込み未使用
// タイマー1 PB1に10kHz方形波出力
TCCR1A = 0b01000000;
// |||| ++---- WGM11,10 CTCモード
// ||++-------- COM1B
// ++---------- COM1A (PB1トグル出力)
TCCR1B = 0b00001001;
// || ||+++---- CS 1/1=16MHz
// || ++------- WGM13,12 CTC
// |+---------- ICES1
// +----------- ICNC1
TIMSK1 = 0b00000000; // 割り込み
// | ||+--- TOIE1
// | |+---- OCIE1A
// | +----- OCIE1B
// +-------- ICIE1
OCR1A = 800 - 1; // 20kHz PB1は1/2で10kHz
// タイマー2:4ms割り込みとPB3に125Hz方形波出力
TCCR2A = 0b01000010; // モード設定
// |||| ++--- WGM:CTC OCR2AがTOP値
// ||++------- COM2B (出力しない)
// ++--------- COM2A (PB3 トグル出力)
TCCR2B = 0b00000110;
// |+++--- CS:16MHz/256 16us
// +------ WGM02
TIMSK2 = 0b00000010;
// ||+---- TOIE2 オーバーフロー割り込み
// |+----- OCIE2A コンペアマッチA割り込み 有効
// +------ OCIE2B コンペアマッチB割り込み
OCR2A = 250 - 1; // 16us * 250 = 4ms
// シリアル
Serial.begin(9600);
sei(); // 割込許可
}
/***** LOOP *****/
// 【1】の間に割り込が入るとカウント読み出し値に抜けが発生
void loop()
{
char c;
sprintf_P(tx_bff, PSTR("%8s %8s %8s %8s"), // 8桁で表示
"cnt_up", "add1", "add2", "add1-2");
Serial.println(tx_bff); // 1行シリアル出力
while(1){
if(cnt_n1){ // n1割り込みあった
cli(); // ★割り込み禁止して
cnt_add1 += (long)cnt_n1; // n1をadd1に加算
cnt_n1 = 0; // 次に備えてゼロクリア
sei(); // ★割り込み有効に戻す
if(cnt_add1 > 99999999) cnt_add1 = 99999999; // max規制
}
if(cnt_n2){ // n2割り込みあった
PB2_H; // (!!!)
cnt_add2 += (long)cnt_n2; // ★1割り込み許可のまま加算
// 【1】この間に割り込みがあると
cnt_n2 = 0; // このゼロクリアで抜けが発生
PB2_L; // (!!!)
if(cnt_add2 > 99999999) cnt_add2 = 99999999; // max規制
}
// 1秒ごとにカウント値をシリアル出力
if(tm_4ms == 0){ // 1秒経過
tm_4ms = 250; // 1秒プリセット
txcnt(); // カウント値シリアル出力
}
// CR受信でカウント値をクリア
if(Serial.available()){ // 受信データあり
c = Serial.read(); // 1文字読み出し
if(c == '\r'){ // CR ?
cli(); // ★いったん割込禁止に
cnt_up = 0; // カウント値クリア
cnt_n1 = 0;
cnt_n2 = 0;
cnt_add1 = 0;
cnt_add2 = 0;
tm_4ms = 0; // タイマークリア
sei(); // ★割込再開
}
}
}
}
・INT0入力で↓エッジのパルス割り込み。
long値cnt_upと1バイト値cnt_n1とcnt_n2を+1。
・1秒ごとに、4つの数値をシリアル出力します。
~~~~~~~~~~~~~~~~~~~~~~
【125Hz】
cnt_up add1 add2 add1-2
0 0 0 0
125 125 125 0
250 250 250 0
375 375 375 0
500 500 500 0
:
【10kHz】
cnt_up add1 add2 add1-2
5 5 5 0
10000 10000 10000 0
20000 20000 20000 0
30000 30000 30000 0
40000 40000 40000 0
:
~~~~~~~~~~~~~~~~~~~~~~
「cnt_up」はINT0割り込みでカウントアップするlong値。
「add1」と「add2」は、INT0割り込みでカウントアップする
1バイト値を、メインループ内で加算して得られるlong値。
加算処理時、add1は割り込み禁止で加算。
add2は割り込み有効のまま加算。
このため、add2ではパルス抜けが生じるかも、となります。
「add1-2」はadd1とadd2の差です。
ちゃんと処理してるcnt_upとadd1は同じ値になりますが、
割り込み禁止にしていないadd2はパルス抜けが生じて、
その差「add1-2」の値がどんどん大きくなっていきます。
リストの【1】部分がそれ。
ここに割り込みが入るとパルス抜けが発生します。
★の所で割り込み禁止と再許可
ハード的には、Arduino-UNOをこんな具合に。
PB1に10kHzの方形波、PB3に125Hzの方形波を出しているので、
これを使ってパルス入力します。
125Hzも10kHzも、ふつうにつないだだけでは割り込み処理が
十分に追いついているので安定してカウントが進みます。
カウント値のシリアル出力やシリアル出力割り込み、タイマー
割り込みなどで、メインループでのカウント処理が遅れること
はありますが、カウント値のミスは出てきません。
ところが・・・
INT0入力クリップを離したり付けたりして
チャタリングを発生させると、一発でアウトに。
チャタリングがあってもcnt_upとadd1は同じ値を維持
していますが、add2がパルス抜けを起こして、
差の値「add1-2」がどんどん大きくなってきます。
~~~~~~~~~~~~~~~~~~~~~~
【チャタリングあり】
cnt_up add1 add2 add1-2
0 0 0 0
1397 1397 1384 13
2805 2805 2781 24
4209 4209 4169 40
5616 5616 5562 54
:
~~~~~~~~~~~~~~~~~~~~~~
オシロで見ると、パルス抜けのタイミングはこんなふうになっ
ています。
ランダムなチャタリングでなく、見やすいような
パルスにしています。
2発目のパルスエッジ、割込は応答してちゃんとカウント
していますがメインループでの加算処理と重なってしまい、
「cnt_n2」がゼロクリアされ、結果的にパルス抜けが
生じています。
cnt_upとadd1が一致しているということで、
割り込みを禁止してのあれこれ処理、重要なのがお分かり
いただけたかと。。。
※チャタリングへの応答
ランダムなチャタリングパルスへの応答、パルス全部を捉えられる
わけではありません。
そこは割り込み処理の応答速度です。
※補足
Arduinoでの割り込み禁止と割り込み許可は、
「noInterrupts()」と「interrupts()」を使えという
のが標準です。 が・・・
「Arduino.h」を見ると、こんな記述が
#define interrupts() sei()
#define noInterrupts() cli()
「sei」と「cli」はAVRマイコンでのアセンブラ・ニーモニック。
Arduino-UNOのATmega328Pを制御するんであれば・・・
使ってヨシ!。 ということで。
※補足
テスト用パルスを出している
#define PB0_H (PORTB |= (1 << PB0))
#define PB0_L (PORTB &= ~(1 << PB0))
この記述で、ポートに対するビット操作命令を生んでくれます。
ポートをダイレクトにH/Lする「SBI」「CBI」命令に置き換わるので、
最高速です。
割り込み処理確認に出しているテストパルスに「digitalWrite()」
なんて、まどろっこしくて使えません。
・Arduino-UNOでtone()の挙動を調べる
・toneパルスの異常 もうちょっと掘り下げて
・digitalWriteの秘密をもうちょっと
※補足 これ↓、何度も言っておきます。
・Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ
※関連
・ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
・Arduinoでロータリーエンコーダーを使う:ラジオペンチ
・ピンチェンジ割込みを使ってロータリーエンコーダーを読む (Arduino):ラジオペンチ
※続き
・Arduino-UNO 割り込み処理のミスあれこれ:パルス計数の抜けを確かめる その2
| 固定リンク
« Amazonベーシック単3(2000mAh) 60%(72分)放電 1200回で終了 | トップページ | Arduino-UNO 割り込み処理のミスあれこれ:パルス計数の抜けを確かめる その2 »
「Arduino」カテゴリの記事
- Arduinoで「ボコスカハンマー」 あれれれれっ?!(2023.12.07)
- Arduino UNO R3で±19.9V表示電圧計(2023.10.14)
- 「御詠歌プレーヤー」の製作 (MP3-TF-16Pモジュールの使用例)(2023.08.10)
- Arduino UNO R3のソケット・・思えば違和感がぁ(2023.07.07)
- 初めて買ったArduino UNO・・・今は(2023.05.25)
「重箱の隅」カテゴリの記事
- C-MOS ICの入力ピンをオープンにすると(2023.11.01)
- トラ技の作図能力が落ちている・・・かもの続き(2023.10.31)
- トラ技の作図能力が落ちている・・・かも(2023.10.27)
- 不安な接続記号「●」(2023.06.07)
- トラ技Jr. 2023年春号(2023.04.12)
「割り込み処理」カテゴリの記事
- 初めて買ったArduino UNO・・・今は(2023.05.25)
- 8ビットマイコンの割り込み処理・・・1バイトに収まるなら1バイトに(2023.03.01)
- 8bitマイコンにも16bitのメモリ読み書き命令があった(2022.10.14)
- 何度も言うぞ! Arduino(8bitマイコン)の割り込みには気をつけろ!(2022.10.11)
- ロータリーエンコーダーのチャタリング波形(2022.09.11)
コメント