割り込みで処理させるwordデータの扱い
Arduinoのアナログ基準電圧入力 に関連して、Arduinoのソースファイルをあれこれ見ていたら、
「そうそう。これこれ。 忘れたらアカンで~」的な内容に遭遇。
「wiring.c」内の、タイマーに関する処理に関して、起動経過時間をミリ秒単位で読み出すmillis()関数はこんな具合に処理されています。
volatile unsigned long timer0_millis = 0; ←Arduino起動からの経過時間(ミリ秒)
TIMER0のオーバーフロー割り込みで固定値が加算される4バイトの値です。
これ、
・割り込みで処理される
・2バイト以上の数値
というのを忘れちゃいけません。
timer0_millisの値は次の関数で得られます。
unsigned long millis() ←その値を返す関数
timer0_millisが割り込みで変化する値じゃなければ、
こんなふうに書けます。
unsigned long millis()
{
return timer0_millis; ←4バイト値を読んでリターン
}
ところが、Arduinoで使っているATmega328は、RAM領域を1バイト単位でしか読み書きできません。
4バイトの値では4回のアクセスが実行されます。
そのアクセス中に割り込みが入ると、アクセス途中で値が変わっちゃって、正しい値が得られません。
そこで・・・割り込みで操作される2バイト以上の数値では、アクセス前にいったん割り込み禁止にして、途中で変更されるのを防ぎます。
これを忘れると、正しい値が読み出せません。
millis()ではこんなソースになっています。
unsigned long millis()
{
unsigned long m; ←4バイト値を入れるローカル変数
uint8_t oldSREG = SREG; ←今が割り込み禁止か許可か?
cli(); ←いったん割り込み禁止にして
m = timer0_millis; ←4バイト値を読み出す
SREG = oldSREG; ←割り込み禁止、許可を元に戻す
return m; ←ローカル変数に保存した値を持ってリターン
}
millis()実行前が割り込み禁止なら割り込み禁止状態で、
割り込み許可かなら許可状態でリターンするために、SREGをアクセスしています。
割り込み状態の復元が必要ないのであれば(いつも割り込み許可でリターン)、SREGの保存は不要です。
こんな具合に簡略化も可能です。
unsigned long millis()
{
unsigned long m; ←4バイト値を入れるローカル変数
cli(); ←割り込み禁止
m = timer0_millis; ←4バイト値を読み出す
sei(); ←割り込み許可
return m; ←ローカル変数に保存した値を持ってリターン
}
割り込み内で処理される2バイト以上の数値は、この手順を忘れてはなりません。
忘れるとどうなるか、Mstimer2を使って試してみます。
#include <MsTimer2.h> // タイマー割込
#include <wiring_private.h> // sbi,cbi命令用
// テストポイント
#define PB5_H (sbi(PORTB,PORTB5)) // PB5(D13) H/L
#define PB5_L (cbi(PORTB,PORTB5))
#define PB4_H (sbi(PORTB,PORTB4)) // PB4(D12) H/L
#define PB4_L (cbi(PORTB,PORTB4))
#define PB3_H (sbi(PORTB,PORTB3)) // PB3(D11) H/L
#define PB3_L (cbi(PORTB,PORTB3))
// タイマー値
volatile word tm_1ms; // 1ms upカウントタイマー
// 1msタイマー処理
void timer1ms(void)
{
PB4_H; // テストパルス
tm_1ms++; // 1msタイマー +1
PB4_L; // 1,2,3…と+1で変化
}
// セットアップ
void setup() {
pinMode(13, OUTPUT); // PB5 基板上黄LED
pinMode(12, OUTPUT); // PB4 タイミング確認
pinMode(11, OUTPUT); // PB3
MsTimer2::set(1, timer1ms); // 1ms割り込み
MsTimer2::start(); // 割り込み処理開始
Serial.begin(9600); // シリアル出力
}
// ループ
void loop() {
word t1, x;
cli(); // 割込禁止
t1 = tm_1ms; // 1ms値をコピー
sei(); // 割込再開
while(1){ // 1ms値の変化を見る
PB5_H; // テストパルス
x = (tm_1ms - t1); // 経過時間 ★割込処理される数値を読んでいる
if(x){ // 0以外の時:変化あり
if(x != 1){ // 差が1msじゃないとき
PB3_H;
Serial.print("t1="); // 値を出力
Serial.print(t1, HEX);
Serial.print(" x="); // 差
Serial.println(x);
PB3_L;
}
cli(); // 割込禁止して
t1 = tm_1ms; // 1ms値をコピー
sei(); // 割込再開
}
PB5_L;
}
}
シリアルモニターにはこんな結果が出てきます。
t1=4FF x=256
t1=BFF x=256
t1=DFF x=256
t1=EFF x=256
t1=10FF x=256
t1=13FF x=256
t1=17FF x=256
t1=19FF x=256
t1=1DFF x=256
t1=1FFF x=256
t1=26FF x=256
t1=2AFF x=256
t1=2FFF x=256
t1=36FF x=256
t1=3DFF x=256
t1=42FF x=256
t1=4AFF x=256
t1=4FFF x=256
t1=55FF x=256
t1=5DFF x=256
t1=62FF x=256
t1=65FF x=256
t1=6CFF x=256
t1=6DFF x=256
t1=72FF x=256
t1=78FF x=256
t1=7EFF x=256
t1=86FF x=256
t1=8AFF x=256
t1=8FFF x=256
t1=96FF x=256
t1=9DFF x=256
t1=A2FF x=256
t1=A6FF x=256
t1=AEFF x=256
t1=B3FF x=256
t1=B7FF x=256
t1=BFFF x=256
t1=C4FF x=256
t1=C8FF x=256
t1=CBFF x=256
t1=D1FF x=256
t1=D7FF x=256
t1=D8FF x=256
t1=DEFF x=256
t1=DFFF x=256
t1=E5FF x=256
t1=E8FF x=256
t1=EFFF x=256
t1=F4FF x=256
t1=F6FF x=256
t1=FDFF x=256
16bit=65536回(1分ちょい)でこれだけのミスが発生。
「xxFF」→「yy00」の桁上がりの読み出し時にミスが起こります。
だもんで、エラー発生するかも!のタイミングは256回。
そのうちの1/5ほどを拾ってしまってミスした数値が出ています。
一番先頭のだと・・・
04FF→0500のカウントの時、先に読んだLSBがカウントマップ前で「FF」。
その間に割り込みが入りカウント値が+1されて「0500」に。
その直後にMSBを読んでカウントアップ後の「05」に。
前回値との差「05FF - 04FF」で「0x0100 = 256」に。
こんな具合です。
オシロ波形では、こんなふうに見えます。
波形の上から、
PB4 1ms割り込み処理タイミング
PB5 毎回ループで出すパルス
PB3 差が1じゃ無い時に出すパルス
文字変換時間
TXD シリアル出力
先頭文字「t=0x74」が見えている
ちなみに、「Z80」の割り込みではこんなバグを体験。
プロセッサ誌1989年3月号に投稿。
『バグタイザー試用レポート Z80のIFF2に関するバグレポート』
割り込み禁止か許可なのか、割り込み状態を見るための命令が、命令の実行と割り込みのタイミングがうまく合うと、結果をミスるというチップ内部のバグです。
その解決方法も示しています。
20世紀にあった与太話としてご覧いただければと。
| 固定リンク
「電子工作」カテゴリの記事
- 予告:「マイコン型導通チェッカー」「電池電圧チェッカー」値上げします(2022.11.16)
- 三和の針式テスター「GP-5」不調(2022.10.18)
- 「ダイソー ミニケース 5個組」が見つからない #2(2022.10.12)
- 「ダイソー ミニケース 5個組」が見つからない(2022.09.29)
- オペアンプの出力につなぐ大容量コンデンサ ほんとにいいの?(2022.02.26)
「Arduino」カテゴリの記事
- 初めて買ったArduino UNO・・・今は(2023.05.25)
- 液晶表示コントローラ HD44780で迎撃(2023.05.16)
- Arduino UNOで3相モーターを回す(2023.05.01)
- Arduino サーミスタを使った温度測定で 【ゼロ除算問題】(2023.03.23)
- A/Dコンバータでサーミスタの抵抗値を読む サーミスタをつなぐ場所は?(2023.03.21)
「割り込み処理」カテゴリの記事
- 初めて買ったArduino UNO・・・今は(2023.05.25)
- 8ビットマイコンの割り込み処理・・・1バイトに収まるなら1バイトに(2023.03.01)
- 8bitマイコンにも16bitのメモリ読み書き命令があった(2022.10.14)
- 何度も言うぞ! Arduino(8bitマイコン)の割り込みには気をつけろ!(2022.10.11)
- ロータリーエンコーダーのチャタリング波形(2022.09.11)
コメント