はじめに

このページの作者は、(ねずみの方の)チンチラ3匹のオーナーです。
夏場はエアコン必須で、計画停電をはじめ、エアコンが止まってしまうと、愛するペットの命に関わります。

このページは、停電後に自動でエアコンをONするリモコンを、なるべく簡単に作ろう、という内容です。

秋月電子通商の電子工作キットを使用してサンプルプログラムを書換えていく方法をとりますので、以下が必須です。
どれも低レベルなのでこれから始める方でもマニュアルや参考サイトを見れば出来ると思いますが、拒否反応が出るような方にはオススメできません。
電子工作なんてしたことない!って方は、こちらのサイトなど、丁寧なサイトが沢山ありますので、イメージしてみてください。

とても便利なので、本ページを参考に、少しでも多くの方のお役に立てれば幸いです。

経緯

同じような悩みを持った方も沢山いたようで、皆様いろいろな対策を公開されており、参考にさせていただいたのですが、どれも不安な点がありました。

例えば・・・
方法 参考URLなど 不安点
タイマー式リモコンを使う エアコン汎用リモコン / オーム電機
http://www.ohm-direct.com/shopdetail/010025000001/
計画停電で時間の決まっているときにしか使えない
夕立の雷での停電など突然の停電には対応できない
専用機器をネットに繋ぐ HOVICA / 三洋電機
http://www.nabit-ch.jp/sec/sec_remote_rev.html
(WEBカメラで温度計を映す)
機器がもう販売されていない
頻繁にアクセスして確認する必要がある
停電と同時にルータもAPも切断されるため、ネットが不通になるかも・・・
HMS-350Y / クールテクノロジーズ
http://www.qool-tech.co.jp/product/h350y/index.html
上記同様、ネットが不通になるかも・・・
頻繁にメールを確認する必要がある

そこで、「仕方ないから自分で頑張って作ってみるか!」と方向転換したところで見付けたのが、

秋月電子通商の「PICマイコン赤外線リモコン学習キット」。
http://akizukidenshi.com/catalog/g/gK-04174/

ポイントは、
これを使えば、ハードウェアの知識がほとんど無い私にも、頑張ればなんとか作れそうです。なんせ「学習キット」だし。

こんなのを作ることにした

停電回復時にエアコンON信号を送信する

このリモコンには特に電源スイッチが無く、ACアダプタを差すと電源が入るみたいなので、停電回復と同時に電源ONになります。
電源ONになってから、以下のように動くようにしました。

電源ON
 ↓10秒
エアコンOFF信号を送信(エラーリセット)
 ↓10秒
エアコンON信号を送信
 ↓10分
省電力モード信号を送信
 ↓
以降1時間毎に(念のため)エアコンON信号を送信

もちろん、プログラムの組み方次第で色々できます。

停電回数をカウントする

帰って停電になったかどうか確認できるように、電源ONになったときに回数をカウントしました。
電源ONの度にカウント追加されてしまうので、ボタン押下でクリアできるようにしました。

先ずは材料を揃える

秋月電子通商で以下を揃えます。
通販する場合の入り口はこちら。通販コードで検索できます。 合計\7,000+送料\500 (2012年5月現在)
PICkit2は材料というより機材なので、材料費としては\3,500。

これに、100円ショップやホームセンターなどで、ケースやゴム板を購入。
ただし、ボタンや赤外線LEDがあるので、ハードケースは難しいかも。先ずは何も無しで作ってみて、出来上がったものを見てから揃えたらよいかと。

組み立てる

電子工作初挑戦の方には、こちらのサイトがとても丁寧で参考になりそうです。

赤外線リモコン学習キットの組み立て

プリント基板なので、部品をぺたぺた貼り付けていくだけです。
説明シートに記載されている、部品表の番号と、基板に印字されている番号を見比べながら、はんだ付けしていきます。
抵抗の色の読み方や部品の形なども説明シートに書いてあるので、そのまま根気良く進めれば何も問題なく出来上がります。

・・・ので、割愛。

LCDディスプレイの取り付け

こちらの取り付け方は説明シートだとわかりづらかったので、補足です。

まず、リモコン本体の方にソケット(メス)を取り付け、L2(おもて面)をはんだでショートさせます。
ディスプレイの方は、ソケット(オス)を取り付けるだけです。説明シートに、抵抗をつける、とか、ショートさせる、とか書いてありますが、何もしなくて大丈夫です。

リモコン本体にLCDディスプレイを差し込めば、組み立て完了です。

あとはお好みでケースをつけるなりご自由に。
ちなみに私は、裏に薄いゴム板を貼り付けて、小さいブックエンドを斜めに曲げて台にしました。

こんな感じ。

動かしてみる

サンプルプログラムがすでにマイコンに仕込まれているので、組み立てが終わればとりあえず動かせます。
説明シートを見ながら、適当に動かしてみて、動作を確認します。

まずはテレビのリモコンを使って、リモコンコード受信(DipスイッチOFF/OFF)を試してみましょう。
一行目に「NEC」もしくは「KADENKYO」、二行目に16進数でリモコンコードが表示されるはずです。

次に、学習モード(DipスイッチON/OFF)にして、テレビリモコンのコードを保存して、送信してみます。
こちらも問題なければ、とりあえずハードウェアは完成です。

テレビリモコンでの確認が済んだら、次はエアコンで同様に試してみます。
すると、エアコンOFFはうまくいくのに、エアコンONはうまくいかない・・・ということがわかると思います。
これは、エアコンのON信号は温度等の細かい設定を送っているため、テレビの信号よりもかなり長いからです。
サンプルプログラムは6byteまでしか対応していないので、エアコンで使うためには、長い信号に対応できるように、プログラムを書き換えなければなりません。

サンプルプログラムをビルドする

必要環境を整える

まず、パソコンに「MPLAB IDE」と「HI-TECH C コンパイラ」をインストールします。
PICkit2にインストールCDがついてくるので、それをインストールしてもいいですし、ネットから最新版をダウンロードしてもいいです。
インストール手順は、こちらのサイトが参考になります。

MPLABの詳しい使い方は、こちらのサイトなどを参考に。

サンプルプログラムを取り込む

以下の手順で、プロジェクトを作成し、サンプルプログラムを取り込みます。

MPLAB IDEを起動します。

メニューよりProject→Newを選択、
「New Project」画面で、Project Name(保存する任意のファイル名)、Project Directory(保存するフォルダ)を指定し、「OK」を押します。
すると、Project Directoryに指定したフォルダに、指定したファイル名に「.mcp」「.mcs」「.mcw」の拡張子がついたファイルができます。
次回から起動するときは、この「.mcw」拡張子のファイルをダブルクリックすれば、直接開けます。

リモコン学習キットについてきた名刺サイズのCDから、サンプルプログラムのソースコード(sourceフォルダ内全部)を、同じフォルダにコピーします。

Project→Add Files to Projectを選択、
ファイル選択画面が開いたら、ファイルの種類を「All Source and Headers」に変更して、全てのソースファイル(.c)とヘッダ(.h)を読み込みます。
ひとつずつ追加してもいいですが、Shiftを押しながら全選択できます。
読み込んだら、File→Save Workspaceでワークスペースを保存しときましょう。

サンプルプログラムをビルドしてみる

メニューよりProject→Buildを選択します。
すると、Output画面にずら〜っとビルドステータスが表示されます。

・・・
Error   [984] C:\Program Files\HI-TECH Software\PICC\9.81\include\pic16f887.h; 439.13 type redeclared
Error   [1098] C:\Program Files\HI-TECH Software\PICC\9.81\include\pic16f887.h; 439.13 conflicting declarations for variable "T1CONbits" (C:\Program Files\HI-TECH Software\PICC\9.81\include\pic16f886.h:408)
Error   [984] C:\Program Files\HI-TECH Software\PICC\9.81\include\pic16f887.h; 472.13 type redeclared
Advisory[1] too many errors (21)

********** Build failed! **********

・・・失敗してしまいました。
エラーの内容から推測するに、pic16f887.h ってインクルードファイルの中の変数が認識できてないっぽいです。

HI-TECHコンパイラ標準のインクルードファイルがインストールしたフォルダにあるはずで、エラーを参考にフォルダの中を見てみます。
(この場合は、C:\Program Files\HI-TECH Software\PICC\9.81\include)
すると、たしかにincludeフォルダの中にpic16f887.hがあるのですが、更にlegacyってフォルダの中にもpic16f887.hがあるみたいです。

legacyフォルダの中のpic16f887.hを使うと問題なさそうなので、コンパイラが探しに行くフォルダに、legacyフォルダを追加してやります。
メニューよりProject→Build Options→Projectを選択します。
Directoriesタブを選択して、Show directories for:から「Include Search Path」を選択します。
「New」ボタンを押下して、下のボックスに追加された行の「...」から、先程のlegacyフォルダを選択します。
パスを確認したら、適用→OKボタン押下で確定させます。

legacyフォルダへのパスが通ったら、メニューからProject→Rebuildで、再度ビルド(=リビルド)します。

・・・
Loaded C:\・・・\・・・.cof.  ←出来上がったプログラムのパス

********** Build successful! **********

これで、上手くビルドできました。

サンプルプログラムを書き換える

注意

ここから本ページのメインになりますが、すべて我が家のエアコンを基に書いています。
我が家のエアコンでの自動転送以外に使う予定がないので、最小限かつハードコーディングで作ってます。
ほかのエアコンで試したことはありませんので、各ご家庭のエアコンに対応できるように工夫しながら進めてください。
もし、この通りに進めてうまくいかなくても、保障いたしかねます・・・。

ちなみに、我が家のエアコンのコードは家電協フォーマットでしたので、NECフォーマットだった方は適宜読み替えながら進めてください。

これから書き換える動作のまとめ

Dipスイッチ 変更前動作 変更後動作
OFF / OFF ボタン押下で定義されているコードを送信 ボタン押下で定義されているコードを送信(コード変更)
ON / OFF 受信したコードを保存 電源ONになったら自動でコードを送信
OFF / ON 保存したコードを送信
ON / ON 受信したコードを表示(NEC:4byte、家電協:6byte 固定) 受信したコードを表示(24byteまで可変)

こんな感じで書き換えていきます。

長いコードを受信できるようにする

まずはエアコンのリモコンコードを知る必要があるので、DIPスイッチON/ONの、表示モードから変更します。
ここが一番の難関です。

デフォルトだと、家電協フォーマット6byte・NECフォーマット4byteなので、入れ物は8byteでとってあります。
それだと足りないので、24byteの入れ物を新設します。

ir_main.c
/*==============================================================================*/
/*  グローバル変数宣言                                                          */
/*==============================================================================*/
unsigned char   rcv_data[TBL_CODE_SIZE];        /* 受信データ                   */
unsigned char   pre_data[TBL_CODE_SIZE];        /* 受信データ比較用             */
unsigned char   rx_format;                      /* 受信データフォーマット種別   */
unsigned char   eep_rdata[EEPROM_TABLE_SIZE];   /* EEPROM読出し用RAM領域        */
EEP_DATA        eep_wdata;                      /* EEPROM保存用 RAM領域         */
unsigned char   rcv_data_all[24];               // ADD 受信データ24byte対応 ←追加
unsigned char   rcvnum;                         // ADD 受信データ数         ←追加
ir_in.c
/*==============================================================================*/
/*  グローバル変数宣言                                                          */
/*==============================================================================*/
extern unsigned char   rcv_data[TBL_CODE_SIZE];    /* IR 受信受信データ         */
extern unsigned char   pre_data[TBL_CODE_SIZE];    /* IR 比較用データ           */
extern unsigned char   rx_format;                  /* IR フォーマット種別       */
extern unsigned char   rcv_data_all[24];           // ADD 受信データ24byte対応 ←追加
extern unsigned char   rcvnum;                     // ADD 受信データ数         ←追加
・・・

/*==============================================================================
*   MODULE        : ir_recieve
*   FUNCTION      : 赤外線受信処理
*   ARGUMENT      : none
*   RETURN        : 受信状態
*                 :     対応可能な赤外線フォーマット    -> 受信成功
*                 :     未対応の赤外線フォーマット      -> 不明なフォーマット
*                 :     リーダーコードの部分が1ms以下   -> ノイズ or リピートコード
*   NOTE          : 受信側では信号が反転しています
*                 :     送信側で Hi の部分が 受信側では Low になります
*===============================================================================*/
unsigned char ir_recieve(void)
{
    ・・・
    
    /* 受信データ初期化 */
    memset(&rcv_data, 0x00, sizeof(rcv_data));
    memset(&rcv_data_all, 0x00, sizeof(rcv_data_all));  ←追加
    
    ・・・
}

受信したコードを格納するのに、元のソースはNEC4byte・家電協6byteを1byte目から1byteずつ処理を書いてありますが、
これを何byte目か限定せずに、計算式に直します。
byte数を管理するために、「j」の変数を使います。

ir_in.c
/*==============================================================================
*   MODULE        : ir_recieve
*   FUNCTION      : 赤外線受信処理
*   ARGUMENT      : none
*   RETURN        : 受信状態
*                 :     対応可能な赤外線フォーマット    -> 受信成功
*                 :     未対応の赤外線フォーマット      -> 不明なフォーマット
*                 :     リーダーコードの部分が1ms以下   -> ノイズ or リピートコード
*   NOTE          : 受信側では信号が反転しています
*                 :     送信側で Hi の部分が 受信側では Low になります
*===============================================================================*/
unsigned char ir_recieve(void)
{
    unsigned char   i, j;    ←jを追加
    ・・・
    
    /***********************************************************
    *   データコード判定(デコード)
    ************************************************************/
    s_data = 0x01;  /* Shift Data 2byte以上の Shift が出来ない */

    /* NEC format   */
    if (rx_format == FORM_NEC) {
        // /* 32bit分繰り返す      */
        // for (i = 0; i < 32; i++) {    ←コメントアウト
        for (i = 0; i < 192; i++) {      ←追加(24byte=192bitに変更)
            ・・・

            /* Hi が 1ms 以上続いた -> 1b       */
            if (TMR1H >= 0x04) {
                // /* 1byte */     ←コメントアウト(ここから)
                // if (i<8) {
                //     rcv_data[0] |= ((s_data & 0x01) << (7 - i));
                // }
                // /* 2byte */
                // else if (i<16) {
                //     rcv_data[1] |= ((s_data & 0x01) << (7 - (i-8)));
                // }
                // /* 3byte */
                // else if (i<24) {
                //     rcv_data[2] |= ((s_data & 0x01) << (7 - (i-16)));
                // }
                // /* 4byte */
                // else {
                //     rcv_data[3] |= ((s_data & 0x01) << (7 - (i-24)));
                // }               ←コメントアウト(ここまで)
                j = i/8;                                                ←追加
                rcv_data_all[j] |= ((s_data & 0x01) << (7 - (i-j*8)));  ←追加
            } else {
                ;   /* 0b */
            }
        }
    }
    /* 家電協 format    */
    else if (rx_format == FORM_KDN) {
        // /* 48bit    */
        // for (i = 0; i < 48; i++) {    ←コメントアウト
        for (i = 0; i < 192; i++) {      ←追加(24byte=192bitに変更)
            ・・・
            
            /* Hi が 800us 以上続いた -> 1b     */
            if (TMR1H >= 0x03) {
                // /* 1byte */     ←コメントアウト(ここから)
                // if (i<8) {
                //     rcv_data[0] |= ((s_data & 0x01) << (7 - i));
                // }
                // /* 2byte */
                // else if (i<16) {
                //     rcv_data[1] |= ((s_data & 0x01) << (7 - (i-8)));
                // }
                // /* 3byte */
                // else if (i<24) {
                //     rcv_data[2] |= ((s_data & 0x01) << (7 - (i-16)));
                // }
                // /* 4byte */
                // else if (i<32) {
                //     rcv_data[3] |= ((s_data & 0x01) << (7 - (i-24)));
                // }
                // /* 5byte */
                // else if (i<40) {
                //     rcv_data[4] |= ((s_data & 0x01) << (7 - (i-32)));
                // }
                // /* 6byte */
                // else {
                //     rcv_data[5] |= ((s_data & 0x01) << (7 - (i-40)));
                // }               ←コメントアウト(ここまで)
                j = i/8;                                                ←追加
                rcv_data_all[j] |= ((s_data & 0x01) << (7 - (i-j*8)));  ←追加
            } else {
                ;   /* 0b */
            }
        }
    }
    ・・・
}

これで合っているのですが・・・
実際に私が動かしたところ、1回毎に微妙に違った値が出てきてしまいました。
処理が追いついていないと仮定して高速化を図ったところ上手くいきましたので、変更を加えます。

掛け算・割り算は処理が遅くなりますので、
×8を3ビットのビットシフトに置き換えて、
バイト数を求めるのに毎回i/8していたのを、forループで管理することにします。

ir_in.c
unsigned char ir_recieve(void)
{
    unsigned char   i, j, k;    ←kも追加
    ・・・
    
    /***********************************************************
    *   データコード判定(デコード)
    ************************************************************/
    s_data = 0x01;  /* Shift Data 2byte以上の Shift が出来ない */

    /* NEC format   */
    if (rx_format == FORM_NEC) {
        // /* 32bit分繰り返す      */
        // for (i = 0; i < 32; i++) {
        // for (i = 0; i < 192; i++) {    ←コメントアウト
        i = 0;                            ←追加
        for(j = 0; j < 24; j++){          ←追加
            for(k = 0; k < 8; k++){       ←追加
                ・・・

                /* Hi が 1ms 以上続いた -> 1b       */
                if (TMR1H >= 0x04) {
                    // /* 1byte */
                    ・・・
                    // j = i/8;                                                ←コメントアウト
                    // rcv_data_all[j] |= ((s_data & 0x01) << (7 - (i-j*8)));  ←コメントアウト
                    rcv_data_all[j] |= ((s_data & 0x01) << (7 - (i-(j<<3))));    ←追加
                } else {
                    ;   /* 0b */
                }
                i++;                ←追加
            }                       ←追加
        }
    }
    /* 家電協 format    */
    else if (rx_format == FORM_KDN) {
        // /* 48bit    */
        // for (i = 0; i < 48; i++) {
        // for (i = 0; i < 192; i++) {    ←コメントアウト
        i = 0;                            ←追加
        for(j = 0; j < 24; j++){          ←追加
            for(k = 0; k < 8; k++){       ←追加
                ・・・

                /* Hi が 800us 以上続いた -> 1b     */
                if (TMR1H >= 0x03) {
                    // /* 1byte */
                    ・・・
                    // j = i/8;                                                ←コメントアウト
                    // rcv_data_all[j] |= ((s_data & 0x01) << (7 - (i-j*8)));  ←コメントアウト
                    rcv_data_all[j] |= ((s_data & 0x01) << (7 - (i-(j<<3))));    ←追加
                } else {
                    ;   /* 0b */
                }
                i++;                ←追加
            }                       ←追加
        }
    }
    ・・・
}

8ms以上信号が来なかったらループを抜けるようにします。

ir_in.c
unsigned char ir_recieve(void)
{
    unsigned char   i, j, k;
    unsigned char   s_data;
    unsigned char   time_out;
    unsigned long   count;       ←追加
    
    ・・・
    
    /* NEC format   */
    if (rx_format == FORM_NEC) {
        ・・・
        i = 0;
        for(j = 0; j < 24; j++){
            for(k = 0; k < 8; k++){
                /* 家電協 format は IR_IN : Hi 時間で bit 判定 */
                /* 従って、IR_IN の Lo を読み飛ばす        */
                // while (!(PORTC & 0x01));        ←コメントアウト
                // Loが8ms以上 -> フレーム終了     ←追加(ここから)
                count = 0;
                while(!(PORTC & 0x01)){
                    count++;
                    if(count >= 3200)
                        goto in_end_nec;
                }                                  ←追加(ここまで)
                
                /* Hi 時間測定開始 -> Timer Reset   */
                Timer_Reset();
                // while (PORTC & 0x01);           ←コメントアウト
                // Loが8ms以上 -> フレーム終了     ←追加(ここから)
                count = 0;
                while(PORTC & 0x01){
                    count++;
                    if(count >= 3200)
                        goto in_end_nec;
                }                                  ←追加(ここまで)

                /* Hi が 1ms 以上続いた -> 1b       */
                ・・・
            }
        }
in_end_nec:         ←追加
    }
    /* 家電協 format    */
    else if (rx_format == FORM_KDN) {
        ・・・
        i = 0;
        for(j = 0; j < 24; j++){
            for(k = 0; k < 8; k++){
                /* 家電協 format は IR_IN : Hi 時間で bit 判定 */
                /* 従って、IR_IN の Lo を読み飛ばす        */
                // while (!(PORTC & 0x01));        ←コメントアウト
                // Loが8ms以上 -> フレーム終了     ←追加(ここから)
                count = 0;
                while(!(PORTC & 0x01)){
                    count++;
                    if(count >= 3200)
                        goto in_end_kdn;
                }                                  ←追加(ここまで)
                
                /* Hi 時間測定開始 -> Timer Reset   */
                Timer_Reset();
                // while (PORTC & 0x01);           ←コメントアウト
                // Loが8ms以上 -> フレーム終了     ←追加(ここから)
                count = 0;
                while(PORTC & 0x01){
                    count++;
                    if(count >= 3200)
                        goto in_end_kdn;
                }                                  ←追加(ここまで)
                               
                /* Hi が 800us 以上続いた -> 1b     */
                ・・・
            }
        }
in_end_kdn:         ←追加
    }
    ・・・
}

最後に、受信したビット数をグローバル変数に格納して、成功を返します。

ir_in.c
unsigned char ir_recieve(void)
{
    ・・・
    
    /* NEC format   */
    if (rx_format == FORM_NEC) {
        ・・・
in_end_nec:
        rcvnum = i-1;           ←追加
    }
    /* 家電協 format    */
    else if (rx_format == FORM_KDN) {
        ・・・
in_end_kdn:
        rcvnum = i-1;           ←追加
    }
    // /* IR_IN の Lo を読み飛ばす (STOP Bit)  */  ←コメントアウト(ここから)
    // while (!(PORTC & 0x01));
    // /* Hi 時間測定開始 -> Timer Reset       */
    // Timer_Reset();
    // /* STOP bit 以降の Data 送信有無確認    */
    // while (PORTC & 0x01) {
    //     /* 規定時間内に Data 無し -> 正常   */
    //     if (TMR1H >= 0x08) {
    //         return SUCCESS;
    //     }
    // };                                          ←コメントアウト(ここまで)
    
    // return UNKNOWN;                             ←コメントアウト
    return SUCCESS;                                ←追加
}

長いコードを表示できるようにする

LCDの表示も24byteに対応させるため、以下のような動作になるように変更します。
LCDディスプレイの1行目にテキストを表示する関数を追加します。

lcd.c
/*==============================================================================
*   MODULE        : lcd_put_text1
*   FUNCTION      : テキスト を LCD 1行目 へ表示
*   ARGUMENT      : text : テキスト先頭アドレス
*   RETURN        : none
*   NOTE          : 1行目の左端へ表示
*===============================================================================*/
void lcd_put_text1(unsigned char* text)
{
    unsigned char i;

    lcd_goto_posi(0x00);

    for(i = 0; i < 20; i++){
        if(text[i] == 0)
            break;
        lcd_put_char(text[i]);
    }

    return;
}

LCDディスプレイの2行目にbit数を表示する関数を追加します。
計算式にするのが面倒なので、100の位までで作っています(型がu_charなので問題なし)。
数を数字の文字にするには0x30を加えてやればOK。

lcd.c
/*==============================================================================
*   MODULE        : lcd_put_bit
*   FUNCTION      : bit数を 2行目 へ表示
*   ARGUMENT      : num : bit数(u_char)
*   RETURN        : none
*===============================================================================*/
void lcd_put_bit(unsigned char num)
{
    unsigned char i, n;

    lcd_goto_posi(0x40);

    if(num >= 100)
        lcd_put_char(num/100+0x30);
    if(num >= 10)
        lcd_put_char((num/10)%10+0x30);
    lcd_put_char(num%10+0x30);
    lcd_put_char(' ');
    lcd_put_char('b');
    lcd_put_char('i');
    lcd_put_char('t');

    return;
}

ヘッダに追加した関数のプロトタイプ宣言を加えます。

lcd.h
/*==========================================================================*/
/*  プロトタイプ宣言                                                        */
/*==========================================================================*/
・・・
void toggle_E(void);
void lcd_put_text1(unsigned char* text);      ←追加
void lcd_put_bit(unsigned char num);          ←追加

メイン関数のDipスイッチOFF/OFFの箇所を書き換えます。。

ir_main.c
/*==============================================================================
*   MODULE        : main
*   FUNCTION      : IR Remocon リモコンメイン関数
*   ARGUMENT      : none
*   RETURN        : none
*   NOTE          : none
*===============================================================================*/
void main()
{
    ・・・
    unsigned char   rcvbyte;                ←追加
    
    ・・・
    
    /****************************************************************/
    /* Dip SW 1:Lo  Dip SW 2:Lo ⇒ LCD表示モード                   */
    /****************************************************************/
    if ((PORTA & MODE_MASK) == RUN_MODE_4) {
        /* LCD Initialize       */
        lcd_init();
        // /* IR 比較データ初期化  */                    ←コメントアウト
        // memset(&pre_data, 0x00, sizeof(pre_data));    ←コメントアウト
        for (;;)
        {
            ・・・・
                if (ir_state == SUCCESS) {
                    lcd_clear();                    /* LCD All Clear        */
                    lcd_put_ir_format(rx_format);   /* IR format Type 表示  */
                    switch (rx_format) {
                      case FORM_NEC:        /* 受信コード = NEC format      */
                        /* NEC format の IR 受信データ表示                  */
                        // lcd_put_ir_data(&rcv_data, FORMAT_DISP_SIZE_NEC);  ←コメントアウト
                        // break;                                             ←コメントアウト(NECも家電協も同じ処理)
                      case FORM_KDN:        /* 受信コード = 家電協 format   */
                        /* 家電協 format の IR 受信データ表示               */
                        // lcd_put_ir_data(&rcv_data, FORMAT_DISP_SIZE_KDN);  ←コメントアウト
                        // 受信バイト数を表示                                 ←追加(ここから)
                        rcvbyte = (rcvnum+7)/8;
                        lcd_put_bit(rcvnum);
                                                for(;;){
                            /* Key Check */
                            key_code = key_input_check();
                            /* Key 押下なし -> CPU Sleep    */
                            if (key_code == KEY_OFF) {
                                /* CPU Sleep -> Wake Up     */
                                cpu_sleep();
                            }
                            /* Key 押下あり -> 何れかの動作 */
                            else {
                                // 1〜8byte
                                if(key_code == KEY_CODE05){
                                    lcd_clear();
                                    lcd_put_text1("1-8byte:");
                                    if(rcvbyte > 8)
                                        lcd_put_ir_data(&rcv_data_all[0], 8);
                                    else
                                        lcd_put_ir_data(&rcv_data_all[0], rcvbyte);
                                }
                                // 9〜16byte
                                else if(key_code == KEY_CODE06){
                                    lcd_clear();
                                    lcd_put_text1("9-16byte:");
                                    if(rcvbyte > 16)
                                        lcd_put_ir_data(&rcv_data_all[8], 8);
                                    else if(rcvbyte > 8)
                                        lcd_put_ir_data(&rcv_data_all[8], rcvbyte-8);
                                }
                                // 17〜24byte
                                else if(key_code == KEY_CODE07){
                                    lcd_clear();
                                    lcd_put_text1("17-24byte:");
                                    if(rcvbyte > 24)
                                        lcd_put_ir_data(&rcv_data_all[16], 8);
                                    else if(rcvbyte > 16)
                                        lcd_put_ir_data(&rcv_data_all[16], rcvbyte-16);
                                }
                                // 再度フォーマットと受信bit数を表示
                                else if(key_code == KEY_CODE08){
                                    lcd_clear();
                                    lcd_put_ir_format(rx_format);
                                    lcd_put_bit(rcvnum);
                                }
                                // 次の受信を待つ
                                else if(key_code == KEY_CODE16){
                                    lcd_clear();
                                    break;
                                }
                            }
                        }                                                     ←追加(ここまで)
                        break;
                      default:              /* 受信コード = 未対応 format   */
                        break;
                    }
                } else if (ir_state == UNKNOWN) {
                    /* 未対応 format の IR 受信データ表示               */
                    lcd_clear();                    /* LCD All Clear        */
                    lcd_put_ir_format(FORM_UKN);   /* IR format Type 表示  */
//                    lcd_put_ir_data(&rcv_data, FORMAT_DISP_SIZE_UKN);
                } else {
                    ;   /* ノイズの為、もう一度受信処理 */
                }
                /* IR 比較データ保持 */
                memcpy(&pre_data, &rcv_data, sizeof(rcv_data));
        }
    }
}

リビルドして、上手くいけばリモコンコード表示の部分は完成です。

リモコンコードを表示させてみる

PICマイコンにプログラムを送り込みます。

リモコンのAC電源を抜いて、PICkitを使って、パソコンのUSBとリモコン本体を接続します。


メニューから、Programmer→Select Programmer→PICkit 2(PICkit3を購入した場合はPICkit3)を選択します。
下記のようなWarningが出ても、OKで抜けて大丈夫です。
PKWarn0003: Unexpected device ID: Please verify that a PIC16F887 is correctly installed in the application. (Expected ID = 0x2080, ID Read = 0x2060)

OutputウィンドウにPICkit2タブができて、そこにエラーが出ていなければ接続できています。(上記のWarningはOK)

・・・
PICkit 2 Ready

再度、Project→Rebuildでリビルドします。

Programmer→Programで、PICマイコンに送信します。
エラーが出ていなければ、書込み完了です。

USBとAC電源を抜き、DipスイッチをOFF/OFFにセットしてから、再度電源を入れます。

作成したリモコンに向けて、本物のリモコンのボタンを押します。
コードが表示されるハズなので、リモコンフォーマットの種類(NECか家電協)と、必要なボタンのコードを調べてメモしておきます。

このページの通りに作った場合の操作を再度載せておきます。 ちなみに、KEY_CODEは、黄色のファンクションボタンの左から01,02,03,04、黒色のボタンの左上から右方向に05,06,07,08、次の行の左から09,10・・・となっているみたいです。

実際に表示させてみると、電源ONのコードがやたら長いことがわかります。
色々な情報が入っていることが推測できます。

コードを送信してみる

DipスイッチON/ONのとき、ボタン押下でコードを送信するようにプログラムを書き換えます。
先程メモしたコードを送信して、正しく動作するか確認するのが目的です。

先ずは、変数を作って、調べたコードを格納します。
例えば、表示されたコードが「1234abcd」だった場合、1byteずつ16進数で格納しますので、「0x12, 0x34, 0xab, 0xcd」とします。
この場合4byteなので、変数のサイズは[4]で確保します。空きは必要ありません。

ここでは、電源ON・電源OFF・省エネモード、の3つを定義しますが、適宜増減させてください。
ボタン12個(ファンクションボタンも使えば16個)まで割り当てられるハズなので。

ir_main.c
void main()
{
    ・・・
    unsigned char data_on[15] = {0x28, 0xc6, 0x00, 0x08, 0x08, 0x3f, 0x10, 0x0c,   
                                 0x86, 0x80, 0x00, 0x00, 0x00, 0x00, 0x76};  // 電源ON   ←追加(例)
    unsigned char data_power[6] = {0x28, 0xc6, 0x00, 0x08, 0x08, 0x90};  // 省エネ       ←追加(例)
    unsigned char data_off[6] = {0x28, 0xc6, 0x00, 0x08, 0x08, 0x40};  // 電源OFF        ←追加(例)
   
    ・・・
}

ir_out.cに、任意のbyte数のコードを出力する関数を追加します。

家電協は「IR_KDN_format」関数を基に、IR_KDN_format_num関数を、
NECは「IR_NEC_format」関数を基に、IR_NEC_format_num関数をそれぞれ作ります。
違いは、forループのサイズKDN_CODE_SIZE/NEC_CODE_SIZEを、引数のnumに変えただけです。

ir_out.c
・・・

↓ 下記2つの関数を追加

/*==============================================================================
*   MODULE        : IR_NEC_format_num
*   FUNCTION      : Dataを NEC formatで 任意byte数出力
*   ARGUMENT      : *tbl_p : Data の先頭アドレス  num : byte数
*   RETURN        : none
*   NOTE          : none
*===============================================================================*/
void IR_NEC_format_num(unsigned char* tbl_p, unsigned char num)
{
    unsigned char  i, j;

    /* Reader Code Output */
    IR_NEC_ReaderCode_Send();

    /* カスタムコード/データコード (4byte) 出力 */
    for (i=0;i<num;i++)
    {
        /* 1bit毎にデータ出力 */
        for(j=0;j> j)) {
                IR_NEC_DataCode_BitOn_Send();
            }
            /* Bit OFF  */
            else {
                IR_NEC_DataCode_BitOff_Send();
            }
        }
    }
    /* Stop Bit Send */
    IR_NEC_DataCode_StopBit_Send();
    /* 1フレーム終了後の Wait Time  */
    __delay_ms(NEC_DATA_END);

    return;
}


/*==============================================================================
*   MODULE        : IR_KDN_format_num
*   FUNCTION      : Dataを 家電協 formatで 任意byte数出力
*   ARGUMENT      : *tbl_p : Data の先頭アドレス  num : byte数
*   RETURN        : none
*   NOTE          : none
*===============================================================================*/
void IR_KDN_format_num(unsigned char* tbl_p, unsigned char num)
{
    unsigned char  i, j;

    /* Reader Code Output */
    IR_KDN_ReaderCode_Send();

    /* カスタムコード/データコード (6byte) 出力 */
    for (i=0;i<num;i++)
    {
        /* 1bit毎にデータ出力 */
        for(j=0;j> j)) {
                IR_KDN_DataCode_BitOn_Send();
            }
            /* Bit OFF  */
            else {
                IR_KDN_DataCode_BitOff_Send();
            }
        }
    }
    /* Stop Bit Send */
    IR_KDN_DataCode_StopBit_Send();
    /* 1フレーム終了後の Wait Time  */
    __delay_ms(KDN_DATA_END);

    return;
}

ヘッダに追加した関数の定義を加えます。

ir_out.h
/*==============================================================================*/
/*  プロトタイプ宣言                                                            */
/*==============================================================================*/

/* NEC format 関連              */
・・・
void IR_NEC_format_num(unsigned char* tbl_p, unsigned char num);   ←追加

/* 家電協 format 関連           */
・・・
void IR_KDN_format_num(unsigned char* tbl_p, unsigned char num);   ←追加

・・・

メイン関数のDipスイッチON/ONの箇所を書き換えます。
動作は以下のようにします。
ir_main.c
void main()
{
    ・・・
    unsigned char   rcvbyte;                ←追加
    
    ・・・
    
    /****************************************************************/
    /* Dip SW が両方 Hi ⇒ 送信モード                               */
    /****************************************************************/
    if ((PORTA & MODE_MASK) == RUN_MODE_1) {
        /* Function Type Init (Function 1 で初期化) */
        func_key_type = KEY_CODE01;
        /* Function1 LED On */
        led_control(LED_MASK);

        for (;;)
        {
            /* Key Check */
            key_code = key_input_check();

            /* Key 押下なし -> CPU Sleep    */
            if (key_code == KEY_OFF) {
                /* CPU Sleep -> Wake Up     */
                cpu_sleep();
            }
            /* Key 押下あり -> 何れかの動作 */
            else {
                // /* Function Key 押下された  */               ←コメントアウト(ここから)
                // if (key_code <= KEY_CODE04) {
                //     /* Function Type 保持   */
                //     func_key_type = key_code;
                //     /* 対応した LED On      */
                //     led_control((LED_MASK << key_code));
                // }
                // /* 動作キーが押下された     */
                // else {
                //     /* 対応したコードを送信 */
                //     ir_out_start(func_key_type, key_code);
                // }                                            ←コメントアウト(ここまで)
 
                // 電源ON                                      ←追加(ここから)
                if(key_code == KEY_CODE05){
                    IR_KDN_format_num(data_on, sizeof(data_on));
                }
                // 省エネ
                else if(key_code == KEY_CODE06){
                    IR_KDN_format_num(data_power, sizeof(data_power));
                }
                // 電源OFF
                else if(key_code == KEY_CODE07){
                    IR_KDN_format_num(data_off, sizeof(data_off));
                }                                              ←追加(ここまで)
            }
        }
    }
    
    ・・・
}

NECフォーマットの場合は「IR_NEC_format_num」関数の方を使うように変更してください。
また、関数の2つめの引数は、送信するコードのbyte数を直接数値で指定してもいいですが、変数定義時にサイズを間違えていなければsizeof関数を使えます。

リビルドしてエラーが出なければ、ProgrammerをPICkitにして送り込みます。
対応するボタンを押してみて、エアコンが思ったとおりの動作をするか確認してください。

停電復帰時の処理を書く

DipスイッチON/OFFもしくはOFF/ONのとき、以下の動作をするようにします。

電源ON
 ↓10秒
エアコンOFF信号を送信(エラーリセット)
 ↓10秒
エアコンON信号を送信
 ↓10分
省電力モード信号を送信
 ↓
以降1時間毎に(念のため)エアコンON信号を送信

DipスイッチON/OFFの場合とOFF/ONの場合とで処理を分けてもいいのですが、特に思い付かないので、勿体無いですが両方同じ処理にしてしまいました。

サンプルソースの元の処理をコメントアウトして、新しく処理を作り直します。

ir_main.c
void main()
{
    ・・・
    
#if 0                                                         ←追加
    /****************************************************************/
    /* Dip SW 1:Lo  Dip SW 2:Hi ⇒ 受信モード (EEPROMへの保存)      */
    /****************************************************************/
    if ((PORTA & MODE_MASK) == RUN_MODE_2) {
        ・・・
    }

    /****************************************************************/
    /* Dip SW 1:Hi  Dip SW 2:Lo ⇒ 学習モード (EEPROM Data 送信)    */
    /****************************************************************/
    if ((PORTA & MODE_MASK) == RUN_MODE_3) {
        ・・・
    }
#else                                                         ←追加
    /****************************************************************/               ←追加(ここから)
    /* Dip SW 1:Lo  Dip SW 2:Hi ⇒ 停電対応                         */
    /* Dip SW 1:Hi  Dip SW 2:Lo ⇒ 停電対応                         */
    /****************************************************************/
    if (((PORTA & MODE_MASK) == RUN_MODE_2) || ((PORTA & MODE_MASK) == RUN_MODE_3)) {
    
    }                                                                                ←追加(ここまで)
#endif                                                        ←追加

・・・
}

処理の中身を書きます。

指定時間待つ関数として、__delay_usと__delay_msという関数が用意されているようです。
ただ、あまり大きな値は無理なようなので、__delay_ms(100)として100msのスリープを、forループでまわしてます。

ir_main.c
void main()
{
    ・・・
    unsigned long   ul;             // カウンタ                       ←追加
    unsigned char   tmptxt[20];     // LCD表示用テキスト              ←追加

    unsigned char data_on[15] = {0x28, 0xc6, 0x00, 0x08, 0x08, 0x3f, 0x10, 0x0c,
                                 0x8e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7a};  // add 電源ON
    unsigned char data_power[6] = {0x28, 0xc6, 0x00, 0x08, 0x08, 0x90};  // add 省エネ
    unsigned char data_off[6] = {0x28, 0xc6, 0x00, 0x08, 0x08, 0x40};  // add 電源OFF

    ・・・
    
    /****************************************************************/
    /* Dip SW 1:Lo  Dip SW 2:Hi ⇒ 停電対応                         */
    /* Dip SW 1:Hi  Dip SW 2:Lo ⇒ 停電対応                         */
    /****************************************************************/
    if (((PORTA & MODE_MASK) == RUN_MODE_2) || ((PORTA & MODE_MASK) == RUN_MODE_3)) {
        led_control(ALL_OFF);	// LED OFF                            ←追加(ここから)
        
        // LCD表示
        lcd_init();
        strcpy(tmptxt, "AUTOSW READY");
        lcd_put_text1(tmptxt);

        // 10秒待つ
        for(ul=0; ul<100; ul++){
            __delay_ms(100);
        }

        // 電源OFF送信(エラー対応)
        led_control((LED_MASK << KEY_CODE03));  // LED3 ON
        IR_KDN_format_num(data_off, sizeof(data_off));  // 電源OFF
        led_control(ALL_OFF);  // LED OFF
        
        // 10秒待つ
        for(ul=0; ul<100; ul++){
            __delay_ms(100);
        }
        
        // 電源ON送信
        led_control((LED_MASK << KEY_CODE01));  // LED1 ON
        IR_KDN_format_num(data_on, sizeof(data_on));  // 電源ON
        led_control(ALL_OFF);  // LED OFF
        
        // 10分待つ
        for(ul=0; ul<6000; ul++){
            __delay_ms(100);
        }
        
        // 省電力モード送信
        led_control((LED_MASK << KEY_CODE02));	// LED2 ON
        IR_KDN_format_num(data_power, sizeof(data_power));  // 省電力モード
        led_control(ALL_OFF);  // LED OFF
        
        // 1時間毎にON送信
        for(;;){
            // 1時間待つ
            for(ul=0; ul<36000; ul++){
                __delay_ms(100);
            }
            // 電源ON送信
            led_control((LED_MASK << KEY_CODE01));  // LED1 ON
            IR_KDN_format_num(data_on, sizeof(data_on));  // 電源ON
            led_control(ALL_OFF);  // LED OFF
        }                                                             ←追加(ここまで)
    }

・・・
}

実際に停電が起きたかわかるように、起動した回数をカウントすることにします。
電源が切れても回数を保持できるように、EEPROMに保存します。
カウントのリセットは、DipスイッチON/ONの送信モードのときに、ボタン16を押すとリセットするように追加します。

ir_main.c
void main()
{
    ・・・
    unsigned char   cnt;                                              ←追加

    ・・・
    
    /****************************************************************/
    /* Dip SW が両方 Hi ⇒ 送信モード                               */
    /****************************************************************/
    if ((PORTA & MODE_MASK) == RUN_MODE_1) {
        ・・・

        for (;;)
        {
            ・・・
            /* Key 押下あり -> 何れかの動作 */
            else {
                ・・・
                // カウンターリセット                                 ←追加(ここから)
                else if(key_code == KEY_CODE16){
                    EEPROM_WRITE(0, 0);
                }                                                     ←追加(ここまで)
            }
        }
    }
    
    ・・・
    
    /****************************************************************/
    /* Dip SW 1:Lo  Dip SW 2:Hi ⇒ 停電対応                         */
    /* Dip SW 1:Hi  Dip SW 2:Lo ⇒ 停電対応                         */
    /****************************************************************/
    if (((PORTA & MODE_MASK) == RUN_MODE_2) || ((PORTA & MODE_MASK) == RUN_MODE_3)) {
        led_control(ALL_OFF);	// LED OFF

        cnt = EEPROM_READ(0) + 1;  // 回数カウンタ                    ←追加
        
        // LCD表示
        lcd_init();
        strcpy(tmptxt, "AUTOSW READY");
        lcd_put_text1(tmptxt);
        lcd_put_ir_data(&cnt, 1);                                     ←追加

        EEPROM_WRITE(0, cnt);  // ON回数をEEPROMに書込                ←追加
        
        ・・・
    }
・・・
}

以上で一連の書き換えが完了です。お疲れさまでした♪

リビルド⇒書込み したら、ACを抜いたり、実際にブレーカーを落としてみたり、正常に動作するかテストしてみてください。

最後に

間違っている点などありましたら、こちらのブログにコメントいただけると助かります。

作ってみた感想や、皆様の工夫された点などもいただけると、とっても嬉しいです。

皆様の可愛いペット達が、健康に過ごせますように。
inserted by FC2 system