Arduino 演練 - 利用七段顯示器實作亂數選號器

最近研讀關於七段顯示器的章節,發現要顯示七段顯示器的程式還比接線路還要簡單的說!

而本演練的重點將在於以顯示七段顯示器的數字當基礎,實作出亂數選號器!

在此不會畫相關線路圖,僅以提供實際拍的圖片,也由於接的線路八成都是來自於書本內,

僅有額外接的元件,如按鍵、紅外線接收器、蜂鳴器等,但這幾個元件的線路都很簡單,因

此可以以書本為原型來接出此線路!

在此使用的素材如下:
1. Arduino Uno R3
2. 麵包板
3. 74HC595*1
4. 共陰極七段顯示器*1
5. 220Ω*7
6. 麵包線*24
7. 10KΩ*1
8. LED燈(藍)*1
9. 按鍵開關*1
10.蜂鳴器*1
11.紅外線接收器*1
12.單晶片遙控器*1

書中的實驗7-2 提供七段顯示器接74HC595藉以減少使用控制板的PIN腳,相關素材為1~6

,而7 ~ 12的部分在此自行加上以此完成亂數選號器的製作!

實際電路接法一

實際電路接法二

遙控器發射訊號 → 紅外線接收器


一、演練效果說明

1. 當板子接上電源後,顯示器初始化顯示為0
2. 當使用者按下按鍵,此時顯示器會從0開始,一路由0 ~ 9自動選號,當過了很多輪之後直到
    程式符合相關條件後才會停下!
3. 當顯示器已經在選號時,若持續按著按鍵將發出聲響提醒已開始選號了,不用再按囉!
4. 當顯示器已經在選號時,若此時利用遙控器點選數字,板子上的LED二極體將發光表示收
    到訊號了,顯示器的選號將立即停止並顯示該數字!

二、簡單說明一下電路接法

排除七段顯示器接74HC595的部分之後,剩下要接為紅外線接收器、按鍵開關、蜂鳴器等!

紅外線接收器(正面)左 Vout → 數位 2
紅外線接收器(正面)中 GND → 接地
紅外線接收器(正面)右 Vcc → 正極
74HC595序列輸入腳  → 數位 3
74HC595暫存時脈腳  → 數位 4
74HC595序列時脈腳  → 數位 5
按鍵開關 → 數位 6
蜂鳴器(+) → 數位 7
遙控器發射訊號 → 輸入數字 0 ~ 9(不相關硬加上XD)

三、程式說明

這邊由於需要使用到紅外線訊號接收的功能,因此需要載入相關程式庫

請將解壓縮後的IRremote目錄放至 Arduino安裝目錄下的libraries\

首先在setup method內進行相關初始化動作及宣告相關全域變數
  1. #include <IRremote.h>
  2. int RECV_PIN = 2;
  3. const byte DATA_PIN = 3;
  4. const byte TMP_PIN = 4;
  5. const byte CLOCK_PIN = 5;
  6. const byte SW_PIN = 6;
  7. const byte BUZZER_PIN = 7;
  8. IRrecv irrecv(RECV_PIN);
  9. void setup() {
  10. // put your setup code here, to run once:
  11. //Serial.begin(9600);
  12. pinMode(DATA_PIN, OUTPUT);
  13. pinMode(TMP_PIN, OUTPUT);
  14. pinMode(CLOCK_PIN, OUTPUT);
  15. pinMode(BUZZER_PIN, OUTPUT);
  16. pinMode(SW_PIN, INPUT);
  17. irrecv.blink13(true);
  18. irrecv.enableIRIn();
  19. randomSeed(analogRead(0));
  20. }

在此需注意的是

irrecv.blink13(true);表示紅外線接收器若收到訊號,則正極接13腳位的二極體會自動閃爍。randomSeed(analogRead(0)); 設定randomSeed初始化,並且assign未被連接的類比腳位

再來是loop()內使用到的全域變數部分
  1. //七段顯示器 共陰極 0 ~ 9
  2. const byte LEDs[10] = {
  3. B01111110,
  4. B00110000,
  5. B01101101,
  6. B01111001,
  7. B00110011,
  8. B01011011,
  9. B01011111,
  10. B01110000,
  11. B01111111,
  12. B01111011
  13. };
  14. int COUNT = 0;
  15. int DELAY_MILLISEC = 20;
  16. boolean autoExec = false;
  17. int index = 0;
  18. long randNumber;

LEDs array的index 0 ~ 9相當於七段顯示器顯示數字的二進位表示
B → 這個base表示為binary,後面可指定 0 or 1共8 bit values (官方說明)
COUNT → 表示亂數選號是否須停止使用
DELAY_MILLISEC → 一開始選號轉動delay的時間
autoExec → 判斷是否為自動執行亂數選號動作
index → 記錄目前要顯示哪個LEDs array的index值
randNumber → 產生的亂數值

loop()程式如下:
  1. void loop() {
  2. // put your main code here, to run repeatedly:
  3. boolean on = digitalRead(SW_PIN);
  4. digitalWrite(TMP_PIN, LOW);
  5. //Get remote control signal and convert to LEDs's index
  6. int sendRECV = convertRECVtoIndex();
  7. if(sendRECV != -1){
  8. index = sendRECV;
  9. showLEDs();
  10. autoExec = false; //interrupt auto exec
  11. if(index == 0){
  12. index++;
  13. }
  14. }
  15. if(COUNT == 0 && index == 0){
  16. showLEDs();
  17. index++;
  18. }
  19. //Auto execute selection number
  20. if(on || autoExec){
  21. //1. about Beating device
  22. if(on && autoExec){
  23. unsigned char i;
  24. for(i=0;i<80;i++){
  25. digitalWrite(BUZZER_PIN, HIGH);
  26. delay(1);
  27. digitalWrite(BUZZER_PIN, LOW);
  28. delay(1);
  29. }
  30. }
  31. //2. about switch
  32. if(on && !autoExec){
  33. if(index == 1){ //current number display 0
  34. initialize();
  35. autoExec = true;
  36. randNumber = generateRandomNum();
  37. //Serial.println(randNumber);
  38. }else{
  39. index = 0;
  40. }
  41. delay(300);
  42. }
  43. //3. if auto execute and get stop condition
  44. checkAutoExecState();
  45. //4. change 七段顯示器燈號 by index
  46. showLEDs();
  47. delay(DELAY_MILLISEC);
  48. //5. condition calculation
  49. index++;
  50. if(index == 10){
  51. index = 0;
  52. }
  53. if(autoExec && COUNT != randNumber){
  54. COUNT++;
  55. }
  56. }
  57. }

由於顯示七段顯示器使用了74HC595,因此在呼叫shiftOut前需先將TMP_PIN set LOW

3.1 紅外線接收訊號處理

再來是呼叫convertRECVtoIndex(),以此將接收到的紅外線訊號轉換成LEDs's index
  1. int convertRECVtoIndex(){
  2. int cindex = -1;
  3. if (irrecv.decode(&results)) { // 接收紅外線訊號並解碼
  4. long receive = results.value;
  5. switch(receive){
  6. case 16738455:{//0
  7. cindex = 0;
  8. break;
  9. }
  10. case 16724175:{//1
  11. cindex = 1;
  12. break;
  13. }
  14. case 16718055:{//2
  15. cindex = 2;
  16. break;
  17. }
  18. case 16743045:{//3
  19. cindex = 3;
  20. break;
  21. }
  22. case 16716015:{//4
  23. cindex = 4;
  24. break;
  25. }
  26. case 16726215:{//5
  27. cindex = 5;
  28. break;
  29. }
  30. case 16734885:{//6
  31. cindex = 6;
  32. break;
  33. }
  34. case 16728765:{//7
  35. cindex = 7;
  36. break;
  37. }
  38. case 16730805:{//8
  39. cindex = 8;
  40. break;
  41. }
  42. case 16732845:{//9
  43. cindex = 9;
  44. break;
  45. }
  46. }
  47. irrecv.resume(); // 準備接收下一個訊號
  48. }
  49. return cindex;
  50. }

針對上述的十進位數值如何取得,乃事前先一一輸入後印出得到的值,再將它設到程式內。

當得到回傳值之後,若得到非-1表示接收到紅外線有效的訊號值

如此一來可以呼叫showLEDs();
  1. void showLEDs(){
  2. shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, LEDs[index]);
  3. digitalWrite(TMP_PIN, HIGH);
  4. }

這一段乃是課本範例使用到的,呼叫shiftOut再將TMP_PIN set to HIGH

這樣子燈號就會顯示相關的數字囉!

關於shiftOut的語法為 shiftOut(dataPin, clockPin, bitOrder, value)

dataPin and clockPin上面已經有宣告了

bitOrder,在此搭配array value及麵包板接的順序須採用最低位元先傳
以LED[0]來說 → B01111110 最低位元在最右邊的0
搭配板子上接的順序 a ~ g(七段顯示器編號) 對應 74HC595為 1 ~ 7
因此最右邊的0先傳給g,其他七段顯示器的編號都將會是1,因此最後燈號顯示的是0
PS. 這邊可以搭配書本7-11的圖片說明及7-14的電路圖來參考較好理解

value表示LEDs's value

再來當接收到符合的紅外線訊號後,若當下已經在自動選號的情況下將會停止

並判斷index若為0需累計1,表示當按下按鍵後可以顯示燈號1

3.2 初始化顯示

當程式初始化時判斷if(COUNT == 0 && index == 0)成立後會顯示0

3.3 亂數選號

從if(on || autoExec)判斷可以得知,進去的條件為按下按鍵或者是自動選號執行當下

當從停下的燈號,第一次按下按鍵後(on => 1)才會開始執行選號動作

請看註解2. about switch這一段code,滿足此時autoExec為false

若index == 1表示目前燈號停留在0,此時呼叫initialize()
  1. void initialize(){
  2. DELAY_MILLISEC = 20;
  3. COUNT = 0;
  4. }

並呼叫generateRandomNum()取得一亂數值
  1. long generateRandomNum(){
  2. long num = random(50,300);
  3. if(num > 150 && num < 200){
  4. num = num - 50;
  5. }else if(num >= 200 && num < 250){
  6. num = num - 100;
  7. }else if(num >= 250){
  8. num = num - 150;
  9. }
  10. return num;
  11. }

取得亂數值越大,數字就會轉動越久,因為每一次的loop會進行COUNT累加,然後再判斷

是否已等於亂數值,同時為了要製造轉動後越來越慢等於快得到結果的效果,因此delay內

的數值會越來越大!  取得亂數值時range是設成50 ~ 300之間,並做簡單的判斷來使亂數值相

對應縮小!

若index != 1表示停下來的燈號是顯示0以外的數值,因此按鍵按下會先歸零且不動

再來請看註解3. if auto execute and get stop condition,此時呼叫checkAutoExecState()
  1. void checkAutoExecState(){
  2. if(autoExec){
  3. DELAY_MILLISEC += 2;
  4. if(COUNT == randNumber && COUNT % 10 == index){
  5. autoExec = false;
  6. }
  7. }
  8. }

這邊就是在檢查自動選號當下,自動累加DELAY_MILLISEC變數使得數字變換會越來越

慢,並且判斷COUNT的計數是否等於亂數值了,原本一開始只設定這個條件,後來發現

每次選號停下來的index會有重複性高的情況! 因此再加上第二個條件,那就是前者成立後

選號還是不會停止,直到COUNT(相當於亂數值)的個位數等於index後才會停止!

如此一來,若此次的亂數值為129,表示最後選號會落在9這個數值! 完全由亂數值來決定

最後的index值,這樣子的效果就比較不會有重複性選到一樣的數字!

註解4. change 七段顯示器燈號 by index已重複提過,顯示燈號並且做delay間隔的效果

註解5. condition calculation,只不過是一些變數的累加

最後則是註解1. about Beating device

這段code是搭配蜂鳴器的使用,程式碼也很簡單。目的是當使用者按下按鍵後,此時已開始

自動選號,若還按著按鍵不放蜂鳴器就會開始響!! 做一個警示作用!

DEMO影片如下:



參考資料
1. 葉難的Arduino練習:紅外線傳送與接收
2. 趙英傑著 超圖解 arduino 互動設計入門第3版第七章節

留言