« Arduinoのアナログ基準電圧入力 | トップページ | SUS304・16mm厚ハンダ付け補助ツールの追加製作 »

2019年3月25日 (月)

割り込みで処理させる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」が見えている

Image000

 

Image001

ちなみに、「Z80」の割り込みではこんなバグを体験。
プロセッサ誌1989年3月号に投稿。
『バグタイザー試用レポート Z80のIFF2に関するバグレポート』

  Z80のIFF2に関するトラブル体験談

割り込み禁止か許可なのか、割り込み状態を見るための命令が、命令の実行と割り込みのタイミングがうまく合うと、結果をミスるというチップ内部のバグです。
その解決方法も示しています。
20世紀にあった与太話としてご覧いただければと。

 

|

« Arduinoのアナログ基準電圧入力 | トップページ | SUS304・16mm厚ハンダ付け補助ツールの追加製作 »

電子工作」カテゴリの記事

Arduino」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« Arduinoのアナログ基準電圧入力 | トップページ | SUS304・16mm厚ハンダ付け補助ツールの追加製作 »