AVR單片機教程——流水燈

本文隸屬於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文件

相關文章
相關標籤/搜索