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

圖7-1 8*8點陣LED

7-2 8*8點陣LED結構原理圖
點陣LED內部原理圖如圖7-2所示,從7-2圖上可以看出來,其實點陣LED點亮原理還是很簡單的。在我們圖上藍色方框外側的就是點陣LED的引腳號,左側的8個引腳是接的內部LED的陽極,上側的8個引腳接的是內部LED的陰極。那從圖上可以看出來,我們的9腳如果是高電平,13腳是低電平的話,最左上角的那個LED小燈就會亮,那我們用程序來實現一下,特別注意,我們現在用的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的導通點陣所用的三極管的方法和數碼管很類似,那我們現在來點亮第二行整行的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); //程序停止在這里
}
從這里我們逐步發現了一個問題,其實我們講一個數碼管就是8個LED小燈,一個點陣是64個LED小燈。同樣的道理,我們還可以把一個點陣理解成8個數碼管。我們前邊掌握了6個數碼管的同時顯示方法,那8個數碼管也應該輕輕松松了。我們先把這個點陣全部點亮。
#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; //設置定時器0為模式1
TH0 = 0xFC;
TL0 = 0x67; //定時值初值,定時1ms
TR0 = 1; //打開定時器0
EA = 1; //打開中中斷
ET0 = 1; //打開定時器0中斷
while(1); //程序停止在這里,定時器運行,等待定時器中斷
}
void InterruptTimer0() interrupt 1 //中斷函數
{
static unsigned char j = 0;
TH0 = 0xFC; //溢出后進入中斷重新賦值
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;
} //動態刷新
P0=0x00;
}
7.4 點陣LED圖形顯示
我們的小燈可以實現流水燈,數碼管可以顯示數字,那點陣LED就得來顯示點花樣了。
我們要顯示花樣的時候,往往要做出來一些小圖形,這些小圖形的數據要轉換到我們的程序當中去,這個時候就需要取模軟件。來給大家介紹一款簡單的取模軟件,這個取模軟件可以在http://www.zg4o1577.cn 的軟件下載區下載到,大家來了解一下如何用,先看一下操作界面,如圖7-3所示。

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

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

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

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

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

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

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

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

圖7-11 動畫取模圖片
30張圖片,終于畫完了,每個圖片是8個字節的模,分別取模得到了30*8個字節的數據,所以我們用二維數組來表示會比較方便一些。
#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; //設置定時器0為模式1
TH0 = 0xFC; //定時器初值,定時1ms
TL0 = 0x67;
TR0 = 1; //打開定時器0
ET0 = 1; //使能定時器0中斷
EA = 1; //打開總中斷開關
while(1);
}
void InterruptTimer0() interrupt 1
{
static unsigned char j = 0;
static unsigned char tmr = 0;
TH0 = 0xFC; //溢出后進入中斷重新賦值
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]; //刷新的是二維數組的列數據
j++;
if (j >= 8)
{
j = 0;
}
tmr++; //圖片刷新頻率控制
if (tmr >= 250) //每隔250ms刷新一幀
{
tmr = 0;
index++; //索引代表了每一行的起始位置
if (index >= 30)
{
index = 0;
}
}
}
下載進到板子上瞧瞧,是不是有一種帥到掉渣的感覺呢。技術這東西,外行人看的是很神秘的,其實我們做出來會發現,也就是那么回事而已,每250ms更改一張圖片,每1ms在定時器中斷里刷新單張圖片的某一行。
不管是上下移動還是左右移動,大家要建立一種概念,就是我們是對一幀幀的圖片的切換,這種切換帶給我們的視覺效果就是一種動態的了。比如我們的DV拍攝動畫,實際上就是快速的拍攝了一幀幀的圖片,然后對這些圖片的快速回放,把動畫效果給顯示了出來。因為我們硬件設計的緣故,所以我們在寫上下移動程序的時候,數組定義的元素比較少,但是實際上大家也得理解成為32張圖片的切換顯示,而并非是真正的“移動”。
7.6 作業
1、掌握變量的作用域以及存儲類別。
2、了解點陣的顯示原理,理解點陣動畫顯示原理。
3、用點陣把I❤U的向下移動以及向右移動獨立編寫實現出來。
4、用點陣做一個9到0的倒計時牌顯示。
5、嘗試把流水燈、數碼管和點陣實現同時顯示。
6、根據出廠程序點陣變化的樣子嘗試自己把效果實現出來。
上一課:第六章 中斷的學習
下一課:第八章 獨立按鍵和矩陣按鍵 |