リスト1 チョロQのリモコンでmbedを制御するchoroq_signal #include "mbed.h" #include "TextLCD.h" // #defineはいろいろな使い方があるが、ここでは文字列を数値に置き換える。 // 例えばプログラム中で何度も使用する値があるとする。プログラムにそのまま値を記述すると // その値を変更する際には、プログラム内で使用しているすべての個所を変更しなくてはいけない。 // 同じ値が複数の個所で違う意味で使用されていたりすると、変更 // すべきでないものを変更してしまったり、間違った値に変更するなどバグの原因になってしまう。 // そのようなとき#defineを使うと、宣言部分の1個所の値を変更するだけでプログラム中の値を // すべて変更できるので、メインテナンス性のよいプログラムを作成することができる。 // 後で変更する可能性のある値は #defineを利用するとよい。 // ちなみに、#defineで使用する文字列は一般的に大文字で表す。 // ここではとりあえず時間のしきい値を200msとした。 #define THR 200 // enumは、例えば1が前進、2が後進、3が左折、4が右折などのように値が意味をもつ場合がある。 // この際に、プログラム中で1や2というマジックナンバを記述しても、後でプログラムをメインテナンス // するときに、プログラムを最初から解読し値についての意味を理解しないと、1や2がどのような意味を // もっているのかわからない。そこで、1であればFORWARD、2であればBACKなどその変数の意味を // 連想させる文字列を値の代わりに使うとプログラムの可読性が上がり、メインテナンス性のよい // プログラムを作成できる。 enum SignalMode{NOT_USED,FORWARD,BACK,LEFT,RIGHT,DASH_FORWARD,FORWARD_LEFT,FORWARD_RIGHT,DASH_FORWARD_LEFT, DASH_FORWARD_RIGHT,BACK_LEFT,BACK_RIGHT,DASH_BACK,DASH_BACK_LEFT,DASH_BACK_RIGHT, STOP}; SignalMode STAT; // 進行方向のデータを格納する変数(4ビット) int steering; // 制御信号を解析する際に使用する変数 int signal; // InterruptInは割り込みp21端子の値に変化が起こると、その変化によって関数が呼ばれる。 // 信号が立ち下がるとtStart関数が呼ばれ、立ち上がりでtEnd関数が呼ばれる。 InterruptIn event(p21) ; // LCDを使うためのLCD用オブジェクトの宣言 //ライブラリ忘れずに読み込むこと。 TextLCD lcd(p24, p26, p27, p28, p29, p30); // mbedの内蔵LEDを使用するためのオブジェクト BusOut myleds(LED1,LED2,LED3,LED4); // 信号の値[signal]はバンド(2ビット)と進行方向(4ビット)の // 合わせて6ビットで構成されている // その6個のビットをカウントする変数 int bitCnt = 0; // 赤外線受光モジュールの出力が[0](Low)の時間を測定し、ビットごとの値が1か0かを判断する // その際にタイマを使用する Timer timer; int begin,end,interval; void tStart() { interval = 0; // 立ち下がった時間を取得する begin = timer.read_us(); } void tEnd() { // 立ち上がりの時間を取得する end = timer.read_us(); // 信号が0か1かを見分けるために、赤外線受光モジュールの出力がLowの状態の時間を // 測定しintervalに代入する。 // ちなみにintervalが500[us]のときがOFFで1000[us]のときがON // 2000[us]の場合はヘッダ interval = end - begin ; // タイマを初期化する timer.reset(); // intervalの値が 1800(1800[us]) < interval < 2200(2200[us])の場合は信号はヘッダ // THRはマージン(余裕)をみている。 if( 2000 - THR < interval && interval < 2000 + THR ){ // Lowの状態が2000[us]の場合はヘッダなので、値を初期化する bitCnt = 0 ; signal = 0; // intervalの値が 300(300[us]) < interval 700(700[us]) の場合は信号は(0) }else if ( 500 -THR < interval && interval < 500 + THR ){ // signalの値を1ビット左にシフトする signal <<= 1; // ビットの数をカウントする bitCnt++; // intervalの値が 800(800[us]) < interval < 1200(1200[us]) の場合は信号は(1) }else if ( 1000 -THR < interval && interval < 1000 + THR ){ // 信号が(1)の場合の処理(ビットを立てる) // signalの値を1ビット左にシフトします signal <<= 1; signal++; // ビットの数をカウントする bitCnt++; } } int main() { int data = 0; timer.start() ; // p21端子の信号が立ち下がったときにtStart関数を呼び出す event.fall(&tStart) ; // p21端子の信号が立ち上がったときにtEnd関数を呼び出す event.rise(&tEnd) ; while(1) { // バンドと進行方向の6bitがそろったらデータを解析する if ( bitCnt == 6 ){ // signalは割込みで値が変わるので、data変数に値を代入する data = signal ; // 信号の値を10進数で表示 lcd.locate(0,0); lcd.printf("%3d",data); // バンドの値を2進で表示 lcd.locate(4,0); // ビット演算で右端から6ビット目、5ビット目の値とANDを取り表示する。 // ビットの表示は3項演算子(条件? 処理1:処理2)を使っている。 // 3項演算子は if(条件){処理1}else{処理2}という処理をコンパクトに記述できる。 // ここで条件が data&0x20で dataの値が2進数で100011だったとする。 // このとき 0010 0011(dataの値)と0010 0000(0x20(条件))の&(ビット演算子 AND)をとり // 演算結果が0以外のときは条件の部分はtrue、0はfalseになりtrueの場合は1を表示し // falseの場合は0を表示している。 lcd.printf("%d%d",(data&0x20)?1:0,(data&0x10)?1:0); // 進行方向の値を2進で表示 lcd.printf("%d%d%d%d",(data&0x08)?1:0,(data&0x04)?1:0,(data&0x02)?1:0,(data&0x01)?1:0); lcd.locate(0,1); // バンドを表示 // バンドA はバンド部が00なのでバンド部と進行方向部を合わせた値が15以下になる if ( data < 16 ) lcd.printf("CH A"); // バンド Bはバンド部が01なので、2進数 (010001)から(011111)となり10進数で16から31になる else if ( data < 32 ) lcd.printf("CH B"); else if ( data < 48) lcd.printf("CH C"); else if ( data < 64) lcd.printf("CH D"); // 進行方向を表示 // バンド部と進行方向部を合わせた値から、下位4ビットとの進行方向の値だけを取り出す。 // バンド部と進行方向部を合わせた値(変数data)と1111の値のANDを求める。 // data & 0000 1111で、下位4bitだけを取り出し、steering変数に代入している。 steering = (int)(data&0x0F) ; lcd.locate(5,1); switch(steering){ case LEFT: // LEFTは3のこと数字を書くと何のことかよくわからない。LEFTと書いてあれば // この部分がLEFTの処理部分を記述していることが直感的にわかる。 lcd.printf("LEFT "); myleds = 0x01; // mbed内蔵LEDの左端を点灯 break; case RIGHT: lcd.printf("RIGHT "); myleds = 0x08; // mbed内蔵LEDの右端を点灯 break; case FORWARD: lcd.printf("FORWARD"); break; case BACK: lcd.printf("BACK "); break; case DASH_FORWARD: lcd.printf("DASH_FOR "); break; case DASH_BACK: lcd.printf("DASH_BACK"); break; case STOP: lcd.printf("STOP "); wait_ms(60); lcd.cls(); myleds =0x00; } } } } --- リスト2 オリジナル・リモコンのプログラム #include "mbed.h" #include "TextLCD.h" // UP,DOWN,LEFT,RIGHT,DASHのそれぞれのしきい値。 // 使用する電源電圧の値や抵抗値によってチューニングのこと。 #define U_THR 0.65 // 3.3[V]*0.65=2.15[V]を超えるとジョイスティックを上に倒したと判断する #define D_THR 0.2 // 3.3[V]*0.2=0.66[V]を下回るとジョイスティックを下に倒したと判断する #define L_THR 0.2 // ここで各センサのしきい値を決める #define R_THR 0.65 #define H_THR 0.6 #define CH_THR 0.6 // ヘッダの時間 2[mS] #define HEADER 2 // 各バンドのインターバル時間 表4を参照 #define CHA_INTERVAL1 130 #define CHA_INTERVAL2 10 #define CHB_INTERVAL1 110 #define CHB_INTERVAL2 30 #define CHC_INTERVAL1 90 #define CHC_INTERVAL2 50 #define CHD_INTERVAL1 70 #define CHD_INTERVAL2 70 // 信号の値をマジックナンバではなく文字列で表している // そのまま0と1でも意味はわかるので、あまりこだわらなくてもよい typedef enum { OFF, ON } state; // LCDを使うためのLCD用オブジェクトの宣言 //ライブラリ忘れずに読み込む。 TextLCD lcd(p24, p26, p27, p28, p29, p30); // LEDを点灯させる際に、PWMにより38[kHz]のパルスを出力する PwmOut out(p21); // ジョイスティックやタクトSWの入力 AnalogIn lr_in(p18); // Joystick LR(Left, Right) AnalogIn ud_in(p19); // Joystick UD(UP,Down) AnalogIn hs_in(p20); // PSW hs (Dash!) AnalogIn ch_in(p17); // PSW ch (Change Band) // ちょっと難しいけど関数のポインタを使う // 詳しくは後述 typedef void (*FUNCPTR)(); FUNCPTR pFuncBand; // onの信号を出力するための関数 // onの信号は赤外線LEDの出力波形で観測すると最初の0.5[ms]はLowで続く1[ms]がHigh // Highのときは38[kHz]のパルス信号を出力する void on(){ int begin; Timer ton; // タイマ開始と同時に時間を取得する ton.start(); begin = ton.read_us(); // タイマの時間が、500[us]経過するまでの間出力は0(Low) while ( 500 > ton.read_us() - begin ) out.write(0.0f) ; // 再び時間を取得 begin = ton.read_us(); // タイマの時間が、1000[us]経過するまでの間38[kHz]の方形波を出力する while ( 1000 > ton.read_us() - begin ) out.write(0.5f) ; // タイマの停止 ton.stop(); } // offの信号を出力するための関数 // offの信号は赤外線LEDの出力波形で観測すると最初の0.5[ms]はLowで続く0.5[ms]がHigh // Highのときは38[kHz]のパルス信号を出力する void off(){ int begin; Timer toff; // タイマ開始と同時に時間を取得する toff.start(); begin = toff.read_us(); // タイマの時間が、500[us]経過するまでの間出力は0(Low) while( 500 > toff.read_us() - begin ) out.write(0.0f) ; // 再び時間を取得 begin = toff.read_us(); // タイマの時間が、500[us]経過するまでの間38[kHz]の方形波を出力する while ( 500 > toff.read_us() - begin ) out.write(0.5f) ; // タイマの停止 toff.stop() ; } int flag=0; // 前進や後進などの進行方向ボタンが押された後の後処理 // プログラム的に必要 void tail() { out.write(0.0f) ; flag = 0; } // バンドによって出力する信号が違う // 表2のバンドを表す値のようにバンドによって出力信号を変更する void bandA(){ off();off(); } void bandB(){ off();on(); } void bandC(){ on();off(); } void bandD(){ on();on(); } Ticker in; Timer timer; float lr, ud, hs; // volatileでch変数の最適化をしないようにする volatile float ch; int interval1, interval2; // ジョイスティックやタクトSWの値を読み込むための処理 void input() { lr = lr_in; ud = ud_in; hs = hs_in; ch = ch_in ; } // バンドの状態を記憶する変数 // バンドA->0, バンドB-> 1 バンドC-> 2, バンドD -> 3 int psw = 0; // ヘッダや信号間のインターバルを取るための関数 // 引数(iTime インターバルの時間 , s ONまたはOFF) void interval(int iTime,state s){ float signal; int t; // sの値がONのときは38[kHz]の方形波を出力する // OFFのときは何も出力しない signal = (s==OFF)? 0.0f : 0.5f; // タイマをスタートし、すぐに時間を取得 timer.start(); t = timer.read_ms(); // 引数iTimeの時間が経過するまでONまたはOFFの信号を出力する while ( iTime > timer.read_ms() - t ) out.write(signal) ; // タイマ停止 timer.stop(); } int main() { // 0.05秒ごとにinput関数を呼び出し、センサから入力を得る in.attach(&input, 0.05); // 26[us]の方形波を出力する設定 1/26[us]は、約38[kHz] out.period_us(26); // LCDの表示を初期化し、バンドAの設定をする(初期設定はバンドA) lcd.cls() ; lcd.locate(1,1); lcd.printf("BAND [A]"); interval1 = CHA_INTERVAL1; interval2 = CHA_INTERVAL2; // バンドA関数のアドレスを関数のポインタに代入する // ここで(pFuncBand)(); 関数を実行すると、ポインタ先のbandA()関数が呼び出される // ポインタ先の呼出し関数をpswの状態により動的に変更することができる // 関数を実際に呼び出す処理は (pFunvBand)(); と記述する (pFuncBand) = &bandA; while(1) { // デバッグ用の表示。TeraTermなどからセンサの値を確認することができる // 最初回路を製作したあと、デバッグ表示でどのくらいの入力値があるか確認し // しきい値が適切か確認する // #DEBUG printf("UD%4.2f HS%4.2f ",ud,hs); // #DEBUG printf("\tLR%4.2f CH%1d %4.2f \r\n",lr,psw,ch); // バンドを変更するための処理 // chの値は通常は 0.0 タクトSWが押されると、今回の回路では0.7程度の入力になる // タクトSWが押されると、バンドを変更する処理をする // ch変数を宣言する際にvolatileの指定をしないとコンパイラが勝手に最適化するため、 // プログラムが正常に動作しない※割込みで入力するchの値が反映されない if( ch >= CH_THR ){ // while(1)は永久ループ while(1){ // タクトSWを押している間はwhile内をループしている // タクトSWから手が離れると、chの値が0.0に戻るのでif文に入る。 if(ch < 0.2 ){ // pswの値が0から2の場合はpswの値をカウント・アップする(pswの値は0以上3以下) if ( psw < 3 ){ psw++; }else{ // pswの値が4以上になったら0に戻す psw=0; } // psw変数をカウント・アップし、break文でwhileの無限ループから抜ける break; } } lcd.locate(1,1); // pswの値が0なら、band Aの設定をする // pswの値が1なら、band Bの設定をする if(psw == 0 ){ // 液晶にバンド Aの表示 lcd.printf("BAND [A]"); // 関数のポインタにbandA()の関数のアドレスを代入 (pFuncBand) = &bandA; // インターバル1と2にバンドAの値を設定 interval1 = CHA_INTERVAL1; interval2 = CHA_INTERVAL2; }else if(psw == 1){ // 液晶にバンドBの表示 lcd.printf("BAND [B]"); // 関数ポインタにbandB()の関数のアドレスを代入 (pFuncBand) = &bandB; // インターバル1と2にバンドBの値を設定 interval1 = CHB_INTERVAL1; interval2 = CHB_INTERVAL2; }else if(psw == 2){; lcd.printf("BAND [C]"); pFuncBand = &bandC; interval1 = CHC_INTERVAL1; interval2 = CHC_INTERVAL2; }else{ lcd.printf("BAND [D]"); pFuncBand = &bandD; interval1 = CHD_INTERVAL1; interval2 = CHD_INTERVAL2; } } // ここまでが、タクトSWを使ってバンドを切り替えるための処理 // いろいろなものに応用が可能なので、覚えておくと便利 // ここからはジョイスティックの値を読み込み、赤外線LEDから制御信号を出力するための処理 lcd.locate(0,0); // udはUP,DOWNの信号でhsはダッシュボタンlrはLeft,Rightの信号 // ここでの条件は、udがU_THRよりも大きく(ジョイスティックを上に倒した状態) // かつ、hsがH_THRよりも大きく(ダッシュボタンが押されている状態) // かつ、lrがL_THRよりも大きく(ジョイスティックが左に倒れていない状態) // かつ、lrがR_THRよりも小さい(ジョイスティックが右に倒れていない状態) // 結局ジョイスティックを上に倒して、ダッシュボタンを押している状態 if ( (U_THR < ud && H_THR < hs) && (L_THR < lr && lr < R_THR)){ // Forward + Dash!(0101) // LCDにチョロQの制御状態を表示 lcd.printf("Forward+Dash! "); // interval1(インターバル1の間OFF、pswの値によって設定されている interval(interval1,OFF); // 2[mS]の間ON interval(HEADER,ON); // pswの値によって呼び出される関数が違う (pFuncBand)(); // 進行方向の信号(表3)を出力 off(); on(); off();on(); // interval2(インターバル2の間OFF、pswの値によって設定されている interval(interval2,OFF); // 2[mS]のヘッダ出力 interval(HEADER,ON); // pswの値によって呼び出される関数が違う、関数のポインタに代入されたアドレスの関数が // 呼び出される (pFuncBand)(); // 進行方向の信号出力 off(); on(); off(); on(); // 出力を0に戻す tail(); // あとは同様の処理を繰り返すだけ }else if((ud < D_THR && H_THR < hs) && (L_THR < lr && lr < R_THR)){ // Backward + Dash!(1100) lcd.printf("Backward+Dash! "); interval(interval1,OFF); // chA Interval1 interval(HEADER,ON); //HEADER (pFuncBand)(); on(); on(); off();off(); // Control Code 1100 interval(interval2,OFF); // chA Interval2 interval(HEADER,ON); // HEADER (pFuncBand)(); on(); on(); off(); off(); // Control Code 1100 tail(); }else if ((U_THR < ud && H_THR < hs) && (lr < L_THR)){ // Forward + Left + Dash! lcd.printf("For+Left+Dash! "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); on(); off(); off(); off(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); on(); off(); off(); off(); tail(); }else if((U_THR < ud && H_THR < hs) && (R_THR < lr)){ // Forward + Right + Dash!; lcd.printf("For+Right+Dash! "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); on(); off(); off(); on(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); on(); off(); off(); on(); tail(); }else if((ud < D_THR && H_THR < hs) && (lr < L_THR)){ // Backward + Left + Dash! lcd.printf("Back+Left+Dash! "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); on(); on(); off(); on(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); on(); on(); off(); on(); tail(); }else if((ud < D_THR && H_THR < hs) && (R_THR < lr)){ // Backward + Right + Dash! lcd.printf("Back+Right+Dash!"); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); on(); on(); on(); off(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); on(); on(); on(); off(); tail(); }else if ( U_THR < ud && lr < L_THR ){ // Forward + Left lcd.printf("Forward+Left "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); off(); on(); on(); off(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); off(); on(); on(); off(); tail(); }else if( U_THR < ud && R_THR < lr){ // Forward + Right lcd.printf("Forward+Right "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); off(); on(); on(); on(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); off(); on(); on(); on(); tail(); }else if( ud < D_THR && lr < L_THR){ // Backward+Left(1010) lcd.printf("Backward+Left "); (pFuncBand)(); interval(HEADER,ON); (pFuncBand)(); on(); off(); on(); off(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); on(); off(); on(); off(); tail(); }else if (ud < D_THR && R_THR < lr){ // Backward + Right (1011) lcd.printf("Backward+Right "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); on(); off(); on(); on(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); on(); off(); on(); on(); tail(); }else if((U_THR <= ud)&&(L_THR < lr && lr < R_THR)){ // Forward(0001) lcd.printf("Forward "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); off();off();off();on(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); off();off();off();on(); tail(); }else if((ud <= D_THR)&&(L_THR < lr && lr < R_THR)){ // Backward(0010) lcd.printf("Backward "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); off();off();on();off(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); off();off();on();off(); tail(); }else if(lr < L_THR && (D_THR <= ud && ud <= U_THR)){ // Left(0011) lcd.printf("Left <= "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); off();off();on();on(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); off();off();on();on(); tail(); }else if(R_THR < lr && (D_THR <= ud && ud <= U_THR)){ // Right(0100) lcd.printf("Right => "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); off();on();off();off(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); off();on();off();off(); tail(); }else if((D_THR <= ud && ud <= U_THR) && (L_THR < lr && lr < R_THR)){ // 停止信号はジョイスティックが中央の位置に戻ったとき、少しの間だけ出力される。 // 停止信号以外の信号が出力されると、tail関数でflagが0に設定される。 // そのとき、ジョイスティックが中央に戻ると処理がこの部分に移る // flagが0なら、停止信号を5回出力し、flagを1に設定する。 // flagが1に設定されたら、次に処理がこの部分に移っても // 停止信号は出力されず、else内の処理が実行されLEDが出力されない状態になる if ( flag == 0 ){ for ( int i = 0; i < 5 ; i++ ){ // STOP(1111); lcd.locate(0,0); lcd.printf("Stop "); interval(interval1,OFF); interval(HEADER,ON); (pFuncBand)(); on();on();on();on(); interval(interval2,OFF); interval(HEADER,ON); (pFuncBand)(); on();on();on();on(); } flag = 1; }else{ // ジョイスティックは中央にあるが、一度停止信号が出力されるとflagが1になるため、 // 次回からは停止信号は出力されない。 out.write(0.0f) ; lcd.locate(0,0); lcd.printf(" "); } } } }