遊戯王真デュエルモンスターズ~封印されし記憶~のポケットステーションで強力なカードを入手するためのツール:チートリモコンを製作した。
今回はチートリモコンのソフトウェア設計②:キー操作を行う。
<チートリモコン作ってみたシリーズ>
・封印されし記憶を語る
・概要編
・ハードウェア編
・ソフトウェア編①:動作確認
・ソフトウェア編②:キー操作 ←いまここ
・ソフトウェア編③:チャタリング対策
・ソフトウェア編④:NECフォーマット
・ソースコード一覧
・入手カード一覧
以下の内容は、ある程度PICやC言語を知っている前提となります。
何をしているのか分からない部分があったら、参考書やwebのPIC入門などを参照してください。
チートリモコンのキー操作は全部で8種類。
- RESETスイッチ :CustomコードとDataコードを0x00にする。
- CUSTOMエンコーダー時計回り :Customコードをアップする。
- CUSTOMエンコーダー反時計回り:Customコードをダウンする。
- CUSTOMエンコーダースイッチ :Customコードの変化させる桁を変える。
- DATAエンコーダー時計回り :Dataコードをアップする。
- DATAエンコーダー反時計回り :Dataコードをダウンする。
- DATAエンコーダースイッチ :Dataコードの変化させる桁を変える。
- SENDスイッチ :LEDをNECフォーマットで点滅させる。
今回は、これらを操作する関数を作成する。
まずは必要な変数をまとめてみる。
int main() { // 変数定義 int nec_cust_cur = 0; // Customコードの桁設定、0:一桁、1:十桁 int nec_data_cur = 0; // Dataコードの桁設定、0:一桁、1:十桁 unsigned char nec_custom = 0; // Customコード unsigned char nec_data = 0; // Dataコード int lcd_status = 0; // コード変化フラグ、bit0:Custom、bit1:Data char NEC_CUSTOMT[2]; // Customコマンド用テキストデータ、2文字分 char NEC_DATAT[2]; // Dataコマンド用テキストデータ、2文字分
PIC18Fの場合、intは16bitになる。
まぁ1~2bitしか使わないけど、こだわりがなければintにするようにしている。
nec_customとnec_dataがコードの変数で、符号なし8bitにしたかったのでunsigned charで定義した。
unsignedで定義しないときれいに値が変化しないので注意。
これらの変数を操作するため、keys.hというヘッダーを作成した。
#ifndef KEYS_H #define KEYS_H // NEC Format // RESETスイッチ int NecDataRst(unsigned char *custom, unsigned char *data); // CUSTOMエンコーダー時計回り int NecCustCw(int cursor, unsigned char *custom); // CUSTOMエンコーダー時計回り int NecCustCcw(int cursor, unsigned char *custom); // CUSTOMエンコーダースイッチ void NecCustSw(int *cursor); // DATAエンコーダー時計回り int NecDataCw(int cursor, unsigned char *data); // DATAエンコーダー反時計回り int NecDataCcw(int cursor, unsigned char *data); // DATAエンコーダースイッチ void NecDataSw(int *cursor); // SENDスイッチ void NecDataSend(unsigned char custom, unsigned char data); #endif /* KEYS_H */
KEYS_H関係は複数のファイルから同じヘッダーを読み込まないようにするための処置。
intで定義した関数はlcd_statusを変化させるもの、voidはその必要がないものと使い分けている。
関数内の変数定義で*があるのは参照渡しという手法で、変数の値ではなくアドレスを参照させ、関数内で値を変更することができる。
一方、*がないものは値渡しで、関数内で値を変更することは出来ない。
そのため、変数の値を変えるものは参照渡し、変えないものは値渡しと使い分けている。
この中身は後にして、とりあえずmain.cのwhileループに組み込んでみる。
// Main Loop while(1){ // Keys if(RST_SW == 0){ lcd_status = NecDataRst(&nec_custom, &nec_data); } else if((CUST_A == 0) && (CUST_B == 1)){ lcd_status = NecCustCw(nec_cust_cur, &nec_custom); } else if((CUST_A == 1) && (CUST_B == 0)){ lcd_status = NecCustCcw(nec_cust_cur, &nec_custom); } else if(CUST_SW == 0){ NecCustSw(&nec_cust_cur); } else if((DATA_A == 0) && (DATA_B == 1)){ lcd_status = NecDataCw(nec_data_cur, &nec_data); } else if((DATA_A == 1) && (DATA_B == 0)){ lcd_status = NecDataCcw(nec_data_cur, &nec_data); } else if(DATA_SW == 0){ NecDataSw(&nec_data_cur); } else if(SEND_SW == 0){ lcd_str(2, 1, "Send"); NecDataSend(nec_custom, nec_data); lcd_str(2, 1, " "); } }
変数の前の&は、アドレスを渡すという意味。
前回書いた通り、各スイッチは0が押した状態となる。
ロータリーエンコーダーは時計回りの場合はA側が先に0になり、反時計回りの場合はB側が先に0になる。
そのため、A側だけが押されていると時計回り、B側だけが押されていると反時計回りと判断するようにした。
ここからは、今回のメインであるキー操作の関数。
まず、RESETスイッチは以下のようにしてみた。
int NecDataRst(unsigned char *custom, unsigned char *data){ // 変数のリセット *custom = 0; *data = 0; // RESETスイッチが戻るまで待ち while(RST_SW == 0); // 終了処理 return 3; }
コメントの通り、変数のリセット、スイッチが戻るまでの待ち、lcd_statusにフラグ返しの順に処理している。
returnを3にすることで、bit0(Customフラグ)とbit1(Dataフラグ)が同時に1になる。
次はCUSTOMエンコーダーの時計回り。
int NecCustCw(int cursor, unsigned char *custom){ // 値の変更 switch(cursor){ case 0 : *custom = *custom + 0x01; break; case 1 : *custom = *custom + 0x10; break; defoult : *custom = *custom + 0x01; break; } // エンコーダーが戻るまで待ち while(CUST_A == 0 || CUST_B == 0); // 終了処理 return 1; }
回転方向はmain.cで判別しているので、値を変更してエンコーダーが戻るのを待つだけ。
反時計回りの場合は+を-に変えればいい。
また、DATAエンコーダーも上記のコードをDATA用に変えるだけでOK。
CUSTOMエンコーダーのスイッチは以下の通り。
void NecCustSw(int *cursor){ // 値の変更 *cursor = *cursor + 1; if(*cursor >= 2) *cursor = 0; // スイッチが戻るまで待ち while(CUST_SW == 0); }
これぐらいだったらもっといい書き方があるけど、当初はもっと桁が多かったりしたのでこのようにした。
DATAエンコーダーのスイッチもほぼ同じ作り。
SENDスイッチは以下のようにした。
void NecDataSend(unsigned char custom, unsigned char data){ // スイッチが戻るまでループ while(SEND_SW == 0){ // NecSend(custom, data); __delay_ms(40); } }
NecSend関数はNECフォーマット編で作るので、今回はコメントにする。
SENDスイッチは押している間LEDを点滅し続ける予定なので、最初からwhileで押している間ループするようにした。
待ち時間の40msは、リモコンの送信間隔が108msとなっているため。
送信にかかる時間は計算上は68msぐらいのため、これで送信間隔は108msぐらいになる。
まぁ送信間隔が長くなっても問題ないはず。
もう1つ重要なのがLCDの表示。
まずは初期化と最初の文字出力。
// LCD Init lcd_init(); lcd_str(1, 1, "NEC Custom Data"); lcd_str(2, 1, " 0x00 0x00");
これは起動時にLCDの初期化と共に実行する。
Customコード、Dataコードの初期値は両方0x00のため固定文字にした。
これ以降、lcd_statusでコードが変化したかを判定し、数値だけを更新する。
という訳で、while(1){ }の無限ループ内にLCDの更新処理を追加する。
場所は上記キー操作の次。
// LCD if(lcd_status & 1){ sprintf(NEC_CUSTOMT, "%02X", nec_custom); lcd_str(2, 10, NEC_CUSTOMT); } if(lcd_status & 2){ sprintf(NEC_DATAT, "%02X", nec_data); lcd_str(2, 15, NEC_DATAT); } lcd_status = 0;
if(lcd_status & 1)について説明すると、ifの( )内はtrueか0以外ならtrue判定となる。
&は各ビットのANDのため、lcd_statusのbit0が1なら結果は1となり、{ }内の処理が行われる。
sprintfは文字列を作る関数で、nec_customの値を16進数の文字データに変換し、NEC_CUSTOMTに格納する。
Dataコード側もほぼ同じ処理をしていて、lcd_statusのbit1で変化したかを判定する。
最後にlcd_statusをリセットし、変化があった時だけLCDを更新するようにした。
これらによって作ったソースコードをコンパイルし、動作確認してみた。
分かってはいたけど、チャタリング防止処理を入れていないのでエンコーダーの変化量が安定しない。
チャタリング防止関係だけでもそれなりに長くなるので、次回に回す。
最後に、今回作成したソースコード一覧です。
pic18f25k22_config.hとLCDlib.hはソフトウェア編①を参照してください。
main.c
#include <stdio.h> #include "xc.h" #include "LCDlib.h" #include "pic18f25k22_config.h" #include "keys.h" #define _XTAL_FREQ 40000000 // 文字列の置き換え #define RST_SW PORTAbits.RA0 #define CUST_A PORTAbits.RA1 #define CUST_B PORTAbits.RA2 #define CUST_SW PORTAbits.RA3 #define DATA_A PORTCbits.RC0 #define DATA_B PORTCbits.RC1 #define DATA_SW PORTCbits.RC2 #define SEND_SW PORTCbits.RC3 #define LED_ON LATB1 = 1; #define LED_OFF LATB1 = 0; int main() { // 変数定義 int nec_cust_cur = 0; // Customコードの桁設定、0:一桁、1:十桁 int nec_data_cur = 0; // Dataコードの桁設定、0:一桁、1:十桁 unsigned char nec_custom = 0; // Customコード unsigned char nec_data = 0; // Dataコード int lcd_status = 0; // コード変化フラグ、bit1:Data、bit0:Custom char NEC_CUSTOMT[2]; // Customコード用テキストデータ、2文字分 char NEC_DATAT[2]; // Dataコード用テキストデータ、2文字分 // I/Oポート初期化 ANSELA = 0x00; //全てデジタル ANSELB = 0x00; //全てデジタル ANSELC = 0x00; //全てデジタル TRISA = 0xCF; //RA7-6,3-0が入力 TRISB = 0x00; //RB7-0が出力 TRISC = 0x0F; //RC3-0が入力 LED_OFF; // LCD初期化 lcd_init(); lcd_str(1, 1, "NEC Custom Data"); lcd_str(2, 1, " 0x00 0x00"); // Main Loop while(1){ // Keys if(RST_SW == 0){ lcd_status = NecDataRst(&nec_custom, &nec_data); } else if((CUST_A == 0) && (CUST_B == 1)){ lcd_status = NecCustCw(nec_cust_cur, &nec_custom); } else if((CUST_A == 1) && (CUST_B == 0)){ lcd_status = NecCustCcw(nec_cust_cur, &nec_custom); } else if(CUST_SW == 0){ NecCustSw(&nec_cust_cur); } else if((DATA_A == 0) && (DATA_B == 1)){ lcd_status = NecDataCw(nec_data_cur, &nec_data); } else if((DATA_A == 1) && (DATA_B == 0)){ lcd_status = NecDataCcw(nec_data_cur, &nec_data); } else if(DATA_SW == 0){ NecDataSw(&nec_data_cur); } else if(SEND_SW == 0){ lcd_str(2, 1, "Send"); NecDataSend(nec_custom, nec_data); lcd_str(2, 1, " "); } // LCD if(lcd_status & 1){ sprintf(NEC_CUSTOMT, "%02X", nec_custom); lcd_str(2, 10, NEC_CUSTOMT); } if(lcd_status & 2){ sprintf(NEC_DATAT, "%02X", nec_data); lcd_str(2, 15, NEC_DATAT); } lcd_status = 0; } return (EXIT_SUCCESS); }
keys.h
#ifndef KEYS_H #define KEYS_H // NEC Format // RESETスイッチ int NecDataRst(unsigned char *custom, unsigned char *data); // CUSTOMエンコーダー時計回り int NecCustCw(int cursor, unsigned char *custom); // CUSTOMエンコーダー反時計回り int NecCustCcw(int cursor, unsigned char *custom); // CUSTOMエンコーダースイッチ void NecCustSw(int *cursor); // DATAエンコーダー時計回り int NecDataCw(int cursor, unsigned char *data); // DATAエンコーダー反時計回り int NecDataCcw(int cursor, unsigned char *data); // DATAエンコーダースイッチ void NecDataSw(int *cursor); // SENDスイッチ void NecDataSend(unsigned char custom, unsigned char data); #endif /* KEYS_H */
keys.c
#include "xc.h" #define RST_SW PORTAbits.RA0 #define CUST_A PORTAbits.RA1 #define CUST_B PORTAbits.RA2 #define CUST_SW PORTAbits.RA3 #define DATA_A PORTCbits.RC0 #define DATA_B PORTCbits.RC1 #define DATA_SW PORTCbits.RC2 #define SEND_SW PORTCbits.RC3 #define _XTAL_FREQ 40000000 int NecDataRst(unsigned char *custom, unsigned char *data){ // 変数のリセット *custom = 0; *data = 0; // RESETスイッチが戻るまで待ち while(RST_SW == 0); // 終了処理 return 3; } int NecCustCw(int cursor, unsigned char *custom){ // 値の変更 switch(cursor){ case 0 : *custom = *custom + 0x01; break; case 1 : *custom = *custom + 0x10; break; defoult : *custom = *custom + 0x01; break; } // エンコーダーが戻るまで待ち while(CUST_A == 0 || CUST_B == 0); // 終了処理 return 1; } int NecCustCcw(int cursor, unsigned char *custom){ // 値の変更 switch(cursor){ case 0 : *custom = *custom - 0x01; break; case 1 : *custom = *custom - 0x10; break; defoult : *custom = *custom - 0x01; break; } // エンコーダーが戻るまで待ち while(CUST_A == 0 || CUST_B == 0); // 終了処理 return 1; } void NecCustSw(int *cursor){ // 値の変更 *cursor = *cursor + 1; if(*cursor >= 2) *cursor = 0; // スイッチが戻るまで待ち while(CUST_SW == 0); } int NecDataCw(int cursor, unsigned char *data){ // 値の変更 switch(cursor){ case 0 : *data = *data + 0x01; break; case 1 : *data = *data + 0x10; break; defoult : *data = *data + 0x01; break; } // エンコーダーが戻るまで待ち while(DATA_A == 0 || DATA_B == 0); // 終了処理 return 2; } int NecDataCcw(int cursor, unsigned char *data){ // 値の変更 switch(cursor){ case 0 : *data = *data - 0x01; break; case 1 : *data = *data - 0x10; break; defoult : *data = *data - 0x01; break; } // エンコーダーが戻るまで待ち while(DATA_A == 0 || DATA_B == 0); // 終了処理 return 2; } void NecDataSw(int *cursor){ // 値の変更 *cursor = *cursor + 1; if(*cursor >= 2) *cursor = 0; // スイッチが戻るまで待ち while(DATA_SW == 0); } void NecDataSend(unsigned char custom, unsigned char data){ // スイッチが戻るまでループ while(SEND_SW == 0){ // NecSend(custom, data); __delay_ms(40); } }