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內進行相關初始化動作及宣告相關全域變數
#include <IRremote.h>

int RECV_PIN = 2;
const byte DATA_PIN = 3;
const byte TMP_PIN = 4;
const byte CLOCK_PIN = 5;
const byte SW_PIN = 6;
const byte BUZZER_PIN = 7;

IRrecv irrecv(RECV_PIN);

void setup() {  
  // put your setup code here, to run once:
  //Serial.begin(9600);
  pinMode(DATA_PIN, OUTPUT);
  pinMode(TMP_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(SW_PIN, INPUT);
  
  irrecv.blink13(true);
  irrecv.enableIRIn();
  
  randomSeed(analogRead(0));
}

在此需注意的是

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

再來是loop()內使用到的全域變數部分
//七段顯示器 共陰極 0 ~ 9
const byte LEDs[10] = {
  B01111110,
  B00110000,
  B01101101,
  B01111001,
  B00110011,
  B01011011,
  B01011111,
  B01110000,
  B01111111,
  B01111011
};

int COUNT = 0;
int DELAY_MILLISEC = 20;
boolean autoExec = false;
int index = 0;
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()程式如下:
void loop() {
  // put your main code here, to run repeatedly:
  boolean on = digitalRead(SW_PIN);
  digitalWrite(TMP_PIN, LOW);

  //Get remote control signal and convert to LEDs's index
  int sendRECV = convertRECVtoIndex();
  if(sendRECV != -1){
    index = sendRECV;
    showLEDs();
    autoExec = false; //interrupt auto exec
    if(index == 0){
      index++;
    }
  }

  if(COUNT == 0 && index == 0){
    showLEDs();
    index++;
  }

  //Auto execute selection number
  if(on || autoExec){
    //1. about Beating device
    if(on && autoExec){
      unsigned char i;
      for(i=0;i<80;i++){
        digitalWrite(BUZZER_PIN, HIGH);
        delay(1);
        digitalWrite(BUZZER_PIN, LOW);
        delay(1);
      }  
    }
    //2. about switch
    if(on && !autoExec){
      if(index == 1){ //current number display 0
        initialize();
        autoExec = true;
        randNumber = generateRandomNum();
        //Serial.println(randNumber);
      }else{
        index = 0;
      }
      delay(300);
    }
    //3. if auto execute and get stop condition
    checkAutoExecState();
    //4. change 七段顯示器燈號 by index
    showLEDs();
    delay(DELAY_MILLISEC);
    //5. condition calculation
    index++;
    if(index == 10){
      index = 0;
    }
    if(autoExec && COUNT != randNumber){
      COUNT++;
    }  
  }
}

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

3.1 紅外線接收訊號處理

再來是呼叫convertRECVtoIndex(),以此將接收到的紅外線訊號轉換成LEDs's index
int convertRECVtoIndex(){
  int cindex = -1;
  if (irrecv.decode(&results)) { // 接收紅外線訊號並解碼
    long receive = results.value;
    switch(receive){
      case 16738455:{//0
        cindex = 0;
        break;
      }
      case 16724175:{//1
        cindex = 1;
        break;
      }
      case 16718055:{//2
        cindex = 2;
        break;
      }
      case 16743045:{//3
        cindex = 3;
        break;
      }
      case 16716015:{//4
        cindex = 4;
        break;
      }
      case 16726215:{//5
        cindex = 5;
        break;
      }
      case 16734885:{//6
        cindex = 6;
        break;
      }
      case 16728765:{//7
        cindex = 7;
        break;
      }
      case 16730805:{//8
        cindex = 8;
        break;
      }
      case 16732845:{//9
        cindex = 9;
        break;
      }
    }
    irrecv.resume(); // 準備接收下一個訊號
  }
  return cindex;
}

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

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

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

這一段乃是課本範例使用到的,呼叫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()
void initialize(){
  DELAY_MILLISEC = 20;
  COUNT = 0;
}

並呼叫generateRandomNum()取得一亂數值
long generateRandomNum(){
  long num = random(50,300);
  if(num > 150 && num < 200){
    num = num - 50;
  }else if(num >= 200 && num < 250){
    num = num - 100;
  }else if(num >= 250){
    num = num - 150;
  }
  return num;
}

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

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

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

對應縮小!

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

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

這邊就是在檢查自動選號當下,自動累加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版第七章節

留言