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

圖7-1 8*8點(diǎn)陣LED

7-2 8*8點(diǎn)陣LED結(jié)構(gòu)原理圖
點(diǎn)陣LED內(nèi)部原理圖如圖7-2所示,從7-2圖上可以看出來,其實(shí)點(diǎn)陣LED點(diǎn)亮原理還是很簡單的。在我們圖上藍(lán)色方框外側(cè)的就是點(diǎn)陣LED的引腳號(hào),左側(cè)的8個(gè)引腳是接的內(nèi)部LED的陽極,上側(cè)的8個(gè)引腳接的是內(nèi)部LED的陰極。那從圖上可以看出來,我們的9腳如果是高電平,13腳是低電平的話,最左上角的那個(gè)LED小燈就會(huì)亮,那我們用程序來實(shí)現(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; //點(diǎn)亮點(diǎn)陣的一個(gè)點(diǎn)
while(1); //程序停止在這里
}
同樣的方法,我們可以點(diǎn)亮點(diǎn)陣的任意一行,74HC 138的導(dǎo)通點(diǎn)陣所用的三極管的方法和數(shù)碼管很類似,那我們現(xiàn)在來點(diǎ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; //點(diǎn)亮小燈
while(1); //程序停止在這里
}
從這里我們逐步發(fā)現(xiàn)了一個(gè)問題,其實(shí)我們講一個(gè)數(shù)碼管就是8個(gè)LED小燈,一個(gè)點(diǎn)陣是64個(gè)LED小燈。同樣的道理,我們還可以把一個(gè)點(diǎn)陣?yán)斫獬?/font>8個(gè)數(shù)碼管。我們前邊掌握了6個(gè)數(shù)碼管的同時(shí)顯示方法,那8個(gè)數(shù)碼管也應(yīng)該輕輕松松了。我們先把這個(gè)點(diǎn)陣全部點(diǎn)亮。
#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è)置定時(shí)器0為模式1
TH0 = 0xFC;
TL0 = 0x67; //定時(shí)值初值,定時(shí)1ms
TR0 = 1; //打開定時(shí)器0
EA = 1; //打開中中斷
ET0 = 1; //打開定時(shí)器0中斷
while(1); //程序停止在這里,定時(shí)器運(yùn)行,等待定時(shí)器中斷
}
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;
} //動(dòng)態(tài)刷新
P0=0x00;
}
7.4 點(diǎn)陣LED圖形顯示
我們的小燈可以實(shí)現(xiàn)流水燈,數(shù)碼管可以顯示數(shù)字,那點(diǎn)陣LED就得來顯示點(diǎn)花樣了。
我們要顯示花樣的時(shí)候,往往要做出來一些小圖形,這些小圖形的數(shù)據(jù)要轉(zhuǎn)換到我們的程序當(dāng)中去,這個(gè)時(shí)候就需要取模軟件。來給大家介紹一款簡單的取模軟件,這個(gè)取模軟件可以在http://www.torrancerestoration.com 的軟件下載區(qū)下載到,大家來了解一下如何用,先看一下操作界面,如圖7-3所示。

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

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

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

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

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

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

圖7-9 取模結(jié)果
大家注意,我們雖然用軟件取模,但是也得知道其原理是什么,在這個(gè)圖片里,黑色的一個(gè)格子表示一個(gè)二進(jìn)制的1,白色的一個(gè)格子表示一個(gè)二進(jìn)制的0。第一個(gè)字節(jié)是0xFF,其實(shí)就是這個(gè)8*8圖形的第一行,全黑就是0xFF;第二個(gè)字節(jié)是0x99,低位在左邊,高位在右邊,大家注意看,黑色的表示1,白色的表示0,就組成了0x99這個(gè)數(shù)字。同理其他的數(shù)據(jù)大家也就知道怎么來的了。
我們把這個(gè)數(shù)據(jù)送到我們的點(diǎn)陣上去,大家看看什么效果。
#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è)置定時(shí)器0為模式1
TH0 = 0xFC;
TL0 = 0x67; //定時(shí)值初值,定時(shí)1ms
TR0 = 1; //打開定時(shí)器0
EA = 1; //打開中中斷
ET0 = 1; //打開定時(shí)器0中斷
while(1); //程序停止在這里,定時(shí)器運(yùn)行,等待定時(shí)器中斷
}
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; //動(dòng)態(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;
}
對(duì)于8*8的點(diǎn)陣來說,我們可以顯示一些簡單的圖形,字符等。一個(gè)漢字正常占的點(diǎn)數(shù)是16*16的,8*8的點(diǎn)陣只能顯示一些簡單筆畫的漢字,大家可以自己取模做出來試試。使用大屏顯示漢字的方法和小的方法是類似的,這個(gè)內(nèi)容我們考慮以后擴(kuò)展實(shí)驗(yàn)的時(shí)候再進(jìn)行講解。
7.5 點(diǎn)陣LED動(dòng)畫顯示
點(diǎn)陣的動(dòng)畫顯示,說到底就是我們對(duì)多張圖片進(jìn)行取模,使用程序算法巧妙的切換圖片,多張圖片組合起來就成了一段動(dòng)畫了,我們所看到的動(dòng)畫片、游戲等等,都是這么做的。
7.5.1 點(diǎn)陣的縱向移動(dòng)
上一節(jié)我們學(xué)了如何在點(diǎn)陣上畫一個(gè)❤形,有時(shí)候我們希望這些顯示是動(dòng)起來的,而不是靜止的。對(duì)于點(diǎn)陣已經(jīng)沒有太多的知識(shí)點(diǎn)可以介紹了,主要就是編程的算法問題了。
比如我們現(xiàn)在要讓我們的點(diǎn)陣顯示一個(gè)I ❤ U這樣的一個(gè)動(dòng)畫,首先我們要把這個(gè)圖形用取模軟件畫出來看一下,如圖7-10所示。

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

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