以多工掃描的方式驅動七段顯示器,要如何避免殘影現象?

很多人實作多工掃描驅動七段顯示器時都遇過「殘影」的情況,正好最近一個案子用到了多工掃描,順便把文章寫一寫紀錄起來,讓大家之後也能參考。

先來解釋一下「殘影」具體是什麼情況可以看下面這張比較圖,左圖可以看到每個位數有右邊一個位數的殘影;右圖則是多工掃描有處理好 


這個案子客戶的要求不複雜,就是要一個可以1~6 KHz輸出頻率可調的方波產生器,外加斷電記憶的功能,下次復電要能輸出上次調整的頻率。當然,調整到幾KHz還是要讓使用者知道阿,於是我就選擇用四位數七段顯示器。

這次選用了新唐的MG51FB9AE,因為它可以跑在24 MHz,而之前常用的N76S003是16 MHz,兩顆是pin-to-pin相容的。在這邊會需要較高的時脈速度並不是為了運算效能,而是輸出頻率的解析度,因為PWM或Timer輸入頻率越高,輸出的除頻步數就要越多,頻率解析度也會越高。如果對輸出頻率或duty cycle精度有要求的讀者,這就可以列入選型的考量了。


先來簡介一下多工掃描:多工掃描是什麼?

下圖是截自專案的部分電路。圖中有一顆億光ELF-511,是非常標準的四位數共陰極七段顯示器,四個位數的a~g和dot都是在顯示器裡面直接並聯,每個位數的共點(陰極)則是分開的。

每個時間點P0的信號只會讓Q1~Q4其中一顆導通,並在導通的時候從P1送出對應a~g和dot的資料。也就是輪流點亮每一個位數,當輪流的速度快過人眼的視覺暫留,我們看上去就會像是它們一直都亮著一樣。


多工掃描的好處?

如果四位數七段顯示器每一個位數獨立控制的話,共點可以直接接電源,還需要4個a~g和dot,共32支接腳來控制。

多工掃描的話就只需要1個a~g和dot加4條掃描線,只要12支接腳就能控制四位數七段顯示器。

少了十多支接腳在設計上是非常可觀的,無論在BOM cost、電路面積上或是layout難易度都有優勢。想一想,12支腳我用TSSOP20的MG51FB9AE就可以做完,若是32支腳的話得挑到48-pin的MCU才能做了。


韌體的寫法就是建一個陣列u8segments[16]把0~F (0~16, 0x0~0xF)排出來,利用餘數除法在switch case中將數字u32num的個、十、百、千位數分別提取出來,然後利用查表法去查u8segments[]將它們送出到P1; switch case則是根據u8digit來選擇,同時也會將u8digit輸出到當成掃描線信號的P0,而u8digit每次執行都會往左移一位,即0x10 -> 0x20 -> 0x40 -> 0x80,到了0x80以後會再回到0x10重新開始;這麼一來,被點亮的位數就會和switch case提取並輸出到P1的數字同步。我將這個過程包成一個showNum()的函式,然後用一個1 ms的迴圈不斷去call它來完成掃描動作。請對照文章最下面的完整程式碼。


這邊來做個錯誤示範:先切換掃描線再切換資料,發現會有殘影的情況。為什麼呢?要記得,程式執行一定是有先後的,掃描線切換到下一位時,前一個位數的資料還持續在輸出,就會將前一個位數短暫的顯示出來。從程式可以看出我們是從右邊掃到左邊的位數,從實作結果也能觀察出,都有右邊那個位數的「影子」。

  1. void showNum(uint32_t u32num)
  2. {
  3. static uint8_t u8digit = 0x10;
  4.  
  5. P0 = u8digit;
  6.  
  7. switch (u8digit) {
  8. case 0x10:
  9. P1 = u8segments[u32num % 10];
  10. break;
  11.  
  12. case 0x20:
  13. P1 = u8segments[u32num / 10 % 10];
  14. break;
  15.  
  16. case 0x40:
  17. P1 = u8segments[u32num / 100 % 10];
  18. break;
  19.  
  20. case 0x80:
  21. P1 = u8segments[u32num / 1000];
  22. break;
  23. default:
  24. u8digit = 0x10;
  25. }
  26.  
  27. if (u8digit < 0x80)
  28. u8digit = u8digit << 1;
  29. else
  30. u8digit = 0x10;
  31. }

OK那麼原因清楚了,正確的做法就是,切換位數之前要將輸出「短暫關閉」,才不會將前一個位數「短暫顯示」出來。所以在這邊我先將P0 = 0;關閉掃描線的輸出,才把P1改成下一位的數字資料,最後再從P0 = u8digit;把掃描線輸出出去。當然,要讓輸出短暫關閉也可以是關閉資料線。可能的順序如下:

關閉掃描線 > 輸出新資料 > 更新掃描線
關閉資料線 > 更新掃描線 > 輸出新資料

  1. void showNum(uint32_t u32num)
  2. {
  3. static uint8_t u8digit = 0x10;
  4.  
  5. P0 = 0;
  6.  
  7. switch (u8digit) {
  8. case 0x10:
  9. P1 = u8segments[u32num % 10];
  10. break;
  11.  
  12. case 0x20:
  13. P1 = u8segments[u32num / 10 % 10];
  14. break;
  15.  
  16. case 0x40:
  17. P1 = u8segments[u32num / 100 % 10];
  18. break;
  19.  
  20. case 0x80:
  21. P1 = u8segments[u32num / 1000];
  22. break;
  23. default:
  24. u8digit = 0x10;
  25. }
  26.  
  27. P0 = u8digit;
  28.  
  29. if (u8digit < 0x80)
  30. u8digit = u8digit << 1;
  31. else
  32. u8digit = 0x10;
  33. }

完整程式碼:

  1. #include "numicro_8051.h"
  2.  
  3. __code const uint8_t u8segments[16] = {
  4. // abcdefg.
  5. 0b11111100, 0b01100000, 0b11011010, 0b11110010, 0b01100110, 0b10110110, 0b00111110, 0b11100000,
  6. 0b11111110, 0b11100110, 0b11101110, 0b00111110, 0b10011100, 0b01111010, 0b10011110, 0b10001110
  7. };
  8.  
  9. void showNum(uint32_t u32num)
  10. {
  11. static uint8_t u8digit = 0x10;
  12.  
  13. P0 = 0;
  14.  
  15. switch (u8digit) {
  16. case 0x10:
  17. P1 = u8segments[u32num % 10];
  18. break;
  19.  
  20. case 0x20:
  21. P1 = u8segments[u32num / 10 % 10];
  22. break;
  23.  
  24. case 0x40:
  25. P1 = u8segments[u32num / 100 % 10];
  26. break;
  27.  
  28. case 0x80:
  29. P1 = u8segments[u32num / 1000];
  30. break;
  31. default:
  32. u8digit = 0x10;
  33. }
  34.  
  35. P0 = u8digit;
  36.  
  37. if (u8digit < 0x80)
  38. u8digit = u8digit << 1;
  39. else
  40. u8digit = 0x10;
  41. }
  42.  
  43. void main(void)
  44. {
  45. uint32_t u32num = 1234;
  46.  
  47. TA = 0xAA;
  48. TA = 0x55;
  49. CKEN |= 0xC0; // EXTEN
  50. while(!(CKSWT & 0x08)); // ECLKST
  51. TA = 0xAA;
  52. TA = 0x55;
  53. CKSWT |= 0x02; // Switch to external clock source
  54. while(CKEN & 0x01); // CKSWTF
  55. TA = 0xAA;
  56. TA = 0x55;
  57. CKEN &= 0xDF; // Disable HIRC
  58.  
  59. P0M1 &= 0x0F;
  60. P0M2 |= 0xF0;
  61. /* P1 as push-pull output mode */
  62. P1M1 = 0x00;
  63. P1M2 = 0xFF;
  64.  
  65. CKCON |= 0x08; // Timer 0 source from Fsys directly
  66. TH0 = (uint8_t)(41536 >> 8); // 65536 - 24000
  67. TL0 = (uint8_t)(41536 & 0xFF);
  68. TMOD |= 0x01; // Timer 0 mode 1
  69. TCON |= 0x10; // Timer 0 run
  70.  
  71. while(1)
  72. {
  73. showNum(u32num);
  74.  
  75. /* 1 ms delay */
  76. while (!(TCON & 0x20)); // Wait until timer 0 overflow
  77. TH0 = (uint8_t)(41536 >> 8); // 65536 - 24000
  78. TL0 = (uint8_t)(41536 & 0xFF);
  79. TCON &= 0xDF; // Clear TF0
  80. }
  81. }

Pentax WG-8鮮明模式

留言

這個網誌中的熱門文章

無法被取代的指針型三用電表(一):前言

關於新唐科技NuMicro ISP的介紹和使用方式

新唐火神板開箱實作(一):NuMaker-Volcano與NuEclipse IDE入門篇