本文隸屬於AVR單片機教程系列。html
上次咱們用 delay 函數與 while 循環實現了一個LED的閃爍。這一次咱們把全部LED加入進來,讓它們依次閃爍,造成流水燈的效果。數組
開發板上有4個LED,咱們能夠用很少的語句把循環體直接描述出來(看看就行,不用敲):函數
1 led_set(LED_RED , LED_ON); 2 delay(250); 3 led_set(LED_RED , LED_OFF); 4 led_set(LED_YELLOW, LED_ON); 5 delay(250); 6 led_set(LED_YELLOW, LED_OFF); 7 led_set(LED_GREEN , LED_ON); 8 delay(250); 9 led_set(LED_GREEN , LED_OFF); 10 led_set(LED_BLUE , LED_ON); 11 delay(250); 12 led_set(LED_BLUE , LED_OFF);
可是若是LED多了怎麼辦?這樣寫一點都不優雅。一個更合理的方法是,用數組把LED存儲起來,在無限循環中套循環,對LED進行遍歷。代碼以下:性能
1 #include <ee1/led.h> 2 #include <ee1/delay.h> 3 4 int main() 5 { 6 led_init(); 7 const uint8_t leds[LED_COUNT] = {LED_RED, LED_YELLOW, LED_GREEN, LED_BLUE}; 8 while (1) 9 { 10 for (uint8_t i = 0; i < LED_COUNT; i++) 11 { 12 led_set(leds[i], LED_ON); 13 delay(250); 14 led_set(leds[i], LED_OFF); 15 } 16 } 17 }
在代碼中,uint8_t 是一種無符號8位整數類型,定義在 <stdint.h> 中。相應地有 int8_t 表示帶符號8位整數,以及 int16_t 、uint16_t 等,一直到64位。avr-gcc還提供了24位整型做爲編譯器擴展:__int24、 __uint24 ,但它們畢竟是編譯器擴展,儘可能別用;同時若是你的單片機程序中有16位整數搞不定的東西,那就應該考慮簡化一下了。ui
咱們用的單片機是8位機,指令只能處理8位整數,若是是16位,則須要多條指令進行組合。而C語言內置類型 int 在這個環境中是16位的。所以,爲了節省空間、提高性能,當一個數能夠用8位表示時,應該使用 int8_t 或 uint8_t 代替 int 。url
講了這麼多,爲何LED能夠用 uint8_t 表示呢?把光標移動到任一表示LED的宏上,右鍵,點擊Goto Implementation,或按下快捷鍵Alt+G,你就能看到頭文件中對這些宏的定義:spa
1 #define LED_RED (uint8_t)(0) 2 #define LED_YELLOW (uint8_t)(1) 3 #define LED_GREEN (uint8_t)(2) 4 #define LED_BLUE (uint8_t)(3)
同時,led_set 等函數都接受 uint8_t 類型的參數表示LED:.net
1 void led_set(uint8_t _which, bool _on); 2 void led_flip(uint8_t _which);
這些都能說明爲何用 uint8_t 就能保存一個表示LED的宏。至於我爲何選擇用 uint8_t ,固然是由於剛纔說過的性能因素。code
順便,咱們能夠發現表示LED狀態的宏本質上就是 true 和 false ,表示LED的宏就是簡單的0、一、二、3,所以程序中能夠不用數組來存儲LED,也再也不須要任何宏,而且 true 和 false 也很容易對應到燈的亮和暗上:htm
1 #include <ee1/ee.h> 2 3 int main(void) 4 { 5 led_init(); 6 uint8_t i = 0; 7 while (1) 8 { 9 led_set(i % 4, true); 10 delay(250); 11 led_set(i % 4, false); 12 i++; 13 } 14 }
第一行包含了頭文件 ee.h ,至關於包含了庫中全部的頭文件。
這樣的程序是否是更好看一些?實際上 LED_RED 等宏的做用都只有讓程序變得直觀,若是直接用數字表示更方便,也徹底沒有問題。
這裏還用到一個小技巧來消除內層循環:在循環外設一個變量,每次循環讓它加1,傳入的參數由它自己改成它對4的模。
你可能會想若是 i 過大了怎麼辦?當 i 等於255時, i++ 會讓 i 變爲0,而此時傳入函數的參數仍是從3回到0,符合控制邏輯。
爲何程序沒有 #include <stdbool.h> 就能直接使用 true 和 false ?由於你包含的頭文件中用到了它們,這個頭文件一定或直接或間接地包含了標準庫頭文件,所以你就能夠直接用了。
還有一個你可能疑惑的地方是,流水燈的邏輯本應是關閉上一個燈,打開下一個燈,而後延時一段時間,爲何在程序中是打開、延時,再關閉?想想你會發現沒什麼不對,不過是把循環體的界限移動了一步。
實際上,若是按前一種邏輯來寫程序的話,循環體得寫成這樣:
1 led_set((i + 3) % 4, false); 2 led_set(i % 4, true); 3 delay(250);
這樣雖然邏輯也正確,但反而更難理解。
你也許還有疑問:把上一個燈關閉了之後,程序還要運行一會才能打開下一個燈,會不會看起來有一段時間是沒有燈亮的?觀察一下你的開發板,你就知道不會,緣由也很簡單,關燈和開燈之間的時間是微秒級的,人眼根本沒法察覺。
除了上述實現,流水燈還有一種效果,就是燈依次亮起,再依次關閉。留做今天的做業吧。
上一篇:閃爍LED
下一篇:燒寫hex文件