Python可以這樣玩(18):四位數七段顯示器


如果不想要自行接線路,那麼市面上有已經銜接好的二合一、四合一等各種位數的七段顯示器,以四合一的七段顯示器為例,由於每個七段顯示器會需要 8 個腳位控制其上的 LED,再加上各 4 個共陰或共陽腳位,因此基本上會有 12 個腳位,當然,如果還要附帶有「:」控制等腳位的話,就會有 12 個腳位以上。

我手邊這個四位數七段顯示器,就是基本的 12 腳位:




依上圖來看的話,下方有六個腳位,上方有六個腳位,最左下方的腳位編號是 1,依逆時針依序編號至 6,然後右上方是 7,依逆時針編號至左上方的 12,這個順序基本上容易,需要背下來的是下面這張圖:




由於我們必須知道這12個腳位,分別是控制哪個 LED,以及哪些腳位控制哪個七段顯示器,所以,與其每次都要參考圖片,不如把它們背起來,在此提供一個小技巧:

首先在紙上畫出一個長方形跟12個腳位,然後從右下角開始逆時鐘轉,右下角填0、空一個空位填12、空兩個空位填3,接著從3的右邊空位開始順時鐘轉,先填A、空一個空位填B、空一個空位填C、空一個空位填D、到達左下角就不須空格直接填E,最後繼續順時鐘到達空位填F、下一空位填G、下一空位填H

比對一下是不是一樣,多練習兩次就不會忘記。

在我們自己畫出來的圖中,有 0 3 以及 A HDP)兩組編碼。先談0 – 30 表示最右邊(個位數=10的零次方)的七段顯示器,1 表示右邊算來第二個(十位數=10的一次方)七段顯示器,3 表示最左邊(千位數=10的三次方)的七段顯示器,如果您使用的是共陰四位數七段顯示器,當 3 為低電位而 012 為高電位時,那麼是控制最左邊的七段顯示器,依此類推。

很不巧,我手上的是共陽顯示器,我們稍後再透過實做來探討共陽與共陰的差異之處。

至於 A HDP),如下圖表示,關於這個部分,我們之前已經探討過,請參考七段顯示器的章節,關於共陽與共陰,基本上就是01相反:



接下來的重點是,如果想要運用這個四位數七段顯示器,可以如下圖的方式銜接電路(使用四個電阻)。特別說明一下,要驅動顯示器時限流電阻肯定是必不可少的,如果沒有接電阻,燈會正常亮沒有錯,但是很容易燒掉。限流電阻有兩種接法,一種是在 d1-d4 陽極接,總共接 4 顆,就是我們下面要用的方式。這種接法好處是需求電阻比較少,但是會產生每一位元上顯示不同數位亮度會不一樣,1 最亮,8 最暗。另外一種接法就是在其他 8 個引腳上接,這種接法亮度顯示均勻,但是用電阻較多。本次實驗使用 4 220Ω電阻(因為我手上只有五個220Ω電阻)




由於稍後我要使用Python Firmata直接測試,所以接線的原則如下:PIN0 PIN1 避開,0 -3 分別接到PIN10 – PIN13A G (H小數點不接) 分別接到 PIN3 – PIN9,然後使用發法一,四個電阻接 PIN10 – PIN13

知道如何接之後,請不要看圖,直接盲接,才能達到學習的效果。如果這點做不到,請不要躁進,回到上一個單元重新練習一次。接好之後如下圖:




完成接線之後,請上傳 Standard Firmata,成功之後就可以開啟Python

指定個位數

開啟 Python 之後,請打開之前寫的程式 StartUp.py,不要破壞之前的程式,請先另存新檔到 4digit.py,然後開始修改,按照前面的說明:0 表示最右邊(個位數)的七段顯示器,如果是共陰,當 0 為低電位而 123 為高電位時,就是控制最右邊的七段顯示器,如果是共陽則完全相反,當 0 為高電位而 123 為低電位時,才是控制最右邊的七段顯示器程式如下:

from pyfirmata import Arduino
from time import sleep

port = 'COM3'
board = Arduino(port)

d0 = [1,0,0,0]  #第一個代表0,依序 1,2,3
def setd(x):
    for i in range(10,14):
        board.digital[i].write(x[i-10])
setd(d0)
def on_light(x):
    board.digital[x].write(0)
def on_all():
    for i in range(3,10):
        on_light(i)
on_all()

紅色部分,就是我用來控制個位數亮燈的程式碼,後面的 on_light(x),其實就是用來設定 PIN3PIN9那些位腳的電位要設定為低電位(共陽),所以呼叫 on_all() 就是全部設定成低電位,個位數就會亮起8了。

到這裡,我們就釐清了共陽與共陰的差異,那就是全部相反。

指定四位數

接下來我們就分別用 d0 – d3來定義四位數,然後讓它們每0.5秒亮一個:

from pyfirmata import Arduino
from time import sleep

port = 'COM3'
board = Arduino(port)

d0 = [1,0,0,0]  #第一個代表0,依序 1,2,3
d1 = [0,1,0,0]  #第一個代表0,依序 1,2,3
d2 = [0,0,1,0]  #第一個代表0,依序 1,2,3
d3 = [0,0,0,1]  #第一個代表0,依序 1,2,3
all_digit = [d0,d1,d2,d3]

def setd(x):
    for i in range(10,14):
        board.digital[i].write(x[i-10])

def off_light(x):
    board.digital[x].write(1)
def off_all():
    for i in range(3,10):
        off_light(i)

def on_light(x):
    board.digital[x].write(0)
def on_all():
    for i in range(3,10):
        on_light(i)

for i in all_digit:
    setd(i)
    on_all()
    sleep(0.5)
    off_all()

顯示數字

在顯示數字之前,我們先對我們的程式做一下最佳化,我把物理PIN腳與程式之前用列表做一個邏輯上的對應。

我簡單的用下面兩個列表來定義實際的PIN

digicontral = [10,11,12,13]
fontcontral = [3,4,5,6,7,8,9]

日後,digicontral[0] – [3] 就代表顯示器的 0 – 3,就會對應到開發板PIN10 – PIN13fontcontral[0] – [6]就代表顯示器的 A – G,對應到開發板的 PIN3 – PIN9,如果未來我們想把小數點H加上去,只要將 fontcontral = [3, 4, 5, 6, 7, 8, 9, 2] 即可。

接下來就可以把原本的 range(10,14)這種含有看不懂數字的程式碼改成下面這樣:

def setd(x):
    for i in digicontral:
        board.digital[i].write(x[i-digicontral[0]])

def off_light(x):
    board.digital[x].write(1)
def off_all():
    for i in fontcontral:
        off_light(i)

def on_light(x):
    board.digital[x].write(0)
def on_all():
    for i in fontcontral:
        on_light(i)

要定義數字,我們借用之前的定義方式不變:

n0 = [0,0,0,0,0,0,1]
n1 = [1,0,0,1,1,1,1]
n2 = [0,0,1,0,0,1,0]
n3 = [0,0,0,0,1,1,0]
n4 = [1,0,0,1,1,0,0]
n5 = [0,1,0,0,1,0,0]
n6 = [0,1,0,0,0,0,0]
n7 = [0,0,0,1,1,1,1]
n8 = [0,0,0,0,0,0,0]
n9 = [0,0,0,0,1,0,0]
all_num = [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9]

我們希望從個位數開始,顯示 1, 2, 3, 4,順便看一下是否可以不要熄滅;



def on_number(x):
    for i in range(7):
        board.digital[i+3].write(x[i])

count=1 
for i in all_digit:
    setd(i)
    on_number(all_num[count])
    count += 1
    sleep(0.5)

程式順利的顯示數字了,但是出現一個現象,就是四個位數無法同時顯示數字,這是因為顯示板的四位數字本來就是個別控制的,每次控制一個,無法一次控制四個。先別著急,我們後面在看如何解決。

顯示文字

文字如果要顯示,就是從左到右了,原本是要顯示 Hello,但是只有四位數字,我們就來顯示西班牙文的 HALO,我們把文字的定義如下:

na = [0,0,0,1,0,0,0]
nb = [1,1,0,0,0,0,0]
nc = [0,1,1,0,0,0,1]
nd = [1,0,0,0,0,1,0]
ne = [0,1,1,0,0,0,0]
nf = [0,1,1,1,0,0,0]
ng = [0,1,0,0,0,0,1]
nh = [1,0,0,1,0,0,0]
nj = [1,0,0,0,1,1,1]
nl = [1,1,1,0,0,0,1]
all_ltr = [na, nb, nc, nd, ne, nf, ng, nh, nj, nl]
halo = [nh, na, nl, n0]

不是每個字母都可以表現出來,我盡力做到 HALO這四個字母都有 (O0代替),然後把 halo 定義成列表。透過 reversed() 函數將顯示方式由左到右,程式碼如下:

count=0
for i in reversed(all_digit):
    setd(i)
    on_number(halo[count])
    count += 1
    sleep(0.5)
    off_all()

同時顯示 HALO?

我們回到最原始的觀念,如果要同時顯示,我們可以用一個 loop一直跑 0 – 3 四個位數顯示的文字,這樣看起來就會是同時顯示了,在 Python 裡面,我使用了下面的程式碼來實現:

while True:
    count=0
    for i in reversed(all_digit):
        setd(i)
        on_number(halo[count])
        count += 1
        sleep(0.005)

結果卻是跳動的,這點是沒有辦法解決的,原因是原本這種快速變換讓我們的眼睛看起來是同時顯示的程式碼是寫在開發板韌體的 loop() 迴圈之中,程式碼會在開發板內部執行,所以會很平順不會跳動,但是如果同樣的方法寫在 Python while 迴圈裡面的時候,這些程式把必須透過COM3 傳入開發板中,因此這種外部的迴圈,有 sleep() 時會跳動,沒有則會讓文字顯示的很不清楚。當然,我也可以做更多的嘗試,例如改成標準的八個電阻,由於時間關係就先不做了,如果各位看官有其他的經驗,可以在此留言,大家共同討論、教學相長。

Python Arduino 的程式開發是走向比較高階的應用,例如搭配 I2CRasberry Pi 等,低階的元件測試有時候我們還是必須回到 AVR C 的韌體開發,下面提供一個Counter 程式,適用於共陽四位數七段顯示器,可以直接使用。

先關閉 Python IDLE,然後打開 Arduino IDE,開一個新的草稿,複製下列的程式碼:

// 定義腳位
#define PIN_0 10
#define PIN_g 9
#define PIN_c 5
#define PIN_h 2
#define PIN_d 6
#define PIN_e 7
#define PIN_b 4
#define PIN_1 11
#define PIN_2 12
#define PIN_f 8
#define PIN_a 3
#define PIN_3 13

// 共有4個七段顯示器,分別由針腳PIN_0PIN_1PIN_2PIN_3控制
// 七段顯示器裡有8LED(包含小數點)
#define POS_NUM 4
#define SEG_NUM 8
const byte pos_pins[POS_NUM] = {PIN_0, PIN_1, PIN_2, PIN_3};
const byte seg_pins[SEG_NUM] = {PIN_a, PIN_b, PIN_c, PIN_d, PIN_e, PIN_f, PIN_g, PIN_h};

// 底下定義由七段顯示器顯示數字時所需要的資料
#define t true
#define f false
const boolean data[10][SEG_NUM] = {
  {f, f, f, f, f, f, t, t}, // 0
  {t, f, f, t, t, t, t, t}, // 1
  {f, f, t, f, f, t, f, t}, // 2
  {f, f, f, f, t, t, f, t}, // 3
  {t, f, f, t, t, f, f, t}, // 4
  {f, t, f, f, t, f, f, t}, // 5
  {f, t, f, f, f, f, f, t}, // 6
  {f, f, f, t, t, t, t, t}, // 7
  {f, f, f, f, f, f, f, t}, // 8
  {f, f, f, f, t, f, f, t}, // 9
};

// 一支方便的函式,以格式字串輸出到序列埠
void pf(const char *fmt, ... ){
    char tmp[128]; // max is 128 chars
    va_list args;
    va_start (args, fmt );
    vsnprintf(tmp, 128, fmt, args);
    va_end (args);
    Serial.print(tmp);
}

// 設定某個七段顯示器所顯示的數字,
// 參數pos0~3,指出想要更新哪一個七段顯示器,
// 參數n0~9,顯示數字
void setDigit(int pos, int n){
  if(pos < 0 || 3 < pos){
    pf("error pos=%d\n", pos);
    return;
  }

  // 控制想要更新哪一個七段顯示器,將其腳位設為LOW
  // 其他腳位則設為HIGH,代表不更新。
  for(int p = 0; p < POS_NUM; p++){
    if(p == pos)
      digitalWrite(pos_pins[p], HIGH);
    else
      digitalWrite(pos_pins[p], LOW);
  }
 
  // 寫入數字
  if(0 <= n && n <= 9){
    for(int i = 0; i < SEG_NUM; i++){
      digitalWrite(seg_pins[i], data[n][i] == t ? HIGH : LOW);
    }
  }
  else{
    for(int i = 0; i < SEG_NUM; i++){
      digitalWrite(seg_pins[i], LOW);
    }
    digitalWrite(PIN_h, HIGH);
    pf("error pos=%d, n=%d\n", pos, n);
  }
}

// 設定整個四合一型七段顯示器想要顯示的數字
// 參數number的範圍應是0~9999
void setNumber(int number)
{
  int n0, n1, n2, n3;
  n3 = number / 1000;
  number %= 1000;
  n2 = number / 100;
  number %= 100;
  n1 = number / 10;
  n0 = number % 10;

  // 求出每個位數的值後,分別更新
  // 注意,此處以delay(5)隔開每個位數的更新
  setDigit(0, n0); delay(5);
  setDigit(1, n1); delay(5);
  setDigit(2, n2); delay(5);
  setDigit(3, n3); delay(5);
}

unsigned long time_previous;
void setup() {
  Serial.begin(115200);
 
  for(int i = 0; i < POS_NUM; i++){
    pinMode(pos_pins[i], OUTPUT);
    digitalWrite(pos_pins[i], HIGH);
  }
  for(int i = 0; i < SEG_NUM; i++){
    pinMode(seg_pins[i], OUTPUT);
    digitalWrite(seg_pins[i], LOW);
  }
 
  time_previous = millis();
}

int number = 0;
void loop() {
  // 經過一秒後就讓number1
  unsigned long time_now = millis();
  if(time_now - time_previous > 1000){
    number++;
    time_previous += 1000;
    pf("number=%d\n", number);
  }

  // 不斷地寫入數字
  setNumber(number);
}

把不斷寫入數字的功能寫在 loop() 迴圈中,然後按上傳按鈕。完成之後,就可以看到從 0001 開始計秒了。如下圖:



下面的影片分享,既是基本的顯示測試,由 Python 控制:


下面影片則是 Counting 功能,由 Arduino 程式控制:





留言

這個網誌中的熱門文章

Python可以這樣玩(16):共陰/共陽七段顯示器

Python可以這樣玩(11):數學繪圖

Python可以這樣玩(15):蜂鳴器與音樂