2013年3月28日木曜日

AVRマイコンのWDT


接続試験をしている時にHA-2モジュールのAVRマイコンにRaspberryPiのサーバーソフトからXBee経由で定期的にコマンドを投げ続けていると102分で固まってしまう問題がありました。
AVRソフトはLEDをPD6につけると1秒毎にHeartBeatするようにMainLoopに仕込みが入れてあるのですが、102分めの時にこれが消えっぱなしになっていましたのでWDTをかけるようにしました。
ただ、これでは根本的な解決にならないのでWDTの設定を少し変更して一度WDT割り込みを発生させてstack上のリターンアドレスをメモリに保存した後再起動し、起動時にそのメモリの内容をXBee経由でRaspberryPiに送信するようにしました。問題自体はQueueのバッファ管理のバグで解決したのですが折角仕組みを組んだので公開しておきます。

avr/wdt.hの中のwdt_enableのdefineをコピーして下記の様にWDIEもセットする(_BV(WDIE)の2箇所)wdti_enableをdefineします。

#  define wdti_enable(value)   \
__asm__ __volatile__ (  \
"in __tmp_reg__,__SREG__" "\n\t"    \
"cli" "\n\t"    \
"wdr" "\n\t"    \
"sts %0,%1" "\n\t"  \
"out __SREG__,__tmp_reg__" "\n\t"   \
"sts %0,%2" "\n\t" \
: /* no outputs */  \
: "M" (_SFR_MEM_ADDR(_WD_CONTROL_REG)), \
"r" (_BV(_WD_CHANGE_BIT) | _BV(WDIE) | _BV(WDE)), \
"r" ((uint8_t) ((value & 0x08 ? _WD_PS3_MASK : 0x00) | \
_BV(WDIE) | _BV(WDE) | (value & 0x07)) ) \
: "r0"  \
)

さらにwdt割り込みの中で発生アドレスとmagic numberを記録するメモリ空間を.noinitセクションにglobal変数として確保します。これは.bssで確保すると再起動時に0に初期化されてしまうので.noinitセクションである必要があります。

unsigned char __attribute__ ((section(".noinit"))) Scheduler::ExceptionDump[4];

WDT割り込みのルーチンはstackのアドレスだけを拾ってSetWDTAddr関数を呼ぶようにします。

ISR(WDT_vect) {

  MainScheduler.SetWDTAddr((unsigned char *)SP);

  while(1);
}

ここでSetWDRAddrの内容を書いてしまわないのは、ここの中身をシンプルにすることでstackに退避される内容や順番を固定化するためです。本来はWDT_vectの中をアセンブラで書けばいいのですがAVRのアセンブラは不慣れなのでコンパイラにお願いしました。

AVRはcalling conventionはC++の場合thisがr25,r24で引数がr23,22,…となるようですね。

逆アセンブルしてみるとr0はテンポラリレジスタ、r1はゼロレジスタとして使用しているのでしょうか?
stackはr1, r0とSREGを退避しています。
その後SPとScheduler classのthisを設定してSchedluer::SetWDT(unsigned char *sp)を呼び出しています。この時のSPを引数としてSetWDTに渡します。

0000018e <__vector_6>:
     18e:       1f 92                  push    r1
     190:       0f 92                  push    r0
     192:       0f b6                  in      r0, 0x3f          ; SREG
     194:       0f 92                  push    r0
     196:       11 24                 eor     r1, r1
     198:       6d b7                 in      r22, 0x3d       ; SPL
     19a:       7e b7                 in      r23, 0x3e       ; SPH
     19c:       80 e0                 ldi     r24, 0x00       ; 0
     19e:       91 e0                 ldi     r25, 0x01       ; 1
     1a0:       0e 94 b3 00      call    0x166           ; 0x166 <_ZN9Scheduler10SetWDTAddrEPh>
     1a4:       ff cf                    rjmp    .-2                ; 0x1a4 <__vector_6+0x16>

SetWDTの関数では
sp + 1 : ISR(WDT_vect)突入時のSREG
sp + 2 : ISR(WDT_vect)突入時のr0
sp + 3 : ISR(WDT_vect)突入時のr1
sp + 4 : ISR(WDT_vect)の戻り番地上位8bit
sp + 5 : ISR(WDT_vect)の戻り番地下位8bit
になるので

void Scheduler::SetWDTAddr(unsigned char *sp) {
  
  static const int wdtAddrOffset = 4;
  unsigned int addr = ((sp[wdtAddrOffset] << 8) | sp[wdtAddrOffset + 1]) << 1;
  ExceptionDump[0] = 'E';
  ExceptionDump[1] = 'x';
  ExceptionDump[2] = addr >> 8;
  ExceptionDump[3] = addr;
}

とExというMagicと共にアドレスを記録します。
ここから戻ってISR(WDT_vect)の中でwhile(1);で待っていると2回目のWDT Expireが起きてリセットがかかります。

リセット後、最初の処理で(今回の場合Scheduler classのconstructor)でExceptionDumpのMagicを確認して、Exだった場合は後でXBee経由で送信するためにWDTAddrに記録しておき、次に発生した場合のためにMagicを消しておきます。

Scheduler::Scheduler() {

  if((ExceptionDump[0] == 'E') && (ExceptionDump[1] == 'x')) {

    WDTAddr = (ExceptionDump[2] << 8) | ExceptionDump[3];
    ExceptionDump[0] = 0;
    ExceptionDump[1] = 0;
  } else {
    WDTAddr = 0xffff;
  }

  :

  :
}

このあと、もう少し上のXBee通信レイヤーの最初で起動要因とWDTAddrの値をサーバーに通知指定ます。


0 件のコメント:

コメントを投稿