以多工掃描的方式驅動七段顯示器,要如何避免殘影現象?
很多人實作多工掃描驅動七段顯示器時都遇過「殘影」的情況,正好最近一個案子用到了多工掃描,順便把文章寫一寫紀錄起來,讓大家之後也能參考。
先來解釋一下「殘影」具體是什麼情況可以看下面這張比較圖,左圖可以看到每個位數有右邊一個位數的殘影;右圖則是多工掃描有處理好
這個案子客戶的要求不複雜,就是要一個可以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它來完成掃描動作。請對照文章最下面的完整程式碼。
這邊來做個錯誤示範:先切換掃描線再切換資料,發現會有殘影的情況。為什麼呢?要記得,程式執行一定是有先後的,掃描線切換到下一位時,前一個位數的資料還持續在輸出,就會將前一個位數短暫的顯示出來。從程式可以看出我們是從右邊掃到左邊的位數,從實作結果也能觀察出,都有右邊那個位數的「影子」。
- void showNum(uint32_t u32num)
- {
- static uint8_t u8digit = 0x10;
- P0 = u8digit;
- switch (u8digit) {
- case 0x10:
- P1 = u8segments[u32num % 10];
- break;
- case 0x20:
- P1 = u8segments[u32num / 10 % 10];
- break;
- case 0x40:
- P1 = u8segments[u32num / 100 % 10];
- break;
- case 0x80:
- P1 = u8segments[u32num / 1000];
- break;
- default:
- u8digit = 0x10;
- }
- if (u8digit < 0x80)
- u8digit = u8digit << 1;
- else
- u8digit = 0x10;
- }
OK那麼原因清楚了,正確的做法就是,切換位數之前要將輸出「短暫關閉」,才不會將前一個位數「短暫顯示」出來。所以在這邊我先將P0 = 0;關閉掃描線的輸出,才把P1改成下一位的數字資料,最後再從P0 = u8digit;把掃描線輸出出去。當然,要讓輸出短暫關閉也可以是關閉資料線。可能的順序如下:
關閉掃描線 > 輸出新資料 > 更新掃描線
關閉資料線 > 更新掃描線 > 輸出新資料
- void showNum(uint32_t u32num)
- {
- static uint8_t u8digit = 0x10;
- P0 = 0;
- switch (u8digit) {
- case 0x10:
- P1 = u8segments[u32num % 10];
- break;
- case 0x20:
- P1 = u8segments[u32num / 10 % 10];
- break;
- case 0x40:
- P1 = u8segments[u32num / 100 % 10];
- break;
- case 0x80:
- P1 = u8segments[u32num / 1000];
- break;
- default:
- u8digit = 0x10;
- }
- P0 = u8digit;
- if (u8digit < 0x80)
- u8digit = u8digit << 1;
- else
- u8digit = 0x10;
- }
完整程式碼:
- #include "numicro_8051.h"
- __code const uint8_t u8segments[16] = {
- // abcdefg.
- 0b11111100, 0b01100000, 0b11011010, 0b11110010, 0b01100110, 0b10110110, 0b00111110, 0b11100000,
- 0b11111110, 0b11100110, 0b11101110, 0b00111110, 0b10011100, 0b01111010, 0b10011110, 0b10001110
- };
- void showNum(uint32_t u32num)
- {
- static uint8_t u8digit = 0x10;
- P0 = 0;
- switch (u8digit) {
- case 0x10:
- P1 = u8segments[u32num % 10];
- break;
- case 0x20:
- P1 = u8segments[u32num / 10 % 10];
- break;
- case 0x40:
- P1 = u8segments[u32num / 100 % 10];
- break;
- case 0x80:
- P1 = u8segments[u32num / 1000];
- break;
- default:
- u8digit = 0x10;
- }
- P0 = u8digit;
- if (u8digit < 0x80)
- u8digit = u8digit << 1;
- else
- u8digit = 0x10;
- }
- void main(void)
- {
- uint32_t u32num = 1234;
- TA = 0xAA;
- TA = 0x55;
- CKEN |= 0xC0; // EXTEN
- while(!(CKSWT & 0x08)); // ECLKST
- TA = 0xAA;
- TA = 0x55;
- CKSWT |= 0x02; // Switch to external clock source
- while(CKEN & 0x01); // CKSWTF
- TA = 0xAA;
- TA = 0x55;
- CKEN &= 0xDF; // Disable HIRC
- P0M1 &= 0x0F;
- P0M2 |= 0xF0;
- /* P1 as push-pull output mode */
- P1M1 = 0x00;
- P1M2 = 0xFF;
- CKCON |= 0x08; // Timer 0 source from Fsys directly
- TH0 = (uint8_t)(41536 >> 8); // 65536 - 24000
- TL0 = (uint8_t)(41536 & 0xFF);
- TMOD |= 0x01; // Timer 0 mode 1
- TCON |= 0x10; // Timer 0 run
- while(1)
- {
- showNum(u32num);
- /* 1 ms delay */
- while (!(TCON & 0x20)); // Wait until timer 0 overflow
- TH0 = (uint8_t)(41536 >> 8); // 65536 - 24000
- TL0 = (uint8_t)(41536 & 0xFF);
- TCON &= 0xDF; // Clear TF0
- }
- }
Pentax WG-8鮮明模式
留言
張貼留言