const int scanPin[ ] = {5, 6, 7, 8}; /* 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] = {0, 0, 0, 0}; /* 四位數顯示器的個位, 十位, 百位,千位顯示緩衝區 */
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 */
}
留言
張貼留言