在當兵期間,用 Arduino 和觸控螢幕做一個夜射控制盒

有一天副連長很匆忙的跑到連上問有沒有電機系和資工系會寫程式和接電線的人,原本以為是簡單的公差,但後來衍生成滿麻煩的任務…營長想要做一個酷酷的裝置,可以在夜射的時候控制整個靶場的燈光,就算剩下 3 週就退伍了,為了完成營長(兩顆梅花)好大喜功的交辦事項,還是硬著頭皮被抓去營長室了。 夜間射擊的運作方式和白天打靶很像,差別在於要在幾乎全黑的情況下打靶,靶的下方會安裝微弱的照明(弱弱的燈泡),在開始射擊之後每次會亮 3 秒、暗兩秒,重複五次,射擊的人要按照節奏打完 5 發子彈。 為了達到「計時開關」的需求,以前是把電源接到由斷路器和計時器所接成的控制盒(普累嘎)上,按按鈕控制計時器運作,比較像是用電路和機械元件來做到計時的效果,但這種裝置通常整體的體積很大,而且看起來不夠「酷」,所以營長一開始想要我們設計一個「由晶片計時(寫程式)、遠端控制、手機控制(可以用滑的)」之類的功能,他的想像是要做到智慧家電那樣可以透過程式去控制靶場的燈。不管這聽起來有多麼沒必要,反正只要能讓自己有面子就可以了。 需求 在經過一連串的討論之後,我們把需求簡化成 7 個輸入控制繼電器開關 4 組燈(靶燈、場燈…共四種),分別為 四組燈的個別開關 全開 + 全關 計時開始(靶燈亮 3 秒,暗 2 秒 共五次) 為了達到用晶片寫程式、減小體積、預算低的需求,我們後來選擇用 Arduino 直接硬做一發。 ![[Pasted image 20231228005200.png]] 其實簡單可以理解成用「晶片控制的延長線」,有沒有電通過 = 燈的開關 ![[Pasted image 20231228005208.png]] 按鈕版 首先,我們做了一個按鈕的版本,總共接了 7 個按鈕,4個繼電器。 ![[Pasted image 20231228005232.png]] 問題 A:按鈕在物理上有接通與不接通的情況,所以要以「狀態變化」當作按鈕有沒有被按下,而不是有無接通 儲存每一個按鈕的初始狀態,當狀態改變代表有被按下 儲存每一個燈的狀態,根據不同的輸入改變狀態(例如:關變開、開變關) 問題 B:關於計時這件事,不能用 **delay()** 而是要去記錄 **millis()** 的時間差 因為在 delay 時程式是暫停的,不能處理其他 input 設計上在計時模式的過程中,如果按了全開或全關(例如有突發狀況),會暫停計時 const int N_BUTTONS = 7; const int N_LIGHTS = 4; const int button_pins[7] = {2,3,4,5,6,7,8}; const int light_pins[4] = {9, 10, 11, 12}; int last_button_state[7] = {0,0,0,0,0,0,0}; int physical_button_state[7] = {0,0,0,0,0,0,0}; int light_state[4] = {0, 0, 0, 0}; bool is_shooting = false; unsigned long shooting_start = 10e6; void read_physical_button_state(){ for(int i=0; i<N_BUTTONS; i++){ physical_button_state[i] = digitalRead(button_pins[i]); } } int detect_change(){ // 0, 1, 2, 3 分別是每個燈泡個別亮暗 // 4, 5 全開 / 全關 // 6 計時模式 read_physical_button_state(); for(int i=0; i<N_BUTTONS; i++){ if(last_button_state[i] != physical_button_state[i]){ last_button_state[i] = physical_button_state[i]; return i; } } return -1; } void setup(){ Serial.begin(9600); for(int i=0; i<N_BUTTONS; i++){ pinMode(button_pins[i], INPUT_PULLUP); } for(int i=0; i<N_LIGHTS; i++){ pinMode(light_pins[i], OUTPUT); } read_physical_button_state(); for(int i=0; i<N_BUTTONS; i++){ last_button_state[i] = physical_button_state[i]; // Serial.print(last_button_state[i]); // Serial.print(", "); // Serial.print(physical_button_state[i]); // Serial.print("\n"); } } void set_light_state(int pin, int state){ digitalWrite(light_pins[pin], state); light_state[pin] = state; } void loop(){ int change = detect_change(); if(change >= 0){ Serial.println(change); } if(change == -1){ // no change }else if((change >= 0 && change <= 3) && !is_shooting){ set_light_state(change, !light_state[change]); }else if(change == 4){ // all open for(int i=0; i<N_LIGHTS; i++){ set_light_state(i, HIGH); } is_shooting = false; shooting_start = 10e6; } else if(change == 5){ // all close for(int i=0; i<N_LIGHTS; i++){ set_light_state(i, LOW); } is_shooting = false; shooting_start = 10e6; }else if(change == 6){ // shooting is_shooting = true; shooting_start = millis(); } if(is_shooting){ unsigned long now = millis(); if(now - shooting_start < 3000){ for(int i=0; i<N_LIGHTS; i++){ set_light_state(i, LOW); } }else if(now - shooting_start < 6000){ set_light_state(0, HIGH); }else if(now - shooting_start < 8000){ set_light_state(0, LOW); }else if(now - shooting_start < 11000){ set_light_state(0, HIGH); }else if(now - shooting_start < 13000){ set_light_state(0, LOW); }else if(now - shooting_start < 16000){ set_light_state(0, HIGH); }else if(now - shooting_start < 18000){ set_light_state(0, LOW); }else if(now - shooting_start < 21000){ set_light_state(0, HIGH); }else if(now - shooting_start < 23000){ set_light_state(0, LOW); }else if(now - shooting_start < 26000){ set_light_state(0, HIGH); }else if(now - shooting_start < 29000){ set_light_state(0, LOW); }else{ is_shooting = false; shooting_start = 10e6; set_light_state(0, LOW); set_light_state(1, HIGH); set_light_state(2, HIGH); set_light_state(3, HIGH); } } delay(300); } 觸控版 為了讓整個設備可以變得更酷一點,我們發現 Arduino 有觸控螢幕的模組(但很難買)可以直接插上去,所以就有了觸控的版本。 ...

June 18, 2023