我們現(xiàn)在走在馬路上,經(jīng)常看到馬路兩側(cè)有一些LED點陣廣告牌,這些廣告牌看起來絢爛奪目,非常吸引人,而且還會變化很多種不同的顯示方式。本章我們就會學(xué)習(xí)到點陣LED的控制方式,按照慣例,先普及部分C語言知識。
7.1 變量的作用域
所謂的作用域就是指變量起作用的范圍。變量按他的作用域可以分為局部變量和全局變量
1.局部變量
在一個函數(shù)內(nèi)部聲明的變量是內(nèi)部變量,他只在本函數(shù)內(nèi)有效,在此函數(shù)以外是不能使用的,這樣的變量就是局部變量。此外,函數(shù)的形參也是局部變量,形參我們后邊詳細(xì)解釋。
比如上節(jié)課定義的unsigned long stopwatch = 0,這個變量是定義在main函數(shù)內(nèi)部的,所以只能由main函數(shù)使用,中斷函數(shù)就不能用這個變量。同理,我們?nèi)绻谥袛嗪瘮?shù)內(nèi)部定義的變量,在main函數(shù)中也是不能使用的。
2.全局變量
在函數(shù)外聲明的變量是全局變量。一個源程序文件可以包含一個或者多個函數(shù),全局變量的作用范圍是從它開始聲明的位置一直到程序結(jié)束。
比如上節(jié)課的unsigned char LedNumber[6] = {0}; 這個數(shù)組的作用域就是從開始定義的位置一直到程序結(jié)束,不管是main函數(shù),還是中斷函數(shù)InterruptTimer0,都可以直接使用這個數(shù)組。
局部變量只有在聲明它的函數(shù)范圍內(nèi)有效,而全局變量可以被作用域內(nèi)的所有的函數(shù)直接引用。所以在一個函數(shù)內(nèi)既可以使用本函數(shù)內(nèi)聲明的局部變量,也可以使用全局變量。在習(xí)慣上,我們把全局變量定義在我們程序所有函數(shù)的最前邊。
由于函數(shù)通常只能有一個返回值,但是我們希望一個函數(shù)運行完了可以提供多個結(jié)果值給我們使用的時候,我們就可以利用全局變量來實現(xiàn)。但是考慮到全局變量的一些特征,應(yīng)該限制全局變量的使用,過多使用全局變量也會帶來一些問題。
(1)全局變量可以被作用域內(nèi)所有的函數(shù)直接引用,可以增加函數(shù)間數(shù)據(jù)聯(lián)系的途徑,但同時加強了函數(shù)模塊之間的數(shù)據(jù)聯(lián)系,使這些函數(shù)的獨立性降低,對其中任何一個函數(shù)的修改都可能會影響到其他函數(shù),函數(shù)之間過于緊密的聯(lián)系不利于程序的維護(hù)。
(2)全局變量的應(yīng)用會降低函數(shù)的通用性,函數(shù)在執(zhí)行的時候過多依賴于全局變量,不利于函數(shù)的重復(fù)利用。我們現(xiàn)在程序編寫比較簡單,就一個.c文件,將來以后我們要學(xué)到一個程序中有多個.c文件,當(dāng)一個函數(shù)被另外一個.c文件調(diào)用的時候,必須將這個全局變量的變量值一起移植,而全局變量不只被一個函數(shù)調(diào)用,這樣會引起一些不可預(yù)見的后果。
(3)過多使用全局變量會降低程序的清晰度,使程序的可讀性下降。在各個函數(shù)執(zhí)行的時候都可能改變?nèi)肿兞恐担y以清楚的判斷出每個時刻各個全局變量的值。
(4)定義全局變量會直接占用單片機的內(nèi)存單元,而局部變量只有進(jìn)入定義局部變量的函數(shù)內(nèi)才會分配內(nèi)存,函數(shù)退出后會自動釋放所占用的內(nèi)存。所以大量的全局變量會額外增加內(nèi)存占用。
綜上所述之原因,我們一項原則就是盡量減少全局變量的使用,能用局部變量代替的就不用全局變量。
還有一種特殊情況,大家在看別人程序的時候注意。C語言是允許局部變量和全局變量同名的,他們定義后在內(nèi)存中占有不同的內(nèi)存單元。如果在同一源文件中,全局變量和局部變量同名,在局部變量作用域范圍內(nèi),只有局部變量有效,全局變量不起作用,也就是說局部變量具有更高優(yōu)先級。但是我們在編寫程序的時候,盡量不要讓變量重名,以避免不必要的誤解。
.2 變量的存儲類別
變量的存儲類別分為自動、靜態(tài)、寄存器和外部這四種。其中后兩種我們暫不介紹,主要是自動變量和靜態(tài)變量這兩種。
函數(shù)中的局部變量,如果不加static這個關(guān)鍵字來進(jìn)行特別聲明,都屬于自動變量,也叫做動態(tài)存儲變量。這些存儲類別的變量,在調(diào)用該函數(shù)的時候系統(tǒng)會給他們分配存儲空間,在函數(shù)調(diào)用結(jié)束后會自動釋放這些存儲空間。動態(tài)存儲變量的關(guān)鍵字是auto,但是這個關(guān)鍵字是可以省略的,所以我們平時都不用。
那么與動態(tài)變量對應(yīng)的就是靜態(tài)變量。首先,全局變量均是靜態(tài)變量,此外,還有一種特殊的局部變量也是靜態(tài)變量。即我們在局部變量聲明前邊加上static這個關(guān)鍵字,加上這個關(guān)鍵字的變量就稱之為靜態(tài)局部變量,他的特點是,在整個生存期中只賦一次初值,函數(shù)調(diào)用的時候,如果是第一次調(diào)用,它的值就是我們給定的那個初值;如果不是第一次調(diào)用,那么它的值就是上一次函數(shù)調(diào)用結(jié)束后的值。
在某一些場合中,一些變量只在一個函數(shù)中使用了,但是這個變量每次變化的值我們還想保存,如果定義成局部動態(tài)變量的話,每次進(jìn)入函數(shù)后上一次的值就丟失了,如果定義成全局變量的話,又違背了我們上面提到的關(guān)于全局變量使用的一般原則,這個時候我們就可以定義成局部靜態(tài)變量了。
比如上節(jié)課中斷程序中有一個用于動態(tài)刷新數(shù)碼管控制的變量j,我們上節(jié)課的程序是定義成了全局變量,現(xiàn)在我們可以直接改成局部靜態(tài)變量來試試。
#include <reg52.h> //包含寄存器的庫文件
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //用數(shù)組來表示數(shù)碼管真值表
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e,
};
unsigned char LedNumber[6] = {0}; //定義全局變量
unsigned int counter = 0;
void main()
{
unsigned long stopwatch =0;
ENLED = 0; ADDR3 = 1; P0 = 0XFF; //74HC138和P0初始化部分
TMOD = 0x01; //設(shè)置定時器0為模式1
TH0 = 0xFC;
TL0 = 0x67; //定時值初值,定時1ms
TR0 = 1; //打開定時器0
EA = 1; //打開中中斷
ET0 = 1; //打開定時器0中斷
while(1)
{
if(1000 == counter) //判斷定時器0溢出是否達(dá)到50次
{
counter = 0;
stopwatch++;
LedNumber[0] = stopwatch%10;
LedNumber[1] = stopwatch/10%10;
LedNumber[2] = stopwatch/100%10;
LedNumber[3] = stopwatch/1000%10;
LedNumber[4] = stopwatch/10000%10;
LedNumber[5] = stopwatch/100000%10;
}
}
}
void InterruptTimer0() interrupt 1 //中斷函數(shù)的特殊寫法,數(shù)字’1’為中斷入口號
{
static unsigned char j = 0;
TH0 = 0xFC; //溢出后進(jìn)入中斷重新賦值
TL0 = 0x67;
counter++; //計數(shù)值counter加1
P0 = 0xFF; //消隱
switch(j)
{
case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]]; break;
case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]]; break;
case 2: ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]]; break;
case 3: ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]]; break;
case 4: ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]]; break;
case 5: ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]]; break;
default: break;
} //動態(tài)刷新
}
大家注意看這個程序的中斷函數(shù)的靜態(tài)變量j,如果加上了static,他的初始化j = 0操作只進(jìn)行一次,下邊的程序會進(jìn)行j++操作,下次進(jìn)入中斷函數(shù)的時候,j會保持上次的值。但是如果去掉static這個關(guān)鍵字,那每次進(jìn)入函數(shù)后,j都會被初始化成0,大家可以自己修改程序做嘗試。
1.3 點陣LED的初步認(rèn)識
點陣LED顯示屏作為一種現(xiàn)代電子媒體,具有靈活的顯示面積(可分割、任意拼裝)、高亮度、長壽命、數(shù)字化、實時性等特點,應(yīng)用非常廣泛。
前邊學(xué)了LED小燈和LED數(shù)碼管后,學(xué)LED點陣就要輕松得多了。一個數(shù)碼管是8個LED組成,同理,一個8*8的點陣是由64個LED小燈組成。圖7-1就是一個點陣LED最小單元,一個8*8的點陣LED,圖7-2是它的內(nèi)部結(jié)構(gòu)圖。

圖7-1 8*8點陣LED

7-2 8*8點陣LED結(jié)構(gòu)原理圖
點陣LED內(nèi)部原理圖如圖7-2所示,從7-2圖上可以看出來,其實點陣LED點亮原理還是很簡單的。在我們圖上藍(lán)色方框外側(cè)的就是點陣LED的引腳號,左側(cè)的8個引腳是接的內(nèi)部LED的陽極,上側(cè)的8個引腳接的是內(nèi)部LED的陰極。那從圖上可以看出來,我們的9腳如果是高電平,13腳是低電平的話,最左上角的那個LED小燈就會亮,那我們用程序來實現(xiàn)一下,特別注意,我們現(xiàn)在用的74HC138是原理圖上的U4。
#include <reg52.h> //包含寄存器的庫文件
sbit LED = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main()
{
ENLED = 0;
ADDR0 = 0;
ADDR1 = 0;
ADDR2 = 0;
ADDR3 = 0; //74HC138開啟三極管
LED = 0; //點亮點陣的一個點
while(1); //程序停止在這里
}
同樣的方法,我們可以點亮點陣的任意一行,74HC 138的導(dǎo)通點陣所用的三極管的方法和數(shù)碼管很類似,那我們現(xiàn)在來點亮第二行整行的LED。
#include <reg52.h> //包含寄存器的庫文件
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main()
{
ENLED = 0;
ADDR0 = 1;
ADDR1 = 0;
ADDR2 = 0;
ADDR3 = 0; //74HC138開啟三極管
P0 = 0x00; //點亮小燈
while(1); //程序停止在這里
}
從這里我們逐步發(fā)現(xiàn)了一個問題,其實我們講一個數(shù)碼管就是8個LED小燈,一個點陣是64個LED小燈。同樣的道理,我們還可以把一個點陣?yán)斫獬?/font>8個數(shù)碼管。我們前邊掌握了6個數(shù)碼管的同時顯示方法,那8個數(shù)碼管也應(yīng)該輕輕松松了。我們先把這個點陣全部點亮。
#include <reg52.h> //包含寄存器的庫文件
sbit LED = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main()
{
ENLED = 0;
ADDR3 = 0;
TMOD = 0x01; //設(shè)置定時器0為模式1
TH0 = 0xFC;
TL0 = 0x67; //定時值初值,定時1ms
TR0 = 1; //打開定時器0
EA = 1; //打開中中斷
ET0 = 1; //打開定時器0中斷
while(1); //程序停止在這里,定時器運行,等待定時器中斷
}
void InterruptTimer0() interrupt 1 //中斷函數(shù)
{
static unsigned char j = 0;
TH0 = 0xFC; //溢出后進(jìn)入中斷重新賦值
TL0 = 0x67;
P0 = 0XFF; //消隱
switch(j)
{
case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; break;
case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; break;
case 2: ADDR0=0; ADDR1=1; ADDR2=0; j++; break;
case 3: ADDR0=1; ADDR1=1; ADDR2=0; j++; break;
case 4: ADDR0=0; ADDR1=0; ADDR2=1; j++; break;
case 5: ADDR0=1; ADDR1=0; ADDR2=1; j++; break;
case 6: ADDR0=0; ADDR1=1; ADDR2=1; j++; break;
case 7: ADDR0=1; ADDR1=1; ADDR2=1; j=0; break;
default: break;
} //動態(tài)刷新
P0=0x00;
}
7.4 點陣LED圖形顯示
我們的小燈可以實現(xiàn)流水燈,數(shù)碼管可以顯示數(shù)字,那點陣LED就得來顯示點花樣了。
我們要顯示花樣的時候,往往要做出來一些小圖形,這些小圖形的數(shù)據(jù)要轉(zhuǎn)換到我們的程序當(dāng)中去,這個時候就需要取模軟件。來給大家介紹一款簡單的取模軟件,這個取模軟件可以在http://www.zg4o1577.cn 的軟件下載區(qū)下載到,大家來了解一下如何用,先看一下操作界面,如圖7-3所示。

圖7-3 字模提取軟件界面
鼠標(biāo)點一下“新建圖形”,根據(jù)我們板子上的點陣,把寬度和高度分別改成8,然后點確定,如圖7-4所示。

圖7-4 新建圖像
我們點左側(cè)的“模擬動畫”菜單,點擊“放大格點”選項,一直放大到最大,那我們就可以在我們的8*8的點陣圖形中用鼠標(biāo)填充黑點,就可以來畫圖形,如圖7-5所示。

圖7-5 字模提取軟件畫圖
經(jīng)過我們一番設(shè)計,畫出來一個心形圖形,并且填充滿,最終出現(xiàn)我們想要的效果圖,如圖7-6所示。

圖7-6 字模軟件心形顯示
由于取模軟件是把黑色取為1,白色取為0,但我們點陣是1對應(yīng)LED熄滅,0對應(yīng)LED點亮,而我們需要的是一顆點亮的“心”,所以我們要選“修改圖像”菜單里的“黑白反顯圖像”這個選項,并且點擊“基本操作”菜單里邊的“保存圖像”可以把我們設(shè)計好的圖片進(jìn)行保存,如圖7-7所示。

圖7-7 保存圖像
保存圖像只是為了你下次使用打開方便,你也可以不保存。操作完了這一步后,點一下“參數(shù)設(shè)置”菜單里的“其他選項”,如圖7-8所示。

圖7-8 選項設(shè)置
這個選項設(shè)置,要根據(jù)我們的圖7-2對照來看,大家可以看到我們的P0總線,控制的是一行,所以我們用的是“橫向取模”,如果控制的是一列,就要選“縱向取模”。“字節(jié)倒序”這個選項,我們選上是因為圖7-2中,我們左邊是低位DB0,右邊是高位DB7,所以必須選上字節(jié)倒序,其他兩個選項大家自己了解,點確定后,選擇“取模方式”這個菜單,點一下“C51 格式”后,在“點陣生成區(qū)”自動產(chǎn)生了8個字節(jié)的數(shù)據(jù),這8個字節(jié)的數(shù)據(jù)就是對應(yīng)取出來的“模”。

圖7-9 取模結(jié)果
大家注意,我們雖然用軟件取模,但是也得知道其原理是什么,在這個圖片里,黑色的一個格子表示一個二進(jìn)制的1,白色的一個格子表示一個二進(jìn)制的0。第一個字節(jié)是0xFF,其實就是這個8*8圖形的第一行,全黑就是0xFF;第二個字節(jié)是0x99,低位在左邊,高位在右邊,大家注意看,黑色的表示1,白色的表示0,就組成了0x99這個數(shù)字。同理其他的數(shù)據(jù)大家也就知道怎么來的了。
我們把這個數(shù)據(jù)送到我們的點陣上去,大家看看什么效果。
#include <reg52.h> //包含寄存器的庫文件
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = {
0xFF,0x99,0x00,0x00,0x00,0x81,0xc3,0xE7
};
void main() //主函數(shù)
{
ENLED = 0;
ADDR3 = 0;
TMOD = 0x01; //設(shè)置定時器0為模式1
TH0 = 0xFC;
TL0 = 0x67; //定時值初值,定時1ms
TR0 = 1; //打開定時器0
EA = 1; //打開中中斷
ET0 = 1; //打開定時器0中斷
while(1); //程序停止在這里,定時器運行,等待定時器中斷
}
void InterruptTimer0() interrupt 1 //中斷函數(shù)
{
static unsigned char j = 0;
TH0 = 0xFC; //溢出后進(jìn)入中斷重新賦值
TL0 = 0x67;
P0 = 0XFF; //消隱
switch(j)
{
case 0: ADDR0=0; ADDR1=0; ADDR2=0; break;
case 1: ADDR0=1; ADDR1=0; ADDR2=0; break;
case 2: ADDR0=0; ADDR1=1; ADDR2=0; break;
case 3: ADDR0=1; ADDR1=1; ADDR2=0; break; //動態(tài)刷新
case 4: ADDR0=0; ADDR1=0; ADDR2=1; break;
case 5: ADDR0=1; ADDR1=0; ADDR2=1; break;
case 6: ADDR0=0; ADDR1=1; ADDR2=1; break;
case 7: ADDR0=1; ADDR1=1; ADDR2=1; break;
default: break;
}
P0 = LedChar[j++];
if(8==j) j=0;
}
對于8*8的點陣來說,我們可以顯示一些簡單的圖形,字符等。一個漢字正常占的點數(shù)是16*16的,8*8的點陣只能顯示一些簡單筆畫的漢字,大家可以自己取模做出來試試。使用大屏顯示漢字的方法和小的方法是類似的,這個內(nèi)容我們考慮以后擴展實驗的時候再進(jìn)行講解。
7.5 點陣LED動畫顯示
點陣的動畫顯示,說到底就是我們對多張圖片進(jìn)行取模,使用程序算法巧妙的切換圖片,多張圖片組合起來就成了一段動畫了,我們所看到的動畫片、游戲等等,都是這么做的。
7.5.1 點陣的縱向移動
上一節(jié)我們學(xué)了如何在點陣上畫一個❤形,有時候我們希望這些顯示是動起來的,而不是靜止的。對于點陣已經(jīng)沒有太多的知識點可以介紹了,主要就是編程的算法問題了。
比如我們現(xiàn)在要讓我們的點陣顯示一個I ❤ U這樣的一個動畫,首先我們要把這個圖形用取模軟件畫出來看一下,如圖7-10所示。

圖7-10 上下移動橫向取模
這張圖片共有40行,每8行組成一張點陣圖片,并且每向上移動一行就出現(xiàn)了一張新圖片,一共組成了32張圖片。
用一個變量index來代表每張圖片的起始位置,每次從index起始向下數(shù)8行代表了當(dāng)前的圖片,250ms改變一張圖片,然后不停的動態(tài)刷新,這樣圖片就變成動畫了。首先我們要對顯示的圖片進(jìn)行橫向取模,雖然這是32張圖片,由于我們每一張圖片都是和下一行連續(xù)的,所以實際的取模值只需要40個字節(jié)就可以完成,我們來看看程序。
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code graph[] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3,0xFF,
0x99,0x00,0x00,0x00,0x81,0xC3,0xE7,0xFF,
0x99,0x99,0x99,0x99,0x99,0x81,0xC3,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
unsigned char index = 0; //圖片刷新索引
void main(void)
{
P0 = 0xFF; //P0口初始化
ADDR3 = 0; //選擇LED點陣
ENLED = 0; //LED顯示總使能
TMOD = 0x01; //設(shè)置定時器0為模式1
TH0 = 0xFC; //定時器初值,定時1ms
TL0 = 0x67;
TR0 = 1; //打開定時器0
ET0 = 1; //使能定時器0中斷
EA = 1; //打開總中斷開關(guān)
while(1);
}
void InterruptTimer0() interrupt 1
{
static unsigned char j = 0;
static unsigned char tmr = 0;
TH0 = 0xFC; //溢出后進(jìn)入中斷重新賦值
TL0 = 0x67;
P0 = 0xFF; //LED點陣動態(tài)刷新
switch (j)
{
case 0: ADDR0=0; ADDR1=0; ADDR2=0; break;
case 1: ADDR0=1; ADDR1=0; ADDR2=0; break;
case 2: ADDR0=0; ADDR1=1; ADDR2=0; break;
case 3: ADDR0=1; ADDR1=1; ADDR2=0; break;
case 4: ADDR0=0; ADDR1=0; ADDR2=1; break;
case 5: ADDR0=1; ADDR1=0; ADDR2=1; break;
case 6: ADDR0=0; ADDR1=1; ADDR2=1; break;
case 7: ADDR0=1; ADDR1=1; ADDR2=1; break;
default: break;
}
P0 = graph[index+j];
j++;
if (j >= 8)
{
j = 0;
}
tmr++; //圖片刷新頻率控制
if (tmr >= 250) //每隔250ms刷新一幀
{
tmr = 0;
index++;
if (index >= 32)
{
index = 0;
}
}
}
大家把這個程序下載進(jìn)去看看效果,一個I ❤ U一直往上走動的動畫出現(xiàn)了,現(xiàn)在還有哪位敢說我們工科同學(xué)不懂浪漫的?還需要用什么玫瑰花取悅女朋友嗎?一點技術(shù)含量都沒有,要玩就玩點高科技,呵呵。
當(dāng)然,別光圖開心,學(xué)習(xí)我們還要繼續(xù)。往上走動的動畫我寫出來了,那往下走動的動畫,大家就要自己獨立完成了,不要偷懶,一定要去寫代碼調(diào)試代碼。瞪眼看只能了解知識,而能力是在真正的寫代碼、調(diào)試代碼這種實踐中培養(yǎng)起來的。
7.5.2 點陣的橫向移動
上下移動移動我們會了,那我們還想左右移動該如何操作呢?
方法一、最簡單,就是把板子側(cè)過來放,縱向取模就可以完成。
這里大家是不是有種頭頂冒汗的感覺?我們要做好技術(shù),但是不能沉溺于技術(shù)。技術(shù)是我們的工具,我們在做開發(fā)的時候除了用好這個工具外,也得多擴展自己的解決問題的思路,要慢慢培養(yǎng)自己的多角度思維方式。
那把板子正過來,左移移動就完不成了嗎?當(dāng)然不是。大家慢慢的學(xué)多了就會培養(yǎng)了一種感覺,就是一旦硬件設(shè)計好了,我們要完成一種功能,大腦就可以直接思考出來能否完成這個功能,這個在我們進(jìn)行電路設(shè)計的時候最為重要。我們在開發(fā)產(chǎn)品的時候,首先是設(shè)計電路,設(shè)計電路的時候,工程師就要在大腦中通過思維來驗證板子硬件和程序能否完成我們想要的功能,一旦硬件做好了,做好板子回來剩下的就是靠編程來完成了。只要是硬件邏輯上沒問題,功能上軟件肯定可以實現(xiàn)。
當(dāng)然了,我們在進(jìn)行硬件電路設(shè)計的時候,也得充分考慮下軟件編程的方便性。因為我們的程序是用P0來控制點陣的整行,所以對于我們這樣的電路設(shè)計,上下移動程序是比較好編寫的。那如果我們設(shè)計電路的時候知道我們的圖形要左右移動,那我們設(shè)計電路畫板子的時候就要盡可能的把點陣橫過來放,有利于我們編程方便,減少軟件工作量。
方法二、利用二維數(shù)組來實現(xiàn),算法基本上和上下移動相似。
二維數(shù)組,前邊提過一次,他的使用其實也沒什么復(fù)雜的。他的聲明方式是:
數(shù)據(jù)類型 數(shù)組名[數(shù)組長度1][數(shù)組長度2];
和一位數(shù)組類似,數(shù)據(jù)類型是全體元素的數(shù)據(jù)類型,數(shù)組名是標(biāo)識符,數(shù)組長度1和數(shù)組長度2分別代表數(shù)組具有的行數(shù)和列數(shù)。數(shù)組元素的下標(biāo)一律從0開始。
例如:
unsigned char a[2][3]; 聲明一個具有2行3列的無符號字符型的二維數(shù)組a
二維數(shù)組的數(shù)組元素個數(shù)是兩個長度的乘積。二維數(shù)組在內(nèi)存中存儲的時候,采用行優(yōu)先的方式來存儲,即在內(nèi)存中先存放第0行的元素,再存放第一行的元素......,同一行中再按照列順序存放,剛才定義的那個a[2][3]的存放形式如表7-1所示。
表7-1 二維數(shù)組的物理存儲結(jié)構(gòu)
a[0][0]
|
a[0][1]
|
a[0][2]
|
a[1][0]
|
a[1][1]
|
a[1][2]
|
二維數(shù)組的初始化方法分兩種情況,我們前邊學(xué)一維數(shù)組的時候?qū)W過,數(shù)組元素的數(shù)量可以小于數(shù)組元素個數(shù),沒有賦值的會自動給0。當(dāng)數(shù)組元素的數(shù)量等于數(shù)組個數(shù)的時候,如下所示:
unsigned char a[2][3] = {{1,2,3},{4,5,6}};或者是
unsigned char a[2][3] = {1,2,3,4,5,6};
當(dāng)數(shù)組元素的數(shù)量小于數(shù)組個數(shù)的時候,如下所示:
unsigned char a[2][3] = {{1,2},{3,4}};等價于
unsigned char a[2][3] = {1,2,0,3,4,0};
而
unsigned char a[2][3] = {1,2,3,4};等價于
unsigned char a[2][3] = {{1,2,3},{4,0,0}};
此外,二維數(shù)組初始化的時候,行數(shù)可以省略,編譯系統(tǒng)會自動根據(jù)列數(shù)計算出行數(shù),但是列數(shù)不能省略。
講這些,只是為了讓大家了解一下,看別人寫的代碼的時候別發(fā)懵就行了,但是我們今后寫程序的時候,我們規(guī)定,行數(shù)列數(shù)都不要省略,全部寫齊,其二,初始化的時候,全部寫成unsigned char a[2][3] = {{1,2,3},{4,5,6}};不允許寫成一維數(shù)組的格式,這樣防止大家出錯,同時也是提高程序的可讀性。
那么下面我們要進(jìn)行橫向做I ❤ U的動畫了,先把我們需要的圖片畫出來,再逐一取模,和上一張圖片類似的是,我們這個圖形共有30張圖片,通過程序每250ms改變一張圖片,并且不停的刷新圖片出來的動畫效果。
但是不同的是,我們這個是要橫向移動,橫向移動的圖片切換的字模數(shù)據(jù)不是連續(xù)的,所以這次我們要對30張圖片分別取模。

圖7-11 動畫取模圖片
30張圖片,終于畫完了,每個圖片是8個字節(jié)的模,分別取模得到了30*8個字節(jié)的數(shù)據(jù),所以我們用二維數(shù)組來表示會比較方便一些。
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code graph[30][8] = {
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}, //動畫幀1
{0xFF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F}, //動畫幀2
{0xFF,0x3F,0x7F,0x7F,0x7F,0x7F,0x7F,0x3F}, //動畫幀3
{0xFF,0x1F,0x3F,0x3F,0x3F,0x3F,0x3F,0x1F}, //動畫幀4
{0xFF,0x0F,0x9F,0x9F,0x9F,0x9F,0x9F,0x0F}, //動畫幀5
{0xFF,0x87,0xCF,0xCF,0xCF,0xCF,0xCF,0x87}, //動畫幀6
{0xFF,0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3}, //動畫幀7
{0xFF,0xE1,0x73,0x73,0x73,0xF3,0xF3,0xE1}, //動畫幀8
{0xFF,0x70,0x39,0x39,0x39,0x79,0xF9,0xF0}, //動畫幀9
{0xFF,0x38,0x1C,0x1C,0x1C,0x3C,0x7C,0xF8}, //動畫幀10
{0xFF,0x9C,0x0E,0x0E,0x0E,0x1E,0x3E,0x7C}, //動畫幀11
{0xFF,0xCE,0x07,0x07,0x07,0x0F,0x1F,0x3E}, //動畫幀12
{0xFF,0x67,0x03,0x03,0x03,0x07,0x0F,0x9F}, //動畫幀13
{0xFF,0x33,0x01,0x01,0x01,0x03,0x87,0xCF}, //動畫幀14
{0xFF,0x99,0x00,0x00,0x00,0x81,0xC3,0xE7}, //動畫幀15
{0xFF,0xCC,0x80,0x80,0x80,0xC0,0xE1,0xF3}, //動畫幀16
{0xFF,0xE6,0xC0,0xC0,0xC0,0xE0,0xF0,0xF9}, //動畫幀17
{0xFF,0x73,0x60,0x60,0x60,0x70,0x78,0xFC}, //動畫幀18
{0xFF,0x39,0x30,0x30,0x30,0x38,0x3C,0x7E}, //動畫幀19
{0xFF,0x9C,0x98,0x98,0x98,0x9C,0x1E,0x3F}, //動畫幀20
{0xFF,0xCE,0xCC,0xCC,0xCC,0xCE,0x0F,0x1F}, //動畫幀21
{0xFF,0x67,0x66,0x66,0x66,0x67,0x07,0x0F}, //動畫幀22
{0xFF,0x33,0x33,0x33,0x33,0x33,0x03,0x87}, //動畫幀23
{0xFF,0x99,0x99,0x99,0x99,0x99,0x81,0xC3}, //動畫幀24
{0xFF,0xCC,0xCC,0xCC,0xCC,0xCC,0xC0,0xE1}, //動畫幀25
{0xFF,0xE6,0xE6,0xE6,0xE6,0xE6,0xE0,0xF0}, //動畫幀26
{0xFF,0xF3,0xF3,0xF3,0xF3,0xF3,0xF0,0xF8}, //動畫幀27
{0xFF,0xF9,0xF9,0xF9,0xF9,0xF9,0xF8,0xFC}, //動畫幀28
{0xFF,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFE}, //動畫幀29
{0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFF} //動畫幀30
};
unsigned char index = 0; //圖片刷新索引
void main(void)
{
P0 = 0xFF; //P0口初始化
ADDR3 = 0; //選擇LED點陣
ENLED = 0; //LED顯示總使能
TMOD = 0x01; //設(shè)置定時器0為模式1
TH0 = 0xFC; //定時器初值,定時1ms
TL0 = 0x67;
TR0 = 1; //打開定時器0
ET0 = 1; //使能定時器0中斷
EA = 1; //打開總中斷開關(guān)
while(1);
}
void InterruptTimer0() interrupt 1
{
static unsigned char j = 0;
static unsigned char tmr = 0;
TH0 = 0xFC; //溢出后進(jìn)入中斷重新賦值
TL0 = 0x67;
P0 = 0xFF; //LED點陣消隱
switch (j)
{
case 0: ADDR0=0; ADDR1=0; ADDR2=0; break;
case 1: ADDR0=1; ADDR1=0; ADDR2=0; break;
case 2: ADDR0=0; ADDR1=1; ADDR2=0; break;
case 3: ADDR0=1; ADDR1=1; ADDR2=0; break;
case 4: ADDR0=0; ADDR1=0; ADDR2=1; break;
case 5: ADDR0=1; ADDR1=0; ADDR2=1; break;
case 6: ADDR0=0; ADDR1=1; ADDR2=1; break;
case 7: ADDR0=1; ADDR1=1; ADDR2=1; break;
default: break;
}
P0=graph[index][j]; //刷新的是二維數(shù)組的列數(shù)據(jù)
j++;
if (j >= 8)
{
j = 0;
}
tmr++; //圖片刷新頻率控制
if (tmr >= 250) //每隔250ms刷新一幀
{
tmr = 0;
index++; //索引代表了每一行的起始位置
if (index >= 30)
{
index = 0;
}
}
}
下載進(jìn)到板子上瞧瞧,是不是有一種帥到掉渣的感覺呢。技術(shù)這東西,外行人看的是很神秘的,其實我們做出來會發(fā)現(xiàn),也就是那么回事而已,每250ms更改一張圖片,每1ms在定時器中斷里刷新單張圖片的某一行。
不管是上下移動還是左右移動,大家要建立一種概念,就是我們是對一幀幀的圖片的切換,這種切換帶給我們的視覺效果就是一種動態(tài)的了。比如我們的DV拍攝動畫,實際上就是快速的拍攝了一幀幀的圖片,然后對這些圖片的快速回放,把動畫效果給顯示了出來。因為我們硬件設(shè)計的緣故,所以我們在寫上下移動程序的時候,數(shù)組定義的元素比較少,但是實際上大家也得理解成為32張圖片的切換顯示,而并非是真正的“移動”。
7.6 作業(yè)
1、掌握變量的作用域以及存儲類別。
2、了解點陣的顯示原理,理解點陣動畫顯示原理。
3、用點陣把I❤U的向下移動以及向右移動獨立編寫實現(xiàn)出來。
4、用點陣做一個9到0的倒計時牌顯示。
5、嘗試把流水燈、數(shù)碼管和點陣實現(xiàn)同時顯示。
6、根據(jù)出廠程序點陣變化的樣子嘗試自己把效果實現(xiàn)出來。
上一課:第六章 中斷的學(xué)習(xí)
下一課:第八章 獨立按鍵和矩陣按鍵 |