概述
最簡單的一個 LED 的實驗之后,自然是增加幾個 LED,咱排成一排來玩吧。最后,再把一排的 LED 排成一個 8 字來玩——七段數碼管。
示例程序
流水燈
第一個出場的肯定是經典的流水燈,也叫跑馬燈
// ---------------------------------------------------------------------------- // flowingLEDS.ino // // Created 2015-05-31 // By seesea <seesea2517#gmail#com> // // 流水燈 // // pin 5 - 12 接到 8 個 LED 正級,LED 負極接 GND // ---------------------------------------------------------------------------- // 配置流水燈引腳 const unsigned char pins[] = { 5, 6, 7, 8, 9, 10, 11, 12 }; void setup() { for (char i = 0; i < sizeof(pins) / sizeof(char); ++i) { pinMode(pins[i], OUTPUT); } } void loop() { static unsigned char i = 0; digitalWrite(pins[i], LOW); i = (i + 1) % 8; digitalWrite(pins[i], HIGH); delay(200); }
流水燈二——端口整體操作
再試流水燈,這里換一個端口整體操作,感受到更簡潔沒?
// ---------------------------------------------------------------------------- // portDFlowingLEDS.ino // // Created 2015-05-31 // By seesea <seesea2517#gmail#com> // // 使用 PORTD 的操作來實現的流水燈 // 體驗端口的整體操作 // // 從官網找到原理圖或管腳對應說明,或者直接找到 arduino IDE 目錄下的頭文件可以看到,數字口 pin 0 - 7 就是 PORTD 對應的低到高的 0 - 7 位 // pin 0 - 7 接到 8 個 LED 正極,LED 負極接 GND // 由於 pin 0 1 通過 LED 接到地了,下載程序的時候需要將 pin 0 1 兩個引腳的接線拔掉,否則下載不了程序 // ---------------------------------------------------------------------------- void setup() { DDRD = 0xFF; // DDRD 用於設置引腳功能是輸入還是輸出,置 1 為輸出。對應 arduino 就是 pinMode 置為 OUTPUT } void loop() { static unsigned char i = 0; i = (i + 1) % 8; PORTD = 1 << i; // 左移位操作。PORTD 用於對引腳輸出的設置,對應 arduino 就是 digitalWrite delay(100); }
流水燈三——74HC959 擴展 IO 口
又是流水燈!這次使用 74HC595 來擴展 IO 口。對於 arduino 來說,IO 口資源是很緊張的,遲早會遇到不夠用的時候,這時候就需要擴展。使用 74HC595 是一種比較常見的方案。
// ---------------------------------------------------------------------------- // 959FlowingLEDS.ino // // Created 2015-06-07 // By seesea <seesea2517#gmail#com> // // 使用 74HC959 擴展 IO 口實現的流水燈 // 只要用三個 IO 口就可以擴展出 8 個 IO 口了,很節省 arudino 的緊張 IO 資源(理論上三個 IO 口可以無限擴展) // // 959 的 Q0-Q7 接 8 個 LED 正極,LED 負極接 GND // 959 的其它引腳按相關說明接即可。VCC 通過 200 ohm 電阻接電源正,偷懶的限流辦法 // ---------------------------------------------------------------------------- const unsigned char latchPin = 11; // 595 的 ST_CP const unsigned char clockPin = 10; // 595 的 SH_CP const unsigned char dataPin = 9; // 595 的 DS void setup () { pinMode(latchPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); } void loop() { static unsigned char i = 0; digitalWrite(latchPin, LOW); shiftOut(dataPin, clockPin, MSBFIRST, 1 << i); digitalWrite(latchPin, HIGH); delay(100); ++i; if (i > 7) i = 0; }
![]() |
雨滴拖尾效果流水燈
不好意思,還是流水燈。這次模擬得更真實一點,加上水滴的拖尾效果。
首先是簡單的方法,使用 uno 的六個 pwm 口來實現。
// ---------------------------------------------------------------------------- // raindropLEDS.ino // // Created 2015-06-04 // By seesea <seesea2517#gmail#com> // // 雨滴流動效果 // 雨滴流動效果與流水燈(跑馬燈)的區別在於雨滴流水效果有拖尾效果,即亮過的燈是慢慢熄滅的 // // 使用 UNO 的六個 PWM 引腳實現雨滴流動的效果 // UNO 的六個 PWM 引腳為 pin 3 5 6 9 10 11,這幾個腳分別接 6 個 LED 正極,LED 負極接 GND // // ---------------------------------------------------------------------------- const unsigned char pins[] = { 3, 5, 6, 9, 10, 11 }; // 六個 pwm 引腳 const unsigned char initPwm = 240; // 最亮的燈的 pwm 值,即移動的時候在最頭一個燈的亮度 const unsigned char deltaPwm = 10; // 燈慢慢熄滅的 pwm 值,后一個燈比前一個燈暗多少。這相當於是一個等差隊列。等差的亮度感覺不大好,所以引入下一個等比的因素 const unsigned char deltaPercent = 30; // 后一個燈比前一個燈暗,其亮度是前一個燈的百分之幾。相對於前面的遞減,這個相當於是等比級數 const unsigned long delayMs = 100; // 移動延遲,單位 ms const unsigned char pinNum = sizeof(pins) / sizeof(pins[0]); // 引腳數量,即 LED 個數 unsigned char ledPwm[pinNum]; // 存放運行時每一個 LED 的亮度 PWM 值 void setup() { for (char i = 0; i < pinNum; ++i) { pinMode(pins[i], OUTPUT); ledPwm[i] = 0; } } void loop() { static unsigned char head = 0; // 每一次進入 loop() 函數都對所有的燈亮度進行處理 for (unsigned char i = 0; i < pinNum; ++i) { ledPwm[i] = ledPwm[i] * deltaPercent / 100; if (ledPwm[i] <= deltaPwm) ledPwm[i] = 0; else ledPwm[i] -= deltaPwm; if (i == head) ledPwm[i] = initPwm; analogWrite(pins[i], ledPwm[i]); } // 移動水滴頭 head = (head + 1) % pinNum; // 延時 delay(delayMs); }
![]() |
雨滴拖尾效果流水燈二——數字 IO 口模擬
前面只有 6 個 PWM 口實在是不過癮啊,來,我們把所有的 IO 口都用上,包括模擬口,它也是可以作為數字 IO 口使用的。
// ---------------------------------------------------------------------------- // digitalRaindropLEDS.ino // // Created 2015-06-04 // By seesea <seesea2517#gmail#com> // // 數字引腳實現的雨滴流動效果 // 雨滴流動效果與流水燈(跑馬燈)的區別在於雨滴流水效果有拖尾效果,即亮過的燈是慢慢熄滅的 // // 使用 UNO 的所有引腳用模擬 PWM 實現雨滴流動的效果,包括模擬輸入口也可以用做數字輸出 // 各引腳接 LED 正極,LED 負極接 GND // ---------------------------------------------------------------------------- const unsigned char leds[] = { A5, A4, A3, A2, A1, A0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }; // 所有的引腳按 LED 接線順序排列 const unsigned int maxPwm = 100; // 手工模擬 PWM,可以自己定義最大的 PWM 值是多少,所以定義一個整百整千的數比較方便計算 const unsigned int initPwm = 100; // 最亮的燈的 pwm 值,即移動的時候在最頭一個燈的亮度 const unsigned int deltaPwm = 1; // 燈慢慢熄滅的 pwm 值,后一個燈比前一個燈暗多少。這相當於是一個等差隊列。等差的亮度感覺不大好,所以引入下一個等比的因素 const unsigned int deltaPercent = 70; // 后一個燈比前一個燈暗,其亮度是前一個燈的百分之幾。相對於前面的遞減,這個相當於是等比級數 const unsigned long delayMs = 70; // 移動延遲,單位 ms const unsigned char ledNum = sizeof(leds) / sizeof(leds[0]); // 引腳數量,即 LED 個數 unsigned int ledPwm[ledNum]; // 存放運行時每一個 LED 的亮度 PWM 值 void setup() { for (char i = 0; i < ledNum; ++i) { pinMode(leds[i], OUTPUT); ledPwm[i] = 0; } } extern volatile unsigned long timer0_millis; // 聲明外部變量 timer0_millis 以便在程序中使用,其實就是 millis() 的返回值——程序運行的毫秒數 void loop() { static unsigned char head = 0; static unsigned long lastTick = timer0_millis; unsigned int i, j; // 先亮燈,等占空比到切換點的時候滅燈 for (i = 0; i < ledNum; ++i) { if (ledPwm[i] == 0) continue; digitalWrite(leds[i], HIGH); } // 水滴頭是最亮的 ledPwm[head] = initPwm; // 這里就是數字口模擬的 PWM 程序了 for (i = 0; i < maxPwm; ++i) { for (j = 0; j < ledNum; ++j) { if (i == ledPwm[j]) digitalWrite(leds[j], LOW); } delayMicroseconds(1); } // 如果延時時間還沒到,先跳出,不進行水滴的移動 // 由於是用數字口模擬的 PWM,程序要不停的跑,不能使用 delay() 來延時,會卡住的 if (timer0_millis - lastTick < delayMs) return; lastTick = timer0_millis; // 處理每一個燈的亮度 for (i = 0; i < ledNum; ++i) { ledPwm[i] = ledPwm[i] * deltaPercent / 100; if (ledPwm[i] <= deltaPwm) ledPwm[i] = 0; else ledPwm[i] -= deltaPwm; if (i == head) ledPwm[i] = initPwm; } // 移動水滴頭 head = (head + 1) % ledNum; }
![]() |
POV搖搖棒
是時候舞起來了,搖起來吧。
// ---------------------------------------------------------------------------- // povLEDS.ino // // Created 2015-05-31 // By seesea <seesea2517#gmail#com> // // 搖搖棒 // // pin 0 - 7 接 LED 正極,LED 負極接 GND // pin 8 接水銀開關一腳,水銀開關另一腳接 GND // 水銀開關水平於搖動方向固定放置。如果實際效果與預想效果水平相反,只要把水銀開關兩腳的接線互換就可以了 // 注意:由於 pin 0 1 通過 LED 接地了,下載的時候需要斷開才能下載成功 // ---------------------------------------------------------------------------- const unsigned char key = 8; // 水銀開關 const unsigned char delayTimeMs = 2; // 掃描時LED的點亮延時 // 取模軟件使用縱向8點上高位的方式取模 unsigned char col[] = { // 心 // 0x30,0x48,0x44,0x22,0x44,0x48,0x30,0x00 // I 心 U 0x82,0xFE,0xFE,0x82,0x00,0x30,0x78,0x7C, 0x3E,0x7C,0x78,0x30,0x00,0xFC,0xFE,0x02, 0x02,0xFE,0xFC,0x00,0x00,0x00 /* 1 2 3 0x00,0x42,0xFE,0x02,0x00,0x00,0x00,0x00, 0x46,0x8A,0x92,0x92,0x62,0x00,0x00,0x00, 0x84,0x82,0x92,0xB2,0xCC,0x00 */ }; unsigned char cols = (sizeof(col) / sizeof(col[0])); void setup() { DDRD = 0xFF; pinMode(key, INPUT_PULLUP); } void loop() { char i = 0; char keyValue = digitalRead(key); char delta = 1; if (keyValue == 1) // 搖動方向由右向左的時候 { // 這里可以嘗試兩種方案 // 一種是向左掃的時候熄滅所有燈 // 另一種是反向點亮各列,從而由於方向也是反的,顯示就變回正的 // 根據需要,把 if 條件改成 0 或 1 可以選擇不同的方案 // 第一種方案的優點是不容易有重影,但缺點是亮度低 // 第二種方案的優缺正好相反 if (0) { // 向左搖熄滅所有燈 i = 0; PORTD = 0x00; // 如果選擇這種方案就不需要繼續下面的掃描了,return掉即可 return; } else { delta = -delta; } } if (delta == 1) { // 向右搖的時候 for (i = 0; i < cols; ++i) { PORTD = col[i]; delay(delayTimeMs); } } else { // 向左搖的時候 for(i = cols - 1; i >= 0; --i) { PORTD = col[i]; delay(delayTimeMs); } } }
接線圖我覺得得上一下,效果圖嘛,從幾百張照片里挑個好看的來:
![]() |
![]() |
![]() |
![]() |
靜態數碼管顯示
總站成一排多無聊,是時候改改造行了。現在排成 8 字吧。先點亮一個 8 字,用靜態顯示方法。
// ---------------------------------------------------------------------------- // static7segLEDS.ino // // Created 2015-06-06 // By seesea <seesea2517#gmail#com> // // 靜態七段數碼管 // 使用 PORTD 靜態顯示七段數碼管,循環顯示 0 - F // // 這里使用的是共陽七段數碼管,如果使用共陰的,只要把段碼表取反即可 // 數碼管段的順序按如下標示排列 // // A // --- // F| G |B // --- // E| D |C // --- . DP // // 然后根據需要顯示的數字生該段點亮輸出 1,可以制作段碼表。這種方式做出來的是共陰的,所以使用的時候取反即為共陽的段碼 // +---+--+--+--+--+--+--+--+--+-----+ // | | A| B| C| D| E| F| G|DP| 段碼| // +---+--+--+--+--+--+--+--+--+-----+ // | 0 | 1| 1| 1| 1| 1| 1| 0| 0| 0xFC| // +---+--+--+--+--+--+--+--+--+-----+ // | 1 | 0| 1| 1| 0| 0| 0| 0| 0| 0x60| // +---+--+--+--+--+--+--+--+--+-----+ // | 2 | 1| 1| 0| 1| 1| 0| 1| 0| 0xDA| // +---+--+--+--+--+--+--+--+--+-----+ // | 3 | 1| 1| 1| 1| 0| 0| 1| 0| 0xF2| // +---+--+--+--+--+--+--+--+--+-----+ // | 4 | 0| 1| 1| 0| 0| 1| 1| 0| 0x66| // +---+--+--+--+--+--+--+--+--+-----+ // | 5 | 1| 0| 1| 1| 0| 1| 1| 0| 0xB6| // +---+--+--+--+--+--+--+--+--+-----+ // | 6 | 1| 0| 1| 1| 1| 1| 1| 0| 0xBE| // +---+--+--+--+--+--+--+--+--+-----+ // | 7 | 1| 1| 1| 0| 0| 0| 0| 0| 0xE0| // +---+--+--+--+--+--+--+--+--+-----+ // | 8 | 1| 1| 1| 1| 1| 1| 1| 0| 0xFE| // +---+--+--+--+--+--+--+--+--+-----+ // | 9 | 1| 1| 1| 1| 0| 1| 1| 0| 0xF6| // +---+--+--+--+--+--+--+--+--+-----+ // | A | 1| 1| 1| 0| 1| 1| 1| 0| 0xEE| // +---+--+--+--+--+--+--+--+--+-----+ // | B | 0| 0| 1| 1| 1| 1| 1| 0| 0x3E| // +---+--+--+--+--+--+--+--+--+-----+ // | C | 0| 0| 0| 1| 1| 0| 1| 0| 0x1A| // +---+--+--+--+--+--+--+--+--+-----+ // | D | 0| 1| 1| 1| 1| 0| 1| 0| 0x7A| // +---+--+--+--+--+--+--+--+--+-----+ // | E | 1| 0| 0| 1| 1| 1| 1| 0| 0x9E| // +---+--+--+--+--+--+--+--+--+-----+ // | F | 1| 0| 0| 0| 1| 1| 1| 0| 0x8E| // +---+--+--+--+--+--+--+--+--+-----+ // ---------------------------------------------------------------------------- // 共陽段碼表 0 - F const unsigned char segTable[] = { ~0xFC, ~0x60, ~0xDA, ~0xF2, ~0x66, ~0xB6, ~0xBE, ~0xE0, ~0xFE, ~0xF6, ~0xEE, ~0x3E, ~0x1A, ~0x7A, ~0x9E, ~0x8E }; void setup() { DDRD = 0xFF; // PORTD 設置為輸出 } void loop() { for (char i = 0; i < sizeof(segTable) / sizeof(segTable[0]); ++i) { PORTD = segTable[i]; delay(500); } }
![]() |
動態數碼管顯示
上面是一位數碼管的顯示,可以使用靜態,如果要顯示多位,那可得動起來了。
// ---------------------------------------------------------------------------- // dynamic7segLEDS.ino // // Created 2015-06-06 // By seesea <seesea2517#gmail#com> // // 動態七段數碼管 // 動態顯示七段數碼管,從 0000 開始計數,9999 后回 0 // // 使用 PORTD 進行段選,使用 pin 8 9 10 11 進行位選 // // 這里使用的是共陽七段數碼管,如果使用共陰的,只要把段碼表取反即可 // 數碼管段的順序按如下標示排列 // // A // --- // F| G |B // --- // E| D |C // --- . DP // // 然后根據需要顯示的數字生該段點亮輸出 1,可以制作段碼表。這種方式做出來的是共陰的,所以使用的時候取反即為共陽的段碼 // +---+--+--+--+--+--+--+--+--+-----+ // | | A| B| C| D| E| F| G|DP| 段碼| // +---+--+--+--+--+--+--+--+--+-----+ // | 0 | 1| 1| 1| 1| 1| 1| 0| 0| 0xFC| // +---+--+--+--+--+--+--+--+--+-----+ // | 1 | 0| 1| 1| 0| 0| 0| 0| 0| 0x60| // +---+--+--+--+--+--+--+--+--+-----+ // | 2 | 1| 1| 0| 1| 1| 0| 1| 0| 0xDA| // +---+--+--+--+--+--+--+--+--+-----+ // | 3 | 1| 1| 1| 1| 0| 0| 1| 0| 0xF2| // +---+--+--+--+--+--+--+--+--+-----+ // | 4 | 0| 1| 1| 0| 0| 1| 1| 0| 0x66| // +---+--+--+--+--+--+--+--+--+-----+ // | 5 | 1| 0| 1| 1| 0| 1| 1| 0| 0xB6| // +---+--+--+--+--+--+--+--+--+-----+ // | 6 | 1| 0| 1| 1| 1| 1| 1| 0| 0xBE| // +---+--+--+--+--+--+--+--+--+-----+ // | 7 | 1| 1| 1| 0| 0| 0| 0| 0| 0xE0| // +---+--+--+--+--+--+--+--+--+-----+ // | 8 | 1| 1| 1| 1| 1| 1| 1| 0| 0xFE| // +---+--+--+--+--+--+--+--+--+-----+ // | 9 | 1| 1| 1| 1| 0| 1| 1| 0| 0xF6| // +---+--+--+--+--+--+--+--+--+-----+ // | A | 1| 1| 1| 0| 1| 1| 1| 0| 0xEE| // +---+--+--+--+--+--+--+--+--+-----+ // | B | 0| 0| 1| 1| 1| 1| 1| 0| 0x3E| // +---+--+--+--+--+--+--+--+--+-----+ // | C | 0| 0| 0| 1| 1| 0| 1| 0| 0x1A| // +---+--+--+--+--+--+--+--+--+-----+ // | D | 0| 1| 1| 1| 1| 0| 1| 0| 0x7A| // +---+--+--+--+--+--+--+--+--+-----+ // | E | 1| 0| 0| 1| 1| 1| 1| 0| 0x9E| // +---+--+--+--+--+--+--+--+--+-----+ // | F | 1| 0| 0| 0| 1| 1| 1| 0| 0x8E| // +---+--+--+--+--+--+--+--+--+-----+ // ---------------------------------------------------------------------------- #define COM_ON HIGH // 共陽數碼管啟用的公共端電平 #define COM_OFF LOW // 共陽數碼管禁用的公共端電平 // 共陽段碼表 0 - F const unsigned char segTable[] = { ~0xFC, ~0x60, ~0xDA, ~0xF2, ~0x66, ~0xB6, ~0xBE, ~0xE0, ~0xFE, ~0xF6, ~0xEE, ~0x3E, ~0x1A, ~0x7A, ~0x9E, ~0x8E }; const unsigned char pinPos[] = { 8, 9, 10, 11 }; // 位選管腳,按從低位到高位的排列 const unsigned int initNum = 0; // 計數初始值 const unsigned int maxNum = 9999; // 最大計數 const unsigned char com_num = sizeof(pinPos) / sizeof(pinPos[0]); // 公共端的數量,用於確定有幾位 const unsigned long delayMs = 50; // 動態掃描延遲時間 void setup() { DDRD = 0xFF; // PORTD 設置為輸出 for (char i = 0; i < sizeof(pinPos) / sizeof(pinPos[0]); ++i) { pinMode(pinPos[i], OUTPUT); } } // 於七段數碼管上顯示數字 num // 將 num 數字的個十百千等各位的數字一位一位分離出來顯示到每一位數碼管上 void display7segLED(unsigned int num) { unsigned int digital; for (unsigned char i = 0; i < com_num; ++i) { digital = num % 10; num /= 10; PORTD = segTable[digital]; digitalWrite(pinPos[i], COM_ON); delayMicroseconds(10); digitalWrite(pinPos[i], COM_OFF); // 傳說中的消隱,避免發生重影 } } void loop() { static unsigned long lastTick = millis(); static unsigned int num = initNum; display7segLED(num); // 動態掃描,不能用 delay 阻塞式的延遲哦,前面試驗過的這種用法現在派上用場了 if (millis() - lastTick < delayMs) return; lastTick = millis(); ++num; if (num >= maxNum) num = initNum; }
![]() |









