Arduino:電子輪盤
Electronic Roulette
Arduino 控制板:Uno
程式碼編寫語言:C 語言
功能
按鈕按下不放,八顆 LED 會快速地以跑馬燈方式依序循環單顆點亮,形成單一亮點快速的循環移動。且每一顆 LED 點亮時,會伴隨「答」的一聲聲響;按鈕一旦鬆開,LED 仍會因先前按鈕按下時持續加速累積的動能,繼續以跑馬燈方式依序循環點亮,但是亮點移動速度會阻力的存在而慢慢變慢,亮點最終會停止移動並停留於隨機位置的 LED 上。
電路接線
硬體電路接線如圖一所示,八顆 LED 的陽極皆接至 $+5V$,陰極分別透過八顆 $220$ $ \Omega$ 電阻接至 Uno 的 pin 9,pin 8,pin 7,pin 6,pin 5,pin 4,pin 3 以及 pin 2。
無源蜂鳴器 $+$ 極接至 $+5V$,$-$ 極接至 2N7000 N-Channel MOSFET 的 D 極。2N7000 的 G 極同時接至 Uno 的 pin 13 與 $4.7K$ $ \Omega$ 的一端〔2N7000 的接腳圖如圖二所示〕,$4.7K$ $ \Omega$ 的另一端再接至地,而 2N7000 的 S 極則是直接接地。
按鈕開關一側接地,另一側同時接 $10K \Omega$ 電阻與 Uno 的 pin 12。$10K \Omega$ 電阻剩下的另一端則是接至 $+5V$。
圖一 Arduino 電子輪盤接線圖
圖二 NMOS 2N7000 接腳圖
程式碼
byte led[ ] ={ 9, 8, 7, 6, 5, 4, 3, 2}; /* 陣列存放連接至 LED 的所有輸出接腳編號 */
bool push_button_detection(); /* 偵測按鈕狀態 (是否被按下或鬆開?) */
byte buzz = 13; /* pin 13 控制無源蜂鳴器是否流通電流 (高態動作) */
byte push_button = 12; /* pin 12 偵測按鈕兩側為短路 (按下) 或開路 (鬆開) */
int j = 0; /* j 的值代表目前點亮的 LED 位置 */
int power = 0; /* power 的值代表按鈕按下時累積的跑馬燈動能 */
int multiple = 1; /* multiple 的值代表跑馬燈最低延遲時間的倍數值 */
int power_max = 100; /* 判定按鈕是否短暫按下的累積 power 可能最大值 */
int power_min = 50; /* 判定按鈕是否短暫按下的累積 power 可能最小值 */
int count_max = 6; /* 按鈕短暫按下 LED 亮點多跑的圈數可能最大值 */
int count_min = 4; /* 按鈕短暫按下 LED 亮點多跑的圈數可能最小值 */
void setup() {
for(int i = 0; i < 8; i++){
pinMode(led[i], OUTPUT); /* 規劃連接 LED 接腳為輸出 */
digitalWrite(led[i], HIGH); /* 所有 LED 初始狀態為熄滅 */
}
randomSeed(analogRead(0)); /* 亂數種子 */
}
void loop() {
while(push_button_detection()){ /* 如果按鈕正被按下,尚未鬆開 */
digitalWrite(led[j], LOW); /* 令位置為 j 的 LED 點亮 */
tone(buzz,1,500); /* 發出「答」的一聲聲響 */
delay(10); /* 延遲 10 ms */
j++; /* 位置 j 值遞加一 */
if(j>=8) /* 若 j 值遞加一後大於等於 8,j 值須回到 0 */
j = 0;
if (!push_button_detection()){ /* 若按鈕按下後鬆開,power 值停止累加 */
if (j == 0) /* 若目前位置 0 的 LED 點亮,則前一個位置為 7 的 LED 須熄滅*/
digitalWrite(led[7], HIGH);
else /* 否則前一個位置 j-1 的 LED 須熄滅 */
digitalWrite(led[j-1], HIGH);
delay(10); // 延遲 10 ms
/* 若短暫按下,須讓 LED 多跑 4~6 圈,以防止 LED 最後停止移動的位置輕易被操控 */
/* 代表多跑圈數的 count 值為亂數產生,其值介於 count_min〔即 4〕與 count_max〔即 6〕
之間 */
int count = random(count_min,count_max+1);
/* x 值介於 power_min〔即 50〕與 power_max〔即 100〕之間,以 power 是否小於 x 來判定
按鈕是否短暫按下 */
int x = random(power_min,power_max+1);
while(count > 0 && power < x){ /* while 迴圈執行條件:若 count >0 且 power < x */
for(int i = j; i < 8;i++){
digitalWrite(led[i], LOW); /* 點亮位置為 i 的 LED */
tone(buzz,1,500);
power +=3; /* 在多跑圈數時,一樣累加 power值 */
delay(10);
digitalWrite(led[i], HIGH); /* 令位置 i 的 LED 熄滅 */
delay(10);
}
j = 0; /* 跑完完整的一圈後,重新開始。點亮的 LED 位置回到位置 0 */
count--; /* 剩餘圈數遞減一 */
}
// 處理按鈕鬆開後的慣性
while(power > 0){
digitalWrite(led[j], LOW);
tone(buzz,1,500);
if(power < 20) /* power 愈小,表示移動愈近尾聲,令其延遲愈久 */
multiple = 17; /* multiple 為延時倍數值 */
else if(power < 35)
multiple = 12;
else if(power < 50)
multiple = 8;
else if(power < 100)
multiple = 5;
else if(power < 150)
multiple = 3;
else if(power < 200)
multiple = 2;
else
multiple = 1;
delay(10+10*multiple); /* power 愈小,multiple 值愈大,延遲愈久 */
if(power < 10) /* 愈近尾聲,讓 power 消耗愈慢 */
power -=1;
else
power -= 3;
if(power >0){ /* power 最終會減至 0 以下 */
digitalWrite(led[j], HIGH);
delay(10+10*multiple);
j++;
if (j>=8)
j = 0;
}
}
}
}
}
bool push_button_detection(){
if (digitalRead(push_button) == 0){ /* 若按鈕按下,尚未鬆開 */
if(j == 0)
digitalWrite(led[7], HIGH);
else
digitalWrite(led[j-1], HIGH);
power +=3;
if (power > 500)
power = 500;
return true;
}
else /*若按鈕未按下,或按下後鬆開 */
return false;
}
程式碼說明
byte led[ ] = { 9, 8, 7, 6, 5, 4, 3, 2};
將分別連接 LED 的 8 個 Uno 接腳編號置於陣列中,方便後面以變數 j 作為陣列的索引值來取出需要控制的接腳編號。
bool push_button_detection();
宣告用來偵測按鈕是否按下的自定函數,若偵測到按鈕按下會回傳 true,否則回傳 false 。
for(int i = 0; i < 8; i++){
pinMode(led[i], OUTPUT);
digitalWrite(led[i], HIGH);
}
利用執行八次的 for 迴圈,逐一執行 pinMode( led[i], OUTPUT) ,將使用到的八個連接 LED 的接腳逐一規劃為輸出接腳,並且逐一執行 digitalWrite( led[i], HIGH) ,令連接的八顆 LED 初始狀態為全熄。注意此 for 迴圈是置於 setup() 函式內,而 setup() 只會執行一次。
I/O 接腳輸出入規劃指令:pinMode(pin, mode)
(1) pin:指定腳編號
(2) mode:指定腳的模式,可為 INPUT,OUTPUT,以及 INPUT_PULLUP
數位輸出指令:digitalWrite(pin, value)
(1) pin:指定腳編號
(2) value:可為 HIGH 或 LOW
randomSeed(analogRead(0));
randomSeed() 可用來產生亂數種子,以決定從亂數表中初次取出亂數的位置,後面若還要取出亂數則依序往下取出。但每次電源重置後,取出的亂數序列都會是相同的。不過,若其傳入的參數不同時,數表中初次取出亂數的位置也會隨之不同。因此,為了讓取出亂數更具隨機性,一般都會讓參數傳入 analogRead(pin_x) 的值,亦即將讀取到某接腳 pin_x 的介於 0 ~ 5 V 間的類比電壓,經 Uno 的類比轉數位轉換器轉換對應的介於 0 ~ 1023 的數值,讓亂數表中初次取出亂數的位置更為隨機。由於我們的硬體電路 pin 0 為浮接,是一個沒有使用到的接腳,因此 analogRead(0) 讀到的是雜訊電壓對應的數值。
初始化從亂數表取出的亂數位置函數:randomSeed(seed)
(1) seed:非 0 的數字,接受的資料型態為 unsigned long
(2) 回傳值:無
讀取類比輸入轉換對應數值函數:analogRead(pin)
(1) pin:指定腳編號
(2) 回傳值:0 - 1023 間數字 (10 位元 A/C 轉換器) 或 0 - 4095 間數字 (12 位元 A/C 轉換器)
bool push_button_detection(){
if (digitalRead(push_button) == 0){
if(j == 0)
digitalWrite(led[7], HIGH);
else
digitalWrite(led[j-1], HIGH);
power +=3;
if (power > 500)
power = 500;
return true;
}
else
return false;
}
自定函數 push_button_detection() 是用來偵測當下按鈕的狀態,是正被按下還是已鬆開?由於我們的硬體情況是按鈕按下時,pin 12 會偵測到低準位〔邏輯 0〕, 按鈕鬆開時,pin 12 會偵測到高準位〔邏輯 1〕。因此,當 digitalRead(push_button) 執行時,會對 pin 12 讀取其數位邏輯狀態。若讀取到邏輯 0,則 if (digitalRead(push_button) == 0){...} 的執行條件成立,{}內的敘述就會依序被執行。
若 if (digitalRead(push_button) == 0){...} 的執行條件成立:
先判斷目前點亮的 LED 位置是否為 0,若是表示前一個點亮 LED 的位置為 7 ,這是一個無法一般化處理的特殊情況,因為兩者相關接腳編號存放在陣列中的對應的索引值並不連續,因此需要另外處理。我們就利用 digitalWrite(led[7], HIGH) 敘述的執行,直接將位置 7 的 LED 熄滅;若不是,意味著先前與現在點亮的 LED 位置,兩者相關接腳編號存放在陣列中的對應的索引值是連續的。故若目前點亮的 LED 位置是 j,則前一個點亮 LED 位置必為 j-1,因此利用 digitalWrite(led[j-1], HIGH) 敘述的執行來將位置 j-1 的 LED 熄滅。
此外,為了增加 LED 最後點亮位置的不可操控性,可設計按鈕按下不放的時間愈長,LED 循環點亮的累積動能就愈大,使得即使按鈕鬆開後,LED 的循環點亮存在一慣性,動作可繼續延續一段時間〔按下愈久不放,儲存動能愈大〕。因此,若每次偵測到按鈕是持續按下的狀態,執行 power += 3,power 值就遞加 3 。然而,為防止按鈕持續按下過長時間,power 累積值過大,會造成 LED 的循環點亮持續過久,而造成等待 LED 點亮位置停止不動的時間過長,因此利用
if (power > 500)
power = 500;
將 power 值最上限設為 500 加以限制。最後回傳 true ,表示偵測到按鈕正被按下中。
若 if (digitalRead(push_button) == 0){...} 的執行條件不成立:
表示按鈕尚未被按下或按下後已鬆開,所以簡單地回傳 false,表示偵測到按鈕是鬆開的狀態。
while(push_button_detection()){...}
while 迴圈的執行條件為 push_button_detection() 執行後回傳 true,表示按鈕被按下尚未鬆開,while 迴圈內的敘述才會被執行。注意 while 迴圈是置於 loop() 函式中,而 loop() 函式是會執行無窮無盡迴圈,直到 Uno 電源關閉或損壞無法運作為止。
while(push_button_detection()){
digitalWrite(led[j], LOW);
tone(buzz,1,500);
delay(10);
j++;
if(j>=8)
j = 0;
......
digitalWrite(led[j], LOW);
tone(buzz,1,500);
delay(10);
j++;
if(j>=8)
j = 0;
......
}
利用執行 digitalWrite (led[j], LOW) 敘述,將位置為 j 的 LED 點亮。接著執行 tone(buzz, 1, 500),讓 pin 13 輸出 1 Hz ,持續 500 ms 的方波給 2N7000 n-channel MOSFET 的 G 極,以控制無源蜂鳴器使其發出類似「答」的聲響。delay(10)
敘述的執行可延遲 10 ms 後,LED 的亮點再往下一個 LED 移動,可讓明滅變化較為明顯。
j++ 敘述的執行可準備讓下一個位置的 LED 點亮。但下一個位置若是 j = 8,因為 $0 \le j \le 7$,因此利用下列敘述
if (j >= 8)
j = 0;
將 j 的值改為 0。
發出特定頻率與持續時間的方波函數:tone(pin, frequency, duration)
(1) pin:Uno 的方波指定輸出接腳
(2) frequency:方波頻率,單位為 Hz,接受的資料型態為 unsigned int
(3) duration:單位為 ms 方波持續時間 (可略),接受的資料型態為 unsigned long
(4) 回傳值:無
while(push_button_detection()){
......
if (!push_button_detection()){
if (j == 0)
digitalWrite(led[7], HIGH);
else
digitalWrite(led[j-1], HIGH);
delay(10);
if (j == 0)
digitalWrite(led[7], HIGH);
else
digitalWrite(led[j-1], HIGH);
delay(10);
......
}
當按鈕沒被按下或已鬆開,push_button_deection() 執行會回傳 false, if(!push_button_deection()){...} 的執行條件成立,{}內的敘述就會被執行。
if (j == 0)
digitalWrite(led[7], High);
else
digitalWrite(led[j-1], HIGH);
delay(10);
上面的敘述表示若目前點亮的 LED 位置為 j = 0, 則前一個位置為 j = 7 〔二者在陣列中對應的接腳位置不連續〕,可直接執行 digitalWrite(led[7], HIGH) 敘述直接將對應陣列接腳編號位置為 7 的 LED 熄滅。否則目前點亮的 LED 對應陣列接腳編號位置若為 j ,則前一個位置必為 j-1。再執行 digitalWrite(led[j-1], HIGH) 敘述,將位置為 j-1 的 LED 熄滅。最後,執行 delay(10) 敘述以延遲 10 ms,讓 LED 亮滅現象明顯。
int count = random(count_min, count_max+1);
int x = random(power_min, power_max+1);
while(count > 0 && power < x){
......
}
若碰到按鈕短暫按下後鬆開,須讓 LED 多跑 4~6 圈,以防止 LED 最後停止移動的位置輕易被操控。在此引入介於 4~6 間以及 50~100 間的兩個亂數值,來讓 LED 循環點亮在此情況下可多跑 4~6 圈。random(cont_min, count_max+1) 敘述執行後,可產生介於 count_min 與 count_max 之間〔即 4~6 間〕的隨機整數;random(power_min, powr_max+1) 敘述執行後,可產生介於 power_min 與 power_max 之間〔即 50~100 間〕的隨機整數。當 power < x,可判定按鈕短暫按下就鬆開,並以 count 值控制 while 迴圈 while(count > 0 && power < x){...} 的執行次數。
產生隨機整數函數:random(min, max)
(1) min:隨機整數的下限值 (包含)
(2) max:隨機整數的上限值 (不包含)
(3) 回傳值:回傳介於 min 與 max-1 之間的整數值,資料型態為 long
while(count > 0 && power < x){
for(int i = j; i < 8;i++){
digitalWrite(led[i], LOW);
tone(buzz,1,500);
power +=3;
delay(10);
digitalWrite(led[i], HIGH);
delay(10);
}
j = 0;
count--;
}
如前所述,因為若按鈕短暫按下就鬆開,需要讓 LED 點亮循環圈數多跑 4 至 6 圈,以減少 LED 亮點停止位置容易被操控。因此 while 迴圈內的 for 迴圈的第一圈是從 i = j 開始,接續按鈕鬆開前的最後一次 LED 點亮位置 j-1。一圈結束之後就都從 i = 0 開始。digitalWrite(led[i], LOW) 敘述在將位置 i 的 LED 點亮。tone(buzz, 1, 500) 用來發出類似「答」的一生聲響。power += 3 使得在多跑圈數時,一樣累加 power 值。delay(10) 延遲 10 ms 可讓亮滅現象明顯。digitalWrite(led[i], HIGH) 敘述可將位置 i 的 LED 熄滅。j = 0 可讓 for 迴圈在第二圈以後讓 LED 從位置 0 開始點亮。count-- 放在最後可令 while 迴圈執行完一圈後,將未執行的剩餘迴圈數遞減一。
while(power > 0){
digitalWrite(led[j], LOW);
......
if(power < 10)
power -= 1;
else
power -= 3;
if(power >0){
digitalWrite(led[j], HIGH);
......
j++;
if (j>=8)
j = 0;
}
}
while(power > 0){...} 用來處理按鈕鬆開後 LED 亮點的慣性移動。下面的敘述
if(power < 10)
power -= 1
else
power -= 3
if(power > 0)
digitalWrite(led[i], HIGH);
......
}
可讓 while 迴圈每執行一次 power -=3 ,power 值就會減少 3。但是在 power 快耗盡時 (power < 10) 執行 power -=1 令其每次只減少 1 ,減少的量比一般少一點,搭配延時時間增加可讓 LED 亮點移動在快完全停止前還可以緩慢移動一些位置。執行 if (power > 0) ,判斷如果 power 減少後的值仍大於 0 ,執行 diritalWrite(led[i], HIGH) 將先前點亮位置 i 的 LED 熄滅 ;否則表示 power 小於等於 0,表示 LED 亮點不再移動,就跳過 diritalWrite(led[i], HIGH) 的執行,讓先前點亮的 LED 不將其熄滅維持點亮狀態。
while(power > 0){
......
if(power < 20)
multiple = 17;
else if(power < 35)
multiple = 12;
else if(power < 50)
multiple = 8;
else if(power < 100)
multiple = 5;
else if(power < 150)
multiple = 3;
else if(power < 200)
multiple = 2;
else
multiple = 1;
delay(10+10*multiple);
......
if(power >0){
......
delay(10+10*multiple);
......
}
}
因為 power 值愈小,表示移動愈近尾聲,加大 multiple 值,搭配 delay(10+10*multiple) 可令 LED 點亮於前一個位置移動至下一個位置的延遲久一些。而這些執行的條件都是必須是 power > 0。


留言
張貼留言