/* * 参考URL * * 意外と知られていない?INPUT_PULLUP スイッチサイエンス マガジン * http://mag.switch-science.com/2013/05/23/input_pullup/ * * Arduinoで物理ボタンを作る (目立つボタンで「ッターーン!」しよう) * http://qiita.com/ie4/items/7c6764469f3d41c93a19 * * Arduinoのスケッチだけでスイッチのチャタリングを回避する * http://jumbleat.com/2016/08/19/switch_without_chatter/ * * 続・プログラミングでチャタリング回避 * http://jumbleat.com/2016/12/01/revision_of_avoiding_chatter/ * * Windows->ArduinoへのピンをON/OFFする情報連携は、バイト数を極力抑えたいため、ピン番号をintでWindowsから連携する際に、ONの時に正数、OFFの時に負数を与えることとしました。 * int情報をシリアル通信で送信する方法は以下の記事を参考にしました * http://d.hatena.ne.jp/kougaku-navi/20140501/p1 * * [プッシュボタンシステムの動作モード] * (1)プッシュボタンのON/OFF情報をWindowsアプリに送信するだけのスタンドアローンモード * (2)(1)に加えてWindowsアプリからリモート操作(ソフト入力、LED出力)ができるリモート操作モード * 電源を入れた時に、プッシュボタンを押していなければ(1)スタンドアローンモードで起動します。 * 電源を入れた時に、3個のプッシュボタンのうちどれか1つを押しっぱなしにすると(2)リモート操作モードで起動します。 * プッシュボタンが押されているかどうかの判定時間は0.5秒です。 * * [button_push for wcs_button のバージョン履歴] * v00.01.04 2017/11/18 フットスイッチに対応、フットスイッチは、LED出力のためピン出力を行う必要がないので、入力ピンの情報だけをPCにできる変数is_foot_switchを追加、デフォルトでtrueとしました。 * ボタンpin、LED出力pinをそれぞれ6個に拡張、入力ピンは8,9,10,11,12,13/出力ピンは2,3,4,5,6,7に変更した。 * v00.01.03 2017/08/12 LEDの出力レベルを負論理にできるようにした(デフォルトは正論理)。led_output_negativeをシリアルから受信コマンドS+0x0001[正論理]/S+0x0002[負論理]で制御できるようにした。 * v00.01.02 2017/08/10 シリアル通信の相手(Windowsなど)からLED出力ピンのON/OFF状態を得て、出力状態を変更できるようにした。 * 相手から受信するデータは、ヘッダが'H'1バイトで、それに続いてピン番号int2バイトを送信。 * ON/OFF状態は数値の正負で表現し、ONは正数、OFFは負数とした。 * v00.01.01 2017/07/30 初版 * * シリアル側から設定できる項目、識別文字'S'1バイト+2バイト整数で表現(S:Setの略) * 2バイト整数は以下の通り * ピン単位に正負論理を設定できるようにした方がよいのかわからない、今のところ未定、未実装 * 現在値の読み出し機能は実装時期は未定ですが、識別文字'G'+2バイト整数で表現(G:Getの略)する予定です。 * 以下の設定はリモート制御を禁止していてもできるようにしています。 * 0x0000: * 0x0001:出力を正論理にする(初期値は正論理) * 0x0002:出力を負論理にする * 0x0003:リモート制御を許可 * 0x0004:リモート制御を禁止 * 0x0005:リモート制御の時に物理スイッチのpin監視を行う * 0x0006:リモート制御の時に物理スイッチのpin監視を行わない * 0x0007:ボタン入力pin+LED出力pinを連携 * 0x0008:ボタン入力pinのみを連携(フットスイッチ用を想定) * 0x0009: * 0x000A: * 0x000B: * 0x000C: * 0x000D: * 0x000E: * 0x000F: */ #define MAYBE_STABLE 1000 // Arduino UNOで実測した限りではループでのctが5000くらいだったのでそれより小さい値として1000くらいにした #define SW_NUM 6 // pin監視するスイッチの数 #define LED_NUM 6 // 出力するポートの数 #define SERIAL_SPEED 115200 // シリアル通信速度(4800,9600,19200,38400,57600,74880,115200,230400) unsigned long exec_start_time=0; // 計測を開始した時間 unsigned long exec_span_millise=500; // モードセットを識別する時間(ミリ秒) const int buttonON = LOW; // ボタンが押されているとピンの値はLOW(PULL UP) const int buttonOFF = HIGH; // ボタンが離されたれるとピンの値はHIGH(PULL UP) boolean led_output_negative = false; // LEDの出力レベルを負論理にする、true:ボタンが押されると出力をLOW,false:ボタンが離されたれると出力をHIGH(既定値) boolean enable_remote_control = true; // リモート制御の可否、true:許可(★テスト中は常に許可)、false:禁止(既定値) boolean polling_physical_btn = true; // 物理スイッチのpin監視を行うかどうか、true:行う(既定値)、false:行わない boolean sw_status_arr[SW_NUM] = {false,false,false,false,false,false};//,false,false,false,false,false,false}; // 最初はOFF const int btn_pin_arr[SW_NUM] = {8,9,10,11,12,13}; //const int btn_pin_arr[SW_NUM] = {2,3,4,5,6,7}; boolean btn_init_status[SW_NUM] = {true, true, true,true,true,true}; // 起動ときに動作モードを識別するため、buttonOFFを検出したらfalse const int led_pin_arr[LED_NUM] = {2,3,4,5,6,7}; // 先頭の3個はbtn_pin_arrの出力を反映するためのポートです11,12,13は自由に設定可能 //const int led_pin_arr[LED_NUM] = {8,9,10,11,12,13}; // 先頭の3個はbtn_pin_arrの出力を反映するためのポートです11,12,13は自由に設定可能 boolean led_status_arr[LED_NUM] = {false,false,false,false,false,false}; // 最初はOFF boolean hold_status_before_push_on=false; // シリアル側からONの指示があった時、ボタンのプッシュ状態を見て、すぐにクリアしてしまうことがないように、通信相手の状態を優先としtrueがセットされます、ArduinoでプッシュボタンをONした時にはfalseをセットして優先権をArduinoに戻します。 boolean is_foot_switch=true; // trueにすると入力ボタンピンの情報のみを返し、LED出力ピンの情報を返しません。フットスイッチを使ったり、ボタン入力だけを連携したい時にtrueにします。falseにすると、入力ピンの情報に加えて、対応するLED出力ピンの情報も連携します void setup() { // Arduinoの標準設定は、デフォルトは8bit、パリティなし、1ストップビット(SERIAL_8N1),通信相手も同じ条件にすること Serial.begin(SERIAL_SPEED); for ( int i=0; i < SW_NUM; i++ ) { pinMode(btn_pin_arr[i], INPUT_PULLUP); } for ( int i=0; i < LED_NUM; i++ ) { pinMode(led_pin_arr[i], OUTPUT); } // 動作モードを認識 exec_start_time= millis(); while ( exec_start_time+1000>millis() ){ for ( int i=0; i < SW_NUM; i++ ) { if(!btn_init_status[i])continue; if(buttonOFF==digitalRead(btn_pin_arr[i])){ btn_init_status[i]=false; } } } // 押しっぱなしのボタンがあったのでリモート制御を許可して起動します for ( int i=0; i < SW_NUM; i++ ) { if(btn_init_status[i]){ // リモート制御を許可して起動する場合は、1秒間すべてのLEDを点灯して消灯します。ボタンが1個しかない場合はわからないですが。 enable_remote_control=true; exec_start_time= millis(); while ( exec_start_time+500>millis() ){ for ( int j=0; j < LED_NUM; j++ ) { // 点灯 digitalWrite(led_pin_arr[j], (led_output_negative? LOW:HIGH)); } } for ( int j=0; j < LED_NUM; j++ ) { // 点灯消灯 digitalWrite(led_pin_arr[j], (led_output_negative? HIGH:LOW)); } break; } } } void serial_send_status(int rec_pin, boolean status) { Serial.print(rec_pin); if ( status ) { // シリアル Serial.println(",on"); } else { Serial.println(",off"); } } void detect_remote_button() { // Windows側からピンを制御,受信バッファに3バイト以上のデータが到着しているか確認 // 動作設定情報は、ヘッダ'S'1バイト+int2バイト=3バイト(数値の意味はコマンド表を参照、とりあえず必要そうなものから実装) // ピン設定情報は、ヘッダ'H'1バイト+int2バイト=3バイト if ( Serial.available() >= sizeof('H') + sizeof(int) ) { // ヘッダの確認 int header=Serial.read(); if ( header == 'G' || header == 'H' || header == 'S' ) { int low = Serial.read(); // 下位バイトの読み取り int high = Serial.read(); // 上位バイトの読み取り int rec_pin = makeWord(high,low); // 上位バイトと下位バイトを合体させてint型データを復元 if (header == 'S' ) { // rec_pinの値を見て設定を実施 if (rec_pin==1){ // 1:出力を正論理 led_output_negative = false; } else if(rec_pin==2) { // 2:出力を負論理 led_output_negative = true; } else if(rec_pin==3) { // 3:リモート制御を許可 enable_remote_control = true; } else if(rec_pin==4) { // 4:リモート制御を禁止 enable_remote_control = false; } else if(rec_pin==5) { // 5:物理スイッチのpin監視を行う polling_physical_btn = true; } else if(rec_pin==6) { // 6:物理スイッチのpin監視を行わない polling_physical_btn = false; } else if(rec_pin==7) { // 7:ボタン入力pin+LED出力pinを連携 is_foot_switch = false; } else if(rec_pin==8) { // 8:ボタン入力pinのみを連携(フットスイッチ用を想定) is_foot_switch = true; } } else if (header == 'G' ) { // header == 'G' // ピンの値を読み取る // 未実装 } else if (header == 'H' ) { // header == 'H' // ピンを制御 if(enable_remote_control){ boolean status =false; if (rec_pin> 0 ){ status=true; }else{ status=false; rec_pin=-rec_pin; } // 入力ボタンピンの制御かどうかを確認する for ( int i=0; i < SW_NUM; i++ ) { if( btn_pin_arr[i]==rec_pin){ sw_status_arr[i]=status; hold_status_before_push_on=true; // 入力ピンは物理ボタンが押されるまではリモート設定された値を保持するのでtrueをセット //int btn_pin=btn_pin_arr[i]; // LEDピンのインデックス番号からプッシュボタンの入力ピンを求める //serial_send_status(btn_pin,status); led_status_arr[i]=status;// 出力ピンのステータスを変更する(リモート優先の時はsw_status_arr[i]がfalseでも対応する出力ピンled_status_arr[i]をfalseにしないので、リモートでfalseを受信したタイミングでled_status_arr[i]をfalseにします) serial_send_status(rec_pin,status); serial_send_status(led_pin_arr[i],status);// 出力ピンのステータスを変更したのでリモート側に通知 return; } } // 出力ピンの制御かどうかを確認する for ( int i=0; i < LED_NUM; i++ ) { if( led_pin_arr[i]==rec_pin){ led_status_arr[i]=status;// 出力ピンのステータスを変更する hold_status_before_push_on=true; // 出力ピンは物理ボタンが押されるまではリモート設定された値を保持するのでtrueをセット //int btn_pin=led_pin_arr[i]; // LEDピンのインデックス番号からプッシュボタンの入力ピンを求める //serial_send_status(btn_pin,status); serial_send_status(rec_pin,status);// 出力するLEDピン番号の変更後の状態値を返す if (status) { // 出力状態を変更 digitalWrite(rec_pin, (led_output_negative? LOW:HIGH)); // loopでまとめてset_output_voltageを実行させているのでコメントにしても大丈夫 } else { digitalWrite(rec_pin, (led_output_negative? HIGH:LOW)); // loopでまとめてset_output_voltageを実行させているのでコメントにしても大丈夫 } } } } } } } } void loop(){ for ( int i=0; i < SW_NUM; i++ ) { detect_button(i); } set_output_voltage(); detect_remote_button(); } void set_output_voltage(){ // if (hold_status_before_push_on) return; // リモート設定された値を保持する場合は個別設定しない(リモート設定の時はled_pin_arrもリモート制御しているので設定しても構いはしないが動作は不要なのでスキップさせている) for ( int i=0; i < LED_NUM; i++ ) { if ( led_status_arr[i] ) { digitalWrite(led_pin_arr[i], (led_output_negative? LOW:HIGH)); } else { digitalWrite(led_pin_arr[i], (led_output_negative? HIGH:LOW)); } } } int detect_button(int idx){ int btn_pin=btn_pin_arr[idx]; int led_pin=led_pin_arr[idx]; boolean status=sw_status_arr[idx]; unsigned long ct=0; boolean detect_changed = false; int buttonState = buttonOFF; // 物理スイッチのpin監視を行う機能を付けた時に0からbuttonOFFに変更した // on状態の変化を検出 buttonState = digitalRead(btn_pin); if (!polling_physical_btn){ // 物理スイッチのping監視を行わない場合は常にボタン状態はOFFにします buttonState=buttonOFF; } while (buttonState == buttonON) { if ( hold_status_before_push_on ){ hold_status_before_push_on=false; // 物理ボタンが押されたので、優先権をArduinoに戻します for ( int i=0; i < SW_NUM; i++ ) {// 全てのボタンをいったんクリアします sw_status_arr[i]=false; } status=sw_status_arr[idx]; } else { // Arduino優先の時は入力ボタンの情報変更はしない } ct++; if ( ct > MAYBE_STABLE ) { if ( ! status ) { status=!status; detect_changed=true; } else { // 既にON状態を識別済み } break; } buttonState = digitalRead(btn_pin); } // off状態の変化を検出 if ( ct == 0 ) { // buttonState = digitalRead(btn_pin);// on状態の変化を検出しようとした時に先に読み込んでいるのでここで再度の読み込みは不要,OFFを検出しています // Arduino側でoff状態の場合は、 if ( hold_status_before_push_on ) { // 後でLEDピンを変更する直前に変更します //buttonState =sw_status_arr[idx]? buttonON:buttonOFF; // ボタンが押されている、押されていない時のピンの状態にする(INPUT_PULLUP指定しているので要注意) } else { while (buttonState == buttonOFF) { ct++; if ( ct > MAYBE_STABLE ) { if ( status ) { status=!status; detect_changed=true; } else { // 既にOFF状態を識別済み } break; } buttonState = digitalRead(btn_pin); if (!polling_physical_btn){ // 物理スイッチのpin監視を行わない場合は常にボタン状態はOFFにします buttonState=buttonOFF; } } } } // シリアルでボタンの状態を通信相手に送信 if ( detect_changed ) { serial_send_status(btn_pin,status); if ( is_foot_switch ) { // LED出力ピンの状態もは連携しない、入力ピンの情報のみを連携 } else { serial_send_status(led_pin,status); } } if (0){ if ( hold_status_before_push_on ) { buttonState =sw_status_arr[idx]? buttonON:buttonOFF; // ボタンが押されている、押されていない時のピンの状態にする(INPUT_PULLUP指定しているので定義値をセット) } if (buttonState == buttonON) { // ボタンが押されていたら digitalWrite(led_pin, (led_output_negative? LOW:HIGH)); } else { digitalWrite(led_pin, (led_output_negative? HIGH:LOW)); } } if (polling_physical_btn){ // 物理スイッチを監視する場合のみ、ステータスを変更します // リモートモードで、物理スイッチの動作を禁止していない場合は、物理スイッチのONでリモートによる出力値を更新しないモードをクリアする、ちょうと、ソフトスイッチの処理のようなコードを書く、整理する必要あり sw_status_arr[idx]=status; // スイッチ状態がfalseでも、リモート操作中でled_status_arr[idx]がtrueの時はled_status_arr[idx]はtrueを保持します if ( ! sw_status_arr[idx] && hold_status_before_push_on && led_status_arr[idx] ) { // リモート操作中で、出力ピン状態がtrueの時は出力値を更新しない,物理スイッチはOFFだがリモートで出力ピンをONにしているので、ON状態を変更しない(リモート操作でスイッチをOFFにした時は、対応する出力ピンはその時にOFFにするようにしている) } else { led_status_arr[idx]=status; } } else { // 物理ボタンを監視しない場合は、何も変更しません。 } return buttonState; }