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の値をサーバーに通知指定ます。


2013年3月25日月曜日

HA-1モジュールの置き換え


 電動シャッター、電動窓、エアコン4台、床暖房のHA-1モジュールを置き換えてすべて動作検証完了しました。
これで以前は検知できていなかった電動シャッター、電動窓の壁スイッチ側での操作状況も取れるようになりました。
エアコンも個別にリモコン制御出来るようになり、動作状況も取得出来ます。
床暖房もXBee経由でHA端子を制御していたため、たまにタイミングエラーを起こしていたのがちゃんと動作するようになりました。

HA-1基板とHA-2基板の比較です。

HA-2基板のLEDはdebug用です。
随分と密度が高くなりました。


交換して回収したHA-1基板と検討用の基板たちです。
XBeeモジュールは外して再利用しています。


沢山無駄遣いしてしまいました。

2013年3月23日土曜日

AVRマイコン書き込み速度


新しいAtmega328pにusbaspでslow設定をし忘れて書き込みをしようとしてハマったので、usbaspのFWとavrdudeのソースコードをどうなっているのか調べてみました。

AVRのISP書き込み時はfoscが12MHz以下の場合、SCKの波形はHigh区間2x 1/fosc,Low区間2x1/foscなので4x1/foscが最小パルス幅になります。
foscが12MHz以上の場合はそれぞれ3x1/foscなので6x1/foscになります。
Atmega328pは初期状態がfosc=1MHzで内部RC発信を使用する場合最高で8HMzですので4x1/foscで計算すればOKです。
最初のLowFuseのCKDIV8を書き込むまでは1MHzなのでSCKは最速で250KHz=4us、LowFuse書き込み後は8MHzの1/4で最速2MHz=0.5usになります。

usbaspのFWのChangelog.txtを見ると2009-02-28(v1.3)で
- added support for software control of ISP speed (based on patch by Jurgis Brigmanis)
とあるのでavrdude-5.11のソースコードを確認するとusbaspで-BオプションでSCKの速度を設定できるようです。(単位はus)実際には適当な分周比になるので指定速度より遅い最速クロックが選択されます。
最初のFuse書き込み時は
avrdude -c usbasp -p atmega328p -B 4 -U efuse:w:0b00000101:m -U hfuse:w:0b11011111:m -U lfuse:w:0b11100010:m
flashの書き込み時は
avrdude -c usbasp -p atmega328p -B 0.5 -U flash:w:main.hex:i
でそれぞれ187.5kHz=5.3usと1.5MHz=0.667usが選択されます。

確認したところAVRISP mkIIも同じ設定で行けます。

2013年3月20日水曜日

HA-2基板量産(その2)

HA-2/SWのほうはひと通り動作確認ができてサーバー側のソフトも安定して動作しているので、残りのHA-2/FC基板の方の動作確認をしていきます。
こちらはAVRマイコンのソフトの確認も一緒にしていきます。
エアコンに取り付けている様子です。


エアコン用はエアコンのHA端子の制御とリモコンの発光機能、温度センサー機能を載せています。
上の写真のFRISKから出ているケーブルの先のオレンジ色のところが温度センサーです。


リモコンの発光部をエアコンのリモコン受光部付近に取り付けます。
このモジュールの赤外線LEDは弱めの発光強度にしてあるので同じ部屋にある他のエアコンには届かないようになっています。
RaspberryPiのほうで学習した赤外線リモコンをXBee経由でこのモジュールのAVRマイコンに転送し、赤外線LEDから出力します。



 エアコンの受光部モジュールを組み立てたところです。

このモジュールはリモコン発光機能だけで制御してもいいのですが、エアコンの状態がわからないのでHA端子も接続してエアコンの状態を取れるようにしています。



HA-2基板量産(その1)


動作試験も兼ねて10枚ほど手マウントしました。
内訳は
HA-2/FC基板 Aircon制御用 4枚
HA-2/FC基板 床暖房用 1枚
HA-2/SW基板 電動窓用 2枚
HA-2/SW基板 シャッター用 3枚
です。

右端の上下2枚は先日作成した
HA-2/FC基板 試験用 1枚

HA-2/SW基板 試験用 1枚

です。

結構細かくて全部作るのに5時間くらいかかってしまいました。



AVRマイコンが載っているHA-2/FC基板の方はAVRのソフトのデバッグもあるので取り敢えず後回しにして、マイコンの載っていないHA-2/SW基板の方を各所に取り付けました。



サーバー側のソフトをHA-2/SW基板用に対応させなくてはならないので動作試験をしながらデバッグしています。

基本機能のopen/stop/closeがサーバーから制御できること(これは以前のHA-1基板でも出来ていた)、壁のスイッチを押されたことの検知(これが今回の新機能)、スイッチを押された順番と時間差から窓やシャッターが開いているのか閉まっているのかをサーバー内で記憶しておいてステータスとして表示する機能の動作確認です。

電動窓には以前からリードスイッチの開閉センサーをつけていたのですが、電動シャッターの方はセンサーの磁石をシャッター側にうまく付けられるところがなく開閉状態を知るすべがありませんでした。
開閉のコマンド発行時に覚えておくことも考えたのですが、壁のスイッチも結構使用するので内部状態と実際が食い違ってしまうため諦めていました。
今回は壁のスイッチを押されたことを検知するようにしたので、サーバー側で状態を監視出来るようになりました。

RaspberryPiのコントロールサーバーソフトにデバッグ用のメッセージを埋め込んで動作確認を順番にやっていきます。


2013年3月13日水曜日

RaspberryPiのケース

RaspberryPi+RP-1/CS基板のケースで丁度いいものがなかったので経木わっぱを加工してケースにしてみました。
基板のサイズが丁度直径120mmで収まるサイズなのでEtherとDC5Vコネクタの穴を開けて、スペーサーで基板を固定してみました。
高さを合わせるために20mmほどカットしていますが、加工するのに失敗して2つほど壊してしまいました。結構割れやすいです。



蓋をした状態です。
この状態でちゃんとIRリモコンの光が透過しています。
感度は若干落ちているようです。向きによってリモコンが効かなくなることがあります。
暫くこの状態で運用してみて厳しいようならLEDの前を少し削って薄くしてみます。

2013年3月10日日曜日

書き込みツール

RP-1基板とHA-2基板のXBeeとAVRの書き込み用に変換コネクタを幾つか作成しました。

1つめ、aitendoのAVR書き込みツールusbaspからRP-1,HA-2基板のdebugコネクタ用の変換です。
aitendoのusbaspは初期状態ではAtmega328pに大きめのhexファイルを書き込むと途中でエラーが出てしまいます。FWを最新版にupdateすることでちゃんと動くようになりました。
FWのupdateのためと、何が悪いか切り分けしたかったので結局AVRISP2も購入してしまいました。


2つめ、秋月通商のUSB-serial変換基板からRp-1,HA-2基板のdebugコネクタ用の変換です。
これはXBeeのFW書き込みや設定値の変更用です。コネクタを挿したらAVRマイコンにresetがかかりTXD/RXDのportを開放してXBeeと直接通信できるようにしています。


3つめ、HA-2基板の電源コネクタに2.1mmDC5Vジャックを繋ぎたかったので、変換コネクタです。

変な小物が増えてしまいました。



2013年3月9日土曜日

HA-2/SW基板

こちらもRasipberryPi+RP-1/CS基板のコントロールサーバーから制御されるモジュールです。
これはシャッタースイッチの制御と温度センサーなどのADCで読み取れる値、窓の開閉を検知するためのマグネットセンサー(リードスイッチ)を読み取るためのGPIOだけに絞ってAVRを不要にしているモジュールです。


これはコントロールサーバー側のソフトを変更すればすぐに動作確認できそうです。

HA-2/FC基板

ZigBee経由でRaspberryPi+RP-1/CS基板のコントロールサーバーから制御されるモジュールです。
これは壁のスイッチ部分やエアコンなどの機器の中に組み込むためFRISKケースサイズで作っています。

密集しているためにRP-1基板よりも部品をつけるのが大変です。
1枚作るのに1時間以上もかかってしまいました。

こちらはAVRのソフトがまだ出来上がってないので赤外線リモコン発光だけしか動作検証できていません。

RP-1/RIR基板

ZigBee経由でRaspberryPi+RP-1/CS基板のコントロールサーバーから制御されるIR-Bluster基板です。
ZigBee経由のコマンドをAVRマイコンで受けて赤外線LEDを発光させます。
コントロールサーバーのおいてある部屋以外の赤外線リモコン機器を制御するために使います。



サーバー側のSWができていないので、ちゃんと繋がっていませんがテストプログラムでサーバーからXBee経由でAVRマイコンにコマンドを投げてリモコンを発光するところまでは動作確認できました。

RP-1/IR基板


RaspberryPi用の赤外線リモコン送受信部+5VDCコネクタの基板です。
先日書いたRaspberryPiの赤外線リモコンのページのプログラムで学習と再生ができます。
 
RaspberryPiの基板はコントロールサーバー用と同じく8pinのピンヘッダーを載せる必要があります。

RaspberryPiと組み合わせたらこのようになります。





コネクタ側です。

赤外線LEDとリモコン受光部の側です。


ケースに穴あけをしてDCコネクタとEtherケーブルの口を開けました。
蓋を閉じた状態で赤外線送受信は使えています。

RP-1/CS基板

コントロールサーバーのRP-1/CS基板です。
RaspberryPiに載せて赤外線学習リモコン機能とZigBeeネットワークのサーバー機能の基板です。


赤外線LED、リモコン受光部、XBee、I2Cの外部センサー用コネクタ、ブザー、RaspberryPiとの接続コネクタ、5V給電用の2.1mmDCコネクタ(RaspberryPiへも給電します)を載せています。


接続するRaspberryPiにはPCM信号の接続用に26pinのGPIOピンヘッダーの下に8pinのピンヘッダーを追加しています。



RaspberryPiに載せた状態です。
寸法はピッタリです。


コネクタ側です。Ether,USBはRaspberryPi、DCコネクタだけRP-1基板です。



反対側、赤外線LED、リモコン受光部とXBeeのアンテナ側、下にRaspberryPiのSDカードが見えています。 


横から見た状態です。下の基盤がRaspberryPi、上の基板がRP-1基板です。I2C用のコネクタが見えています。

動作確認したらちゃんと赤外線リモコンの記録、再生ができていました。












基板が上がって来ました

P板.comに注文していた基板が上がって来ました。
今回はレジストを黒にしてみました。綺麗です。









とりあえず目的ごとに部品をマウントしていきます。
作る基板は右側のRP-1基板で3種類
RaspberryPiと組み合わせてコントロールサーバーにするためのRP-1/CS基板、リモート赤外線リモコンブラスターのRP-1/RIR基板、RaspberryPiに赤外線学習リモコン機能を追加するためのRP-1/IR基板
左のHA-2基板で2種類
HA端子制御機能+電動シャッタースイッチ制御機能+赤外線リモコンブラスター機能+センサー入力機能のHA-2/FC基板、電動シャッタースイッチ制御機能+センサー入力機能のみのHA-2/SW基板
です。

2013年3月6日水曜日

Raspberry Piの赤外線リモコン


もうすぐ基板が出来上がってくるので前準備です。
RaspberryPiに載せて赤外線リモコンを送受信するための回路とプログラムをまとめておきます。

■回路図

























GPIO4(GPIO_CLK0)で赤外線リモコンの出力時のPWMを作るために76KHz(38KHzの倍)のclkを出力します。
それをPCM_CLKに入力しサンプリングCLKとして使用します。
PCM_INに赤外線受光部の信号を入れてサンプリング、PCM_OUTからFETを通して赤外線LEDを制御します。


■RaspberryPiの環境
linuxはRaspbian wheezyを使用。X-Windowは重たいのでoff。基本的にsshでloginして制御しています。

iraccess.tgzを適当なdirectoryに展開するとirremote/以下にソースコードが展開されるので
irremoteに移動してmakeするとirremote/irremoteが作成されます。

/dev/memをとおしてPCM I/Fを操作するのでirremoteの実行は
# sudo irremote [record|send] <file>
とroot権限で実行して下さい。/dev/memの属性を666にしてもOKですが、危険なのでお勧めしません。
record時のファイル名は何でも大丈夫ですが、MAKER_CATEGORY_MODEL_COMMAND.rmcとしたほうが後々整理しやすくて良いかと思います。
例えばsonyのTV KDL-32EX720の1chのボタンの場合はSONY_TV_KDL-32EX720_1.rmcなどです。

記録するには
# sudo irremote record SONY_TV_KDL-32EX720_1.rmc
を実行し、リモコンの1chボタンを押すと少ししてファイルを作って終了します。

コマンドを発行するには
# sudo irremote send SONY_TV_KDL-32EX720_1.rmc
とすると赤外線リモコンコードを出力します。

動作原理
GPIO_CLK0をOSC CLKの19.2MHzから分周して76KHzを作り出力する設定にします。
これをHWでPCM_CLKに入力し、サンプリングCLKとして使用しています。
受信時はPCMを16bit x 2chでCH1 16bit, CH2 16bitと間を開けないモードに設定しサンプリングします。
これで76KHzで1bitずつsamplingされたデータがPCMのFIFOに入ってくるので、SWでFIFOからメモリに読み出しをします。
FIFOは32bitx64段あるので26.9ms以上task switchが回ってこないと取りこぼしますが、相当重たい処理を裏で実行してない限り大丈夫かと思います。FIFOを何段位使っているかを計測したところ通常は11段程度までしか使用されていませんでした。
メモリに読み込んだデータは、メモリ上でNEC/AEHA/SONY/Otherの解析をして、ファイルへ出力するデータを生成します。NEC/AEHA/SONY以外のフォーマットの場合はOtherになりますが、この場合はちょっとサイズが大きめになります。

送信時は受信時の逆で、ファイルからデータを読み込み、PCMで出せる形にメモリ上で展開します。
この時、受信時には赤外線受光部で搬送波が除かれた状態で出力されていますが、送信時は38KHzの搬送波に載せる必要があるので、Highの区間は偶数bitのみ1、奇数bitは0にします。サンプリング周波数が76KHzなのでこれで38KHzの搬送波になります。

基板ができてきました。-> RP-1/IR基板