單片機學習(三)開發板動態數碼管的控制


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

可以看出來,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,我們可以

  1. toSet = toSet & 0b1111 0000,此時toSet的值為0b1010 0000,即我們將它的低4位設置為了0
  2. toSet = 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; 進行測試。):
在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM