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