Arduino 控制板
大概是前年(2015年)底開始接觸到 Arduino 這個平台. 這個採用新一代 ATmega 微處理的單晶片開發控制板, 試用一陣子後發現, 果然比舊有的 8051 好用的多. 有點相見恨晚的感覺!
一開始只是好奇啦, 想說奇怪啊, 這個 Arduino 怎麼這麼紅啊, 來試玩看看吧. 於是從網拍上花了 NT$700元買了一片義大利原廠的 Arduino Uno 開發版, 當作我的第一片 Arduino 入門板, 開始鑑賞這個國內外頗受歡迎的平台囉.
上Arduino官網下載了它們的開發平台, 稍微試玩一下發現難怪會如此受歡迎. 開發語言採用受歡迎的 C++ 語言, 從此跟低階的 CPU 機械語言說掰掰! 新一代 ATmega 微處理, 採用 flash 來儲存程式, 加上 bootloader 跟 USB 的完美整合, 開發語言環境的完備, 寫程式, 上傳/燒錄程式至處理器, 完全不費吹灰之力. 完善的網路資源跟開源各式外部 I/O 函式庫的支援, 完成各式各樣的硬體軟體的控制專案跟計畫, 真的是非常的簡單跟方便.
所以像我們這種業餘的 DIY Maker, 在家需要一些自動控制的東東來做一些奇奇怪怪好玩的東西時, 首選就是它囉!
LED七段顯示器
在做 Arduino 應用時, 例如讀到溫度濕度壓力... etc, 各種感測器的資訊, 假如不接笨重龐大的電腦, 但最後還是免不了要跟使用者溝通, 顯示感測器所讀到的資訊. 一般人最方便應該就是接上LCD液晶螢幕, 可以顯示大量的資訊.
但是筆者是比較老派的人, 不太喜歡 LCD液晶螢幕: 第一, 總是覺得太大台了, 有了液晶螢幕就不容易小型化. 第二, 覺得太貴了, 一片也要百來塊. 再來, 閱讀受限, 燈光昏暗的時候就看不清楚囉.
相較之下, 筆者是比較喜歡LED七段顯示器啦! 因為簡單, 因為控制非常直接, 因為價格非常便宜, 因為小型化, 因為自主發光的關係, 即使有段距離, 還是肉眼可見, 非常清楚.
當然, 缺點也不少, 一個七段顯示器就會用掉8個 digital I/O port 有點多. 再來資訊也比較受限, 只能顯示數字跟一些簡單的文字. 但是因為我所開發出來的大部分 Arduino 應用為了省事, 假如沒有特別的需求的話還是會直接用LED七段顯示器來顯示, 所以還是來篇 blog 來介紹我自己所發展出來的通用電路跟程式碼囉! 這樣就不用每次都介紹一次, 直接塞這篇 blog 就好囉! (哈哈, 手叉腰)
LED七段顯示器, 基本上就是數字的七個段落跟小數點一共八個 LED. 習慣上為了方便, 每一個LED都有對應的名稱代碼: a, b, c, d, e, f, g, dp 圖示如下. 這裡沿用這樣的標準定義.
Arduino digital I/O 控制 LED
要控制七段顯示器這八個 LED 要先了解一下如何利用 Arduino 的 digital I/O port 來控制 LED.
要用 digital I/O port 來控制 LED, 有底下兩種接法.
第一種:
LED發光二極體的正極接上限流電阻後, 接上 Arduino 的 digital I/O port, 負極直接接地.
這種控制方式, 當程式讓 I/O port 為 High 的時候, 電位為 +5V, LED發光二極體間有了電位差造成電流流動造成發光. 電流由 digital I/O port 提供 (source).
當程式讓 I/O port 為 Low 的時候, 電位為 0V, LED發光二極體間沒有電位差, 自然沒有電流流動而不會發光.
LED發光二極體的亮度由流過的電流來決定, 這裡由上面那顆限流電阻來決定. 一般的LED發光二極體會消耗約 2V ~ 3V 的電位差, 所以 限流電阻間的電位差為 3V ~ 2V, 要 LED 發亮約需要 10mA 的電流, 所以需要 3V / 10mA ~ 2V / 10mA = 300 ohm ~ 200 ohm 左右的電阻.
第二種:
LED發光二極體的正極接上限流電阻後, 直接接上 +5V 的電源. 負極則接上 Arduino 的 digital I/O port.
這種控制方式, 當程式讓 I/O port 為 High 的時候, 電位為 +5V, LED發光二極體間反而因為等電位而沒有電位差, 自然也沒有電流流動而造成LED發光.
當程式讓 I/O port 為 Low 的時候, 電位為 0V, 電位差就出來了, 因而造成電流流動而發光. 這種接法, 發光的時候 digital I/O port 需要承接電流. (Sink)
三位LED七段顯示器
控制一個LED七段顯示器會需要8個 digital I/O, 要控制三位LED七段顯示器就會需要 8x3 = 24 個 digital I/O, 天哪! 直接用24個digital I/O來控制這也太多也太蠢了啦! 還有, 為了方便, 我也不希望多加 LED控制 IC 來控制, 那就失去簡單的原意了! 假如要這樣, 那倒不如直接用 LCD 還方便些.
那怎麼辦呢? 所幸, 還有一個方法呀! 就是用掃描的方式: 三位LED七段顯示器, 一次只點亮一位數, 這樣只要8個 digital I/O, 再用3個 digital I/O 選擇要點亮的位位置. 讓三個不同位位置的LED七段顯示器, 以很快的速度輪流點亮發光. 因為速度很快, 人眼察覺不出來, 會以為三個位置的LED七段顯示器是同時發光的. 這樣, 就可以用 8 + 3 = 11 個 digtial I/O, 直接控制三位LED七段顯示器囉! 直接! 方便! 不需要額外的硬體! 花費也便宜許多!
很開心的, 市面上有賣現成的三位LED七段顯示器, 就是以這樣的方式包裝的! 其對應電路如下 (共陽極)
一如一般的LED七段顯示器, 它有兩種格式, 一種是共陽極, 一種是共陰極.
共陽極電路如上, 三個位數的陽極獨立出來形成 1, 2, 3 pin. 而剩下三個位數的8個陰極, 三三為一組互相相連接在一起. 也因為陰極三三接在一起, 假如三個陽極都同時供電的話, 這三個位數都會亮出相同的數字. 所以顯然不是這樣用的.
限制一次只能點亮一位數字, 假如要點亮三位數字, 須輪流點亮每一位數字, 再利用人眼視覺暫留的特性, 讓人眼以為三個位數的數字是同時存在的!
實體包裝的接腳位置跟編號
共陰極的情況這裡就不解釋了, 電路一樣, 只是 LED 的電流流向跟極性相反而已.
控制電路的實例 (共陽極)
上面的例子, 要讓第二位數字 LED 中的 b 發亮時, 只要把2腳位打開接 Vcc (High), b腳位接地也打開接地 (Low), 這樣 b 就會亮囉.
根據這樣的概念, 最正統的電路如這個網站所描述. 考量到 digital I/O port 的供電流能力的不足, 所以 1, 2, 3 接腳的地方接 Vcc 來提供足夠的電流, 然後外加電晶體來當電流開關, Arduino 的 digital I/O 來控制電晶體開關的導通於否. 這是最正確最保險的方式!
但是假如這樣的話, 要多用三個電晶體, 這真的是太複雜了啦, 失去了當初使用7段LED電路簡單的原意. 特別是對我們這種業餘的 DIY Maker, 其實沒那麼嚴謹啦, 最終只要沒大問題, 可以動就好了.
所以我採用比較簡單的方式, 1, 2, 3 腳直接由三個 Arduino 的 digital I/O 來驅動, 不透過電晶體來控制了, 然後直接在這三個腳加上限流電阻, 來限制通過 digital I/O port 的最大電流, 避免超過 I/O port 的上限而燒掉.
這樣的做法缺點是, 總電流由限流電阻決定後再分給八個 LED, 因為每個不同數字只會亮八個 LED 中的其中幾個, 所以會因為亮 LED 數目的不同而造成亮度的不平均. 不過實際試用的情況發現也還好, 因為需要不斷的掃描來維持三位數的 LED數字恆亮的錯覺, 所以亮度不均的情況並不嚴重.
Arduion Uno 的 digital I/O port:
介紹一下 Arduino 控制板的 I/O ports 的規劃跟使用
1. pin 0 ~ pin 13 一共 14 個腳位, 是 Arduino 標準的 digital I/O port (編號為 D0 ~ D13). Arduino 的 digtial I/O port 是雙向的, 可以當輸入, 也可以當輸出. 由程式指定I/O的方向 (輸入/或輸出).
2. pin 0 ~ pin 13 這 14 個腳位中, 要注意 pin0 (RX), pin1 (TX) 已被 USB 佔用, 用來跟電腦通訊. 再來 pin2 (INT0), pin3 (INT1) 這兩個是給外部中斷用, 假如要使用外部中斷訊號的話, 要避開這兩個 I/O port. pin10 ~ pin13 這四個腳位是給 SPI 通訊用的, 假如有用 SPI 通訊功能的話, 要避開這四個腳位.
3. A0 ~ A5 這 6 個腳位為 Analog I/O IN 類比輸入, 但也可以規劃當作Arduino 的 digtial I/O port (編號為 D14 ~ D19), 所以 Arduino UNO 最多可以有 14 + 6 = 20 個雙向的 digital I/O port.
根據這樣的規則, 這個測試程式只需要避開 pin0, pin1 的 RX, TX 囉, 避免破壞跟電腦的 USB 通訊, 8 + 3 = 11 一共 11個 digital I/O port, 從 pin2 ~ pin12 依序被規劃使用來控制三位數的LED七段LED顯示器.
Arduion Uno 接腳示意圖
所以最終的電路跟 Arduino Uno 的實際接法如下面囉, 只需要三個限流電阻就完全搞定囉, 簡單吧! ^___^ v
2020.10.28 補記
我又另外寫了一篇關於 FORTH 語言在 Arduino Uno 上七段顯示器的控制。
假如你想更深入了解 Atmega328 的 Digital I/O 埠控制跟暫存器的關係的話,或是對 FORTH 語言有興趣的話,可以參考!
程式列表跟解說
完整程式碼如下
// // 3 digits 7 segent LED test v3 for common anode // // Frank Lin 12.19.2015 // // output pins definations #define digit1 12 #define digit2 9 #define digit3 8 #define aa 11 #define bb 7 #define cc 5 #define dd 3 #define ee 2 #define ff 10 #define gg 6 #define dp 4 // number definations // 1 = bb cc // 2 = aa bb gg ee dd // 3 = aa bb gg cc dd // 4 = ff gg bb cc // 5 = aa ff gg cc dd // 6 = aa ff ee dd cc gg // 7 = aa bb cc // 8 = aa bb cc dd ee ff gg // 9 = aa bb cc ff gg // 0 = aa bb cc dd ee ff // - = gg // data structure 8bits = dp aa bb cc dd ee ff gg // 0 to 9, '-' const byte segs_data[11]={B01111110, B00110000, B01101101, B01111001, B00110011, B01011011, B01011111, B01110000, B01111111, B01110011, B00000001} ; const byte seg_pins[8]={gg,ff,ee,dd,cc,bb,aa,dp}; const byte digit_pins[3]={digit1,digit2,digit3}; void LightChar(byte digit, byte index, boolean dot) { // digit = 0, 1, 2 // index = 0..10 byte segments = segs_data[index]; if (dot) segments |= B10000000; // select digit for ( byte i=0; i<3; i++) { // common anode, set output to HIGH to select digit what you want, the other digits must be set to LOW // common cathode, set output to LOW to select digit what you want, the other digits must be set to HIGH if (i==digit) digitalWrite(digit_pins[i], HIGH); else digitalWrite(digit_pins[i], LOW); } // according char data, light each segments on digit for ( byte i=0; i<8; i++) { // common anode, set output to LOW to light up LED, HIGH to off LED // common cathode, set output to HIGH to light up LED, LOW to off LED if ((segments & B00000001) == 1) digitalWrite(seg_pins[i], LOW); else digitalWrite(seg_pins[i], HIGH); segments >>=1; } } void setup() { pinMode(digit1, OUTPUT); pinMode(digit2, OUTPUT); pinMode(digit3, OUTPUT); pinMode(aa, OUTPUT); pinMode(bb, OUTPUT); pinMode(cc, OUTPUT); pinMode(dd, OUTPUT); pinMode(ee, OUTPUT); pinMode(ff, OUTPUT); pinMode(gg, OUTPUT); pinMode(dp, OUTPUT); Serial.begin(9600); Serial.println(" 3 digits, 7 segments LED test!"); } void loop() { for (byte i=0; i<3;i++) { for (byte j=0; j<11;j++) { LightChar(i, j, true); delay(250); } } }
原理解說
來解釋一下程式碼囉!
腳位定義由下面所定義, 非常有彈性的, 硬體的腳位不一樣時, 只要在這裡更改就可以囉. 其他都不用動. 要特別提醒的, 這裡的 digit1 指的是三位數中最左邊那個位數. 依序由左往右 digit1 | digit2 | digit3
八段 LED 的符號 a,b,c,d,e,f,g,dp 這裡用 aa,bb,cc,dd,ee,ff,gg,dp 來代替
// output pins definations
#define digit1 12
#define digit2 9
#define digit3 8
#define aa 11
#define bb 7
#define cc 5
#define dd 3
#define ee 2
#define ff 10
#define gg 6
#define dp 4
接下來是字型定義表, 這個表格存放各種字型 LED 點亮的定義. 這個字型表這個程式只定義了 11個字型, 它是可以根據需求再擴充的, 只要利用陣列方式取值就可以囉.
const byte segs_data[11]={B01111110, B00110000, B01101101, B01111001, B00110011, B01011011, B01011111, B01110000, B01111111, B01110011, B00000001} ;
資料結構是用一個 byte 的八個 bits 來代表八個 LED 是否要點亮的資料. 依序是 8bits = dp aa bb cc dd ee ff gg
例如: B01111001 由左邊算過來, 依序是 dp:off, aa:on, bb:on, cc:on, dd:on, ee:off, ff:off, gg:on 所以就是點亮 LED 中的 a, b, c, d, g 這個就是讓 LED 點亮顯示數字 3
點亮 LED 的工作由函式 LightChar(byte digit, byte index, boolean dot) 來負責,
需要告訴它三個參數:
第一個參數 digit 是所要顯示三位數七段顯示器的位置. 0 代表最左邊那位, 1 代表中間那位, 2代表右邊那位.
第二個參數 index 是所要顯示的字型, 其字形資料在字型表裡面的位置. 為了方便這裡把 0,1,2,3...8,9 這幾個字型剛好照陣列的 index 排列, 所以 index=0, 會取到字型'0'的資料, 其他依序類推.
第三個參數 dot 是個布林值, 用來告訴函式是否要顯示小數點, ture 代表要, false 代表不要.
這個函式工作的原理很簡單, 它會用個 do-loop 迴圈, 依序的將8 bits的字型資料右移1個 bit, 再利用跟遮罩 B00000001 AND 運算後取出這個最右邊的 bit, 假如是 1, 就送訊號點亮對應的 LED I/O port. 這樣做8次, 字形資料裡所對應到的 LED 訊號就會正確的被解碼送出.
再來要注意的是, 如前面控制原理所說明的, 一次只能有一個位數的 LED 被控制, 所以其他的兩個位數必須被關掉. 否則電路會衝突到, 不同位數間會互相干擾啦! 這由下面這段程式碼負責!
if (i==digit) digitalWrite(digit_pins[i], HIGH);
else digitalWrite(digit_pins[i], LOW);
打開一個位數的控制, 關掉其他兩個!
最後整個測試程式, 運作起來像這樣
Now, it works! :) so stupid spent whole night yesterday by 2 broken #7segments 3 digits #LED, #arduino #arduinouno #ledtest
這個程式只是測試三位七段的顯示, 沒用到視覺暫留的現象.
下面這個程式就更完整了, 從1 數到 999, 有用到視覺暫留的現象. 裡面定義了一個 LightNum 函式 除了將數字轉換成對應的字碼外, 再利用 LightChar 函式, 快速的在三個位數之間掃描, 讓人以為這三個數字是一起亮的!
要稍微解釋一下, 下面的這個程式 I/O腳有稍微調整過, 因為要預留未來外部中斷 pin2 (INT0)的使用. 所以修改從 pin3 ~ pin13 作為此三位七段顯示器的控制輸出.
// // 3 digits 7 segent LED test v4 for common anode // // Frank Lin 12.19.2015 // // output pins definations #define digit1 13 #define digit2 10 #define digit3 9 #define aa 12 #define bb 8 #define cc 6 #define dd 4 #define ee 3 #define ff 11 #define gg 7 #define dp 5 // number definations // 1 = bb cc // 2 = aa bb gg ee dd // 3 = aa bb gg cc dd // 4 = ff gg bb cc // 5 = aa ff gg cc dd // 6 = aa ff ee dd cc gg // 7 = aa bb cc // 8 = aa bb cc dd ee ff gg // 9 = aa bb cc ff gg // 0 = aa bb cc dd ee ff // - = gg // data structure 8bits = dp aa bb cc dd ee ff gg // 0 to 9, '-' const byte segs_data[11]={B01111110, B00110000, B01101101, B01111001, B00110011, B01011011, B01011111, B01110000, B01111111, B01110011, B00000001} ; const byte seg_pins[8]={gg,ff,ee,dd,cc,bb,aa,dp}; const byte digit_pins[3]={digit1,digit2,digit3}; void LightChar(byte digit, byte index, boolean dot) { // digit = 0, 1, 2 // index = 0..10 byte segments = segs_data[index]; if (dot) segments |= B10000000; // select digit for ( byte i=0; i<3; i++) { // common anode, set output to HIGH to select digit what you want, the other digits must be set to LOW // common cathode, set output to LOW to select digit what you want, the other digits must be set to HIGH if (i==digit) digitalWrite(digit_pins[i], HIGH); else digitalWrite(digit_pins[i], LOW); } // according char data, light each segments on digit for ( byte i=0; i<8; i++) { // common anode, set output to LOW to light up LED, HIGH to off LED // common cathode, set output to HIGH to light up LED, LOW to off LED if ((segments & B00000001) == 1) digitalWrite(seg_pins[i], LOW); else digitalWrite(seg_pins[i], HIGH); segments >>=1; } } void LightNum(int num) { LightChar(2,num % 10, true); delay(5); num /=10; LightChar(1,num % 10, false); delay(5); num /=10; LightChar(0,num % 10, false); delay(5); } void setup() { pinMode(digit1, OUTPUT); pinMode(digit2, OUTPUT); pinMode(digit3, OUTPUT); pinMode(aa, OUTPUT); pinMode(bb, OUTPUT); pinMode(cc, OUTPUT); pinMode(dd, OUTPUT); pinMode(ee, OUTPUT); pinMode(ff, OUTPUT); pinMode(gg, OUTPUT); pinMode(dp, OUTPUT); } void loop() { for(int i=0;i<1000;i++) { LightNum(i); delay(10); } }
這段程式執行結果如下
#arduino Very simple 3 digits #7segment #led #7segmentdisplay counting test #arduinouno #arduinoshield uploaded for the use of my blog ;)
xx
文章標籤
全站熱搜
離婚證人 、台北離婚證人 、新竹離婚證人 、彰化離婚證人 、高雄離婚見證人
遺囑見證人 、結婚證人
留言列表