Arduino:抽獎器

Electronic Raffle Machine


Arduino 開發板:Uno

程式撰寫語言:C


功能


電源打開,四位數七段顯示器如圖一所示,顯示「----」。按鈕按下後,四位數七段顯示器的每一位數會快速地各自從一隨機數值開始持續遞加一顯示,如圖二所示。一段時間後,千位數的數字先停止遞加一,該位數顯示數字不動,其餘位數則仍繼續快速地持續遞加一顯示,如圖三所示。再一段時間後,百位數的數字也停止遞加一,該位數顯示數字不動,而其餘位數則仍繼續快速地持續遞加一顯示,如圖四所示。又一段時間後,十位數的數字也停止遞加一,該位數顯示數字不動,剩下的個位數則仍繼續快速地持續遞加一顯示,如圖五所示。接著再過一段時間,個位數的數字也停止遞加一,該位數顯示數字不動,如圖六所示最終,抽獎抽到的號碼其所有位數就靜止顯示在顯示器上。

四位數七段顯示器顯示「----」
圖一    四位數七段顯示器一開始顯示「----」

圖二    四位數七段顯示器的每一位數會快速地各自從一隨機數值開始遞加一顯示

圖三    一段時間後,千位數的數字先停止不動,其餘位數則仍繼續快速地遞加一顯示

圖四    百位數的數字停止遞加一後,顯示的數字不動,而其餘位數則仍繼續快速地遞加一顯示

圖五  十位數的數字停止遞加一後,顯示的數字不動,而個位數則仍繼續快速地遞加一顯示

圖六    最後個位數的數字也停止遞加一後,顯示數字不動


電路圖


如圖七所示。


圖七    電路圖


硬體接線圖


如圖八所示。


硬體接線圖
圖八    硬體接線圖


完整程式碼



int scanPin[] = {5678}; 
int dataPin = 10, latchPin = 11, clockPin = 12;
int sevenSegCode[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0xBF}

int horizontalLinePos = 10;
int dataOut[] = {0000};  
int processingData[4]; 
int startData[4];     
int finalData[4];                
int scanDigitNum = 4;
const int pushButtonPin = 4;

bool digitStop[]={0000};  
bool allDigitStop = false;    
bool btPushed = false;

long randNumberStarter;
long randNumberTarget;
long thousandsCountNumber;
long hundredsCountNumber;
long tenthsCountNumber;
long unitsCountNumber;

void push_button_detection();
void calCountDigit(int);
void digitNumInc(int);
 
unsigned long previous_time, now_time;
bool begin = true;
 
int numThousandsDigit, numHundredsDigit, numTenthsDigit, numUnitsDigit;  
int remainder;
 
void setup() {
  for (int i = 0; i < scanDigitNum; i++){
    pinMode(scanPin[i], OUTPUT);
    digitalWrite(scanPin[i], 1);  
  }
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(pushButtonPin, INPUT);
  digitalWrite(latchPin,0); 
  randomSeed(analogRead(A0));
  randNumberStarter = random(09999 + 1);    
  calCountDigit(randNumberStarter);           
  for (int i = 0;i <scanDigitNum; i++){
    dataOut[i] = processingData[i];
    startData[i] = processingData[i];
  }
}
 
void loop() {  
  while(begin){
    push_button_detection();
    for (int i = 0; i < scanDigitNum; i++){
      digitalWrite(latchPin, 0); 
      shiftOut(dataPin, clockPin,
               MSBFIRST, sevenSegCode[horizontalLinePos]);
      digitalWrite(latchPin, 1); 
      digitalWrite(scanPin[i], 0); 
      delay(1);
      digitalWrite(scanPin[i], 1); 
    }
  }
  if(!begin){
    previous_time = millis();
    now_time = millis();
    while((now_time - previous_time) < 80){   
 
     for (int i = 0; i < scanDigitNum; i++){
      digitalWrite(latchPin, 0); 
      shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[dataOut[i]]);
      digitalWrite(latchPin, 1); 
      digitalWrite(scanPin[i], 0); 
      delay(1);
      digitalWrite(scanPin[i], 1);      
    }
    now_time = millis();  
   }  
    
   digitNumInc();
   if (allDigitStop){
    for(int i = 0; i < scanDigitNum; i++){
      digitStop[i] = false;
    }
    allDigitStop = false;
   }
  push_button_detection();
 }
}
 
void push_button_detection(){
  while(!digitalRead(pushButtonPin)){  
    randNumberTarget = random(09999 + 1);    
    calCountDigit(randNumberTarget);            
    thousandsCountNumber = random(4055);   
    hundredsCountNumber = thousandsCountNumber + random(4055);
    tenthsCountNumber = hundredsCountNumber + random(4055);
    unitsCountNumber = tenthsCountNumber + random(2540);
 
    for(int i = 0; i < scanDigitNum; i++){
      finalData[i] =  processingData[i];
    }
    previous_time = millis();
    while(!digitalRead(pushButtonPin));    
    now_time = millis();
    if ((now_time-previous_time) >= 50 ){
      begin = false;
      btPushed = true;                 
    }
  }
}
 
void calCountDigit(int num){
  numThousandsDigit = num/1000;
  remainder = num%1000;
  numHundredsDigit = remainder/100;
  remainder =  remainder%100;
  numTenthsDigit = remainder/10;
  remainder =  remainder%10;
  numUnitsDigit = remainder;
  processingData[3] = numThousandsDigit;
  processingData[2] = numHundredsDigit;
  processingData[1] = numTenthsDigit;
  processingData[0] = numUnitsDigit;
}
 
 
void digitNumInc(){
  if ((digitStop[0] && digitStop[1] && digitStop[2] && digitStop[3]) || !btPushed ) 
    return;
 
  for (int i = scanDigitNum-1; i >= 0 ; i-- ){ 
    if (digitStop[i])       
      continue;
    else{
      startData[i]++;
      if (startData[i] > 9)
        startData[i] = 0;
      thousandsCountNumber--;
      hundredsCountNumber--;
      tenthsCountNumber--;
      unitsCountNumber--;
      if (i == scanDigitNum-1 && !digitStop[i] && thousandsCountNumber<= 0){
        if (startData[i] == finalData[i]){
          digitStop[i] = true; 
        }
      }
      else if(i == scanDigitNum-2 && !digitStop[i] && hundredsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
          }
      }
      else if(i == scanDigitNum-3 && !digitStop[i] && tenthsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
          }
      }
      else if(i == scanDigitNum-4 && !digitStop[i] && unitsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
            allDigitStop = true;    
            btPushed = false;
          }
      }      
     }
     dataOut[i] = startData[i];
    }  
  }
 
 

程式碼說明



const int scanPin[4] = {5, 6, 7, 8};

指定 Arduino Uno 第 5~8 腳為掃描訊號的輸出接腳,依序分別負責四位數七段顯示器的個位,十位,百位,以及千位的掃瞄。採用掃描顯示方式的理由,主要在於能節省大量的 I/O 接腳,簡化硬體接線的複雜度。所以若能用快速掃描的方式,逐次送出對應位數數字的七段顯示碼,分別讓顯示器的四位數依序單獨顯示,因為人類眼睛有視覺暫留現象,人的眼睛感知到的會是四位數數字同時顯示

 

const int dataPin = 10, latchPin = 11, clockPin = 12;

接腳定義:Uno 的 pin 10、pin 11 與 pin 12 分別接至 74HC595 的 SER、RCLK 與 SRCLK 接腳。


const int sevenSegCode[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0xBF};

將數字 0 ~ 9 以及符號「-」對應的共陽型七段顯示碼分別存放於 sevenSegCode[ ] 陣列中。


int horizontalLinePos = 10;

變數 horizontalLinePos 存放的是符號「-」在陣列 sevenSegCode[ ] 中的索引值。


int dataOut[] {0000};

陣列 dataOut[ ] 作為輸出四位數顯示器的數字資料的緩衝區,依序存放個位、十位、百位以及千位的數字資料,初始值皆設為 0。此緩衝區資料,決定了此四位數七段顯示器的顯示數字為何。


int processingData[4]; 

陣列 processingData[ ] 作為暫存任何傳入自定函數 calCountDigit(int ) 的整數的個位、十位、百位與千位數。其中 processingData[0] 存傳入整數的個位數,processingData[1] 存十位數,processingData[2] 存百位數,而 processingData[3] 則是存千位數。


int startData[4];

陣列 startData[ ] 作為儲存顯示器各位數在第一次其數字遞加一開始起跑前的初始數字 (由亂數取得)。其中 startData[0] 儲存個位數,startData[1] 儲存十位數,startData[2] 儲存百位數,而 startData[3] 則是儲存千位數。


int finalData[4];

陣列 finalData[ ] 作為儲存顯示器各位數開始遞加一起跑後至停止的終點數字 (一樣由亂數取得)。其中 finalData[0] 儲存個位數,finalData[1] 儲存十位數,finalData[2] 儲存百位數,而 finalData[3] 則是儲存千位數。

int scanDigitNum = 4;

將掃描顯示的位數個數,儲存於變數 scanDigitNum 中。在此是掃描 4 個位數,分別是千位數、百位數、十位數以及個位數。

const int pushButtonPin = 4;

因按鈕開關連接 Uno 的第 4 隻腳,將 4 儲存於變數 pushButtonPin 中。


bool digitStop[]={0000};

將顯示器四個位數的顯示數字是否停止變動的狀態,儲存於陣列 digitStop[] 中。從索引 0 開始,依序儲存的是個位數、十位數、百位數以及千位數的狀態。預設上狀態皆為假 (或 0) ,表示四個位數的顯示數字為「非停止變動」的狀態。


bool allDigitStop = false;

變數 allDigitStop 儲存的是所有位數數字是否全部處於停止變動的狀態,預設為假。


bool btPushed = false;

變數 btPushed 儲存按鈕狀態為是否已被按下,預設為假。


long randNumberStarter;
long randNumberTarget;

變數 randNumberStarter 儲存顯示器四個位數的起始顯示數字;變數 randNumberTarget 儲存顯示器四個位數的終止的顯示數字。


long thousandsCountNumber;
long hundredsCountNumber;
long tenthsCountNumber;
long unitsCountNumber;

變數 thousandsCountNumer 、hundredsCountNumer、 tenthsCountNumer 以及 unitsCountNumer 各自儲存一亂數值,分別用來決定顯示器千位數、百位數、十位數以及各位數數字在停止變動前還須持續遞加一顯示的時間。


void push_button_detection();

宣告無回傳值的自定函數 push_button_detecton( ),用來偵測按鈕的狀態。


void calCountDigit(int);

自定函數 calCountDigit(int) 用來將傳入的引數〔資料型態為整數〕,找出其千位數值、百位數值、十位數值以及個位數值各為多少。再透過陣列以查表的方式找出各自對應的七段顯示碼,方便後續以掃描的方式作四位數七段顯示器的顯示。


void digitNumInc(int);

自定函數 digitNumInc(int) 用來處理將顯示器的每一位數顯示值遞加一的工作。


unsigned long previous_time,now_time;

以變數 privious_time 以及變數 now_time 儲存需要作按鍵防彈跳時取得所需的不同時間值,以其時間差達某特定值以上作為避開彈跳的依據


bool begin = true;

以變數 begin 作為記錄系統是處於一開始按鈕尚未按下前的初始狀態 (此時 begin 為真) 還是按鈕已按下過的狀態 (此時 begin 為假)。而其預設值設為真。


int numThousndsDigit, numHundredsDigit, numTenthsDigit, numUnitsDigit; 

變數 numThousandsDigitnumHundredsDigitnumTenthsDigit 以及 numUnitsDigit 分別暫存 calCountDigit(int) 函數將傳入的引數〔資料型態為整數〕,找出該引數的千位數值、百位數值、十位數值以及個位數值。


int remainder;

變數 remainder 暫存執行了 calCountDigit(int) 函數後,在找出傳入引數的千位數值、百位數值、十位數值以及個位數值過程中產生的餘數。

void setup() {
  for(int i = 0; i < 4; i++){
    pinMode(scan[i], OUTPUT);
    digitalWrite(scan[i], 1);  
  }
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(pushButtonPin, INPUT);
  digitalWrite(latchPin,0);
  randomSeed(analogRead(A0));
  randNumberStarter = random(0, 9999 + 1);      
  calCountDigit(randNumberStarter);          
  for (int i = 0;i <scanDigitNum; i++){
     dataOut[i] = processingData[i];
     startData[i] = processingData[i];
  }
}

setup( ){ }先於 loop( ){ } 執行,且只執行一次。


for(int i = 0; i < 4; i++){
     pinMode(scanPin[i], OUTPUT);
     digitalWrite(scanPin[i], 1)
}                                                   

會依序將 pin 5~pin8 (scan[0]~scan[3]) 的掃描接腳規劃為輸出,且先關閉掃描信號的輸出。

數位輸出指令:digitalWrite(pin, value)


(1) pin:指定腳編號
(2) value:可為 HIGH 或 LOW
(3) 回傳值:無

pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(latchPin, OUTPUT);
pinMode(pushButtonPin, INPUT);


會將 pin 10 (dataPin)、pin 11 (latchPin) 以及 pin 12 (clockPin) 規劃為輸出;將 pin 4 (pushButtonPin)  規劃為輸入。


接腳輸出入規劃指令:pinMode(pin, mode)

(1) pin:指定腳編號
(2) mode:可指定為   INPUT、OUTPUT   或   INPUT_PULLUP
(3) 回傳值:無


digitalWrite(latchPin,0);

會將 pin 11 (latchPin) 輸出 0,解除 74HC595資料栓鎖。


randomSeed(analogRead(A0));  

函數 randomSeed() 可用來產生亂數種子,以決定從亂數表中初次取出亂數的位置,後面若還要取出亂數則依序往下取出。但每次電源重置後,取出的亂數序列都會是相同的。不過,若其傳入的參數不同時,數表中初次取出亂數的位置也會隨之不同。因此,為了讓取出亂數更具隨機性,一般都會讓參數傳入 analogRead(pin_x) 的值,亦即將讀取到某接腳 pin_x 的介於 0 ~ 5 V 間的類比電壓,經 Uno 的類比轉數位轉換器轉換對應的介於 0 ~ 1023 的數值,讓亂數表中初次取出亂數的位置更為隨機。由於我們的硬體電路 pin A0 為浮接,是一個沒有使用到的接腳,因此 analogRead(A0) 讀到的是雜訊電壓對應的數值。


初始化從亂數表取出的亂數位置函數:randomSeed(seed)

(1) seed:非 0 的數字,接受的資料型態為 unsigned long
(2) 回傳值:無


讀取類比輸入轉換對應數值函數:analogRead(pin)

(1) pin:指定腳編號
(2) 回傳值:0 - 1023 間數字 (10 位元 A/C 轉換器) 或 0 - 4095 間數字 (12 位元 A/C 轉換器)

randNumberStarter = random(0, 9999 + 1);
calCountDigit(randNumberStarter);

執行 random(0, 9999 + 1) 以產生一 0 - 9999 間的整數亂數,並儲存於變數 randNumberStarter 中。接著執行 calCountDigit(randNumberStarter) ,將該亂數的千位數、百位數、十位數以及個位數找出,分別作為按鈕按下後四位數顯示器各個位數持續遞加一起跑前的起始數字。而上述找到的個位數、十位數、百位數以及千位數,會分別儲存於 processingData[0]processingData[1]processingData[2] 以及 processingData[3] 中。


for (int i = 0;i <scanDigitNum; i++){
     dataOut[i] = processingData[i];
     startData[i] = processingData[i];
}

for 迴圈執行次數為掃描的位數 (即 scanDigitNum 次),可將  processingData[i] 的值複製給顯示緩衝區 dispData[i] 以及儲存各個位數開始持續遞加一起跑前的起始數字 startData[i] 陣列中,其中 i 為 1 至  scanDigitNum 間的整數數字

void loop() {  
  while(begin){
    push_button_detection();
    for (int i = 0; i < scanDigitNum; i++){
      digitalWrite(latchPin, 0);  
      shiftOut(dataPin, clockPin,
               MSBFIRST, segCode[horizontalLinePos]);     
      digitalWrite(latchPin, 1);  
      digitalWrite(scanPin[i], 0);  
      delay(1);
      digitalWrite(scanPin[i], 1);  
    }
     ...............
 }
}

loop(){...}為無窮迴圈函數。while(begin){...},執行的條件為 begin 為真,亦即系統處於電源一打開,按鈕尚未被按下前的狀態下會被執行。執行 push_button_detection() 以掌握目前按鈕按下與否的狀態。

for (int i = 0; i < scanDigitNum; i++){
    digitalWrite(latchPin, 0);  
    shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[horizontalLinePos]);     
    digitalWrite(latchPin, 1);  
    digitalWrite(scanPin[i], 0);  
    delay(1);
    digitalWrite(scanPin[i], 1); 
}

for 迴圈執行次數為掃描的位數數 (即 scanDigitNum 次),從顯示器個位數 ( i = 0 ) 開始,執行  digitalWrite(latchPin, 0) 解除資料栓鎖,接著執行 shiftOut(dataPin, clockPin, MSBFIRST, segCode[horizontalLinePos]) 將符號「-」的七段顯示碼以序列方式送出,再執行 digitalWrite(latchPin, 1) 進行資料栓鎖後,執行 digitalWrite(scanPin[i], 0) 送出掃描信號給顯示器個位數,讓顯示器個位數顯示符號「-」。執行 delay(1) 延遲 1 ms,最後執行 digitalWrite(scanPin[i], 1) 關閉個位數的掃描信號,結束第一個迴圈接著同樣的程序對十位數、百位數以及千位數各作一次,以結束 for 迴圈的執行。

八位元資料串列傳輸指令:shiftOut(dataPin, clockPin, bitOrder, value)

(1) dataPin:指定串列傳輸輸出接腳
(2) clockPin:指定時脈輸出接腳
(3) bitOrder:指定八位元資料串列傳輸方式,是最高有效位元先 (即指定 MSBFIRST) 或最低有效位元先 (即指定 LSBFIRST)。
(4) 回傳值:無

void loop() {  
  .........
    if(!begin){
        previous_time = millis();
        now_time = millis();
        while((now_time - previous_time) < 80){    
        for (int i = 0; i < scanDigitNum; i++){
          digitalWrite(latchPin, 0);  
          shiftOut(dataPin, clockPin,
                   MSBFIRST, segCode[dispData[i]]);
          digitalWrite(latchPin, 1);  
          digitalWrite(scanPin[i], 0);  
          delay(1);
          digitalWrite(scanPin[i], 1);       
        }
        now_time = millis();  
   }  
   ......
}


if(!begin){...} 執行的條件為 begin 為假,亦即在按鈕已被按下過的情況下就會執行其內的程式區塊。previous_time = millis(),會將執行 millis() 後取得 Arduino 從一開始執行本程式至此所歷經的時間 (單位 ms),再指派給變數 previous_timenow_time = millis() 再取得一次 Arduino 從一開始執行本程式至此所歷經的時間。

while((now_time - previous_time) < 80) {...},執行的條件為 (now_time - previous_time) < 80,用意在控制顯示器的各位數所顯示數字亮的時間可維持接近 80 ms。

for (int i = 0; i < scanDigitNum; i++){
    digitalWrite(latchPin, 0);  
    shiftOut(dataPin, clockPin,
             MSBFIRST, sevenSegCode[dataOut[i]]);
    digitalWrite(latchPin, 1);  
    digitalWrite(scanPin[i], 0);  
    delay(1);
    digitalWrite(scanPin[i], 1);       
}

for 迴圈執行次數為掃描的位數數 (即 scanDigitNum 次),從顯示器個位數 ( i = 0 ) 開始,執行  digitalWrite(latchPin, 0) 解除資料栓鎖,接著執行 shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[dataOut[i]將要輸出的置放於顯示緩衝區的個位數資料的七段顯示碼以序列方式送出給 74HC595。接著再執行 digitalWrite(latchPin, 1) 進行資料栓鎖後,執行 digitalWrite(scanPin[i], 0) 送出掃描信號給顯示器的個位數,讓顯示器個位數顯示顯示緩衝區的個位數資料。執行 delay(1) 延遲 1 ms,最後執行 digitalWrite(scanPin[i], 1) 關閉個位數的掃描信號,結束第一個迴圈接著同樣的程序對十位數、百位數以及千位數各作一次,以結束 for 迴圈的執行。

now_time = millis()取得 Arduino 從一開始執行本程式至此所歷經的時間 (單位 ms),再指派給變數 now_time


void loop() {  
    .........
    digitNumInc();
    if (allDigitStop){
       for(int i = 0; i < scanDigitNum; i++){
           digitStop[i] = false;
       }
       allDigitStop = false;
    }
    push_button_detection();
    .........
}

執行 digitNumInc(),該自定函數會根據一些已出現的狀態或條件,決定顯示器各位數的數字是否要遞加一。if (allDigitStop){ ... } 的執行條件為 allDigitStop 為真,亦即所有位數停止遞加一的狀態 (抽出的號碼已靜態顯示於顯示器) 已被標註情況下,執行其內的程式區塊。執行 for(int i = 0; i < scanDigitNum; i++){ digitStop[i] = false;} 會將各位數停止遞加一的標註取消,回復到初始狀態。allDigitStop = false,取消所有位數停止遞加一的狀態,回復到初始狀態。再來執行 push_button_detection( ),偵測按鈕最新的狀態。

取得 Arduino 開發版開始執行目前程式至當下歷經時間millis()

回傳值:Arduino 開發版開始執行目前程式至當下歷經時間,單位為毫秒 (ms),資料型態為 unsigned long。


void push_button_detection(){
  while(!digitalRead(pushButtonPin)){   
    randNumberTarget = random(0, 9999 + 1);    
    calCountDigit(randNumberTarget);            
    thousandsCountNumber = random(40, 55);    
    hundredsCountNumber = thousandsCountNumber + random(40, 55);
    tenthsCountNumber = hundredsCountNumber + random(40, 55);
    unitsCountNumber = tenthsCountNumber + random(25, 40);
 
    for(int i = 0; i < scanDigitNum; i++){
      finalData[i] =  processingData[i];
    }
    previous_time = millis();
    while(!digitalRead(pushButtonPin));     
    now_time = millis();
    if ((now_time-previous_time) >= 50 ){
      begin = false;
      btPushed = true;                   
    }
  }
}

自定函數 push_button_detection(){ ... },用來偵測按鈕是否按下、處理若按鈕按下後,取得目標亂數 (顯示器最後靜態顯示數字) 以及決定各個位數的顯示數字在停止遞加一之前的隨機循環次數。while(!digitalRead(pushButtonPin)){ ... },執行的條件為按鈕按下,即執行 digitalRead(pushButtonPin) 的回傳值為 0。執行 randNumberTarget = random(0, 9999 + 1),將取得一介於 0-999 的隨機整數亂數,並將其指派給變數 randNumberTarget 作為顯示器在按鈕按下各位數開始遞加一至最後靜止的目標數字。接著執行 calCountDigit(randNumberTarget) 找出 randNumberTarget 的千、百、十以及個位數數值各為何。 

執行 thousandsCountNumber = random(40, 55),取得一介於 40-55 的隨機整數亂數,並將其指派給變數 thousandsCountNumber,作為控制顯示器千位數數字從持續遞加一至靜止前所需跑的循環次數;hundredsCountNumber = thousandsCountNumber + random(40, 55),先取得一介於 40-55 的隨機整數亂數,再加上 thousandsCountNumber 值,作為控制顯示器百位數數字從持續遞加一至靜止前所需跑的循環次數,且該值大於 thousandsCountNumber,以確保百位數數字停止跳動的時間晚於千位數數字tenthsCountNumber = hundredsCountNumber + random(40, 55),一樣先取得一介於 40-55 的隨機整數亂數,再加上 hundredsCountNumber 值,作為控制顯示器百位數數字從持續遞加一至靜止前所需跑的循環次數,且該值大於 hundredsCountNumber,以確保十位數數字停止跳動的時間晚於千位數以及百位數數字 unitsCountNumber = tenthsCountNumber + random(25, 40)取得一介於 25-40 的隨機整數亂數,,再加上 tenthsCountNumber 值,以確保十位數數字停止跳動的時間晚於千位數、百位數以及十位數數字。

for(int i = 0; i < scanDigitNum; i++){
      finalData[i] =  processingData[i];
}

將先前的找出 randNumberTarget 的個、十、百、以及千位數數值存入儲存抽獎數字 finalData[] 陣列中。 

previous_time = millis();
while(!digitalRead(pushButtonPin));     
now_time = millis();
if ((now_time-previous_time) >= 50 ){
    begin = false;
    btPushed = true;                   
}
 
執行 millis(),取得從一開始執行程式至此所歷經的時間,將該值指派給 previous_time 變數。while(!digitalRead(pushButtonPin)); 的執行條件為 pin 4 (即 pushButtonPin) 偵測到 0。一旦等到 pin 4 偵測到 0,則跳開 while 迴圈,並隨即再執行一次 millis(),取得從一開始執行程式至此所歷經的時間,將該值指派給 now_time 變數。if((now_time-previous_time) >= 50 ){ ... } 執行的條件為 (now_time-previous_time) >= 50,也就是按鈕按下時所產生彈跳已避開。若彈跳已避開,則標註 begin 為假 (非初始狀態) 且 btPushed 為真 (按鈕已偵測到被按下)。


void calCountDigit(int num){
  numThousandsDigit = num/1000;
  remainder = num%1000;
  numHundredsDigit = remainder/100;
  remainder =  remainder%100;
  numTenthsDigit = remainder/10;
  remainder =  remainder%10;
  numUnitsDigit = remainder;
  processingData[3] = numThousandsDigit;
  processingData[2] = numHundredsDigit;
  processingData[1] = numTenthsDigit;
  processingData[0] = numUnitsDigit;
}

自定函數 calCountDigit(int num){ ... } 用來找出傳入引數的千位數、百位數、十位數以及個位數。num/1000,可找出傳入引數 num 的千位數數值,再將其指派給變數 numThousandsDigitnum%1000可找出傳入引數 num 去除千位數數值剩餘的部分,再將其指派給變數 remainder。remainder/100,可找出傳入引數 num 去除千位數數值剩餘的部分的百位數值,再將其指派給變數 numHundredsDigitremainder%100,可找出傳入引數 num 去除千位數以及百位數數值剩餘的部分,再將其指派給變數 remainderremainder/10,可找出傳入引數 num 去除千位數以及百位數數值剩餘的部分的十位數值,再將其指派給變數 numTenthsDigitremainder%10,可找出傳入引數 num 去除千位數以、百位數以及十位數數值剩餘的部分 (即個位數數值),再將其指派給變數 remaindernumUnitsDigit = remainder,將傳入引數 num 的個位數數值指派給變數 numUnitsDigit

processingData[3] = numThousandsDigit;
processingData[2] = numHundredsDigit;
processingData[1] = numTenthsDigit;
processingData[0] = numUnitsDigit;

分別將傳入引數 num 的個位數值、十位數值、百位數值以及千位數值依序存入陣列 processingData[] 中。

 
void digitNumInc(){
  if ((digitStop[0] && digitStop[1] && digitStop[2] && digitStop[3]) || !btPushed ) 
    return;
 
  for (int i = scanDigitNum-1; i >= 0 ; i-- ){ 
    if (digitStop[i])        
      continue;
    else{
      startData[i]++;
      if (startData[i] > 9)
        startData[i] = 0;
      thousandsCountNumber--;
      hundredsCountNumber--;
      tenthsCountNumber--;
      unitsCountNumber--;
      if (i == scanDigitNum-1 && !digitStop[i] && thousandsCountNumber<= 0){
        if (startData[i] == finalData[i]){
          digitStop[i] = true; 
        }
      }
      else if(i == scanDigitNum-2 && !digitStop[i] && hundredsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
          }
      }
      else if(i == scanDigitNum-3 && !digitStop[i] && tenthsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
          }
      }
      else if(i == scanDigitNum-4 && !digitStop[i] && unitsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
            allDigitStop = true;    
            btPushed = false;
          }
      }      
     }
     dataOut[i] = startData[i];
    }  
  }
  
自定函數 digitNumInc(){ ... },用來處理顯示器各個位數持續遞加一與否的工作。if ((digitStop[0] && digitStop[1] && digitStop[2] && digitStop[3]) || !btPushed ) 執行的條件為顯示器個位數、十位數、百位數以及千位數皆已被標註停止遞加一顯示 (即 digitStop[0]digitStop[1]digitStop[2] 以及 digitStop[3]皆為真) 或是系統處於按鈕尚未按下之狀態 (即 btPushed 為假)。若 if 執行條件成立,則執行 return 跳出此自定函數。

for (int i = scanDigitNum-1; i >= 0 ; i-- ){ ... } 迴圈,執行 scanDigitNum 次 (即顯示的位數數),且從千位數開始檢查是否須停止遞加一顯示,接著檢查百位數,再來十位數,最後才是個位數。

if (digitStop[i])              
    continue;
else{
    startData[i]++;
    if (startData[i] > 9)
        startData[i] = 0;
    thousandsCountNumber--;
    hundredsCountNumber--;
    tenthsCountNumber--;
    unitsCountNumber--;
    ..................
}

若第 i 位數已被標註停止遞加一顯示 (digitStop[i] 為真),則執行 continue 跳出本次迴圈剩餘須執行的敘述,直接執行下一個迴圈;否則就執行 else{ ... } 內的程式區塊。startData[i]++,將陣列 startData[] 索引 i (對應顯示位數第 i 位數) 的內容值遞加一。if (startData[i] > 9) 執行的條件為 startData[i] 的值遞加一後超過 9,須執行
startData[i] = 0 令 startData[i] 的值回到 0。接下來執行 thousandsCountNumber--hundredsCountNumber--tenthsCountNumber-- 以及 unitsCountNumber--,則分別將控制顯示器千位數、百位數、十位數以及個位數數字從持續遞加一至靜止前所需跑的循環次數遞減一。

if (i == scanDigitNum-1 && !digitStop[i] && thousandsCountNumber<= 0){
    if (startData[i] == finalData[i]){
          digitStop[i] = true; 
    }
}

if 的執行條件為當顯示器千位數 (即 i == scanDigitNum-1) 顯示數字尚未靜止且持續遞加一至靜止前所需跑的循環次數小於等於 0。若上述的執行條件成立,則執行 if (startData[i] == finalData[i]),判斷目前千位數持續遞加一的值是否已等於最後的目標值,若是則執行 digitStop[i] = true,標註千位數數值無須再遞加一顯示。接著

 else if(i == scanDigitNum-2 && !digitStop[i] && hundredsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
          }
 }
 else if(i == scanDigitNum-3 && !digitStop[i] && tenthsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
          }
 }
 else if(i == scanDigitNum-4 && !digitStop[i] && unitsCountNumber<= 0){
          if (startData[i] == finalData[i]){
            digitStop[i] = true;
            allDigitStop = true;    
            btPushed = false;
          }
 }      
     
以上的程式碼只是重複上面對千位數作的事,只不過對象變成百位數、十位數以及個位數而已 (注意:顯示數字靜止的順序是先千位數再百位數,接著十位數,最後才是個位數)。不過,當個位數的顯示也靜止時,須額外執行 allDigitStop = true,標註所有顯示位數停止遞加一跳動。接著執行 btPushed = false,標註按鈕最新的狀態為尚未按下,接受使用者重新按鈕取得抽獎號碼。

最後執行 dataOut[i] = startData[i],將陣列 startData[] 內的值指派給顯示緩衝區陣列 dataOut[] 中,以方便作顯示輸出。



留言

這個網誌中的熱門文章

三段式電子開關電路

陣列 (C++)

首數、尾數與位數

分壓偏壓 BJT 放大電路的直流分析及其近似解的條件

MOSFET 共汲極放大電路 (源極隨耦器) 小訊號分析

為什麼理想的 OPA 電壓放大器有虛短路與虛斷路現象

具有倒數計時自動回復功能的行人穿越道號誌控制電路