本文隸屬於AVR單片機教程系列。
之前我們做的閃爍LED和流水燈,燈效都是循環的。這次我們來嘗試一些不一樣的——每一次隨機選擇一個LED並點亮。
要實現隨機的效果,我們要用C語言標准庫中的相關設施:
1 #define RAND_MAX /*implementation defined*/ 2 int rand(); 3 void srand(unsigned seed);
以上設施都定義在 <stdlib.h> 中。其中,rand() 可以返回[0, RAND_MAX ]范圍內的偽隨機整數,srand() 用於給 rand() 提供種子,當種子相同時,多次調用 rand() 得到的序列是相同的,這就是為什么稱 rand() 產生的數為“偽隨機數”。如果使用 rand() 之前沒有調用過 srand() ,則相當於調用過 srand(1) 。
利用這些工具,很容易就能寫出一個隨機LED的程序:
1 #include <ee1/led.h> 2 #include <ee1/delay.h> 3 4 #include <stdlib.h> 5 6 int main() 7 { 8 led_init(); 9 // srand(1); 10 while (1) 11 { 12 led_set(rand() % 4, LED_ON); 13 delay(200); 14 led_off(); 15 } 16 }
rand() 返回[0, RAND_MAX ]范圍內的整數,但 led_set 的第一個參數只有在 [0, 3] 范圍內才有效,因此我們把 rand() 的返回值對4取模。
srand(1) 被打上注釋,是因為這行調用沒有必要。
把這段代碼編譯並燒寫進單片機,你會發現LED閃爍的時間是不等長的,這是因為可能存在連續兩次亮相同燈的情況。為了解決這個問題,我們引入一個變量,保存當前亮的LED,並讓下一個亮的LED與當前的不同。代碼如下:
1 #include <ee1/led.h> 2 #include <ee1/delay.h> 3 4 #include <stdint.h> 5 #include <stdlib.h> 6 7 int main() 8 { 9 led_init(); 10 // srand(0); 11 uint8_t cur = rand() % 4; 12 while (1) 13 { 14 led_set(cur, LED_ON); 15 delay(200); 16 uint8_t next = rand() % 3; 17 if (next >= cur) 18 next++; 19 led_set(cur, LED_OFF); 20 cur = next; 21 } 22 }
使連續兩次不亮相同燈的核心代碼是16~18行。程序生成一個[0, 2]范圍內的隨機值,3種取值概率相等,然后當此值大於或等於當前亮燈值時,讓它自增。假設當前亮燈為1,則生成的隨機數在值為0、1、2的情況下分別變成(映射為)0、2、3,因此下一次亮燈就是在當前沒有亮的3個燈中等概率地選擇一個。
按開發板上的RESET鍵可以讓單片機復位。觀察LED序列,你會發現對於每一次復位,LED序列都是一樣的。這個問題我們暫時無法解決。
今天的作業:一個更復雜的隨機效果,每次亮1~2個燈,連續兩次不能有相同的燈亮,也不能都亮2個,總體來看亮2個的概率為1/3。
這里有一個hex文件,是作業的一個實現,以及一個.c源文件,把單片機程序的main函數復制到文件最后,用計算機的C編譯器編譯運行可以檢查算法是否正確。一個正確的結果應該跟這個差不多: