2014年9月5日金曜日

リモコン受信 AVRマイコン編 その2

前回のTimeout割込みで最終bitの処理をして通常Threadをkickしたところからの続きです。

□通常Threadの処理
FIFOに64パルス分残っているのでこれの記録処理を行います。
次にデータのリピートを確認します。ここでは同じパターンが4回以上連続しているもの、2回、3回連続で丁度データが終わっているものをリピートしているとみなし1回分のデータに短縮します。
これは、HA2moduleで受信したコードをHA1ControlServerに転送し判別して何か動作させるためにコードが同じであることを比較しなくてはならず、リピートも回数の不一致を無視する必要があるので比較しやすいようにこのようなことをしています。最後にデータの先頭にHeaderを書き込み、これでFormatOtherとしてのデータが完成します。
フォーマットは上記の理由でそのままではないですがこちらの電子牛乳さんを参考にしています。
FormatOther
 code[0],code[1]    = length
 code[2]            = FormatOther 0xff
 code[3]            = DataSize
 code[4]            = RepeatCount/PatternTableSize
 code[5]-           = Data .....
    Data:
     0xxxxxxxx(続くbit patternのbit数-1) bit pattern + padding(byte align)
     1xxxxxxxx(PatternTableの番号) : 上位2つ以外のパターンを指定
 code[5+DataSize]-  = PatternTable
    PatternTable:
      [pattern 0] [pattern 1] ... [pattern N]
        [pattern x] : xx xx yy yy (4bytes) : xxxx=HiCLK / yyyy=LoCLK

その後Leader,Dataをチェックし、AEHAフォーマットなら以下のコードに置きまえます。
FormatAEHA
 code[0],code[1]    = length
 code[2]            = FormatAEHA 0x01
 code[3]            = TWIDTH
 code[4] -          = CustomerCode/Parity/Data0, Data1, Data2, ... DataN

NECフォーマットなら以下のコードに置きまえます。
FormatNEC
 code[0],code[1]    = length
 code[2]            = FormatNEC 0x02
 code[3]            = TWIDTH
 code[4] - code[7]  = CustomerCode, Data, ~Data

SONYフォーマットなら以下のコードに置き換えます。
FormatSONY
 code[0],code[1]    = length
 code[2]            = FormatSONY 0x03
 code[3]            = TWIDTH
 code[4]            = AddrLen (5/8/13)
 code[5]            = Data
 code[6],code[7]    = Address

どれでもなければOtherFormatのままXBeeのパケットとして送信します。

□データの記録結果
これで特に長いと評判のダイキンエアコンの電源ボタンをおした時のコードを記録してみると
00 49 ff 30 05 04 00 82 83 3f 11 da 27 00 c5 00
00 d7 84 83 3f 11 da 27 00 42 00 00 54 84 83 7f
11 da 27 00 00 38 32 00 a0 0f 00 06 60 00 01 c0
17 00 00 52 84 00 26 00 1e 00 25 00 61 00 27 07
bc 01 11 00 82 00 27 0a 9e
となりました。
FormatOtherで全体で0x0049=73バイト、データ0x30=48バイト、パターンテーブル5個
PatternTableは後半部分High/Lowの表記で
0026/001e
0025/0061
0027/07bc
0111/0082
0027/0a9e
の5つです。先頭から2つがbit = 0, bit = 1を表すパターンになります。

最初にbit 0 x 5 + Stop





04 00 82 でbit 0(0026/001e) x 5 + Stop(0027/07bc)
 04は続く(4+1)bitがデータパターンであることを示しています。ここでは5bitとも0です。
 82は最上位bitが1なのでパターンテーブルの2番目を示しています。
少し空いてLeader+Data 64bit+Stop




83 3f 11 da 27 00 c5 00 00 d7 84 でLeader(0111/0082) + Data 64bit + Stop(0027/0a9e)
 83はパターンテーブル3番目
 3fは続く(0x3f + 1 = 64(10進数))bitがデータパターン
 84はパターンテーブル4番目を示しています。
少し空いてLeader+Data 64bit+Stop
83 3f 11 da 27 00 42 00 00 54 84 でLeader(0111/0082) + Data 64bit + Stop(0027/0a9e)
少し空いてLeader+Data 152bit+Stop
83 7f 11 da 27 00 00 38 32 00 a0 0f 00 06 60 00 01 c0 でLeader(0111/0082) + Data 128bit
 83はパターンテーブル3番目
 7fは続く(0x7f + 1 = 128)bitがデータパターン
17 00 00 52 84 でData 24bit + Stop(0027/0a9e)
 17は続く(0x17 + 1 = 24)bitがデータパターン
 (ここでデータパターンが2つに分かれているのは1つ目が最上位bitを0にした7bitで表せる最長の値0x7fまでのデータで一度区切っているためです。データパターン長を1引いた値で管理しているのは最長の0x7fの時に丁度16byteで収まる128bitにするためです。1引かずに表してしまうと1つ目が127bitで2つ目が25bitになってしまい1byteデータが伸びてしまいます。)
というフォーマットでした。
波形と見比べてみると合っていそうです。

これらを実装したHA2moduleのコードはここにあります。

2014年8月29日金曜日

リモコン受信 AVRマイコン編 その1

以前RaspberryPiでリモコン受信するやり方を書きましたが、今回はHA2module上のAVRマイコンでリモコン受信してコード化する方法について書きます。色々とあって長いので3回に分けて書きます。

赤外線リモコンのフォーマットは色々あって全てに対応するのは難しいですが、国内の一般的な赤外線リモコンはサブキャリアが38kHz前後で、AV機器は数十bit程度の長さが多いようです。エアコンの中にはかなり長く300bitを超えるものも存在しています。
赤外線受信モジュールはサブキャリアをフィルターしてパルスデータだけを出力してくれるので、GPIOで受けてパルスの変化のタイミングの間隔を見ていけばデコードできます。今回はAVRのPinChangeInterruptでTimerのCounter値を読んで前回の変化からの時間を記録するようにしました。
最初は自宅にある機器をメインに考えていたためAEHA,NEC,SONYのリモコンだけ対応すればよかったので、この割込みの処理の中でフォーマットの解釈もする実装にしていました。
ところが色々なリモコンを検討し始めたところ、このやり方ではダイキンやパナソニックのエアコンコードなどが対応出来ないため再度実装しなおしました。
新しい実装では、まずはじめにPinChange割込み処理中にHigh区間幅、Low区間幅のペアの頻度統計を取りながらコード化して記録します。
全体を記録し終えたところで押し続けることによる繰り返しパターンの判別をし、AEHA,NEC,SONYのフォーマットを解釈していずれかであればそのフォーマット、何れでもなければ記録したフォーマットをXBee経由で送るようにしています。
以下、実際の処理の詳細です。

□PinChange割込み中の処理
パルスをHigh区間、Low区間(実際の受光モジュールの出力は逆ですが、わかりやすいように無信号状態をLowとしています)をそれぞれ76kHzのカウント値で数えペアの値とします。このペアの値をPatternTableを検索し近い値があればそのPatternとして、なければ新規に登録して、そのPattern番号を求めます。
その番号を記録バッファに記録していくのですがそのまま番号を記録していくと4bit単位で記録していったとしても300bit越えのエアコンフォーマットだと余裕をみて196byte位必要になってしまいます。
普通のリモコンコードはデータを表すパターン2種類が0と1を表していて頻出するはずなのでこの2つを1bitの値として扱い、それ以外のパターンを特殊扱いすれば全体として短くすることが出来るはずです。ところがリモコンを受信し始めたタイミングではどのパターンがデータを表す2種類のパターンなのかは判りません。
そこで出現頻度を判断できるように先頭の64パルスを圧縮処理を遅らせるためにFIFOに登録しながら統計をとり、64パルス揃ったところで出現率の多いPatternを2つ選び、2つのうちHigh+Lowが小さい、等しい場合はHighが小さい方をパターン番号0、大きい方をパターン番号1とします。それ以外のパターンは出現順にパターン番号2から順番に登録します。普通のリモコンコードの場合、大体5〜6種類程度に収まります。
64パルス目以降はFIFOにパルス幅を登録しながらFIFOの先頭から順番に値を取り出します。
この時64パルス目のタイミングでそれまでのデータをまとめて処理してしまうと割込み中の処理が重すぎて取りこぼしが発生するので、FIFOでずらしたその割込みの64パルス前のパルスの処理だけを行うようにしています。
この説明だけでは判りにくいかと思いますが、あとで実際のエアコンのフォーマットを使って説明します。

□Timeout割込み
PinChange割込みのたびにTimerに100msのTimeoutを登録、更新します。
最後のPinChange割込みから100ms経つとTimeout割込みが入り、最終ビットパターンの処理を行い、通常Threadに処理を渡します。

長くなってきたので、一旦ここで切ります。
次回は通常Threadで後処理をするところからです。

2014年8月21日木曜日

子供用のスイッチボックス

1歳の息子が壁の電気のスイッチに興味をもって、頻繁にスイッチを押すために抱っこをせがむので子供用にスイッチボックスを作りました。 



よく、自治体の子育て施設とかに行くと壁にスイッチを沢山つけたものとか設置してあるので、子供は興味を持つものなのですね。
押してもなにも反応しないとすぐ飽きられてしまうので、中にモジュールをいれて単1電池で電源を取るようにしました。
これをコントロールサーバーとXBee経由で接続しLEDテープをランダムな色で光らせるようにしました。
中身はこんな感じです。


XBeeを間欠モードで動かすことで消費電力を抑えているので多分半年ほどは電池交換なしで行けるかと思います。
投げられても電池が外れないように固定して、新聞紙で間を埋めてガムテープで箱を閉じてます。
一見、ただ段ボール箱にスイッチを付けただけでどこにも繋がってないように見えるのに実際にスイッチとして動くのが面白いかと思ったのですが1歳の子供には理解できないですね。


2014年8月17日日曜日

LEDTape

PHILIPSのHueのライトリボンをみて作ってみようと思い部品を探してみました。
スイッチサイエンスにフルカラーシリアルLEDがあったので試しに購入。簡単に点くかと思ったら意外とタイミングが厳しく、8MHz動作のAVRの場合割り込み禁止してアセンブラでクロック数を数えながら無駄なく書かないとうまく点きませんでした。
写真は明るいキッチンカウンターの上でとっていますが、LEDが強すぎてLED以外の部分が真っ暗になってしまってうまく撮れなかったので見た目に合わせて色補正かけています。実際に見るとかなり眩しいです。



このLEDは1m60個のフルカラーLEDで、今回は半分に切って50cm30個で使っています。
本当は1灯ずつフルカラーで色を指定できるのですが、実際に使うとなると全体を同じ色で光らせれば十分なのでLEDの個数とR,G,Bを指定してPORTC4につないだシリアルLED を光らせる関数を作りました。
この関数を呼ぶと30us x LEDの個数分割り込み禁止になります。
1m品の場合60個なので1.8msの間blockされます。UARTでフロー制御していない場合、AVRはFIFOが1段しかないので4800bps以下に設定しないと通信を取りこぼします。
HA2moduleで接続しているので、XBeeを4800bpsで接続してコントロールサーバー経由で制御するようにしています。

void SetLED(unsigned char count, unsigned char r, unsigned char g, unsigned char b) {

  asm volatile(
               "  in __tmp_reg__,__SREG__ \n" // interrupt block 30us x count
               "  cli                     \n"
               "  in r6, %[port]          \n"
               "  or r6, %[tmp]           \n"
               "  mov r5, r6              \n"
               "  eor r5, %[tmp]          \n"
               "  mov r2, %[byteg]        \n"
               "loop:                     \n"
               "  ldi %[tmp], 8           \n" // 1
               "loopg:                    \n"
               "  out %[port], r5         \n" // 1
               "  nop                     \n" // 1
               "  sbrs r2, 7              \n" // 1 - 2
               "  out %[port], r6         \n" // 1
               "  lsl r2                  \n" // 1
               "  mov r3, %[byter]        \n" // 1
               "  dec %[tmp]              \n" // 1
               "  out %[port], r6         \n" // 1
               "  brne loopg              \n" // 2
               "                          \n"
               "  ldi %[tmp], 8           \n" // 1
               "loopr:                    \n"
               "  out %[port], r5         \n" // 1
               "  nop                     \n" // 1
               "  sbrs r3, 7              \n" // 1 - 2
               "  out %[port], r6         \n" // 1
               "  lsl r3                  \n" // 1
               "  mov r4 , %[byteb]       \n" // 1
               "  dec %[tmp]              \n" // 1
               "  out %[port], r6         \n" // 1
               "  brne loopr              \n" // 2
               "                          \n"
               "  ldi %[tmp], 8           \n" // 1
               "loopb:                    \n"
               "  out %[port], r5         \n" // 1
               "  nop                     \n" // 1
               "  sbrs r4, 7              \n" // 1 - 2
               "  out %[port], r6         \n" // 1
               "  lsl r4                  \n" // 1
               "  mov r2, %[byteg]        \n" // 1
               "  dec %[tmp]              \n" // 1
               "  out %[port], r6         \n" // 1
               "  brne loopb              \n" // 2
               "  dec %[count]            \n" // 1
               "  brne loop               \n" // 2
               "  out __SREG__,__tmp_reg__\n"
               : 
               : [count] "r" (count),
                 [byter]  "r" (r),
                 [byteg]  "r" (g),
                 [byteb]  "r" (b),
                 [tmp]    "r" ((1 << PORTC4)),
                 [port]   "I" (_SFR_IO_ADDR(PORTC))
               : "r0", "r2", "r3", "r4", "r5", "r6");
}

2014年8月9日土曜日

bootの書き込みツール(AVR USB-ISP)



HA2モジュールは大きさの関係で特殊なコネクタを使用していますが、コネクタを変換すれば3.3V対応のAVR用のwriterを使用できます。
AtmelのAVR ISP mkIIが一番無難ですが、嵩張るのでaitendo AVR-USBasp-Bを改造して(5V->3.3V)使用していました。最近もう1つ必要で購入しようとしたら在庫切れで買えなくなっていたので、同じくaitendoのUSB-ASPの方を買って改造しました。




このUSB-ASPはFWが公開されておらずそのままではMacで使えないので、こちらのページ

を参考にFWを書き換え、B面に3端子レギュレータを載せて5VとVCCをつないでいるパターンをカットして3.3Vに変更しています。小型でなかなか便利なので最近の愛用品です。


2014年7月31日木曜日

XBeeSetup

XBeeのFW書き換えや各種設定をするのにX-CTUやmoltosenso Network Managerを使用するのが普通のやり方なのですが、terminalから設定ファイルを読み込むコマンドの形で使いたかったのでFW更新して設定するプログラムを作りました。
FWはDigi Internationalのページからダウンロードしてきて下さい。
 XBee / XBee-Pro ZB (S2) Modulesを選択、次のページでFirmware Updatesを開いてXBee / XBee-PRO ZB firmware ver. 2xA7を選ぶと82001817_G.zipがダウンロードされるので展開し、82001817_G/ebl_files/のXB24-ZB_21A7.eblからXB24-ZB_29A7.eblをXBeeSetupのdirectoryにコピーします。

configファイルの記述方法はsample.confを参考にしてもらえばわかると思いますが、
書き込むfirmwareのversion(XBeeの動作モード毎にfirmwareが違う。例えばCoordinator APIで使う場合は21A7)をfirmware:の行に記述
setup:以降の行はXBeeに送るATコマンドを記述します。
./XBeeSetup -c sample.conf -d /dev/cu.usbserial (serialのdeviceファイルは環境に合わせて下さい)
を実行するとFWのverisonを確認し、必要ならupdateして設定を書き込みます。
setupの最後にWRを書いておくことでXBeeの不揮発性メモリに設定が記録されます。

ソースコードはここにあります。

2014年7月25日金曜日

HA2module

HA2bootから起動されるHA-2/FC、RP-1/RIR用のプログラムです。
基本的なXBeeとの通信機能の他、赤外線リモコンの送信、受信、HA端子の制御、監視、接点スイッチの制御と監視、ADCの監視などの機能を実装してあります。
赤外線リモコンの送受信は今のところNEC,SONY,家製協フォーマットのみ対応です。
赤外線リモコン受信、HA端子、接点スイッチの監視はイベントが発生するとホスト宛に通知が出るようにしています。
プログラムの構成はScheduler,Timer,XBee,Queue,Usartでシステムとしてのスケジュール管理とXBee経由の通信管理を行い、それ以外の物が各種コマンド実行とそれに付随するデバイスドライバになっています。
このプログラムはXBeeからのコマンドでそれぞれの機能が動作するだけの物なので単独では役に立ちません。
ホスト側のControlDaemonとの組み合わせで機能するようになっています。
ソースコードはこちらにあります。
makeするとha2module.haファイルが出来上がります。

落雷

昨日の夕方、かなり強烈な落雷がありました。
家の中で色々リセットされてました。
対策を入れてあったので今回は壊れたモジュールは有りませんでした。
対策が有効だったということかと思います。

2014年7月17日木曜日

elf2ha

avr-gccで生成された.elfファイルをHA2bootで書き込める.haファイルに変換するためのツールです。
.haフォーマットは先頭16byteのヘッダーとプログラムの先頭からの連続領域のイメージからなる単純なフォーマットです。
ヘッダーの中身はBigEndianで
struct HeaderSt {
  longword Magic; // 'HAUP'の文字列
  word SetId; // moduleの種類を表す16bitの値
  word Version; // プログラムのバージョンを表す16bitの値
  word Addr; // 先頭アドレスだがリセットベクタがあるので必然的に0x0000
  word Size; // プログラムの先頭からのサイズ
  word Reserve;
  word CRC; // プログラムの先頭から終わりまでのCRC
};
です。

HA1Controlプログラムのconfigファイルでこの.haファイルを指定しておくと起動時に各moduleのバージョンチェックをして必要なら更新作業をします。
sudo make installすると/usr/local/binにelf2haが作られます。
HA2moduleのイメージを作るのに必要になります。

ソースコードはここにあります。

2014年7月11日金曜日

HA2boot

HA-2/FCモジュールのAVRのFWをFW更新用のbootプログラムと通常使用時に動く本体FWの2つに分離しました。
bootプログラムは一番最初にAVRのboot領域に書き込みツールで書き込みます。
本体FWはbootプログラムによってXBee経由でAVRのアプリケーション領域に書き込まれます。
通常動作時は本体FWが動作しており、bootに遷移するコマンドを受けるとbootプログラムにjumpします。
bootの書き込みツールは色々あるので別途書きます。
bootは起動するとボーレートを変えながらXBeeにバージョン問い合わせのパケットを投げて帰ってきたパケットを確認することでXBeeとAVR間のボーレートを認識します。
現在のコードでは2400bps, 4800bps, 9600bps, 19200bps, 38400bps, 76800bps, 250000bpsを確認しています。この値はAVRを8MHzで動かした場合に誤差が少なくてちゃんと通信が成立しそうな値を選択しています。
boot自体はアプリケーション領域の本体FWを受け取って書き込む機能に特化しているので、それに必要なコマンドが届くのを待ち続けコマンドが来たら実行するだけの単純なものですが実装して見ると意外と大きくなってしまいATMega328のboot領域ギリギリまで使用してしまいました。
コードをgithubに置きました。