我們現(xiàn)在走在馬路上,經(jīng)?吹今R路兩側(cè)有一些LED點(diǎn)陣廣告牌,這些廣告牌看起來絢爛奪目,非常吸引人,而且還會變化很多種不同的顯示方式。本章我們就會學(xué)習(xí)到點(diǎn)陣LED的控制方式,按照慣例,先普及部分C語言知識。
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)該限制全局變量的使用,過多使用全局變量也會帶來一些問題。
(1)全局變量可以被作用域內(nèi)所有的函數(shù)直接引用,可以增加函數(shù)間數(shù)據(jù)聯(lián)系的途徑,但同時(shí)加強(qiáng)了函數(shù)模塊之間的數(shù)據(jù)聯(lián)系,使這些函數(shù)的獨(dú)立性降低,對其中任何一個(gè)函數(shù)的修改都可能會影響到其他函數(shù),函數(shù)之間過于緊密的聯(lián)系不利于程序的維護(hù)。
(2)全局變量的應(yīng)用會降低函數(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)用,這樣會引起一些不可預(yù)見的后果。
(3)過多使用全局變量會降低程序的清晰度,使程序的可讀性下降。在各個(gè)函數(shù)執(zhí)行的時(shí)候都可能改變?nèi)肿兞恐担y以清楚的判斷出每個(gè)時(shí)刻各個(gè)全局變量的值。
(4)定義全局變量會直接占用單片機(jī)的內(nèi)存單元,而局部變量只有進(jìn)入定義局部變量的函數(shù)內(nèi)才會分配內(nèi)存,函數(shù)退出后會自動釋放所占用的內(nèi)存。所以大量的全局變量會額外增加內(nèi)存占用。
綜上所述之原因,我們一項(xiàng)原則就是盡量減少全局變量的使用,能用局部變量代替的就不用全局變量。
還有一種特殊情況,大家在看別人程序的時(shí)候注意。C語言是允許局部變量和全局變量同名的,他們定義后在內(nèi)存中占有不同的內(nèi)存單元。如果在同一源文件中,全局變量和局部變量同名,在局部變量作用域范圍內(nèi),只有局部變量有效,全局變量不起作用,也就是說局部變量具有更高優(yōu)先級。但是我們在編寫程序的時(shí)候,盡量不要讓變量重名,以避免不必要的誤解。
.2 變量的存儲類別
變量的存儲類別分為自動、靜態(tài)、寄存器和外部這四種。其中后兩種我們暫不介紹,主要是自動變量和靜態(tài)變量這兩種。
函數(shù)中的局部變量,如果不加static這個(gè)關(guān)鍵字來進(jìn)行特別聲明,都屬于自動變量,也叫做動態(tài)存儲變量。這些存儲類別的變量,在調(diào)用該函數(shù)的時(shí)候系統(tǒng)會給他們分配存儲空間,在函數(shù)調(diào)用結(jié)束后會自動釋放這些存儲空間。動態(tài)存儲變量的關(guān)鍵字是auto,但是這個(gè)關(guān)鍵字是可以省略的,所以我們平時(shí)都不用。
那么與動態(tài)變量對應(yīng)的就是靜態(tài)變量。首先,全局變量均是靜態(tài)變量,此外,還有一種特殊的局部變量也是靜態(tài)變量。即我們在局部變量聲明前邊加上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è)變量每次變化的值我們還想保存,如果定義成局部動態(tài)變量的話,每次進(jìn)入函數(shù)后上一次的值就丟失了,如果定義成全局變量的話,又違背了我們上面提到的關(guān)于全局變量使用的一般原則,這個(gè)時(shí)候我們就可以定義成局部靜態(tài)變量了。
比如上節(jié)課中斷程序中有一個(gè)用于動態(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’為中斷入口號
{
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;
} //動態(tài)刷新
}
大家注意看這個(gè)程序的中斷函數(shù)的靜態(tài)變量j,如果加上了static,他的初始化j = 0操作只進(jìn)行一次,下邊的程序會進(jìn)行j++操作,下次進(jìn)入中斷函數(shù)的時(shí)候,j會保持上次的值。但是如果去掉static這個(gè)關(guān)鍵字,那每次進(jìn)入函數(shù)后,j都會被初始化成0,大家可以自己修改程序做嘗試。
1.3 點(diǎn)陣LED的初步認(rèn)識
點(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的引腳號,左側(cè)的8個(gè)引腳是接的內(nèi)部LED的陽極,上側(cè)的8個(gè)引腳接的是內(nèi)部LED的陰極。那從圖上可以看出來,我們的9腳如果是高電平,13腳是低電平的話,最左上角的那個(gè)LED小燈就會亮,那我們用程序來實(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;
} //動態(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.zg4o1577.cn 的軟件下載區(qū)下載到,大家來了解一下如何用,先看一下操作界面,如圖7-3所示。

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

圖7-4 新建圖像
我們點(diǎn)左側(cè)的“模擬動畫”菜單,點(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對應(yīng)LED熄滅,0對應(yīng)LED點(diǎn)亮,而我們需要的是一顆點(diǎn)亮的“心”,所以我們要選“修改圖像”菜單里的“黑白反顯圖像”這個(gè)選項(xiàng),并且點(diǎn)擊“基本操作”菜單里邊的“保存圖像”可以把我們設(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對照來看,大家可以看到我們的P0總線,控制的是一行,所以我們用的是“橫向取模”,如果控制的是一列,就要選“縱向取模”。“字節(jié)倒序”這個(gè)選項(xiàng),我們選上是因?yàn)閳D7-2中,我們左邊是低位DB0,右邊是高位DB7,所以必須選上字節(jié)倒序,其他兩個(gè)選項(xiàng)大家自己了解,點(diǎn)確定后,選擇“取模方式”這個(gè)菜單,點(diǎn)一下“C51 格式”后,在“點(diǎn)陣生成區(qū)”自動產(chǎn)生了8個(gè)字節(jié)的數(shù)據(jù),這8個(gè)字節(jié)的數(shù)據(jù)就是對應(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; //動態(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的點(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動畫顯示
點(diǎn)陣的動畫顯示,說到底就是我們對多張圖片進(jìn)行取模,使用程序算法巧妙的切換圖片,多張圖片組合起來就成了一段動畫了,我們所看到的動畫片、游戲等等,都是這么做的。
7.5.1 點(diǎn)陣的縱向移動
上一節(jié)我們學(xué)了如何在點(diǎn)陣上畫一個(gè)❤形,有時(shí)候我們希望這些顯示是動起來的,而不是靜止的。對于點(diǎn)陣已經(jīng)沒有太多的知識點(diǎn)可以介紹了,主要就是編程的算法問題了。
比如我們現(xiàn)在要讓我們的點(diǎn)陣顯示一個(gè)I ❤ U這樣的一個(gè)動畫,首先我們要把這個(gè)圖形用取模軟件畫出來看一下,如圖7-10所示。

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

圖7-11 動畫取模圖片
30張圖片,終于畫完了,每個(gè)圖片是8個(gè)字節(jié)的模,分別取模得到了30*8個(gè)字節(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點(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í)我們做出來會發(fā)現(xiàn),也就是那么回事而已,每250ms更改一張圖片,每1ms在定時(shí)器中斷里刷新單張圖片的某一行。
不管是上下移動還是左右移動,大家要建立一種概念,就是我們是對一幀幀的圖片的切換,這種切換帶給我們的視覺效果就是一種動態(tài)的了。比如我們的DV拍攝動畫,實(shí)際上就是快速的拍攝了一幀幀的圖片,然后對這些圖片的快速回放,把動畫效果給顯示了出來。因?yàn)槲覀冇布O(shè)計(jì)的緣故,所以我們在寫上下移動程序的時(shí)候,數(shù)組定義的元素比較少,但是實(shí)際上大家也得理解成為32張圖片的切換顯示,而并非是真正的“移動”。
7.6 作業(yè)
1、掌握變量的作用域以及存儲類別。
2、了解點(diǎn)陣的顯示原理,理解點(diǎn)陣動畫顯示原理。
3、用點(diǎn)陣把I❤U的向下移動以及向右移動獨(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ú)立按鍵和矩陣按鍵 |