|
首先再次聲明我是菜鳥,我寫出來的目的是給沒有接觸過lcd的朋友看得,我本人水平有限,錯(cuò)誤在所難免,歡迎指出
之所 以產(chǎn)生寫這個(gè)東西的想法,是因?yàn)槌弥龠@段時(shí)間看了看學(xué)習(xí)板的源程序,在看lcd的時(shí)候很不順利,花了五個(gè)晚上才看完(白天偷懶了^_^!,畢竟在春節(jié) 嘛)。聯(lián)想起還有很多水平和我差不多甚至可能還低一點(diǎn)的菜鳥,就覺得有義務(wù)把五個(gè)晚上的學(xué)習(xí)心得寫出來,順便也給自己整理整理思路,嘿嘿。
這個(gè)心得是針對學(xué)習(xí)板上的lcd 驅(qū)動(dòng)芯片SSD181X系列寫的,當(dāng)然思想應(yīng)該是相通的。其實(shí)是我沒有用過其他系列l(wèi)cd(眾人嘔吐ing……)。
首先你要去網(wǎng)上下載一個(gè)SSD181X系列的PDF文件,這種文件網(wǎng)上到處都是,偶就不多說了。剛開始需要借用51論壇電子白菜大蝦的文章作為總領(lǐng),因?yàn)榕急容^懶,不想寫這么多拉~~~(不要飛雞蛋……)當(dāng)然還是有部分內(nèi)容增加的拉。
我 介紹的這個(gè)是MOTO的手機(jī)屏,大小為96*54(96列54行)。不過這個(gè)小小的LCD屏也是很不錯(cuò)的。對比度可調(diào),而且還有背光:)具體驅(qū)動(dòng)器是: SSD1815,是黑白點(diǎn)陣驅(qū)動(dòng)芯片,最多支持132個(gè)SEG和64個(gè)COM,還有一個(gè)ICON(功能設(shè)置)行。說起SEG,COM,也許很多人沒接觸 過,其實(shí)玻璃屏的LCD都是使用SEG,COM的掃描來驅(qū)動(dòng)的,為使需要點(diǎn)亮的內(nèi)容呈顯示狀態(tài),須將交流驅(qū)動(dòng)電壓加在LCD的段電極與公共電極之間。原理 涉及物理化學(xué)問題,這里就不多說,只打個(gè)比喻:一個(gè)‘井’字,是兩行兩列組成的,共有四個(gè)交點(diǎn),其實(shí)就好比是2個(gè)SEG,2個(gè)COM,要驅(qū)動(dòng)一個(gè)固定的 點(diǎn),要相應(yīng)的一條COM和一條SEG都有效如果要幾個(gè)點(diǎn)亮,幾個(gè)點(diǎn)滅,就需要用到掃描的原理,還記得行列鍵盤的原理嗎?其實(shí)它的驅(qū)動(dòng)原理就差不多了:)
上面說的它支持132個(gè)SEG,64個(gè)COM,所以它支持最多132*64個(gè)點(diǎn)的LCD。但實(shí)際上如果我們的LCD沒有那么多個(gè)點(diǎn)話,就 需要在上電的時(shí)候?qū)?815進(jìn)行初始化,指定COM和SEG的數(shù)目,還有BIAS數(shù),說到BIAS,唉,又是個(gè)專業(yè)問題了,LCD的驅(qū)動(dòng)波形由幾級電平組 成,為防止對比度不均勻,在不點(diǎn)亮象素對應(yīng)的電極上仍加有一定電壓,這對降低點(diǎn)亮象素產(chǎn)生 的交叉干擾和防止對比度不均勻很重要。LCD中非點(diǎn)亮象素(非 選點(diǎn))的電壓有效值與點(diǎn)亮象素(選擇點(diǎn))電壓有效值之比(1/n)稱為偏壓比。確實(shí)比較難以理解,你可以這樣看:BIAS是電平強(qiáng)度,和COM有關(guān)的東 西。以上這些都要在LCD上電的時(shí)候初始化好,才能正確地顯示圖文。
1815還自帶顯示RAM,英文是Graphic Display Data Ram就是圖象顯示數(shù)據(jù)存儲器,簡稱GDRAM。對于現(xiàn)在很多現(xiàn)成的LCM屏來說,控制芯片都帶有了GDRAM的,大小就和他的最大顯示點(diǎn)數(shù)相當(dāng)。
我估計(jì)你看到現(xiàn)在還是一頭霧水,呵呵,沒關(guān)系,當(dāng)初我看的時(shí)候也是一頭霧水,看到后面你就會(huì)明白他們的原理了。
接著是1815的接口問題了,它支持8080,6800,I2C總線。8080,就是和我們51一樣的總線,有CS,WR,RD,然后是數(shù)據(jù)地址線; 6800又叫摩托羅拉總線,有R/W,E,然后是地址數(shù)據(jù)線;I2C總線,呵呵,當(dāng)然就是I2C總線咯,還用說么?
由于這個(gè)是摩托羅拉的LCD屏,出廠的時(shí)候已經(jīng)配置為6800總線了,所以我們不能用正常的51總線來驅(qū)動(dòng),但實(shí)際上6800是可以兼容8080總線的,先等我介紹下這個(gè)總線吧:
R/W腳:讀寫腳,為1的時(shí)候是讀,0的時(shí)候是寫。
E腳:使能腳,功能如同51總線的CS,也是低有效的,但數(shù)據(jù)D0-D7在高的時(shí)候鎖存。
D/C腳,這個(gè)是數(shù)據(jù),命令選擇腳,1的時(shí)候代表總線傳輸?shù)氖菙?shù)據(jù)(不論是讀還是寫),0的時(shí)候代表總線傳輸命令(不論是讀還是寫)。
D0-D7:理所當(dāng)然的數(shù)據(jù)腳咯。
驅(qū)動(dòng)原理很簡單,在E為低的時(shí)候?qū)/W腳輸入讀寫信號,讀就是1,寫就是0;然后選擇你讀寫的是數(shù)據(jù)還是命令,D/C腳1為數(shù)據(jù),0為命令;最后就是數(shù)據(jù)腳的數(shù)據(jù)了。
以上原理,用單片機(jī)IO做是很簡單的事情,不過使用IO的缺點(diǎn)就是不能復(fù)用,浪費(fèi)了很多的資源,所以在學(xué)習(xí)板上是使用總線驅(qū)動(dòng)LCD的。之前說了,某些6800總線是可以用8080總線模擬的,而這個(gè)LCD就是可以使用8080總線的了。
6800總線讀寫控制只需要1只腳R/W,1為讀0為寫,而8080總線是兩只腳RD,WR,其中的某個(gè)腳為低就對應(yīng)響應(yīng)功能。根據(jù)這個(gè)邏輯關(guān)系,我們很容易就猜想到為什么6800的R/W腳可以直接接上8080的WR了。
這些都是墊場的開場白拉。接下來我就以丁丁編寫的1815的使用程序,來介紹使用1815驅(qū)動(dòng)lcd的詳細(xì)步驟。
為了讓大家能夠理解清楚,我絕對按照分段的形式,一段一段解釋。當(dāng)然由于本菜鳥水平有限,如果解釋錯(cuò)誤還請大家不吝賜教。開始的程序我會(huì)講的很細(xì),后面的我就講個(gè)大概了,只要理解了這個(gè)過程就不難了。
注意哦,這個(gè)程序是來自"51單片機(jī)世界",作者版主丁。櫺∶停,未經(jīng)允許,不得抄襲作為商業(yè)用途。
/*****************************************
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long
#include
#include
#include "study.h"
#define LCMD XBYTE[0xAf00] // 液晶數(shù)據(jù)口
#define LCMC XBYTE[0xAb00] // 液晶命令口
#define LCD_DOT_X 98 // lcd 的寬點(diǎn)數(shù)
#define LCD_DOT_Y 54 //lcd 的高點(diǎn)數(shù)
#define LCD_MAX_X 11 //每行字符, ( LCD_MAX_X +1 ) * 8 點(diǎn)
#define LCD_MAX_Y 5 //共多少行,( LCD_MAX_Y +1 ) * 8 點(diǎn)
********************************************/
如果你寫過單片機(jī)程序,就會(huì)知道任何程序的初始化階段都是這么些步驟,定義再定義,最后是某些功能的初始化函數(shù)。這些就是剛開始的聲明定義過程。前面的幾個(gè)define,include我就不多說了,如果你連這個(gè)都看不懂,只能先去補(bǔ)習(xí)補(bǔ)習(xí)c語言了。
#define LCMD XBYTE [0xAf00]和#define LCMC XBYTE[0xAb00]兩句是用來定義外部總線接口地址的。0xAf00用來傳遞數(shù)據(jù),0xAb00用 來傳遞命令,之前說過,是傳輸命令還是數(shù)據(jù)主要是取決于A10的,上面兩個(gè)地址對應(yīng)的A10其實(shí)就是0xaf00->A10=1,0xab00- >A10=0。照電子白菜大蝦的話說,地址的取值只要確保在A000~AFFF 之間就可以了,所以以下定義一樣能正確驅(qū)動(dòng)LCD:
#define LCMD XBYTE[0xA400] // 液晶數(shù)據(jù)口
#define LCMC XBYTE[0xA000] // 液晶命令口
雖然地址不同了,但對A10的作用依然不變。
接下來的兩句則是用LCD_DOT_X來表示lcd 的寬點(diǎn)數(shù),LCD_DOT_Y表示高點(diǎn)數(shù)。想象坐標(biāo)軸,呵呵,是不是好理解了?這就說明我們現(xiàn)在用的lcd是LCD_DOT_X寬LCD_DOT_Y高的。
最后兩句則是告訴我們,這個(gè)lcd可以顯示5行,每行11個(gè)字符。這些參數(shù)都是可以根據(jù)lcd不同而變化的。
/*****************************************
struct cursortype
{
uchar x;
uchar y;
};
struct cursortype cursor;
void wridata(uchar ch)
{
LCMD=ch;
}
void wricmd(uchar ch)
{
LCMC=ch;
}
*****************************************/
這幾句也都屬于初始化步驟。首先struct cursortype這個(gè)結(jié)構(gòu)體是用來定義光標(biāo)的,光標(biāo)在屏幕上當(dāng)然可以用x和y兩個(gè)值表示,這樣我們到時(shí)候用cursor.x或者 cursor.y確定光標(biāo)位置就方便多了。
Wridata 和 wricmd兩個(gè)函數(shù)則是寫數(shù)據(jù)和寫命令功能。仔細(xì)看,LCMD就是液晶數(shù)據(jù)口,LCMC就是液晶命令口,我們對他們進(jìn)行的操作實(shí)際上就是對 0xAf00或0xA000地址進(jìn)行操作。例如我們用wridata(0x33),也就是說我們把0x33這個(gè)數(shù)據(jù)通過0xAf00這個(gè)外部總線接口地址 送到SSD181X中。由于是0xAf00的地址,A10必然為1,控制器就知道此時(shí)你要傳遞的是數(shù)據(jù)拉~怎么樣?很方便吧。
void light(uchar n)
{
LCMC=0x81;
LCMC=n;
}
把 這個(gè)程序單獨(dú)列出來是因?yàn)橄胫v講程序如何傳遞能夠作用的有效參數(shù)。首先這是一個(gè)調(diào)節(jié)lcd亮度(其實(shí)更加嚴(yán)格得說是對比度)的函數(shù)。我們看到他用的 LCMC,說明此時(shí)我們是發(fā)送程序給lcd,讓他知道我們要調(diào)整亮度了。那么究竟傳遞什么東西,控制器才能知道我們的意圖呢?不用擔(dān)心,pdf上會(huì)告訴你 按什么格式發(fā)送指令給控制器才有效。這是SSD181X pdf上面的格式之一:
為了方便我只截取其中一個(gè)功能設(shè)置來進(jìn)行說明。最左邊的數(shù)字表示你發(fā)送的命令必須是這樣的格式,中間的英文表示該格式命令所起的作用,右邊的文字表示這些XXXX,也就是不確定數(shù)字的值該如何選取才能獲得我們想要的效果。
就 以這個(gè)方框?yàn)槔桑紫任覀兛粗虚g這個(gè)小方框,它告訴我們這條指令的作用是設(shè)置對比度寄存器,也就是說我們可以通過對該寄存器的修改來實(shí)現(xiàn)調(diào)節(jié)對比度得調(diào) 整。那么具體如何調(diào)整呢?我們再來看看最左邊的方框,這個(gè)內(nèi)容告訴我們,我們需要用LCMC發(fā)送兩個(gè)字節(jié)數(shù)據(jù),其中一個(gè)字節(jié)是固定的10000001就是 0x81,第二個(gè)字節(jié)則是自己決定。怎么決定呢?再看看第三個(gè)小方框吧。如果你e文好,馬上就能看懂它的意思。如果你e文不好……嘿嘿,建議金山詞霸! (倒,誰扔的香蕉皮????!)
這里的意思就是說,對比度分為64個(gè)等級,這樣XXXXXX6位二進(jìn)制數(shù)就可以表示完,那么我們需要 用哪個(gè)等級就設(shè)置哪個(gè)數(shù),比如現(xiàn)在我們需要等級n的對比度,因此程序中出現(xiàn)了LCMC=n。這個(gè)n哪里來的?void light(uchar n),嘿 嘿,主函數(shù)傳遞進(jìn)來的。
呵呵,現(xiàn)在明白命令是怎么傳送的了吧?一會(huì)兒還有例子,如果不明白不要慌張。
/*****************************************
void cls(uchar ch)
{
uchar i,j;
for(j=0x0;j
{
wricmd(0xb0+j);//set page //1011xxxx
wricmd(0x10);//set column msb
wricmd(0x0);//set column lsb
wricmd(0xe0);//set modify-read mode
for(i=0;i<98;i++)
{
wridata(0x00); //填滿0即清屏
}
wricmd(0xee);//reset modify-read mode
}
}
*****************************************/
這里就開始進(jìn)入實(shí)質(zhì)性的編程部分了。這是一個(gè)清屏函數(shù),傳遞的參數(shù)ch=9就表示連帶ICON一起清除,如果ch=8則表示不清除ICON。這里的ch實(shí)質(zhì)上表示GDDRAM中的page頁面值。
這里有點(diǎn)難以理解。先看看GDDRAM是怎么構(gòu)成的。
這 是pdf中關(guān)于GDDRAM地址分布圖的說明,我們可以從這里清晰看到page的概念:一個(gè)page實(shí)際上是一個(gè)擁有8個(gè)高點(diǎn)的行,64點(diǎn)高的屏幕就有8 個(gè)page,每個(gè)page的寬度都是和整個(gè)RAM寬度是一樣的。從pdf的最開始我們就可以看到1815的特性是132×64+1 icon line, 也就是說高為64,換算過來就是8個(gè)page。而GDDRAM的數(shù)據(jù)分布圖與lcd實(shí)際的數(shù)據(jù)顯示圖是完全一致的,實(shí)際上lcd的顯示的就是GDDRAM 的映射。因此我們清屏的目的就是為了清除GDDRAM中的值。因此從for(j=0x0;j語句中我們得知,循環(huán)次數(shù)是由ch決定的。如果ch=8,那么 循環(huán)8次,就只能清除這8個(gè)page(頁面)。只有ch=9,才能清除最后的那一個(gè)1 icon line。
好,我們接著往下看。進(jìn)入循環(huán)之后,程序要做的事就是一個(gè)page一個(gè)page得清除信息。首先wricmd(0xb0+j),翻翻pdf,找到相關(guān)信息:
這下知道了,這個(gè)語句的意思就是選擇要進(jìn)行讀寫操作的頁面。因?yàn)榭偣仓挥?個(gè)頁面,所以4個(gè)不確定位就夠拉。這也就是wricmd() 的參數(shù)是“0xb0+j”的原因:page數(shù)只由j,也就是ch控制。
接下來兩句wricmd(0x10)和wricmd(0x0),我們同樣可以通過pdf的信息得知,作用時(shí)用來設(shè)置列的低地址和列的高地址。參數(shù)高四位為0001的表示設(shè)置高地址,0000的表示設(shè)置低地址。默認(rèn)情況下高低地址均為0X0000。
接下來就是設(shè)置工作模式wricmd(0xe0)。這個(gè)語句的作用是set read-modify-read mode,就是設(shè)置成讀-改-寫模式。???????????
最 后把本次循環(huán)我們選擇的page填0,也就達(dá)到了請零的目的。需要注意的是I的范圍是0-97,為什么?因?yàn)槲覀儸F(xiàn)在舉例用的lcd寬點(diǎn)數(shù)只有98。 wridata(0x00)的作用是把某一列寫入0。沒想到吧?呵呵,因?yàn)橐壕弦粋(gè)字節(jié)的顯示是豎的一排排的,存儲在GDDRAM中當(dāng)然也是豎著拉~也 就是一列代表一個(gè)字節(jié)8位^_^。
剛才我們把工作模式設(shè)置為讀-改-寫,現(xiàn)在當(dāng)然要結(jié)束這個(gè)模式拉,這就是wricmd(0xee)這個(gè)語句所起的作用。
最后繼續(xù)循環(huán)。Cls的工作就完成了~
/*****************************************
show_asc(uchar ch);
//***********************
//初始化液晶
uchar lcdlight=32;
void initlcd(void)
{ P2=0x00; //P2作為總線時(shí),其寄存器的值對總線沒有影響。
//初始化為0,是為了更好的配合液晶的6800總線。motorola的液晶內(nèi)部固定了6800總線方式。
//如果液晶是8080總線,則無需這樣做。
wricmd(0x2f);//SET POWER CONTROL,開啟一系列與電源有關(guān)的功能
wricmd(0x20);//REGULATOR RESISTOR SELECT,內(nèi)部反饋增益最小
wricmd(0x81);
wricmd(lcdlight);設(shè)置對比度值
wricmd(0x40);//設(shè)置初始顯示線,從哪里開始是玻璃上的布線決定的
wricmd(0xa0);//ADC=0(SEG1~SEG132)
wricmd(0xc8);//SHL=0(COM1~COM64)
wricmd(0xa2);//設(shè)置LCD BIAS為1/9
cls(9);//全部清除,包括icon
wricmd(0xaf);//開啟顯示,也就是把GDRAM上的數(shù)值顯示到屏上
setcursor(0,0);//設(shè)置光標(biāo)到左上角
}
*****************************************/
到 這里,就正是進(jìn)入我們的初始化了。什么?弄了半天你還沒有講初始化啊?各位看官不要著急,此初始化非彼初始化也。剛才我們講的都是整個(gè)程序的初始化,而現(xiàn) 在我們進(jìn)入的是液晶屏的初始化。讓我們看看這個(gè)過程吧。首先是uchar lcdlight=32,顧名思義,這個(gè)參數(shù)的作用就是調(diào)節(jié)lcd對比度的拉。 motorola的液晶默認(rèn)對比度為32。先讓lcdlight=32,到時(shí)候利用wricmd函數(shù),直接一個(gè)wricmd(lcdlight)語句,多 方便~^_^。
閑話少說,接著往下看。下面是一個(gè)initlcd函數(shù),這是可是如假包換的液晶初始化函數(shù)。液晶在每次上電使用都需要初始化,而大多數(shù)初始化程序我們都可以不去理會(huì),因?yàn)槟切┒际前凑照f明書所說的,用于設(shè)置COM數(shù)和SEG數(shù)還有BIAS值的。
這 里再解釋一下P2=0x00;的作用。開始的時(shí)候我們就說過,這個(gè)液晶使用的是6800總線,這段,其實(shí)是為了兼容6800總線加上的,LCD的D/C腳 在一開始的時(shí)候應(yīng)該設(shè)為0,也就是寫成:P22=0;就可以了,P22就是接到LCD的D/C腳上的。另外再提醒一點(diǎn),一些必須的值,如COM,SEG, BIAS,顯示模式等,在使用中這些設(shè)置用戶是不應(yīng)該改變的。
到這里,LCD初始化正式完成,已經(jīng)可以供用戶正常使用了。
/*****************************************
show_asc(uchar ch)
{
uint addr;
uchar hzdata[16];
uchar xdot,i;
addr=16*ch;
readeprom(addr,hzdata,16); //讀出16個(gè)字節(jié)的點(diǎn)陣數(shù)據(jù)
xdot=cursor.x*8;
wricmd(0xb0+cursor.y); //將y位置送入液晶
wricmd(xdot & 0x0f); //將x位置送入液晶
wricmd( 0x10 | (xdot >> 4 ));
wricmd(0xe0);
for(i=0;i<8;i++)
{
wridata(hzdata[i]); //寫上半個(gè)字符
}
wricmd(0xee);
wricmd(0xb0+cursor.y+1);
wricmd(xdot & 0x0f);
wricmd(0x10 | (xdot >> 4 ));
wricmd(0xe0);
for(;i<16;i++) //寫下半個(gè)字符
{
wridata(hzdata[i]);
}
wricmd(0xee);
}
*****************************************/
要 理解這段程序,必須先知道點(diǎn)陣表示的含義。我們知道,字母和漢字是按字模位信息顯示的,那如何得到漢字的字模信息呢?難道要我們自己去做?NO。DOS前 輩們經(jīng)過艱辛的努力,將制作好的字模放到了一個(gè)個(gè)標(biāo)準(zhǔn)的庫中以免去后輩的麻煩,這就是點(diǎn)陣字庫文件。一般我們使用16*16的點(diǎn)陣宋體字庫,所謂 16*16,是每一個(gè)漢字在縱、橫各16點(diǎn)的區(qū)域內(nèi)顯示的,前一個(gè)16表示列,后一個(gè)十六表示行。不過后來又有了HZK12、HZK24,HZK32和 HZK48字庫及黑體、楷體和隸書字庫。
這段程序是用來在當(dāng)前光標(biāo)位置顯示一個(gè)6×12 點(diǎn)陣的ASC碼字符的。其實(shí)標(biāo)準(zhǔn)的ASC碼 字符應(yīng)該是8×16點(diǎn)陣區(qū)域表示,這也就是為什么我們常說“一個(gè)漢字占據(jù)兩個(gè)字符位”的原因。Ch表示的是asc字符的值。首先說明一點(diǎn),一個(gè)ASC字符 分兩部分顯示,也就是說,把分為上下兩個(gè)“半ASC碼字符”。漢字同樣應(yīng)該如此顯示。當(dāng)然這只是我們目前介紹的這個(gè)lcd的特性。如果你使用的是別的種 類,千萬不要生搬硬套,一定要仔細(xì)閱讀說明書。
再回頭看程序。首先就是一堆定義,無符號整形數(shù)addr表示的是點(diǎn)陣在flash中的 物理位置,表示ASC碼字符‘1’的字符點(diǎn)陣占據(jù)的物理位置是0-15。為什么?你看,16*8點(diǎn)陣區(qū)域,一個(gè)點(diǎn)用1bit表示,‘0’就是滅,‘1’就 是亮,那么總共128bit,是不是就是16byte?同理,字符‘2’是16-30,以此類推。所以addr=16*ch。比如我傳遞進(jìn)來的ch是4, 則addr為64。而hzdata這個(gè)數(shù)組是用來存儲讀出的數(shù)據(jù)的。Xdot表示的是橫向點(diǎn)位置,在下面我們可以知道它的計(jì)算公式是 cursor.x*8,就是光標(biāo)橫坐標(biāo)值乘以8。由于之前我們將光標(biāo)設(shè)置在左上角,所以cursor.x為0,因此此時(shí)橫向點(diǎn)位置也為0。如果我們已經(jīng)顯 示了一個(gè)ASC字符,此時(shí)的cursor.x就應(yīng)該為1,那么xdot就應(yīng)該為8:這應(yīng)該很好理解,從點(diǎn)陣區(qū)域的大小我們可以知道一個(gè)ASC碼字符從橫坐 標(biāo)上看占用的是8個(gè)點(diǎn)(0-7),下一個(gè)ASC碼字符當(dāng)然應(yīng)該從8開始拉。
接下來是readeprom(addr,hzdata,16)這個(gè)函數(shù)。什么意思呢?由于它是屬于另外一個(gè).c文件,這里只是給出原型:
Readeprom函數(shù)原型如下:
readeprom(ulong ad,uchar *pst,uint n)
{
union {ulong addr_l;struct {uint a32;uint a10;}addr_i;struct {uchar a3;uint a21;uchar a0;}addr;} address;
uint i;
uchar xdata *flash;
P1=0xff; //P1口如被占用暫停讀取
while(P1!=0xff);
address.addr_l=ad;
P1=(P1&0xc0) | (address.addr.a21/0x20); //設(shè)置bank線,每塊8K字節(jié)
flash=0x8000+address.addr_i.a10%0x2000; //flash窗口地址范圍0x8000-0x9ffff
for(i=0;i 讀N個(gè)字節(jié)//
{
*(pst++)=*(flash++);
if(flash==0xa000) //如果地址跨頁則翻到下一個(gè)bank
{
P1++;
flash=0x8000;
}
}
}
在 這里我們就不單獨(dú)解釋了,只是說說他的大概作用,即讀N個(gè)flash中的字節(jié),每次最多65535字節(jié)。入口參數(shù)的含義分別是:ulong ad為字符在 flash中的物理地址,uchar *pst表示讀出來放在內(nèi)存中的指針首位置,uint n表示讀出多少個(gè)字節(jié)。
也就是說,readeprom(addr,hzdata,16)后,我們已經(jīng)把表示ch需要用的16個(gè)字節(jié)傳到了hzdata數(shù)組中,到時(shí)候就可以直接拿來用了。
在 接下來是送x,y的位置,為什么y位置是0xb0+cursor.y呢?0xb0是一個(gè)命令指令,表示設(shè)置page。剛才說了,一個(gè)page是8行組成, 也就是說高8個(gè)點(diǎn),也就是說一個(gè)字符或者漢字都應(yīng)該由兩個(gè)page來提供數(shù)據(jù)。剛才還說了,顯示是分上下兩半部分組成的,所以先page設(shè)置成0xb0+ cursor.y,然后再設(shè)置成0xb0+cursor.y+1,這樣是不是先后選中兩個(gè)page?嘿嘿,很順理成章吧?由于我們這個(gè)系列l(wèi)cd默認(rèn)是 132點(diǎn)寬,所以橫向點(diǎn)的數(shù)目(也就是列地址)至少需要8位表示才夠了(7位只有128),但是x位置送的列地址是xdot & 0x0f,高8 位的4個(gè)0是命令標(biāo)志,只有低4位,明顯不能表示完。怎么辦呢?我們就分兩步送,先送低4位,再說高四位。忘了這個(gè)設(shè)置列地址的指令?回頭去看看cls函 數(shù)中的內(nèi)容吧^_^。高4位怎么送?wricmd( 0x10 | (xdot >> 4 ));這是設(shè)置高4位列地址的指令。低四位表示列 的高地址。
繼續(xù)。wricmd(0xe0)的作用是設(shè)置成讀-改-寫模式。這個(gè)前面已經(jīng)介紹過了。在接下來就是送我們要送顯的數(shù)據(jù)到 GDDRAM中的過程了,這個(gè)過程很簡單,大體就是一列一列的送,送了一個(gè)字節(jié)后列GDDRAM中的列地址自動(dòng)加一,數(shù)組下標(biāo)也加一,然后再繼續(xù)送,其實(shí) 我們從cls函數(shù)的過程中就能領(lǐng)悟到。送完上半部分,高低列地址重新送,page+1,再重復(fù)這個(gè)過程。具體指令就不介紹了,聰明的你一定能夠理解~呵 呵。
/*****************************************
show_hz(uchar ch1, uchar ch2)
{
ulong addr;
uchar hzdata[32];
uchar xdot,i;
if (ch1>=0xb0) //尋址漢字在flash中的物理位置
{
addr=(ch1-0xb0)*94+ ch2-0xa1;
addr=addr*32+0x5a40;
}
else
{
addr=(ch1-0xa1)*94+ ch2-0xa1;
addr=addr*32+0x800;
}
readeprom(addr,hzdata,32); //讀出32個(gè)漢字點(diǎn)陣數(shù)據(jù)
xdot=cursor.x*8; //計(jì)算X位置
wricmd(0xb0+cursor.y);
wricmd(xdot & 0x0f);
wricmd( 0x10 | (xdot >> 4 ));
wricmd(0xe0);
for(i=0;i<16;i++) //顯示上半個(gè)漢字
{
wridata(hzdata[i]);
}
wricmd(0xee);
wricmd( 0xb0+cursor.y+1);
wricmd(xdot & 0x0f);
wricmd( 0x10 | (xdot >> 4 ));
wricmd(0xe0);
for(;i<32;i++) //顯示下半個(gè)漢字
{
wridata(hzdata[i]);
}
wricmd(0xee);
}
*****************************************/
這個(gè)程序的作用就是在當(dāng)前光標(biāo)位置顯示一個(gè)16*16 漢字或者全角字符。入口參數(shù)ch1表示該漢字的區(qū)碼,入口參數(shù)ch2表示位碼。
說 到這里可能有一些朋友不理解了。下面我來講一下。前面已經(jīng)介紹了漢字庫的一些知識,雖然漢字庫種類繁多,但都是按照區(qū)位的順序排列的。前一個(gè)字節(jié)為該漢字 的區(qū)號,后一個(gè)字節(jié)為該字的位號。每一個(gè)區(qū)記錄94個(gè)漢字,位號則為該字在該區(qū)中的位置。因此,漢字在漢字庫中的具體位置計(jì)算公式為:94*(區(qū)號-1) +位號-1。減1是因?yàn)閿?shù)組是以0為開始而區(qū)號位號是以1為開始的。這僅為以漢字為單位該漢字在漢字庫中的位置,那么,如何得到以字節(jié)為單位得到該漢字在 漢字庫中的位置呢?只需乘上一個(gè)漢字字模占用的字節(jié)數(shù)即可,即:(94*(區(qū)號-1)+位號-1)*一個(gè)漢字字模占用字節(jié)數(shù),而按每種漢字庫的漢字大小不 同又會(huì)得到不同的結(jié)果。以16*16點(diǎn)陣字庫為例,計(jì)算公式則為:(94*(區(qū)號-1)+(位號-1))*32。漢字庫文該從該位置起的32字節(jié)信息即記 錄了該字的字模信息。
Ok,明白了這些,這個(gè)程序就很好理解了,其實(shí)質(zhì)是和我們分析的上一個(gè)顯示ASC字符的程序是差不多的,首先除了定義,就是尋址漢字在flash中的物理位置。
由于在中文環(huán)境下,輸入的是漢字的內(nèi)碼,我們必須將之轉(zhuǎn)換成區(qū)位碼,算出偏移量,從字庫中找到對應(yīng)的漢字,將其字模顯示即可。內(nèi)碼轉(zhuǎn)換成區(qū)位碼方法如下:
qh=c1-0xa0 wh=c2-0xa0
其區(qū)位碼就是:
qw=qh*0xff+wh
該漢字在字庫中離起點(diǎn)的位置是:
offset=(94*(qh-1)+(wh-1))*32L??????????????
其他步驟和上一個(gè)函數(shù)幾乎一模一樣,就不多廢話了~。
/*****************************************
void setcursor(uchar x, uchar y)
{
if ( x<= LCD_MAX_X )
cursor.x= x;
else
cursor.x= LCD_MAX_X;
if ( y<= LCD_MAX_Y )
cursor.y= y;
else
cursor.y= LCD_MAX_Y-1;
}
*****************************************/
再 看接下來的這個(gè)程序。這個(gè)程序的作用是設(shè)置光標(biāo)位置,以8×8點(diǎn)陣為一個(gè)光標(biāo)單位,入口參數(shù)表示x方向和y方向光標(biāo)位置。LCD_MAX_X和 LCD_MAX_Y的含義和計(jì)算公式在剛開始的時(shí)候我們就已經(jīng)說過了。至于這里為什么是LCD_MAX_Y-1,前面已經(jīng)說了一個(gè)字符或者漢字都應(yīng)該由兩 個(gè)page來提供數(shù)據(jù),現(xiàn)在我需要在7,8頁顯示東西,那么我應(yīng)該是把縱坐標(biāo)點(diǎn)設(shè)置到7頁吧?呵呵,其實(shí)點(diǎn)破就很簡單了。
/*****************************************
void lcdstring( uchar *pst)
{
while ( *pst != 0 )
{
if ( *pst < 0x80 ) //小于0x80是字符
{
if (*pst==0x0a) setcursor(0, cursor.y+2); //處理回車換行
else if (*pst== 0x0d) setcursor(0, cursor.y );
else
{
show_asc(*pst); //顯示ASC字符
cursor.x++;
if (( cursor.x > LCD_MAX_X )&& (*(pst+1)!=0x0a)){cursor.x=0;cursor.y=cursor.y+2;}
}
pst++; //下一個(gè)要顯示的字符
}
else //大于0x80是漢字
{
if ( cursor.x>= LCD_MAX_X )
{
show_asc(0x20); //一行的尾部只有半個(gè)漢字位置的處理,加一個(gè)空格,在下一行開始顯示
cursor.x=0;cursor.y=cursor.y+2;
if (cursor.y> LCD_MAX_Y) cursor.y=0; //
}
show_hz(*pst, *(pst+1)); //顯示一個(gè)漢字
cursor.x+=2;
pst+=2;
if ((cursor.x> LCD_MAX_X)&& (*(pst+1)!=0x0a)){cursor.x=0;cursor.y=cursor.y+2;}
}
if ( cursor.y> LCD_MAX_Y ){ cursor.x=0;cursor.y=0;return;}
}
}
*****************************************/
這 個(gè)程序呢,也就是我們這篇文章介紹的內(nèi)容的中心程序!地位重要吧。那么他的作用是什么呢?就是在當(dāng)前光標(biāo)位置顯示字符串拉。入口參數(shù)就是要顯示的字符串。 更關(guān)鍵的是同時(shí)也顯示漢字!。什么?你還是沒有理解到這個(gè)東西的好處?這樣說吧,只要你的程序中包含我們所介紹的這些程序,那么在應(yīng)用程序中,我們?nèi)绻?讓lcd顯示“風(fēng)”,那么我們就直接lcdstring(“風(fēng)”)就行了。哈哈,這么神奇的功能,實(shí)現(xiàn)起來其實(shí)是很簡單的拉。下面我門就一起來分析分析這 個(gè)函數(shù)。
首先是字符處理函數(shù)。前面的while,if語句中的內(nèi)容都很好理解,大家看程序注釋就行了,這里我要說的是if (*pst==0x0a) setcursor(0, cursor.y+2); 這句是處理回車換行,為什么?因?yàn)?x0a在asc碼表中就是表示換 行的,而接下來的0x0d是表示回車的,還有后來漢字顯示需要用的0x20表示space。呵呵,因?yàn)槲耶?dāng)時(shí)看的時(shí)候半天沒有找到asc碼表,為了避免讓 大家也都去找,這里直接說出來了。為什么是cursor.y+2呢?相信大家已經(jīng)知道答案了。什么?你不知道?ohyeah,請看上一頁~
這里有必要再講講cursor.x++;這句。為什么是cursor.x自增一?橫坐標(biāo)一個(gè)字符不是占用8個(gè)點(diǎn)么?千萬不要忘了,橫坐標(biāo)的點(diǎn)是用xdot表示的。計(jì)算公式是什么呢?xdot=cursor.x*8;。明白了吧?
接下來就是漢字處理部分。其流程和前一部分完全一樣,只不過cursor.x和pst都是自增2,原因當(dāng)然是因?yàn)闈h字是由區(qū)碼和位碼決定的,因此占用的字節(jié)數(shù)是字符的兩倍~呵呵,你看看顯示漢字的語句不是show_hz(*pst, *(pst+1))么?
到這里,我們的學(xué)習(xí)歷程就基本結(jié)束了。整個(gè)程序的精華已經(jīng)給大家介紹完了。當(dāng)然最后還剩兩個(gè)“在當(dāng)前光標(biāo)以十進(jìn)制方式在液晶上顯示一個(gè)字節(jié)的值”和“在當(dāng)前光標(biāo)顯示一個(gè)ASC字符”兩個(gè)函數(shù),不過非常簡單,這里只是作為附錄附在后面,有興趣的朋友可以試試自己分析一下。
這只是許多種lcd控制器其中一種的使用方法。但是不要怕,我這幾天又看了看其他的驅(qū)動(dòng)控制器,原理其實(shí)是一樣的,只不過是實(shí)現(xiàn)功能的程序可能不同,所以最關(guān)鍵的還是要理解思想。思想理解了,學(xué)別的類似東西也就相通了。
希望看到這個(gè)文章各位都能獲得一些知識和心得。如果能夠幫助你,將是我莫大的榮幸。
附錄:
//**********************************************
//在當(dāng)前光標(biāo)以十進(jìn)制方式在液晶上顯示一個(gè)字節(jié)的值
//入口:要顯示的值
lcddigit(uchar ch)
{
uchar i[4];
i[0]=(0x30+ch/100);
i[1]=(0x30+(ch%100)/10);
i[2]=(0x30+ch%10);
i[3]=0; //添加結(jié)束符號
lcdstring(i);
}
//***********************************
//在當(dāng)前光標(biāo)顯示一個(gè)ASC字符
//入口:要顯示的字符
lcdchar(uchar ch)
{
uchar i[2];
i[0]=ch;
i[1]=0; //添加結(jié)束符號
lcdstring(i);
}
|
|