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 圖示如下. 這裡沿用這樣的標準定義.

7 seg LED symbol.png

 

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 左右的電阻.

ARDUINO LED1.png

 

第二種:

LED發光二極體的正極接上限流電阻後, 直接接上 +5V 的電源. 負極則接上 Arduino 的 digital I/O port.

這種控制方式, 當程式讓 I/O port 為 High 的時候, 電位為 +5V, LED發光二極體間反而因為等電位而沒有電位差, 自然也沒有電流流動而造成LED發光.

當程式讓 I/O port 為 Low 的時候, 電位為 0V, 電位差就出來了, 因而造成電流流動而發光. 這種接法, 發光的時候 digital I/O port 需要承接電流. (Sink)

 

ARDUINO LED2.png

 

 

三位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七段顯示器, 就是以這樣的方式包裝的! 其對應電路如下 (共陽極)

3 digit 7 seg LED.png

一如一般的LED七段顯示器, 它有兩種格式, 一種是共陽極, 一種是共陰極.

共陽極電路如上, 三個位數的陽極獨立出來形成 1, 2, 3 pin. 而剩下三個位數的8個陰極, 三三為一組互相相連接在一起. 也因為陰極三三接在一起, 假如三個陽極都同時供電的話, 這三個位數都會亮出相同的數字. 所以顯然不是這樣用的.

限制一次只能點亮一位數字, 假如要點亮三位數字, 須輪流點亮每一位數字, 再利用人眼視覺暫留的特性, 讓人眼以為三個位數的數字是同時存在的!

 

實體包裝的接腳位置跟編號

3 digits 7 seg LED pins.png

共陰極的情況這裡就不解釋了, 電路一樣, 只是 LED 的電流流向跟極性相反而已.

 

控制電路的實例 (共陽極)

3 digits 7 seg LED control.png

上面的例子, 要讓第二位數字 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 ~ A56 個腳位為 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 pins.png

 

 

所以最終的電路跟 Arduino Uno 的實際接法如下面囉, 只需要三個限流電阻就完全搞定囉, 簡單吧! ^___^ v

final circuit.png

 

 

 

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

Frank Lin(@ohiyooo)張貼的影片 於 張貼

 

這個程式只是測試三位七段的顯示, 沒用到視覺暫留的現象.

 

下面這個程式就更完整了, 從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);  
  }     
  
}


這段程式執行結果如下

 

xx

 

 

arrow
arrow

    ohiyooo2 發表在 痞客邦 留言(2) 人氣()