分析動態數碼管控制的原理

可以看出來,P0寄存器的每一個bit分別與輸入a~dp聯系起來,當對應的引腳為高電平時,對應的LED則點亮。
但是我們也可以發現每個8位數碼管都是由輸入a~dp進行控制的,那豈不是我們輸入一個值,每個LED都呈現相同的內容了?但其實除了P0寄存器之外,P2的第2-4位作為了動態數碼管的使能位,例如當這三位為1,1,1時,此時編號為0的寄存器處於使能狀態,它可以輸出發光的內容,而其他寄存器處於禁用狀態。
由此可見,我們在同一個時刻最多只能有1個八位數碼管可以發光,其他的7個都是熄滅的,那如何使我們能看到它們同時發光呢?其實很簡單,只要使能位切換得夠快,我們肉眼就會發現變化的幾個數碼管都是發光的了。
編碼實現效果
數碼管計數器
首先我們需要先將0~F這16個字符使用數碼管表示出來,例如0這個字符,輸入a,b,c,d,e,f應該為1(即高電平),則我們輸入給P0的值應該是0b0011 1111,即0x3f,這樣再設置P2[2..4]=0b111(設置使能位,使第0個數碼管可以工作),這樣我們就可以在第0個數碼管看到字符0了,其他以此類推,就可以得到這16個字符對應的二進制編碼。
為了方便使用,我們使用一個數組將這16個字符的編碼存儲起來:
u8 code smgduan[16]={
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71
};
然后我們可以實現計數器,即使一個數碼管從0到F不斷地變化,核心代碼如下:
int main() {
u8 i;
P0 = smgduan[2];
while (1)
{
for(i=0;i<16;i++) {
P0 = smgduan[i];
defaultDeley();
}
}
}
因為P2[2..4]默認為0b111,即默認是第一個數碼管是可以顯示的,所以我們會看到第一個數碼管由0變化到F。
運行效果:

數碼管流水燈
之前已經實現了LED流水燈,這次希望可以實現數碼管的流水燈。要實現它,我們需要完成數碼管索引到使能位的映射,即當我指定一個編號的數碼管使能時,它就能工作。
我們先看一下映射關系:
這里0號數碼管使能時,P2[2..4]=0b111;當1號數碼管使能時,P2[2..4]=0b110...
可以發現這里有個明顯的對應關系,設數碼管編號為index,則只需令P2[2..4]=0b111-index
,即可使對應的數碼管工作了,但是如何使結果賦值到P2[2..4]這三位中呢?
此時我們可以使用“與置零,或置一”的方法進行部分賦值,例如下面的一個例子:
有變量toSet == 0b1010 0011
此時我們希望使它的低4位變成1010
,我們可以
toSet = toSet & 0b1111 0000
,此時toSet
的值為0b1010 0000
,即我們將它的低4位設置為了0toSet = toSet | 0b0000 1010
,此時toSet
的值為0b1010 1010
,完成了要求
即我們對某幾位賦值時可以先用與運算符清零,再使用或運算符賦值完成操作,而這里需要修改P[2...4]三位,我們先使用0x1110 0011(即~(0x7<<2)
)使用與運算符進行清零,再將(0b111-index)<<2
使用或運算符進行賦值即可,代碼如下:
void enableIndexLED(u8 index) {
P2 = P2 & ~(0x07 << 2);
P2 = P2 | (0x7 - index)<<2;
}
如此一來,我們的流水燈就可以輕松實現,核心代碼如下(這里流水燈的數字為‘2’):
int main() {
u8 i = 0;
P0 = smgduan[2];
while (1)
{
for(i = 0;i<8;i++) {
enableIndexLED(i);
defaultDeley();
}
}
}
運行效果:

顯示0~65535的數字
首先我們將各個數碼管需要顯示的數字使用一個數組進行存儲:u8 array[8];
,這個數組的元素的值被初始化為notDisplay:
#define notDisplay 255
int main() {
u8 array[8];
u8 n= 8;
for(i=0;i<n;i++) {
array[i] = notDisplay;
}
}
然后我們編寫一個函數,接收一個變量num,然后將它分解后填入到array數組中的位置中:
void updateArray(u16 num, u8* array, u8 n) {
u8 i = n-1;
if (num == 0) {
array[i] = 0;
return;
}
while (num)
{
array[i] = num %10;
num /=10;
i--;
}
}
例如數字65535會使數組變為:
array=[notDisplay,notDisplay,notDisplay,6,5,5,3,5]
然后我們將數組的每個非notDisplay的元素映射到數碼管即可。
顯示數字函數:
// 第index個數碼管顯示num數字
void displayOneNum(u8 index, u8 num) {
enableIndexLED(index);
if (num == notDisplay) {
P0 = 0x00;
}else {
P0 = smgduan[num];
}
}
void display(u16 num, u8* array, u8 n) {
u8 i;
updateArray(num, array, n);
for(i=0;i<n;i++) {
displayOneNum(i, array[i]);
deley(100);
P0=0x00;//消隱
}
}
主函數:
int main() {
u8 i = 0;
u8 array[8];
u8 n = 8;
u16 cnt = 1;
for(i=0;i<n;i++) {
array[i] = notDisplay;
}
while (1) {
display(12345,array,n);
}
}
這樣我們就可以在數碼管中看到“65535”的數字字符串了,運行結果:

計數器升級版
這次的計數器是可以從0計數到65535的
displayForAWhile()
函數如下所示:
void displayForAWhile(u16 num, u8* array, u8 n) {
u8 times = 125;
while (times--)
{
display(num,array,n);
}
}
當輸入一個數字時,這個函數中會重復顯示這個數字125次,因為1s大概是deley(100000)消耗的時間,這里渲染一次一個數字的時間大概是deley(100*8)=deley(800),然后渲染125次的時間約為deley(100000),即這個函數會顯示num約一秒鍾,所以我們可以用來做個簡單的計數器,主函數代碼如下:
int main() {
u8 i = 0;
u8 array[8];
u8 n = 8;
u16 cnt = 1;
while (1)
{
for(i=0;i<n;i++) {
array[i] = notDisplay;
}
for(cnt = 0; cnt <= 65535; cnt++) {
displayForAWhile(cnt, array, n);
}
}
}
這樣它就會從0一直變化到65535了。
運行結果(這是加快后的運行結果):

模擬時鍾
tips: 在設計的過程中其實使用到了面向對象的思想,那些Time_xxx的函數其實在觀念上是Time類的成員函數或靜態函數,這樣設計可以更加有條理。
首先先設計存儲時間的結構體:
typedef struct {
u8 hour, minute, second;
} Time;
時間遞增函數,每調用一次秒數加一,然后再處理進位:
void Time_increace(Time *time) {
time->second++;
if (time->second==60)
{
time->second = 0;
time->minute++;
if (time->minute==60)
{
time->minute = 0;
time->hour++;
if (time->hour == 24)
{
time->hour = 0;
}
}
}
}
數字分解函數:
void Time_split(u8 num, u8* first, u8* second) {
*second = num%10;
num/=10;
*first = num%10;
}
即功能是輸入一個數字,這里是時或秒或分,把它分解成兩個數並存儲到first和second指針指向的空間。
數據展示函數:
static void Time_displayData(u8 num, u8 startIndex) {
u8 first, second;
Time_split(num, &first, &second);
displayOneNum(startIndex, first);
deley(100);
P0=0x00;//消隱
displayOneNum(startIndex+1,second);
deley(100);
P0=0x00;//消隱
}
輸入一個數字,為時或秒或分,先將這個數字分解成兩個數字,然后分別展示到第startIndex個數碼管和第startIndex+1個數碼管上。
中間分隔符展示函數:
void Time_displaySplitor() {
displayOneChar(2,0x40);
deley(100);
P0=0x00;//消隱
displayOneChar(5,0x40);
deley(100);
P0=0x00;//消隱
}
總的時間展示函數:
void Time_show(Time* time) {
Time_displayData(time->hour, 0);
Time_displayData(time->minute, 3);
Time_displayData(time->second, 6);
Time_displaySplitor();
}
展示一個時間一秒鍾的函數:(即展示一個時間100次,大概1s的時間)
Time_showForASecond(Time* time) {
u8 times = 100;
while (times--) {
Time_show(time);
}
}
主函數:
int main() {
Time time;
time.hour = time.minute = time.second = 0;
while (1)
{
Time_showForASecond(&time);
Time_increace(&time);
}
}
這樣我們就實現了計時的功能。
運行效果(這里使用time.hour = 23; time.minute = 59; time.second = 50;
進行測試。):