Arduino:可指定範圍亂數號碼產生器

 Arduino 開發板:Uno

程式撰寫語言:C


功能

在數字 0~999 範圍內,設定好欲選取亂數的號碼範圍 (範圍至多可涵蓋 500 個連續數字) 後,最多可連續選取 500 個不同的亂數號碼顯示。

電源打開後,四位數七段顯示器顯示「    0」,如圖一所示。

按鍵 1 (最小號碼設定鍵) 初次按下,如圖二所示顯示器閃爍顯示「L  0」,表示可以開始調整可選取的亂數最小號碼。短按該按鍵後放開,號碼會逐次遞加一並閃爍顯示;長按按鍵不放,號碼快速連續遞加一閃爍顯示。調整亂數最小號碼完成後,按下按鍵 3 (設定鍵),亂數最小號碼就會完成設定,顯示器會靜態顯示「L  X」(X 為能選取亂數最小號碼的最終設定值)。

按鍵 2 (最大號碼設定鍵) 初次按下,如圖三所示顯示器閃爍顯示「H  Y」,表示可以開始調整亂數最大號碼 (Y為能選取的最小亂數號碼設定值再加一)。短按按鍵後放開,號碼逐次遞加一閃爍顯示;長按按鍵不放,號碼快速連續遞加一閃爍顯示。調整亂數最大號碼完成後,按下按鍵 3 (設定鍵),亂數最大號碼就會完成設定,顯示器靜態顯示「H  Y」(Y 為能選取亂數最小號碼的最終設定值)。

按鍵 4 (亂數號碼選擇鍵) 每按下一次,如圖四所示顯示器會顯示指定範圍內不重複的亂數號碼。 若已無不重複的亂數可選時,顯示器如圖五所示會顯示「End 」。

若先調整能取的亂數最大號碼而不是亂數最小號碼,順序不對則如圖六所示顯示器會顯示 「Err 」 表示錯誤 (正確順序為須先設定最小號碼,再設最大號碼)。

最小號碼與最大號碼在調整中會閃爍 (例如「L  1」表示最小號碼設為 1,「H  6」表示最大號碼設為 6 ) ,設定完成後則停止閃爍。

亂數號碼選擇過程中,可隨時調整亂數最小號碼與亂數最大號碼,以重新挑選新範圍的亂數號碼。但是順序只能是先調整亂數最小號碼,再來才是調整亂數最大號碼,否則系統會顯示錯誤。


圖一    電源打開後,四位數七段顯示器顯示「0」


圖二   按鍵 1 (最小號碼設定鍵) 初次按下時,顯示器閃爍顯示「L  0」 


圖三    按鍵 2 (最大號碼設定鍵) 初次按下,顯示器閃爍顯示「H  Y」,(Y為能選取的最小號碼設定值再加一)


圖四  按鍵 4 (亂數號碼選擇鍵) 按下一次,如圖四所示顯示器會顯示指定範圍內不重複的亂數號碼


圖五  已無不重複的亂數可選時,顯示器會顯示「End 」


圖六  若先調整能取的亂數最大號碼而不是亂數最小號碼,順序不對顯示器會顯示「Err」



電路圖


如圖七所示:


圖七  電路圖




電路接線圖

如圖八所示:

圖八  電路接線圖


完整程式碼

const int scanPin[ ] = {5678};   /* 5~8 腳對應掃描接腳 scanPin[0]~scanPin[3], 對應個位, 十位, 百位,千位 */
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,0xC7,0x89,0xFF};            /* 0~9,L,H,以及空白的共陽七段顯示碼 */
const int errorCode[] = {0x86,0x8F, 0x8F};   /*E,r,r 的共陽七段顯示碼 */
const int endCode[] = {0x86,0xAB,0xA1};   /* E,n,d 的共陽七段顯示碼 */
int dataOut[4] = {0000};   /* 四位數顯示器的個位, 十位, 百位,千位顯示緩衝區 */
int numPicked[500];                        /* 儲存已選取亂數的陣列,最多可存 500 個 */

const int lowerBoundSetPin = 4;    /* pin 4 接「調整亂數可選取最小號碼按鈕」*/
const int higherBoundSetPin = 1;   /* pin 1 接「調整亂數可選取最大號碼按鈕」*/
const int setPin = 3;                         /* pin 3 接「設定亂數可選取最小與最大號碼按鈕」*/
const int startToPickPin = 0;           /* pin 0 接「亂數號碼選取按鈕」*/

const int lPosInSegCodeArray = 10  /*「L」的七段顯示碼在陣列的位置 */
const int hPosInSegCodeArray = 11;   /*「H」的七段顯示碼在陣列的位置 */
const int nonePosInSegCodeArray = 12;  /*「空白」的七段顯示碼在陣列的位置 */

int tmpLBoundCount = 0;       /* 累計偵測到「調整亂數可選取最小號碼按鈕」按下次數的暫存變數 */
int tmpHBoundCount = 0;       /* 累計偵測到「調整亂數可選取最大號碼按鈕」按下次數的暫存變數 */
int lowerBound = 0;                 /* 亂數可選取的最小號碼 */
int higherBound = 0;                /* 亂數可選取的最大號碼 */
int iCount = 0;                          /* 已取得的亂數號碼累積個數 */

unsigned long flashPreviousTime, flashNowTime, previous_time,now_time;   /* 紀錄時間的變數 */
bool begin = true;                         /* 電源打開,尚無任何按鈕按下時的狀態 */
bool lowBoundarySet = false;      /* 亂數可選取的最小號碼尚未設定完成 */
bool highBoundarySet = false;      /* 亂數可選取的最大號碼尚未設定完成 */
bool lowerBoundSetting = false;   /* 亂數可選取的最小號碼非調整中 */
bool higherBoundSetting = false /* 亂數可選取的最大號碼非調整中 */
bool error = false;                           /* 調整亂數可選取的最小與最大號碼未發生錯誤 */
bool maxNumOfPickReached = false;  /* 亂數挑選不重複的號碼個數尚未到達最大極限 */
bool numDisplayEnd = false;         /* 亂數挑選不重複的號碼尚未挑盡 */
bool randNumPicking = false;       /* 非亂數挑選號碼中狀態*/

void push_button_detection();       /* 偵測按鈕狀態的自定函數 */
void calCountDigit(int);                 /* 分解亂數挑選出號碼的百位、十位與個位值的自定函數 */
void Display(int);                           /* 顯示亂數挑選出號碼的自定函數 */
int countThousndsDigit, countHundredsDigit, countTenthsDigit, countUnitsDigit;  /* 儲存千位、百位、以及十位顯示值的暫存變數 */
int remainder;                                /* 儲存在分解亂數挑選出號碼的百位、十位與個位值過程中的餘數 */
int scanDigitNum = 1;                    /* 掃描位數 (初始值為一位數)  */
long randNumber;                          /*  隨機挑選的亂數值 */

void setup() {                                 
  for (int i = 0; i < 4; i++){              /*  for 迴圈執行 i = 0, 1, 2, 3 四次 */
    pinMode(scanPin[i], OUTPUT);     /*  規劃第  i 個掃描接腳為輸出接腳 */
    digitalWrite(scanPin[i], 1);              /* 關閉第  i 個掃描接腳 */
  }
  pinMode(dataPin, OUTPUT);       /*  規劃 pin 10 為輸出接腳 */
  pinMode(clockPin, OUTPUT);      /*  規劃 pin 12 為輸出接腳 */
  pinMode(latchPin, OUTPUT);       /*  規劃 pin 11 為輸出接腳 */
  pinMode(lowerBoundSetPin, INPUT);    /規劃 pin 4 為輸入接腳 */
  pinMode(higherBoundSetPin, INPUT);   /規劃 pin 1 為輸入接腳 */
  pinMode(setPin, INPUT);                         /規劃 pin 3 為輸入接腳 */
  pinMode(startToPickPin, INPUT);            /規劃 pin 0 為輸入接腳 */
  digitalWrite(latchPin,0);                            / latchPin (pin 11) 輸出初值 0   */
  randomSeed(analogRead(A0));                / 設定亂數種子  */
}

void loop() {  
  while(begin){                     /* while 迴圈執行條件:若電源打開後,四按鈕皆不曾被按下過 */
    Display(scanDigitNum);   /* 呼叫 Display() 函式,作掃描 scanDigitNum 位數顯示  */
    push_button_detection();   /* 呼叫 push_button_detection()  函式,偵測各按鍵的狀態 */
  }
  
  if (!begin){                            /*  若已有按鈕被按下過 */
    push_button_detection();    /*  呼叫 push_button_detection()  函式,偵測各按鍵的狀態 */
    if (lowerBoundSetting)                   /*  若正在調整能挑選的亂數最小值 */
        calCountDigit(lowerBound);      /*  計算出調整中的亂數最小值的千、百、十以及個位值 */
    else if(higherBoundSetting)              /*  若正在調整「能挑選的亂數最大值」 */
        calCountDigit(higherBound);        /*  計算出調整中「能挑選的的亂數最大值」的千、百、十以及個位值 */
    flashPreviousTime =  millis();           /*  取得 Uno 開始執行本程式至此歷經的時間存入 flashPreviousTime 變數 */ 
    flashNowTime =  millis();                 /*  取得 Uno 開始執行本程式至此歷經的時間,入  flashNowTime 變 */ 
    while((flashNowTime - flashPreviousTime) < 250 ){ /* while 迴圈執行條件:flashNowTime 與 flashPreviousTime 時間差小於 250 ms (顯示器靜態顯示 0.25s) */
        if (lowerBoundSetting)                  /* 如果正在調整「能挑選的亂數最小值」*/
          calCountDigit(lowerBound);       /*  計算出調整中「能挑選的的亂數最小值」的千、百、十以及個位值 */
        else if(higherBoundSetting)          /* 如果正在調整「能挑選的亂數最大值」*/
          calCountDigit(higherBound);      /*  計算出調整中「能挑選的的亂數最大值」的千、百、十以及個位值 */
        Display(scanDigitNum);               /* 呼叫 Display() 函式,作掃描 scanDigitNum 位數顯示  */
        push_button_detection();              /* 偵測按鈕狀態 */
        flashNowTime =  millis();            /*  取得自 Uno 接上電源至此歷經的時間,入  flashNowTime 變 */ 
      } 
      if ((lowerBoundSetting || higherBoundSetting) && !error  && !numDisplayEnd)  /* 如果未發生錯誤且還有未重複的亂數可取的情況下,正在調整能挑選的的亂數最小值」或能挑選的的亂數最大值」*/
        delay(250);   /* 延遲 250 ms,以產生顯示器熄 0.25s 的效果 */ 
  }
}

void push_button_detection(){
  while(!digitalRead(lowerBoundSetPin)){  /*  pin 4 (lowerBoundSetPin) 偵測到 "0" */
    
    if (randNumPicking || error){  /*  若按下 pin 4 (lowerBoundSetPin) 前,亂數挑選到一半或出現錯誤 */
      randNumPicking = false;      /*  系統目前的狀態非正在挑選亂數 */ 
      error = false;                         /*  系統目前的狀態非錯誤狀態 */ 
      lowerBound = 0;                   /*  能挑選的的亂數最小設定值」回到初始值 0 */ 
      tmpLBoundCount = 0;         /*  能挑選的的亂數最小暫時值」回到初始值 0 */ 
      break;      /* 跳出 while 迴圈,以避免下面執行 lowerBound++ 後,直接顯示「L  1」而非希望顯示的「L  0」*/
    }   
    tmpLBoundCount++;              /*  能挑選的的亂數最小暫時值」遞加一 */ 
    if (tmpLBoundCount > 85){   /*  若能挑選的的亂數最小暫時值」大於 85 */ 
      tmpLBoundCount = 0;          /*能挑選的的亂數最小暫時值」回到 0 */ 
      lowerBound++;                     /*能挑選的的亂數最小值」遞加一 */ 
      if (lowerBound >999)           /*   若能挑選的的亂數最小值」超過 999 */
        lowerBound = 0;                 /*能挑選的的亂數最小值」回到 0 */ 
     
    }
    error = false;                            /*  系統目前的狀態非錯誤狀態 */ 
    calCountDigit(lowerBound);   /*  計算出調整中「能挑選的的亂數最小值」的千、百、十以及個位值,並存入顯示緩衝區 */
    Display(scanDigitNum);          /*  呼叫 Display() 函式,作掃描 scanDigitNum 位數顯示  */
    begin = false;                            /*  系統目前的狀態非無任何按鈕按下的狀態 */ 
    lowerBoundSetting = true;       /*  系統目前的狀態為正在調整能挑選的的亂數最小值」*/ 
    higherBoundSetting = false;     /*  系統目前的狀態非正在調整能挑選的的亂數最大值」*/ 
    lowBoundarySet = false;           /*  最終的能挑選的的亂數最小值」尚未設定完成 */ 
    highBoundarySet = false;          /*  最終的能挑選的的亂數最大值」尚未設定完成 */
    for(int i = 0; i <500;i++){         /*   清除 (存入 -1) 用來儲存已選取的亂數值陣列所有元素 */
      numPicked[i] = -1;
    }
    iCount = 0;                                     /*  儲存「已選取的亂數值的陣列」索引值回到 0 */
    higherBound = lowerBound + 1;   /*能挑選的亂數最大值」的預設值為能挑選的亂數最小值」加一 */
    maxNumOfPickReached = false;  /*  尚未達到能挑選的不重複亂數」最大數目 */
    numDisplayEnd = false;                /*  系統尚未達已無不重複亂數可挑選的狀態 */
    tmpHBoundCount = 0;                  /*  為避免重設上下限值時,以先前的值累計,故令「能挑選的的亂數最大值」的暫存值為 0 */
  }

  while(!digitalRead(higherBoundSetPin)){   /* 若 pin 1 (higherBoundSetPin) 偵測到 "0" */
   if(!lowBoundarySet){                     /*  若能挑選的亂數最小值」尚未完成設定 */
      error = true;                                  /*  標註發生了錯誤 */ 
      lowerBound = 0;                          /*  能挑選的亂數最小值」回到預設值 0 */
      break;                                           /*   跳出 while 迴圈 */
    } 
    if(randNumPicking){  /*  處理亂數挑選到一半,按下 pin  1 (higherBoundSetPin) 的情況 */
      randNumPicking = false;           /* 標註目前狀態並非正在作亂數挑選   */
      error = true;                                /*  標註發生了錯誤 */ 
      lowerBound = 0;                        /*  能挑選的亂數最小值」回到預設值 0 */
      break;                                         /*   跳出 while 迴圈 */  
    }  
    tmpHBoundCount++;                  /*  「能挑選的亂數最大值」的暫存值遞加一 */
    if (tmpHBoundCount > 85){        /*  若「能挑選的亂數最大值」的暫存值大於 85 */
      tmpHBoundCount = 0;               /*  「能挑選的亂數最大值」的暫存值回到 0 */
      higherBound++;                          /*  「能挑選的亂數最大值」遞加一 */
      if (higherBound > 999 || (higherBound -lowerBound)>= 500 ){  /*  若能挑選的亂數最大值」大於 999 或 能挑選的亂數最大值」減「 能挑選的亂數最小值」大於等於 500 */
        randNumPicking = false;           /* 標註目前狀態並非正在作亂數挑選   */
        error = true;                                /* 標註發生了錯誤 */ 
        lowerBound = 0;                        /* 能挑選的亂數最小值」回到預設值 0 */
        break;                                         /*  跳出 while 迴圈 */  
      }
   }
   calCountDigit(higherBound);        /*  計算出調整中「能挑選的的亂數最大值」的千、百、十以及個位值,並存入顯示緩衝區 */
   Display(scanDigitNum);               /*  呼叫 Display() 函式,作掃描 scanDigitNum 位數顯示  */
   begin = false;                                /*  標註系統目前的狀態非無任何按鈕按下的狀態 */ 
   lowerBoundSetting = false;          /*  標註系統目前為非正在調整能挑選的亂數最小值」狀態 */
   higherBoundSetting = true;          /* 標註 系統目前為正在調整能挑選的亂數最大值」狀態 */
   highBoundarySet = false;             /* 標註能挑選的亂數最大值」尚未完成設定 */
 }

  while(!digitalRead(setPin)){         /*  若 pin 3 (set pin) 偵測到 "0" */
    if (lowerBoundSetting ){             /*  若系統目前為正在調整能挑選的亂數最小值」狀態 */
      lowBoundarySet = true;            /*  標註能挑選的亂數最小值」已設定 */
      lowerBoundSetting = false;      /*  標註並非正在調整能挑選的亂數最小值」*/
    }
    else if (higherBoundSetting){     /*  若系統目前正在調整能挑選的亂數最大值」*/
      highBoundarySet = true;           /*  標註能挑選的亂數最大值」已設定 */
      higherBoundSetting = false;     /*  標註並非正在調整能挑選的亂數最大值」*/
    } 
    // 處理設定錯誤情況
    if (lowerBound != 0 && higherBound != 0 && (higherBound <= lowerBound)){ /*  若在能挑選的亂數最小值」與能挑選的亂數最大值」皆不等於 0 的情況下,能挑選的亂數最大值」小於等於能挑選的亂數最小值」*/
      error = true;                               /*  標註發生錯誤 */
      begin = true;                              /*  回到初始狀態 */
      lowerBoundSetting = false;      /*  標註並非正在調整能挑選的亂數最小值」*/
      higherBoundSetting =  false;    /*  標註並非正在調整能挑選的亂數最大值」*/
      lowBoundarySet = false;          /*  標註能挑選的亂數最小值」尚未完成設定 */
      highBoundarySet = false;         /*  標註能挑選的亂數最大值」尚未完成設定 */
      lowerBound = 0;                       /*  能挑選的亂數最小值」回到預設值 0 */
      higherBound = 0;                      /*  能挑選的亂數最大值」回到預設值 0 */
      tmpLBoundCount = 0;              /*  能挑選的亂數最小值」的暫存值回到 0 */
      tmpHBoundCount = 0;              /*  能挑選的亂數最大值」的暫存值回到 0 */
    }
  }  

  while(!digitalRead(startToPickPin)){  /*  pin 0 (startToPickPin) 偵測到 "0" */
    if (maxNumOfPickReached){           /*  如果亂數挑選的號碼已挑盡 */
        numDisplayEnd = true;                 /*  標註無法再作亂數號碼挑選 */           
        break;                                             /*  跳出 while 迴圈 */ 
     }
    if (!lowBoundarySet || !highBoundarySet){   /*  能挑選的亂數最小值」尚未完成設定或能挑選的亂數最大值」尚未完成設定 */
      error = true;                                      /*  標註發生錯誤 */
      lowerBound = 0;                              /*  能挑選的亂數最小值」回到預設值 0 */
      higherBound = 0;                             /*  能挑選的亂數最大值」回到預設值 0 */
      break;                                                /*  跳出 while 迴圈 */
    }
    randNumPicking = true;                     /*  標註正在作亂數號碼挑選 */
    previous_time = millis();                    /*  取得 Uno 接上電源後至此歷經的時間存入 previous_time 變數 */   
    while(!digitalRead(startToPickPin));  /*  等待 pin 0 (startToPickPin) 再次偵測到 "0"  */   
    now_time = millis();                            /*  取得 Uno 接上電源後至此歷經的時間存入 now_time 變數 */   
    if ((now_time-previous_time) >= 50 ){ /* 避開彈跳 */
      if(lowBoundarySet && highBoundarySet){ /* 若亂數上下限皆已設定,則可開始取亂數 */
        bool randNumFound = false;             /* 設一開始尚未找到不重複的亂數 */
       
        while(!randNumFound  && !maxNumOfPickReached){  /* 若不重複的亂數尚未找到且亂數尚未取盡 */
          randNumber = random(lowerBound, higherBound + 1);  /* 試取上下限範圍內的亂數 */
          randNumFound = true;                   /* 假設剛取得的亂數先前未取過 */
          for(int i=0;i < iCount;i++){            /* 比對亂數先前是否已出現過 */
            if (numPicked[i]== randNumber){   /* 若亂數出現過 */
              randNumFound = false;              /* 不重複的亂數取得失敗,直接回到 while 起頭,重新取亂數 */
              break;                                           /*  跳出 while 迴圈 */                        
            }               
          }
          if(randNumFound){                                /*  若取得不重複亂數 */
            numPicked[iCount] = randNumber;      /*  將取得不重複亂數存入陣列中 */
            if (iCount >= (higherBound -lowerBound)) {  /* 若已取得最大允許的亂數數 */
              maxNumOfPickReached = true;   /*  標註已取盡不重複亂數 */
              begin = true;                                  /*  回到初始狀態 */ 
              lowerBoundSetting = false;          /*  標註並非正在調整能挑選的亂數最小值」*/
              higherBoundSetting =  false;        /*  標註並非正在調整能挑選的亂數最大值」*/
              lowBoundarySet = false;              /*  標註能挑選的亂數最小值」尚未設定完成 */
              highBoundarySet = false;             /*  標註能挑選的亂數最大值」尚未設定完成 */
              lowerBound = 0;                           /*  能挑選的亂數最小值」回到初始值 0 */        
              higherBound = 0;                          /*  能挑選的亂數最大值」回到初始值 0 */
              tmpLBoundCount = 0;                  /*  能挑選的亂數最小值」的暫存值回到 0 */ 
              tmpHBoundCount = 0;                   /* 「 能挑選的亂數最大值」的暫存值回到 0 */ 
            }
            else                                                   /*  若不重複的亂數尚未取盡 */ 
              iCount++;                                       /*  將存放已挑選出不重複亂數值的陣列的索引值遞加一 */ 
          }  
        }          
        randNumFound = true;                         /*  標註取得不重複的亂數號碼 */
        calCountDigit(randNumber);               /*  計算出取得不重複的亂數號碼的千、百、十以及個位值,並存入顯示緩衝區 */
        Display(scanDigitNum);                       /*  呼叫 Display() 函式,作掃描 scanDigitNum 位數顯示  */
        tmpLBoundCount = 0;                          /*  「能挑選的亂數最小值」的暫存值回到 0,避免重設上下限值時,以先前的值累計 */       
      } 
    }  
  }    
}

void Display(int DigitNum){ 
  if(!error && !numDisplayEnd){               /* 無錯誤且亂數未全部挑盡 */
      for (int i = 0; i < DigitNum; i++){        /* 處理 DigitNum 個位數的顯示 */
      digitalWrite(latchPin, 0);                      /* 解除資料栓鎖 */
      shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[dataOut[i]]);   /* 輸出顯示資料 */
      digitalWrite(latchPin, 1);                     /* 栓鎖資料 */
      digitalWrite(scanPin[i], 0);                       /* 輸出掃描信號至 scanPin[i] 對應接腳 */
      delay(1);                                              /* 延遲 1 ms */
      digitalWrite(scanPin[i], 1);                       /* 關閉 scanPin[i] 對應接腳的掃描信號 */
    }
    /*  若正在調整或設定能挑選的亂數最小值」或 能挑選的亂數最大值」,則千位數位置須顯示 L 或 H */
    if((lowerBoundSetting || higherBoundSetting || lowBoundarySet || highBoundarySet)){ 
      digitalWrite(latchPin, 0);                   /* 解除資料栓鎖 */
      if((lowerBoundSetting || lowBoundarySet) && !higherBoundSetting && !highBoundarySet && !randNumPicking)                                   /* 若正在調整或設定能挑選的亂數最小值」且不是正在調整或設定能挑選的亂數最大值」也不是在作亂數號碼挑選 */
        shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[lPosInSegCodeArray]);   /* 輸出 L 的七段顯示碼 */
       
      else if (!lowerBoundSetting && lowBoundarySet && (higherBoundSetting || highBoundarySet) && !randNumPicking)               /* 若正在調整或設定能挑選的亂數最大值」且不是正在調整且「能挑選的亂數最小值」已完成設定,也不是正在作亂數號碼挑選 */
        shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[hPosInSegCodeArray]);  /* 輸出 H 的七段顯示碼 */
      else          /* 若無上面的兩種情況 */
        shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[nonePosInSegCodeArray]);    /* 輸出空白的七段顯示碼 */
      digitalWrite(latchPin, 1);                       /* 栓鎖資料 */
      digitalWrite(scanPin[3], 0);                         /* 輸出掃描信號至 scan[3] (千位數) 對應接腳 */
      delay(1);                                                 /* 延遲 1 ms */
      digitalWrite(scanPin[3], 1);                         /* 關閉 scan[3] (千位數) 對應接腳的掃描信號 */
    }
  } 
  else if(error){                                              /* 若發生錯誤,輸出 Err 的七段顯示碼 */
    for(int i = 0;i < 3; i++){
      digitalWrite(latchPin, 0);                        /* 解除資料栓鎖 */
      shiftOut(dataPin, clockPin, MSBFIRST, errorCode[i]);     /* 依 i 的值依序輸出 E,r,r 的七段顯示碼 */
      digitalWrite(latchPin, 1);                       /* 栓鎖資料 */
      digitalWrite(scanPin[3-i], 0);                      /* 輸出掃描信號 */
      delay(1);                                                 /* 延遲 1 ms */
      digitalWrite(scanPin[3-i], 1);                      /* 關閉掃描信號 */
    }
  } 
  else if (numDisplayEnd){                          /* 若可挑選的亂數號碼已挑盡 */
     for(int i = 0;i < 3; i++){
      digitalWrite(latchPin, 0);                        /* 解除資料栓鎖 */
      shiftOut(dataPin, clockPin, MSBFIRST, endCode[i]);     /* 依 i 的值依序輸出 E,n,d 的七段顯示碼 */
      digitalWrite(latchPin, 1);                       /* 栓鎖資料 */
      digitalWrite(scanPin[3-i], 0);                      /* 輸出掃描信號 */
      delay(1);                                                 /* 延遲 1 ms */
      digitalWrite(scanPin[3-i], 1);                      /* 關閉掃描信號 */
    }
  }
}

void calCountDigit(int count){
  countThousndsDigit = count/1000;           /* 找出千位數值 */
  remainder = count%1000;
  countHundredsDigit = remainder/100;      /* 找出百位數值 */
  remainder =  remainder%100;
  countTenthsDigit = remainder/10;             /* 找出十位數值 */
  remainder =  remainder%10;
  countUnitsDigit = remainder;                    /* 找出個位數值 */
  dataOut[3] = countThousndsDigit;          /* 將千位數值存入顯示緩衝區 */
  dataOut[2] = countHundredsDigit;          /* 將百位數值存入顯示緩衝區 */
  dataOut[1] = countTenthsDigit;               /* 將十位數值存入顯示緩衝區 */
  dataOut[0] = countUnitsDigit;                 /* 將個位數值存入顯示緩衝區 */
  if (countThousndsDigit != 0)                     /* 若千位數值不為 0,則為四位數數值 */
    scanDigitNum = 4;                                   /* 掃描位數為 4 */
  else if (countHundredsDigit != 0)              /* 若千位數值為 0 且百位數不為 0,則為三位數數值 */
    scanDigitNum = 3;                                   /* 掃描位數為 3 */
  else if (countTenthsDigit != 0)                   /* 若千位數值為 0 、百位數皆為 0,且十位數不為 0 ,則為二位數數值 */
    scanDigitNum = 2;                                   /* 掃描位數為 2 */
  else                                                             /* 若千位數值為 0 、百位數、十位數皆為 0,則為一位數數值 */
    scanDigitNum = 1;                                   /* 掃描位數為 1 */                       
}



程式碼說明

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

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



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, 0xC7, 0x89, 0xFF};

將數字 0 ~ 9 、L、H 以及空白對應的共陽型七段顯示碼存放於 sevenSegCode[ ] 陣列中〔共陽型七段顯示碼以及 74HC595 IC 串列輸入並列輸出七段顯示碼至顯示器顯示說明參見 Arduino:四位數七段顯示器顯示按鈕按下的計數值  一文〕。


const int errorCode[] = {0x86,0x8F,0x8F};

符號「E」、「r」「r」的共陽型七段顯示碼依序存放於 errorCode[ ] 陣列中。


const int endCode[] = {0x86,0xAB,0xA1};

符號「E」、「n」「d」的共陽型七段顯示碼依序存放於 endCode[ ] 陣列中。


int dataOut[] {0000};

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


int numPicked[500];

陣列 numPicked[ ] 作為儲存每一回亂數挑選出不重複的號碼,因限於 Uno 記憶體限制,在此設最多可儲存 500 個不重複的號碼


const int lowerBoundSetPin = 4;
const int higherBoundSetPin = 1;
const int setPin = 3;
const int startToPickPin = 0;

定義按鍵 1~4 連接 Uno 的接腳編號:按鍵 1 按下用來調整可選取最小的亂數號碼;按鍵 2 按下用來調整可選取最大的亂數號碼;按鍵 3 按下用來設定調整好的可選取最小與最大的亂數號碼;按鍵 4 按下用來挑選指定範圍內的亂數號碼。


const int lPosInSegCodeArray = 10;
const int hPosInSegCodeArray = 11;
const int nonePosInSegCodeArray = 12;

定義符號「L」、「H」以及空白在 segCode[ ] 陣列中的索引值分別為 10、11 以及 12。


void push_button_detection();

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


int tmpLBoundCount = 0;
int tmpHBoundCount = 0;
int lowerBound = 0;
int higherBound = 0;

宣告變數 tmpLBoundCount 用來暫存調整中的「可選取最小的亂數號碼」;宣告變數 tmpHBoundCount 用來暫存調整中的「可選取最大的亂數號碼」;宣告變數 lowerBound 用來暫存「可選取最小的亂數號碼」的最終設定值;宣告變數 higherBound 用來暫存「可選取最大的亂數號碼」的最終設定值。上述變數的初始值皆為 0。


int iCount = 0;

宣告變數 iCount 用來暫存存放已挑選出不重複的亂數號碼 numPicked[ ] 陣列的索引值。


unsigned long flashPreviousTime, flashNowTime, previous_time,now_time;

以變數 flashPreviousTime 以及變數 flashNowTime 儲存需要作閃爍顯示時取得的不同時間值,以其時間差作為控制閃爍顯示的持續時間依據;以變數 privious_time 以及變數 now_time 儲存需要作按鍵防彈跳時取得的不同時間值,以其時間差達某特定值以上作為避開彈跳的依據。


bool begin = true;
bool lowBoundarySet = false;
bool highBoundarySet = false;
bool lowerBoundSetting = false;
bool higherBoundSetting = false;
bool error = false;
bool maxNumOfPickReached = false;
bool numDisplayEnd = false;
bool randNumPicking = false;

宣告變數 begin 且預設為真,表示系統處於尚未有任何按鈕被按下過的初始狀態;宣告變數 lowBoundarySet 且預設為假,表示系統非處於「最小可挑選亂數號碼」已設定完成的狀態;宣告變數 highBoundarySet 且預設為假,表示系統非處於「最大可挑選亂數號碼」已設定完成的狀態;宣告變數 lowerBoundarySetting 且預設為假,表示系統非處於調整「最小可挑選亂數號碼」中狀態;宣告變數 higherBoundarySetting 且預設為假,表示系統非處於調整「最大可挑選亂數號碼」中狀態;宣告變數 error 且預設為假,表示系統非處於設定錯誤狀態;宣告變數 maxNumOfPickReached 且預設為假,表示系統非處於已到達「不重複亂數號碼」已挑盡狀態;宣告變數 numDisplayEnd 且預設為假,表示系統非處於亂數挑選結束狀態;宣告變數 randNumPicking 且預設為假,表示系統非處於正在作亂數挑選狀態。


void calCountDigit(int);

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


void Display(int);  

自定函數 Display(int) 傳入的引數〔資料型態為整數〕,用來決定四位數七段顯示器掃描的位元數。該函數負責處理顯示輸出部分的工作。


int countThousndsDigit, countHundredsDigit, countTenthsDigit, countUnitsDigit;  

宣告變數 countThousndsDigitcountHundredsDigit、countTenthsDigit 以及  countUnitsDigit 分別作為儲存千位、百位、以及十位顯示值。


int remainder;

宣告變數 remainder 作為儲存自定函數 calCountDigit(int) 在找傳入的引數的千位數值、百位數值、十位數值以及個位數值過程中產生的餘數。


int scanDigitNum = 1;

宣告變數 scanDigitNum 用來儲存掃描位數個數,預設值一位數


long randNumber;

宣告變數 randNumer 用來儲存取得的亂數號碼。


void setup() {
  for(int i = 0; i < 4; i++){
    pinMode(scanPin[i], OUTPUT);
    digitalWrite(scanPin[i], 1);  
  }
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(lowerBoundSetPin, INPUT);
  pinMode(higherBoundSetPin, INPUT);
  pinMode(setPin, INPUT);
  pinMode(startToPickPin, INPUT);
  digitalWrite(latchPin,0);
  randomSeed(analogRead(A0));
}

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


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

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

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


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

pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(latchPin, OUTPUT);
pinMode(lowerBoundSetPin, INPUT);
pinMode(higherBoundSetPin, INPUT);
pinMode(setPin, INPUT);
pinMode(startToPickPin, INPUT);

會將 pin 10 (dataPin)、pin 11 (latchPin) 以及 pin 12 (clockPin) 規劃為輸出;將 pin 4 (lowerBoundSetPin) 、pin 1 (higherBoundSetPin)、pin 3 (setPin) 以及 pin 0 (startToPickPin) 規劃為輸入。


接腳輸出入規劃指令: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 轉換器)


void loop() {  
  while(begin){
    Display(scanDigitNum);
    push_button_detection();  
  }
.......
}

loop(){ } 為一無窮迴圈函數。


while(begin){
    Display(scanDigitNum);
    push_button_detection();  
}

其中的 while 迴圈執行的條件為變數 begin 須為真。begin 為真的情況發生在:電源打開後,尚未有任何按鍵按下時、若已取得的不重複亂數數目已達上限或按鍵設定發生錯誤。其餘情況,begin 則會為假。Display(scanDigitNum) 負責將顯示緩衝區 (dataOut[]陣列) 的顯示資料輸出至四位數七段顯示器,且其顯示器顯示位數係由傳入的引數 scanDigitNum 決定。由於 scanDigitNum 的初始值為一,且顯示緩衝區的初始資料為{0, 0, 0, 0},因此四位數七段顯示器只會顯示一位數 0。

push_button_detection() 用來偵測所有按鍵的狀態,並依狀態作適當的反應。


if(!begin){
   push_button_detection();
  if(lowerBoundSetting)
        calCountDigit(lowerBound);
   else if(higherBoundSetting)
        calCountDigit(higherBound);
   flashPreviousTime =  millis();
   flashNowTime =  millis();
   while((flashNowTime - flashPreviousTime) < 250 ){
        if(lowerBoundSetting)
          calCountDigit(lowerBound);
        else if(higherBoundSetting)
          calCountDigit(higherBound);
        Display(scanDigitNum);
        push_button_detection();
        flashNowTime =  millis();
  }
   if((lowerBoundSetting ||higherBoundSetting)
&& !error && !numDisplayEnd)  
        delay(250);  
}


if (!begin){...} 執行的條件為變數 begin 為假, 亦即除了電源打開後,尚未有任何按鍵按下時、若已取得的不重複亂數數目已達上限或按鍵設定發生錯誤的三種情況以外皆會執行。push_button_detection() 用來偵測按鍵最新的狀態。

if(lowerBoundSetting)
    calCountDigit(lowerBound);
else if(higherBoundSetting)
    calCountDigit(higherBound);

如果按鍵一 (調整亂數選取下限鍵) 被按下,變數 lowerBoundSetting 會被設為真,調整中且儲存在變數 lowerBound 的「亂數選取下限值」會傳入 calCountDigit() 函數中,以找出「亂數選取下限值」的千位數、百位數、十位數以及個位數的值,方便後續在四位數顯示器上的作顯示。如此一來,使用者才在作調整時才知道「亂數選取下限值」調整到哪裡。或若按鍵二 (調整亂數選取上限鍵) 被按下,則變數 higherBoundSetting 會被設為真,調整中且儲存在變數 higherBound 的「亂數選取上限值」會傳入 calCountDigit() 函數中,以找出「亂數選取上限值」的千位數、百位數、十位數以及個位數的值,方便後續在四位數顯示器上的作顯示。如此一來,使用者才在作調整時才知道「亂數選取上限值」調整到哪裡。


flashPreviousTime =  millis();
   flashNowTime =  millis();
   while((flashNowTime - flashPreviousTime) < 250 ){ 
        if(lowerBoundSetting)
          calCountDigit(lowerBound);
        else if(higherBoundSetting)
          calCountDigit(higherBound);
        Display(scanDigitNum);
        push_button_detection();
        flashNowTime =  millis();
   } 

millis() 執行後會回傳 Uno 開始執行本程式至此所歷經的時間 (單位 ms),首先 flashPreviousTime = millis() 與 flashNowTime = millis() 會先後將回傳時間存入 flashPreviousTime 變數與 flashNowTime 變數中。

while((flashNowTime - flashPreviousTime) < 250){...} 執行的條件為當上述兩變數的差小於 250 (表示時間差小於 250 ms),while 迴圈內的程式區塊會被重複執行。而while 迴圈內必須要有 flashNowTime = millis() 敘述,隨時取得目前最新時間。而while 迴圈內的程式區塊為

if(lowerBoundSetting)
        calCountDigit(lowerBound);
else if(higherBoundSetting)
        calCountDigit(higherBound);
Display(scanDigitNum);
push_button_detection();

確保正在調整「亂數選取下限值」與「亂數選取上限值」時,調整值會靜態顯示在顯示器上持續 250 ms。此外,持續以 push_button_detection() 監控按鍵的最新狀況。

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

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


void push_button_detection(){
  while(!digitalRead(lowerBoundSetPin)){   
    if(randNumPicking || error){  
      randNumPicking = false;
      error = false;
      lowerBound = 0;
      tmpLBoundCount = 0;
      break;        
    }    
    tmpLBoundCount++;
    if (tmpLBoundCount > 85){
      tmpLBoundCount = 0;
      lowerBound++;
      if (lowerBound >999)
        lowerBound = 0;
     
    }
    error = false;
    calCountDigit(lowerBound);
    Display(scanDigitNum);
    begin = false;  
    lowerBoundSetting = true;
    higherBoundSetting = false;
    lowBoundarySet = false; 
    highBoundarySet = false;  
    for(int i = 0; i <500;i++){
      numPicked[i] = -1;
    }
    iCount = 0;
    higherBound = lowerBound + 1;
    maxNumOfPickReached = false;
    numDisplayEnd = false;
    tmpHBoundCount = 0;  
  }
  .......
}

while 迴圈的執行條件為執行 digitalRead(lowerBoundSetPin) 後的回傳值為 0,亦即偵測到按鈕一被按下。

if(randNumPicking || error){
     randNumPicking = false;
     error = false;
     lowerBound = 0;
     tmpLBoundCount = 0;
     break;        
}    

若使用者原本正在選取亂數號碼 (randNumPicking 為真) 或發生錯誤 (error 為真) 再按按鈕一,則 if  執行的條件成立:設 randNumPicking 為假、error 為假、lowerBound (亂數選取下限值) 回到初始值 0且 tmpLBoundCount (亂數選取下限暫存值) 回到初始值 0,再以 break 跳離 while 迴圈。

while(!digitalRead(lowerBoundSetPin)){   
    .......
    tmpLBoundCount++;
    if (tmpLBoundCount > 85){
      tmpLBoundCount = 0;
      lowerBound++;
      if (lowerBound >999)
        lowerBound = 0;     
   }
    .......
}

執行 tmpLBoundCount++ ,令亂數選取下限暫存值遞加一。 當亂數選取下限暫存值達 85 以上,if (tmpLBoundCount > 85){....} 的執行條件成立,令亂數選取下限暫存值回到 0,並將亂數選取下限值 lowerBound 遞加一。如果亂數選取下限值大於 999 ,令其值回到 0。上述的作法可在偵測按鈕狀態未除彈跳的情況下,若使用者逐次按,lowerBound (亂數選取下限值) 可隨次遞加ㄧ;且若使用者按鈕按著不放,lowerBound 值自動持續遞加ㄧ,直至按鈕鬆開。tmpLBoundCount > 85 的判斷條件,必要時須經實際測試,使得按鈕按下ㄧ次 lowerBound 值確實僅遞加ㄧ;而按鈕按著不放,lowerBound 值自動持續遞加ㄧ的速度又不至於過慢。

error = false;
calCountDigit(lowerBound);
Display(scanDigitNum);
begin = false;  
lowerBoundSetting = true;
higherBoundSetting = false;
lowBoundarySet = false; 
highBoundarySet = false; 

error = false 標註目前並非處於錯誤狀態。calCountDigit(lowerBound) 將目前 lowerBound 值的千位數、百位數、十位數以及個位數找出並判斷其位數值。Display(scanDigitNum) 將 lowerBound 值輸出至四位數七段顯示器。begin = false 標註目前並非處於無任何按鈕按下的初始狀態。lowerBoundSetting = true 標註目前為正在調整 lowerBound (亂數選取下限值) 中的狀態。 higherBoundSetting = false 標註目前並非處於正在調整 higherBound (亂數選取上限值) 中的狀態。lowBoundarySet = false 標註目前並非處於亂數選取下限值設定完成的狀態。 highBoundarySet = false 標註目前並非處於亂數選取上限值設定完成的狀態。

for(int i = 0; i <500;i++){ 
    numPicked[i] = -1;
 }

將儲存已選取不重複亂數號碼的陣列清除 ( 存入 -1 表示清除) 。

iCount = 0 將目前儲存已選取不重複亂數號碼的陣列的索引指向第 0 個元素。higherBound = lowerBound + 1 令 higherBound (亂數選取上限值) 的初始值比 lowerBound (亂數選取上限值) 多ㄧ,已減少亂數選取上限值的調整時間。maxNumOfPickReached = false 標註目前並非處於不重複亂數號碼已挑選至最大數目。numDisplayEnd = false 標註目前並非處於不重複亂數號碼挑選結束的狀態。tmpHBoundCount = 0 令亂數選取上限值的暫存值為 0。


while(!digitalRead(higherBoundSetPin)){  
   if(!lowBoundarySet){
      error = true;
      lowerBound = 0; 
      break;
    } 
    if(randNumPicking){ 
      randNumPicking = false;
      randNumPicking = false;
      error = true;
      lowerBound = 0;    
      break;
    }  
    tmpHBoundCount++;
    if (tmpHBoundCount > 85){
      tmpHBoundCount = 0;
      higherBound++;
      if (higherBound > 999 || (higherBound -lowerBound)>= 500 ){
        randNumPicking = false;
        error = true;
        lowerBound = 0;     
        break;
      }
   }
   calCountDigit(higherBound);
   Display(scanDigitNum);
   begin = false;  
   lowerBoundSetting = false;
   higherBoundSetting = true;
   highBoundarySet = false;
 }   
    
while 迴圈的執行條件為執行 digitalRead(higherBoundSetPin) 後的回傳值為 0,亦即偵測到按鈕二被按下。  
      
if(!lowBoundarySet){
      error = true;
      lowerBound = 0; 
      break;

如果亂數選取下限值未完成設定,則 lowBoundarySet 為假,if (){ ... } 的執行條件成立,error 會被設為真,標註目前狀態為錯誤。lowerBound 值回到初始值 0,並以 break 跳出 while 迴圈。

tmpHBoundCount++;
if (tmpHBoundCount > 85){
      tmpHBoundCount = 0;
      higherBound++;
      if (higherBound > 999 || (higherBound -lowerBound)>= 500 ){
        randNumPicking = false;
        error = true;
        lowerBound = 0;     
        break;
      }
}

若亂數選取下限值已完成設定,則亂數選取下限值的暫存值 tmpHBoundCount 遞加一。當亂數選取上限暫存值達 85 以上,if (tmpHBoundCount > 85){ ... } 的執行條件成立,令亂數選取上限暫存值回到 0,並將亂數選取下限值 higherBound 遞加一。如果亂數選取下限值大於 999 或 亂數選取上限值 higherBound 減去亂數選取下限值 lowerBound 大於等於 500 (亦即錯誤設定) ,randNumPicking = false 標註並非在亂數選取中狀態。error = true 標註處於錯誤狀態中。lowerBound = 0 令亂數選取下限值回到 0,再以 break 跳離 while 迴圈。

calCountDigit(higherBound);
Display(scanDigitNum);
begin = false;  
lowerBoundSetting = false;
higherBoundSetting = true;
highBoundarySet = false;

calCountDigit(higherBound將目前 higherBound 值的千位數、百位數、十位數以及個位數找出並判斷其位數值。Display(scanDigitNum) 將 higherBound 值輸出至四位數七段顯示器。begin = false 標註目前並非處於無任何按鈕按下的初始狀態。lowerBoundSetting = false 標註目前為非正在調整 higherBound (亂數選取下限值) 中的狀態。higherBoundSetting true 標註目前處於正在調整 higherBound (亂數選取上限值) 中的狀態。highBoundarySet = false 標註目前並非處於亂數選取上限值設定完成的狀態。


while(!digitalRead(setPin)){   
      if(lowerBoundSetting ){ 
      lowBoundarySet = true;
      lowerBoundSetting = false; 
    }
    else if(higherBoundSetting){ 
      highBoundarySet = true;
      higherBoundSetting = false;   
    } 
    if (lowerBound !=0 && higherBound !=0 && (higherBound <= lowerBound)){
      error = true;
      begin = true;
      lowerBoundSetting = false;
      higherBoundSetting =  false;
      lowBoundarySet = false;
      highBoundarySet = false;
      lowerBound = 0;
      higherBound = 0;
      tmpLBoundCount = 0;
      tmpHBoundCount = 0;
    }
}  

while 迴圈的執行條件為執行 digitalRead(SetPin) 後的回傳值為 0,亦即偵測到按鈕三被按下。 

if(lowerBoundSetting ){ 
      lowBoundarySet = true;
      lowerBoundSetting = false; 
}
else if(higherBoundSetting){ 
      highBoundarySet = true;
      higherBoundSetting = false;
}

若按下按鈕三前,使用者正在調整亂數選取下限值,變數 lowerBoundSetting 會是真,則  lowBoundarySet = true 標註目前狀態為亂數選取下限值已設定。lowerBoundSetting = false 標註目前並非正在調整亂數選取下限值;否則若是按下按鈕三前,使用者正在調整亂數選取上限值, highBoundarySet = true 標註目前狀態為亂數選取上限值已設定。higherBoundSetting = false 標註目前並非正在調整亂數選取上限值。

if (lowerBound !=0 && higherBound !=0 && (higherBound <= lowerBound)){
      error = true;
      begin = true;
      lowerBoundSetting = false;
      higherBoundSetting =  false;
      lowBoundarySet = false;
      highBoundarySet = false;
      lowerBound = 0;
      higherBound = 0;
      tmpLBoundCount = 0;
      tmpHBoundCount = 0;
}

若亂數選取下限值 lowerBound 不為 0 且亂數選取上限值 higherBound 不為 0 的情況下,亂數選取上限值 higherBound 小於等於亂數選取下限值 lowerBound,則 error = true 標註發生錯誤。接下來令變數 beginlowerBoundSettinghigherBoundSettinglowBoundarySet highBoundarySetlowerBound higherBound tmpLBoundCount 以及 tmpHBoundCount  回到初始值。


while(!digitalRead(startToPickPin)){  
    if (maxNumOfPickReached){
        numDisplayEnd = true;
        break;
     }
    if(!lowBoundarySet || !highBoundarySet){  
      error = true;
      lowerBound = 0;
      higherBound = 0;
      break;
    }
    randNumPicking = true;
    previous_time = millis();
    while(!digitalRead(startToPickPin));
    now_time = millis(); 
    if ((now_time-previous_time)>=50 ){
      if(lowBoundarySet && highBoundarySet){ 
        bool randNumFound = false;
       
        while(!randNumFound  && !maxNumOfPickReached){ 
          randNumber = random(lowerBound, higherBound + 1);
          randNumFound = true;       
          for(int i=0;i < iCount;i++){ 
            if(numPicked[i]== randNumber){ 
              randNumFound = false;       
              break;
            }               
          }
          if(randNumFound){
            numPicked[iCount] = randNumber;
            if (iCount >= (higherBound -lowerBound)) {  
              maxNumOfPickReached = true;  
              begin = true; 
              lowerBoundSetting = false;
              higherBoundSetting =  false;
              lowBoundarySet = false;
              highBoundarySet = false;
              lowerBound = 0;
              higherBound = 0;
              tmpLBoundCount = 0;
              tmpHBoundCount = 0;          
            }
            else
              iCount++;
          }  
        }   
        randNumFound = true;
        calCountDigit(randNumber); 
        Display(scanDigitNum); 
        tmpLBoundCount = 0;      
      }   
    }   
  }    

while 迴圈的執行條件為執行 digitalRead(startToPickPin) 後的回傳值為 0,亦即偵測到按鈕四被按下。

if (maxNumOfPickReached){
        numDisplayEnd = true;
        break;
}

如果已取不重複的亂數號碼數目已達上限,變數 maxNumOfPickReached 為真 if (maxNumOfPickReached{ ... } 中的程式區塊會被執行:即 numDisplayEnd = true 標註已無法再取不重複的亂數號碼,且執行 break 離開 while 迴圈。 

if(!lowBoundarySet || !highBoundarySet){  
      error = true;
      lowerBound = 0;
      higherBound = 0;
      break;
}

若使用者亂數選取下限值或亂數選取上限值尚未設定完成 (即 lowBoundarySet = 0  highBoundarySet = 0 ) 就按下按鈕四要作亂數選取,則以 error = true 標註狀態錯誤,且令亂數選取下限值 lowerBound 以及亂數選取上限值 higherBound 皆為 0 接著執行 break 離開 while 迴圈。

randNumPicking = true;
previous_time = millis();
while(!digitalRead(startToPickPin));
now_time = millis(); 
if ((now_time-previous_time)>=50 ){
.......
}

以 randNumPicking = true 標註目前的狀態為正在選亂數號碼。previous_time = millis() 取得 Uno 開始執行本程式至此的時間 (單位 ms)。while(!digitalRead(startToPickPin)); 等待下一次又偵測到 pin 0 (按鍵 4) 為 0 才跳出迴圈。now_time = millis() 取得 Uno 開始執行本程式至此的時間 (單位 ms)。若 (now_time-previous_time)>=50,表示已避開彈跳,就會執行 if ((now_time-previous_time)>=50){...}內的程式區塊。

if(lowBoundarySet && highBoundarySet){ 
        bool randNumFound = false;           
        .......
 }

若彈跳已避開,且若亂數選取下限值以及亂數選取上限值皆已設定 (即 lowBoundarySet 為真且highBoundarySet 也為真),則先令區域變數 randNumFound = false,標註此次按鍵四按下後亂數號碼尚未找到。

while(!randNumFound  && !maxNumOfPickReached){ 
         randNumber = random(lowerBound, higherBound + 1);
         randNumFound = true;       
         .......
}

while 迴圈執行的條件為 randNumFound 為假且 maxNumOfPickReached 也為假亦即此次按鍵四按下後亂數號碼尚未找到且先前已選取的不重覆亂數號碼數目未達上限值。randNumber = random(lowerBound, higherBound + 1),可隨機挑選出介於 lowerBound 值與 higherBound 值之間 (包含 lowerBound 與 higherBound的號碼randNumFound = true,標註此次按鍵四按下後亂數號碼已找到。


取得虛擬亂數數字指令:random(minmax)

(1) min: 亂數取得下限 (包含)
(2) max:亂數取得上限 (不包含)
(3) 回傳值:回傳介於 min 與 max-1 之間的長整數


for(int i=0;i < iCount;i++){ 
       if(numPicked[i]== randNumber){ 
              randNumFound = false;       
              break;
       }    
}           

上述產生亂數號碼還須經過驗證是否與先前取出的亂數重複,若是的話就必須重新再取一次。在此可透過 for 迴圈,執行 iCount 次 (iCount 值為已取出不重複的亂數號碼),逐一比對存放在儲存已挑選出不重複亂數的陣列 numPicked[ ] 的元素一旦比對出一樣,則最新取得的亂數號碼已重複。因此,以 randNumFound = false 標註此次不重複的亂數號碼並未取得,再以 break 跳出 for 迴圈。

if(randNumFound){
        numPicked[iCount] = randNumber;
        if (iCount >= (higherBound -lowerBound)) {  
              maxNumOfPickReached = true;  
              begin = true; 
              lowerBoundSetting = false;
              higherBoundSetting =  false;
              lowBoundarySet = false;
              highBoundarySet = false;
              lowerBound = 0;
              higherBound = 0;
              tmpLBoundCount = 0;
              tmpHBoundCount = 0;          
        }
        else
              iCount++;
 }  
       
若不重複的亂數號碼已取得,randNumFound 會為真,if (randNumFound) { ... } 內的程式區塊會被執行。numPicked[iCount] = randNumber 會將最新取得的不重複的亂數號碼存入 numPicked[] 陣列索引 iCount 所指的位置。如果 iCount >= (higherBound -lowerBound) 表示允許的範圍內可取得不重複的亂數號碼已取盡,if (iCount >= (higherBound -lowerBound)) { ... } 內的程式區塊會被執行。 maxNumOfPickReached = true ,標註不重複的亂數號碼已取盡。begin = true ,標註回到初始狀態。接著將 lowerBoundSettinghigherBoundSetting          lowBoundarySethighBoundarySetlowerBoundhigherBoundtmpLBoundCount            tmpHBoundCount 等變數回復為初始值。若允許的範圍內可取得不重複的亂數號碼尚未取盡,則執行  iCount++,將 numPicked[] 陣列索引值遞加一,指到下一個可儲存不重複的亂數號碼的最近位置。

randNumFound = true;
calCountDigit(randNumber); 
Display(scanDigitNum); 
tmpLBoundCount = 0; 

randNumFound = true,標註已取得不重複的亂數號碼。calCountDigit(randNumber),將此亂數號碼的千位、百位、十位、個位以及位數找出。Display(scanDigitNum),將此亂數號碼輸出至七段顯示器。因為亂數號碼挑選至一半,使用者可能想更動挑選範圍而按下按鈕一作亂數號碼下限的調整,因此令 tmpLBoundCount 為 0,避免重設下限值時,以先前的值累計



void Display(int DigitNum){ 
        if(!error && !numDisplayEnd){   
              for (int i = 0; i < DigitNum; i++){   
                  digitalWrite(latchPin, 0);  
                  shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[dataOut[i]]);
                  digitalWrite(latchPin, 1);  
                  digitalWrite(scanPin[i], 0);  
                  delay(1);
                  digitalWrite(scanPin[i], 1);    
            }
   
           if((lowerBoundSetting || higherBoundSetting || lowBoundarySet ||
                     highBoundarySet) ){
                 digitalWrite(latchPin, 0);  
          if((lowerBoundSetting || lowBoundarySet) && !higherBoundSetting &&
                            !highBoundarySet && !randNumPicking) 
                shiftOut(dataPin, clockPin, MSBFIRST, segCode[lPosInSegCodeArray]);
         
          else if (!lowerBoundSetting && lowBoundarySet && (higherBoundSetting ||
                             highBoundarySet) && !randNumPicking)
               shiftOut(dataPin, clockPin, MSBFIRST,
                                   sevenSegCode[hPosInSegCodeArray]);
          else
              shiftOut(dataPin, clockPin, MSBFIRST, sevensegCode[nonePosInSegCodeArray]);
          digitalWrite(latchPin, 1);  
          digitalWrite(scanPin[3], 0);  
          delay(1);
          digitalWrite(scanPin[3], 1);  
    }
  } 
  else if(error){  
    for(int i = 0;i < 3; i++){
      digitalWrite(latchPin, 0);  
      shiftOut(dataPin, clockPin, MSBFIRST, errorCode[i]);
      digitalWrite(latchPin, 1);  
      digitalWrite(scanPin[3-i], 0);  
      delay(1);
      digitalWrite(scanPin[3-i], 1);   
    }
  } 
  else if (numDisplayEnd){     
     for(int i = 0;i < 3; i++){
          digitalWrite(latchPin, 0); 
          shiftOut(dataPin, clockPin, MSBFIRST, endCode[i]);
          digitalWrite(latchPin, 1);  
          digitalWrite(scanPin[3-i], 0);  
          delay(1);
          digitalWrite(scanPin[3-i], 1);   
    }
  }
}

void Display(int DigitNum){ ... } 是負責處理顯示工作的自定函數,呼叫時須一併傳入數字顯示的位數。

如果沒有錯誤 (error 為假) 且挑選亂數號碼未結束 (numDisplayEnd 為假),則 if (!error && !numDisplayEnd) { ... } 內的程式區塊會被執行。

for (int i = 0; i < DigitNum; i++){   
      digitalWrite(latchPin, 0);  
      shiftOut(dataPin, clockPin, MSBFIRST, segCode[DispData[i]]);
      digitalWrite(latchPin, 1);  
      digitalWrite(scan[i], 0);  
      delay(1);
      digitalWrite(scan[i], 1);    
}

for 迴圈執行的迴圈數由顯示位數 DigitNum 決定。執行 digitalWrite(latchPin, 0),可解除 74HC595 的資料栓鎖。shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[dataOut[i]]) 將顯示緩衝區索引值i所指位置的資料以序列方式輸出給 74HC595 。digitalWrite(latchPin, 1),將序列方式已傳輸給 74HC595 的 8 位元顯示資料栓鎖,以確保 74HC595 在將序列輸入資料轉換成並列輸出時不會發生錯誤。digitalWrite(scanPin[i], 0),可送出掃描信號給 scan[i] 儲存的接腳號碼。delay(1),延遲 1 ms。digitalWrite(scanPin[i], 1),則是關閉 scanPin[i] 儲存的接腳號碼的掃描信號。 

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

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


if((lowerBoundSetting || higherBoundSetting || lowBoundarySet ||
                     highBoundarySet) ){
                 digitalWrite(latchPin, 0);  
          if((lowerBoundSetting || lowBoundarySet) && !higherBoundSetting &&
                            !highBoundarySet && !randNumPicking) 
                shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[lPosInSegCodeArray]);
         
          else if (!lowerBoundSetting && lowBoundarySet && (higherBoundSetting ||
                             highBoundarySet) && !randNumPicking)
               shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[hPosInSegCodeArray]);
          else
              shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[nonePosInSegCodeArray]);
          digitalWrite(latchPin, 1);  
          digitalWrite(scanPin[3], 0);  
          delay(1);
          digitalWrite(scanPin[3], 1);  
    }
  } 

如果正在調整可挑選亂數的下限值 (lowerBoundSetting 為真),或是正在調整可挑選亂數的上限值  (higherBoundSetting 為真),或是可挑選亂數的下限值設定完成 (lowBoundarySet 為真) 或是可挑選亂數的上限值設定完成 (highBoundarySet 為真),則 if ((lowerBoundSetting || higherBoundSetting || lowBoundarySet || highBoundarySet) ){ ... } 內的程式區塊會被執行

如果正在調整可挑選亂數的下限值或可挑選亂數的下限值設定完成其中一種情況出現,且非在調整可挑選亂數的上限值,也沒有完成可挑選亂數的上限值設定,也沒有正在作亂數挑選,則 if ((lowerBoundSetting || lowBoundarySet) && !higherBoundSetting &&!highBoundarySet && !randNumPicking) { ... } 內程式區塊會被執行。執行 shiftOut(dataPin, clockPin, MSBFIRST, segCode[lPosInSegCodeArray]) ,以序列方式送出字母 L 的七段顯示碼。

如果不是正在調整可挑選亂數的下限值或可挑選亂數的下限值已設定完成,且正在調整可挑選亂數的上限值或可挑選亂數的下限值設定完成其中一種情況出現,且也沒有正在作亂數挑選,則 if (!lowerBoundSetting && lowBoundarySet && (higherBoundSetting || highBoundarySet) && !randNumPicking){ ... } 中的程式區塊會被執行。執行 shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[hPosInSegCodeArray]) ,以序列方式送出字母 H 的七段顯示碼。

若系統非處於上述狀態,程式執行 else 底下的 shiftOut(dataPin, clockPin, MSBFIRST, sevenSegCode[nonePosInSegCodeArray]),以序列方式送出空白的七段顯示碼。

接著執行 digitalWrite(latchPin, 1) ,作資料栓鎖。digitalWrite(scanPin[3], 0) ,送出掃描信號至控制千位數的 pin 8。delay(1), 延遲 1 ms。 digitalWrite(scanPin[3], 1),將控制千位數的 pin 8 的掃描信號關閉。

 else if(error){  
    for(int i = 0;i < 3; i++){
      digitalWrite(latchPin, 0);  
      shiftOut(dataPin, clockPin, MSBFIRST, errorCode[i]);
      digitalWrite(latchPin, 1);  
      digitalWrite(scanPin[3-i], 0);  
      delay(1);
      digitalWrite(scanPin[3-i], 1);   
    }
}

倘若狀況是發生錯誤,則必須讓顯示器的千位數出現「E」、百位數出現「r」以及十位數出現「r」。for 迴圈執行三次。i = 0 時, 先以 digitalWrite(latchPin, 0) 取消資料栓鎖,再以 shiftOut(dataPin, clockPin, MSBFIRST, errorCode[i]) 送出「E」的七段顯示碼。接著 digitalWrite(latchPin, 1) 作資料栓鎖後,digitalWrite(scanPin[3-i], 0) 送出掃描信號給控制顯示器千位數的掃描接腳。delay(1) 延遲 1 ms 後,digitalWrite(scanPin[3-i], 1) 將控制顯示器千位數的掃描接腳的掃描信號關閉。類似的過程 i = 1 與 i = 2 再作 2 次,只不過第二次與第三次送出的是「r」的七段顯示碼,且掃描的分別改成顯示器百位數與十位數。


 else if (numDisplayEnd){     
     for(int i = 0;i < 3; i++){
          digitalWrite(latchPin, 0); 
          shiftOut(dataPin, clockPin, MSBFIRST, endCode[i]);
          digitalWrite(latchPin, 1);  
          digitalWrite(scanPin[3-i], 0);  
          delay(1);
          digitalWrite(scanPin[3-i], 1);   
    }
  }

倘若狀況是範圍內不重複的亂數已挑盡 ( numDisplayEnd 為真) ,則必須讓顯示器的千位數出現「E」、百位數出現「n」以及十位數出現「d」。for 迴圈執行三次。i = 0 ,先以 digitalWrite(latchPin, 0) 取消資料栓鎖,再以 shiftOut(dataPin, clockPin, MSBFIRST, endCode[i]) 送出「E」的七段顯示碼。接著 digitalWrite(latchPin, 1) 作資料栓鎖後,digitalWrite(scanPin[3-i], 0) 送出掃描信號給控制顯示器千位數的掃描接腳。delay(1) 延遲 1 ms 後,digitalWrite(scanPin[3-i], 1) 將控制顯示器千位數的掃描接腳的掃描信號關閉。類似的過程再作 2 次,只不過第二次與第三次 i = 1 與 i = 2 ,送出的是「r」的七段顯示碼,且掃描的分別改成顯示器百位數與十位數。


void calCountDigit(){
       countThousndsDigit = count/1000;        
        remainder = count%1000;
        countHundredsDigit = remainder/100;     
        remainder =  remainder%100;
        countTenthsDigit = remainder/10;       
        remainder =  remainder%10;
        countUnitsDigit = remainder;           
        dataOut[3] = countThousndsDigit;      
        dataOut[2] = countHundredsDigit;      
        dataOut[1] = countTenthsDigit;        
        dataOut[0] = countUnitsDigit;         
        if (countThousndsDigit != 0)           
            scanDigitNum = 4;
        else if (countHundredsDigit != 0)      
            scanDigitNum = 3;
        else if (countTenthsDigit != 0)  
            scanDigitNum = 2;
        else                                   
            scanDigitNum = 1;
}



calCountDigit() 自定函數用來找出計數值的千位、百位、十位以及個位的值,並分別將這些值存入顯示緩衝區,最後再判斷掃描的位數。例如計數值若為 365,則 365 除以 1000,可得到商為 0 〔亦即其千位數數值 countThousndsDigit 為 0〕,且餘數〔remainder〕為 365。再將餘數除以 100,可得到商為 3 〔亦即其百位數數值 countHundredsDigit 為 3〕,且餘數為 65。再將餘數 65 除以 10,可得到商為 6 〔亦即其十位數數值 countTenthsDigit 為 6〕,且餘數為 5。而最後的餘數 5 即為個位數數值〔countUnitsDigit〕。接著再將 countThousndsDigit、countHundredsDigit、countTenthsDigit 以及 countUnitsDigit 分別存入顯示緩衝區 dataOut[3]、dataOut[2]、dataOut[1] 及 dataOut[0] 中。

經上面的說明,下列的程式碼就應該容易理解。

countThousndsDigit = count/1000;        
remainder = count%1000;
countHundredsDigit = remainder/100;     
remainder =  remainder%100;
countTenthsDigit = remainder/10;       
remainder =  remainder%10;
countUnitsDigit = remainder;    
dataOut[3] = countThousndsDigit;      
dataOut[2] = countHundredsDigit;      
dataOut[1] = countTenthsDigit;        
dataOut[0] = countUnitsDigit;  

最後判斷掃描位數的部分,執行 if (countThousndsDigit != 0) ,判斷計數值的千位數值是否為 0,若是表示須掃描四位數,則  scanDigitNum = 4;若不是往下繼續執行  else if (countHundredsDigit != 0) ,判斷百位數值是否為 0,若是表示須掃描三位數,則 scanDigitNum = 3;若不是往下繼續執行 else if (countTenthsDigit != 0) ,判斷十位數值是否為 0,若是表示須掃描二位數,則 scanDigitNum = 2;若不是執行 else,且 scanDigitNum = 1,表示只須掃描一位數。                      
          


留言

這個網誌中的熱門文章

三段式電子開關電路

陣列 (C++)

首數、尾數與位數

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

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

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

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