使用 74HC595 驅動七段顯示器

七段顯示器是一種具有 8 顆 LED 的顯示元件,其中 7 顆 LED 用來顯示阿拉伯數字中不同部位的筆畫 (下圖 AG 的部分),而另外一顆 LED 則用來當作小數點顯示使用 (如下圖所示的 DP)。

取決於模組是共陽極共陰極的架構,對應的電路圖如下:

共陽極七段顯示器

或者是

共陰極七段顯示器

因此,最直接的七段顯示器操作方法,是使用八隻 GPIO 接腳連接至顯示器的 ABCDEFG DP 的八隻訊號腳,並如同平常點亮 LED 的方式,獨立控制各訊號的開關,如此就能使七段顯示器亮起各種線段組合的結果。

使用 P7 ~ P14 連接共陽極七段顯示器的接線方式

七段顯示器畫面的各種組合 (不含 DP)

使用 shift register 減少 GPIO 使用量

在上面的範例中,需使用 8 根 GPIO 接腳才能驅動一組七段顯示器,雖是直接易懂的做法,但卻很耗費 GPIO 接腳數目。舉例來說,以此作法驅動一組四位數的顯示器,則需要 32 根 GPIO (每位數 8 根 x 四位數) 才能實現。一般很少有開發板能提供 32 隻 GPIO 讓開發者使用,而且在使用如此多 GPIO 的狀況下,電路接線也會變得非常複雜。為了解決這個問題,可使用 shift register 晶片以減少所需的 GPIO 數量。接下來的教學,將以 74HC595 作為 shift register 的範例,介紹如何使用三根 GPIO 驅動一組七段顯示器,並以此為基礎,進一步擴充至使用單一 shift register 控制四位數七段顯示器。

74HC595 shift register

為了減少控制七段顯示器所需的 GPIO 數目,可使用 shift register 以序列轉並列的資料傳送方式,將所需的訊號送至顯示器。其運作原理是,利用單一隻訊號輸入腳位,依序地送 8 個位元的資料 (即七段顯示器的八個筆畫) 進去 shift register。待全部資料都送進去後,shift register 即可將該 8 個位元的資料同時輸出至本身的 8 根輸出腳位,使七段顯示器顯示開發者所需的字樣。

操縱 74HC595 時,會使用到的腳位如下 (腳位名稱請參考 datasheet 上的定義):

輸入腳位

  • 資料輸入訊號 (DS)

  • 輸入資料的時脈訊號 (SHCP)

  • 輸出資料的時脈訊號 (STCP)

輸出腳位

  • 資料輸出訊號 x8 (Q0 ~ Q7)

控制腳位

  • 啟動開關 (OE)

  • 資料重置訊號 (MR)

接下來將說明如何接線使用 shift register。

使用 74HC595 驅動單個七段顯示器

下圖的麵包板接線示範了如何將 LinkIt 7697 連接到 74HC595,其中橘色線為輸入訊號、藍色線為輸出訊號。

使用下列的 sketch 程式碼可讓此例中的共陽極七段顯示器反覆顯示十六位元數字 (0 ~ 9 以及 A ~ F):

//
// Use one 74HC595 to control a common-anode seven-segment display
//

// pin 11 of 74HC595 (SHCP)
const int bit_clock_pin = 16;
// pin 12 of 74HC595 (STCP)
const int digit_clock_pin = 15;
// pin 14 of 74HC595 (DS)
const int data_pin = 14;

// digit pattern for a 7-segment display
const byte digit_pattern[16] =
{
  B00111111,  // 0
  B00000110,  // 1
  B01011011,  // 2
  B01001111,  // 3
  B01100110,  // 4
  B01101101,  // 5
  B01111101,  // 6
  B00000111,  // 7
  B01111111,  // 8
  B01101111,  // 9
  B01110111,  // A
  B01111100,  // b
  B00111001,  // C
  B01011110,  // d
  B01111001,  // E
  B01110001   // F
};

unsigned int counter = 0;

void setup()
{
  pinMode(data_pin, OUTPUT);
  pinMode(bit_clock_pin, OUTPUT);
  pinMode(digit_clock_pin, OUTPUT);  
}
 
void update_one_digit(int data)
{
  int i;
  byte pattern;
  
  // get the digit pattern to be updated
  pattern = digit_pattern[data];

  // turn off the output of 74HC595
  digitalWrite(digit_clock_pin, LOW);
  
  // update data pattern to be outputed from 74HC595
  // because it's a common anode LED, the pattern needs to be inverted
  shiftOut(data_pin, bit_clock_pin, MSBFIRST, ~pattern);
  
  // turn on the output of 74HC595
  digitalWrite(digit_clock_pin, HIGH);
}

void loop()
{ 
  int i;
  unsigned int digit_base;
  
  counter++;
  
  digit_base = 16;

  // get the value to be displayed and update one digit
  update_one_digit(counter % digit_base);
  
  delay(500);
}

範例說明

  • 構成每個數字筆畫的資訊被定義在 digit_pattern 陣列中。

  • 呼叫 shiftOut() API 將筆畫資訊送至 shift register 裡。注意:由於此範例使用共陽極七段顯示器、開發板控制的是負極 (接地端) 訊號,所以 digit_pattern 中的資料需經反相 (~pattern) 處理後送至 shift register 才能正確地控制筆畫明滅。反之,若使用共陰極七段顯示器、開發板控制的是陽極端訊號,那麼 digit_pattern 與筆畫明滅的狀態就會一致,不需要額外做反相運算。

  • digit_clock_pin 是用來控制 74HC595 STCP 腳位的訊號,負責開/關 shift register 的輸出。

透過使用 shift register,此範例從原本需要 8 根 GPIO 才能控制單一個七段顯示器、減少為只需要三根 GPIO 即可完成。也就是說,用這個方法驅動 N 位數七段顯示器,就需要 N 個 shift register、3N 根 GPIO 訊號配合控制。接下來的章節將介紹另一個範例,說明如何做到使用單一 shift register 即能達成驅動四位數七段顯示器的方法。

使用單個 74HC595 驅動四位數七段顯示器

當要顯示的數字超過個位數時,就需要搭配更多的七段顯示器。從上面的範例得知,要驅動 N 位數字就需要 N 個 shift register 以及 3N 隻訊號腳。接下來將介紹另一種可節省訊號腳的方式,達成使用單個 74HC595 即能驅動四位數七段顯示器。此例中將使用常見的四位數七段顯示器模組作為示範元件。

該顯示器模組的電路圖表示如下:

此為四位數共陽極七段顯示器,與 LinkIt 7697 的接線方式為:

與上個例子相似,從 LinkIt 7697 連至 74HC595 的輸入訊號為橘色線、輸出訊號為藍色線。此外,還需要額外的四根控制訊號 (黃色線) 用來開關模組上的各個顯示器。本範例使用下面的 sketch 程式碼驅動顯示器,並會如影片中的裝置持續累加顯示數字:

Sketch 程式碼

//
// Use one 74HC595 to control a 12-pin common-anode 4-digit seven-segment display with fast scanning
// the display: http://www.icshop.com.tw/product_info.php/products_id/19357
//

#define DELAY_FACTOR  (100)
#define NUM_OF_DIGITS (4)

// 4 display on/off pin (for the common anode/cathode)
const int control_pins[NUM_OF_DIGITS] = {17, 5, 4, 3};
// pin 11 of 74HC595 (SHCP)
const int bit_clock_pin = 16;
// pin 12 of 74HC595 (STCP)
const int digit_clock_pin = 15;
// pin 14 of 74HC595 (DS)
const int data_pin = 14;

// digit pattern for a 7-segment display
const byte digit_pattern[16] =
{
  B00111111,  // 0
  B00000110,  // 1
  B01011011,  // 2
  B01001111,  // 3
  B01100110,  // 4
  B01101101,  // 5
  B01111101,  // 6
  B00000111,  // 7
  B01111111,  // 8
  B01101111,  // 9
  B01110111,  // A
  B01111100,  // b
  B00111001,  // C
  B01011110,  // d
  B01111001,  // E
  B01110001   // F
};

int digit_data[NUM_OF_DIGITS] = {0};
int scan_position = 0;

unsigned int counter = 0;

void setup()
{
  int i;

  // set related pins as output pins
  for (i = 0; i < NUM_OF_DIGITS; i++)
  {
    pinMode(control_pins[i], OUTPUT);
  }

  pinMode(data_pin, OUTPUT);
  pinMode(bit_clock_pin, OUTPUT);
  pinMode(digit_clock_pin, OUTPUT);  
}
 
void update_one_digit()
{
  int i;
  byte pattern;
  
  // turn off all digit
  for (i = 0; i < NUM_OF_DIGITS; i++)
  {
    digitalWrite(control_pins[i], LOW);
  }

  // get the digit pattern of the position to be updated
  pattern = digit_pattern[digit_data[scan_position]];

  // turn off the output of 74HC595
  digitalWrite(digit_clock_pin, LOW);
  
  // update data pattern to be outputed from 74HC595
  // because it's a common anode LED, the pattern needs to be inverted
  shiftOut(data_pin, bit_clock_pin, MSBFIRST, ~pattern);
  
  // turn on the output of 74HC595
  digitalWrite(digit_clock_pin, HIGH);

  // turn on the digit to be updated in this round
  digitalWrite(control_pins[scan_position], HIGH);

  // go to next update position
  scan_position++;
  
  if (scan_position >= NUM_OF_DIGITS)
  {
    scan_position = 0; 
  }
}

void loop()
{ 
  int i;
  unsigned int number;
  unsigned int digit_base;
  
  counter++;
  
  // get the value to be displayed
  number = counter / DELAY_FACTOR;

  digit_base = 10;

  // get every values in each position of those 4 digits based on "digit_base"
  //
  // digit_base should be <= 16
  //
  // for example, if digit_base := 2, binary values will be shown. If digit_base := 16, hexidecimal values will be shown
  //
  for (i = 0; i < NUM_OF_DIGITS; i++)
  {
    digit_data[i] = number % digit_base;
    number /= digit_base;
  }

  // update one digit
  update_one_digit();
  
  delay(4);
}

範例說明

  • 如何僅使用一個 shift register 來驅動四個顯示器?此例使用視覺暫留的特性,利用 sketch 的 loop() 函式快速更新顯示器上的各個位數。

  • 各個位數的狀態開/關透過 control_pins 陣列進行控制。

  • 當數字筆畫資訊送達至 shift register 後,sketch 即透過將 control_pins[scan_position] 訊號設成 HIGH 以開啟對應的七段顯示器。

  • 由於每次的 loop() 函式僅會更新一個位數,為了達成視覺暫留效果,loop() 函式必須頻繁地不斷執行。

綜合以上要點,開發者即可使用單個 shift register 控制四位數顯示器。相較於原本「8 根 GPIO 才能控制一個七段顯示器」的做法,此範例僅需 LinkIt 7697 的 7 根控制訊號即能驅動四位數顯示器 (而非 32 根訊號),達到減少訊號腳使用量及降低電路佈線複雜度的效果。

結論

在與使用者互動的各式場景中,七段顯示器是最為常用的顯示裝置之一,如何正確且有效率地使用七段顯示器也因此變得重要。在本教學中介紹了三種驅動七段顯示器的方法:

  1. 單純的 LED 開關機制。

  2. 使用單個 shift register 驅動單個七段顯示器。

  3. 透過視覺暫留,使用單一 shift register 驅動四個七段顯示器。

經由操作這三種方式的過程,可瞭解到七段顯示器的驅動原理、使用 shift register 作序列轉並列資料轉換的方式、以及視覺暫留特性的實際應用。在下一篇教學裡,將示範如何讓 LinkIt 7697 搭配七段顯示器專用驅動晶片 MAX7219 操作八位數七段顯示器模組。

Last updated