接 口
LCD1602是很多單片機愛好者較早接觸的字符型液晶顯示器,它的主控芯片是HD44780或者其它兼容芯片。剛開始接觸它的大多是單片機的初學者。由于對它的不了解,不能隨心所欲地對它進行驅(qū)動。經(jīng)過一段時間的學習,我對它的驅(qū)動有了一點點心得,今天把它記錄在這里,以備以后查閱。
與此相仿的是LCD12864液晶顯示器,它是一種圖形點陣顯示器,能顯示的內(nèi)容比LCD1602要豐富得多,除了普通字符外,還可以顯示點陣圖案,帶有漢字庫的還可以顯示漢字,它的并行驅(qū)動方式與LCD1602相差無幾,所以,在這里花點時間是值得的。
一般來說,LCD1602有16條引腳,據(jù)說還有14條引腳的,與16腳的相比缺少了背光電源A(15腳)和地線K(16腳)。我手里這塊LCD1602的型號是HJ1602A,它有16條引腳:
圖2
它的16條引腳定義如下:
表:引腳說明
對表格的說明:
(1)VSS接電源地。
(2)VDD接+5V。
(3)VO是液晶顯示的偏壓信號,可接10K的3296精密電位器,或同樣阻值的RM065/RM063藍白可調(diào)電阻。見圖3。
圖3
(4)RS是命令/數(shù)據(jù)選擇引腳,接單片機的一個I/O,當RS為低電平時,選擇命令;當RS為高電平時,選擇數(shù)據(jù)。
(5)RW是讀/寫選擇引腳,接單片機的一個I/O,當RW為低電平時,向LCD1602寫入命令或數(shù)據(jù);當RW為高電平時,從LCD1602讀取狀態(tài)或數(shù)據(jù)。如果不需要進行讀取操作,可以直接將其接VSS。
(6)E,執(zhí)行命令的使能引腳,接單片機的一個I/O。
(7)D0—D7,并行數(shù)據(jù)輸入/輸出引腳,可接單片機的P0—P3任意的8個I/O口。如果接P0口,P0口應(yīng)該接4.7K—10K的上拉電阻。如果是4線并行驅(qū)動,只須接4個I/O口。
(8)A背光正極,可接一個10—47歐的限流電阻到VDD。
(9)K背光負極,接VSS。見圖4所示。
圖4
基本操作
LCD1602的基本操作分為四種:
讀狀態(tài)。輸入RS=0,RW=1,E=高脈沖。輸出:D0—D7為狀態(tài)字。
讀數(shù)據(jù)。輸入RS=1,RW=1,E=高脈沖。輸出:D0—D7為數(shù)據(jù)。
寫命令。輸入RS=0,RW=0,E=高脈沖。輸出:無。
寫數(shù)據(jù)。輸入RS=1,RW=0,E=高脈沖。輸出:無。
讀操作時序圖,如圖5所示:
圖5
寫操作時序圖,如圖6所示:
圖6
時序時間參數(shù),如圖7所示:
圖7
DDRAM(Display Data RAM)就是顯示數(shù)據(jù)RAM,用來寄存待顯示的字符代碼。共80個字節(jié),其地址和屏幕的對應(yīng)關(guān)系如下(如圖8):
圖8
DDRAM相當于計算機的顯存,我們?yōu)榱嗽谄聊簧巷@示字符,就把字符代碼送入顯存,這樣該字符就可以顯示在屏幕上了。同樣LCD1602共有80個字節(jié)的顯存,即DDRAM。但LCD1602的顯示屏幕只有16×2大小,因此,并不是所有寫入DDRAM的字符代碼都能在屏幕上顯示出來,只有寫在上圖所示范圍內(nèi)的字符才可以顯示出來,寫在范圍外的字符不能顯示出來。這樣,我們在程序中可以利用下面的“光標或顯示移動指令”使字符慢慢移動到可見的顯示范圍內(nèi),看到字符的移動效果。
前面說了,為了在液晶屏幕上顯示字符,就把字符代碼送入DDRAM。例如,如果想在屏幕左上角顯示字符‘A’,那么就把字符‘A’的字符代碼41H寫入DDRAM的00H地址處即可。至于怎么寫入,后面會有說明。
那么為什么把字符代碼寫入DDRAM,就可以在相應(yīng)位置顯示這個代碼的字符呢?我們知道,LCD1602是一種字符點陣顯示器,為了顯示一種字符的字形,必須要有這個字符的字模數(shù)據(jù),什么叫字符的字模數(shù)據(jù),看看下面的這個圖就明白了(如圖9)。
圖9
上圖的左邊就是字符‘A’的字模數(shù)據(jù),右邊就是將左邊數(shù)據(jù)用“○”代表0,用“■”代表1。從而顯示出‘A’這個字形。從圖12可以看出,字符‘A’的高4位是0100,低4位是0001,合在一起就是01000001b,即41H。它恰好與該字符的ASCII碼一致,這樣就給了我們很大的方便,我們可以在PC上使用P2=‘A’這樣的語法。編譯后,正好是這個字符的字符代碼。
在LCD1602模塊上固化了字模存儲器,就是CGROM和CGRAM,HD44780內(nèi)置了192個常用字符的字模,存于字符產(chǎn)生器CGROM(Character Generator ROM)中,另外還有8個允許用戶自定義的字符產(chǎn)生RAM,稱為CGRAM(Character Generator RAM)。圖12說明了CGROM和CGRAM與字符的對應(yīng)關(guān)系。
從ROM和RAM的名字我們也可以知道,ROM是早已固化在LCD1602模塊中的,只能讀取;而RAM是可讀寫的。也就是說,如果只需要在屏幕上顯示已存在于CGROM中的字符,那么只須在DDRAM中寫入它的字符代碼就可以了;但如果要顯示CGROM中沒有的字符,比如攝氏溫標的符號,那么就只有先在CGRAM中定義,然后再在DDRAM中寫入這個自定義字符的字符代碼即可。和CGROM中固化的字符不同,CGRAM中本身沒有字符,所以要在DDRAM中寫入某個CGROM不存在的字符,必須在CGRAM中先定義后使用。程序退出后CGRAM中定義的字符也不復存在,下次使用時,必須重新定義。
圖10
圖10說明的是5×8點陣和5×10點陣字符的字形和光標的位置。先來說5×8點陣,它有8行5列。那么定義這樣一個字符需要8個字節(jié),每個字節(jié)的前3個位沒有被使用。例如,定義攝氏溫標的符號{0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00}。
圖11
圖11說明的是設(shè)置CGRAM地址指令。從這個指令的格式中我們可以看出,它共有aaaaaa這6位,一共可以表示64個地址,即64個字節(jié)。一個5×8點陣字符共占用8個字節(jié),那么這64個字節(jié)一共可以自定義8個字符。
也就是說,圖11的6位地址中的DB5DB4DB3用來表示8個自定義的字符,DB2DB1DB0用來表示每個字符的8個字節(jié)。這DB5DB4DB3所表示的8個自定義字符(0--7)就是要寫入DDRAM中的字符代碼。我們知道,在CGRAM中只能定義8個自定義字符,也就是只有0—7這8個字符代碼,但在下面的這個表(如圖12)中一共有16個字符代碼(××××0000b--××××1111b)。
實際上,如圖11所示,它只能表示8個自定義字符 (××××0000b=××××1000b, ××××0001b=××××1001b……依次類推)。也就是說,寫入DDRAM中的字符代碼0和字符代碼8是同一個自定義字符。5×10點陣每個字符共占用16個字節(jié)的空間,所以CGRAM中只能定義4個這樣的自定義字符。
那么如何在CGRAM中自定義字符呢?在上面的介紹中,我們知道有一個設(shè)置CGRAM地址指令,同寫DDRAM指令相似,只須設(shè)置好某個自定義字符的字模數(shù)據(jù),然后按照上面介紹的方法,設(shè)置好CGRAM地址,依次寫入這個字模數(shù)據(jù)即可。我們在后面的例子中再進行說明。
圖12
LCD1602指令
1、工作方式設(shè)置指令(圖13)
圖13
×:不關(guān)心,也就是說這個位是0或1都可以,一般取0。
DL:設(shè)置數(shù)據(jù)接口位數(shù)。
DL=1:8位數(shù)據(jù)接口(D7—D0)。
DL=0:4位數(shù)據(jù)接口(D7—D4)。
N=0:一行顯示。
N=1:兩行顯示。
F=0:5×8點陣字符。
F=1:5×10點陣字符。
因為是寫指令字,所以RS和RW都是0。LCD1602只能用并行方式驅(qū)動,不能用串行方式驅(qū)動。而并行方式又可以選擇8位數(shù)據(jù)接口或4位數(shù)據(jù)接口。這里我們選擇8位數(shù)據(jù)接口(D7—D0)。我們的設(shè)置是8位數(shù)據(jù)接口,兩行顯示,5×8點陣,即0b00111000也就是0x38。(注意:NF是10或11的效果是一樣的,都是兩行5×8點陣。因為它不能以兩行5×10點陣方式進行顯示,換句話說,這里用0x38或0x3c是一樣的)。
2、顯示開關(guān)控制指令(圖14)
圖14
D=1:顯示開,D=0:顯示關(guān)。
C=1:光標顯示,C=0:光標不顯示。
B=1:光標閃爍,B=0:光標不閃爍。
這里的設(shè)置是顯示開,不顯示光標,光標不閃爍,設(shè)置字為0x0c。
3、進入模式設(shè)置指令(圖15、16)
圖15
I/D=1:寫入新數(shù)據(jù)后光標右移。
I/D=0:寫入新數(shù)據(jù)后光標左移。
S=1:顯示移動。
S=0:顯示不移動。
圖16
這里的設(shè)置是0x06。
4、光標或顯示移動指令(圖17、18)
圖17
圖18
在需要進行整屏移動時,這個指令非常有用,可以實現(xiàn)屏幕的滾動顯示效果。初始化時不使用這個指令。
5、清屏指令(圖19)
圖19
清除屏幕顯示內(nèi)容。光標返回屏幕左上角。執(zhí)行這個指令時需要一定時間。
6、光標歸位指令(圖20)
圖20
光標返回屏幕左上角,它不改變屏幕顯示內(nèi)容。
7、設(shè)置CGRAM地址指令(圖21)
圖21
這個指令在上面已經(jīng)介紹過。用法在后面例子中說明。
8、設(shè)置DDRAM地址指令(圖22)
圖22
這個指令用于設(shè)置DDRAM地址。在對DDRAM進行讀寫之前,首先要設(shè)置DDRAM地址,然后才能進行讀寫。前面我們說過,DDRAM就是LCD1602的顯示存儲器。我們要在它上面進行顯示,就要把要顯示的字符寫入DDRAM。同樣,我們想知道DDRAM某個地址上有什么字符,也要先設(shè)置DDRAM地址,然后將它讀出到單片機。
9、讀忙信號和地址計數(shù)器AC(圖23)
圖23
這個指令用來讀取LCD1602狀態(tài)。對于單片機來說,LCD1602屬于慢速設(shè)備。當單片機向其發(fā)送一個指令后,它將去執(zhí)行這個指令。這時如果單片機再次發(fā)送下一條指令,由于LCD1602速度較慢,前一條指令還未執(zhí)行完畢,它將不接受這新的指令,導致新的指令丟失。因此這條讀忙指令可以用來判斷LCD1602是否忙,能否接收單片機發(fā)來的指令。
當BF=1,表示LCD1602正忙,不能接受單片機的指令;當BF=0,表示LCD1602空閑,可以接收單片機的指令。RS=0,表示是指令;RW=1,表示是讀取。
這條指令還有一個副產(chǎn)品:即可以得到地址記數(shù)器AC的值(address counter)。LCD1602維護了一個地址計數(shù)器AC,用來記錄下一次讀寫CGRAM或DDRAM的位置。
需要強調(diào)的是:這條指令我一次也沒有執(zhí)行成功。很多網(wǎng)友似乎也是這樣。好在我們有另外的辦法,也就是延時。通過查看每條指令的執(zhí)行時間,再經(jīng)過一些試驗,可以確定指令的延時。這樣就可以在上一條指令執(zhí)行完畢后再執(zhí)行下一條指令了。
10、寫數(shù)據(jù)到CGRAM或DDRAM指令(圖24)
圖24
RS=1,數(shù)據(jù);RW=0,寫。指令執(zhí)行時,要在DB7—DB0上先設(shè)置好要寫入的數(shù)據(jù),然后執(zhí)行寫命令。
11、從CGRAM或DDRAM讀數(shù)據(jù)指令(圖25)
圖25
RS=1,數(shù)據(jù);RW=1,讀。先設(shè)置好CGRAM或DDRAM的地址,然后執(zhí)行讀取命令。數(shù)據(jù)就被讀入后DB7—DB0。
實 例
下面我們就以一個實例來結(jié)束這篇文章。
先介紹一下背景:
單片機最小系統(tǒng)(擴充了外部RAM 62256)。
采用STC89C52RC,晶振22.1184MHZ。
以5×8點陣,16×2行,8位數(shù)據(jù)端口。
首先在第一行顯示“I love MCU!”,第二行顯示“LCD1602 Test!”。延時一段時間,清屏。
然后在第一行顯示自定義字符:攝氏溫標標志。第二行顯示圓周率(pai)標志。再延時一段時間,清屏。
最后在第一行顯示“Welcome to my blog!”,顯示方式是從屏幕右面移入,左面移出。
周而復始(如圖26)。
圖26
代碼如下:
←左右滑動,查看代碼→
//File1
#ifndef __ZHANGTYPE_H__
#define __ZHANGTYPE_H__
#define uint8 unsigned char
#define uint16 unsigned short int
#define uint32 unsigned long int
#define int8 signed char
#define int16 signed short int
#define int32 signed long int
#define uint64 unsigned long long int
#define int64 signed long long int
#endif
//File2
#ifndef __FUN_H__
#define __FUN_H__
#include “ZhangType.h”
#include
void Delay(uint16 time);
#endif
//File3
#include “fun.h”
void Delay(uint16 time)
{
while(time--);
}
//File4
#ifndef __1602_H__
#define __1602_H__
#include
#include “ZhangType.h” //變量類型
#include “fun.h” //常用函數(shù)
#define SETMODE 0x38 //16*2顯示,5*7點陣,8位數(shù)據(jù)接口
#define DISOPEN 0x0C //顯示開,不顯示光標,光標不閃爍
#define DISMODE 0x06 //讀寫字符后地址加1,屏顯不移動
#define SETADDR 0x80 //設(shè)置數(shù)據(jù)地址指針初始值
#define CLEAR 0x01 //清屏,數(shù)據(jù)指針清零
#define RET 0x02 //回車,數(shù)據(jù)指針清零
#define PORT P2 //I/O口
sbit RS = P1^0;
sbit RW = P1^1;
sbit E = P1^2;
void Init1602(void); //初始化1602
void Write1602_Com(uint8 com); //寫命令
void Write1602_Dat(uint8 dat); //寫數(shù)據(jù)
void CheckBusy(void); //檢查忙
void Write1602_One_Dat(uint8 X,uint8 Y,uint8 dat);
//寫一個數(shù)據(jù)
void Write1602_Str(uint8 addr,uint8 length,uint8 *pbuf);
//寫一個數(shù)據(jù)串
#endif//
//File5
#include “1602.h”
void Write1602_Com(uint8 com)
{
E=0;
RS=0; //命令
Delay(50); //延時
RW=0; //寫
Delay(50);
PORT=com; //端口賦值
Delay(50);
E=1; //高脈沖
Delay(50);
E=0;
}
void Write1602_Dat(uint8 dat)
{
E=0;
RS=1; //數(shù)據(jù)
Delay(50); //延時
RW=0; //寫
Delay(50);
PORT=dat; //端口賦值
Delay(50);
E=1; //高脈沖
Delay(50);
E=0;
}
void CheckBusy(void)
{
uint8 temp;
RS=0; //命令
RW=1; //讀
E=0;
while(1)
{
PORT=0xFF; //端口為輸入
E=1; //高脈沖
temp=PORT;
E=0;
if ((temp&0x80)==0) //檢查BF位是否為0
break;
}
}
void Init1602(void)
{
Write1602_Com(SETMODE); //模式設(shè)置
Delay(500);
Write1602_Com(DISOPEN); //顯示設(shè)置
Delay(500);
Write1602_Com(DISMODE); //顯示模式
Delay(500);
Write1602_Com(CLEAR); //清屏
Delay(500);
}
void Write1602_One_Dat(uint8 x,uint8 y,uint8 dat)
{
x&=0x0f;
y&=0x01;
if(y)
x|=0x40;
x|=0x80;
Write1602_Com(x);
Write1602_Dat(dat);
}
void Write1602_Str(uint8 addr,uint8 length,uint8 *pbuf)
{
uint8 i;
Write1602_Com(addr);
for(i=0;i
{
Write1602_Dat(pbuf[i]);
}
}
//File6
/*******************************************************
*名稱:主文件(_main.c)
*功能:測試
*日期:2014/09/09
*******************************************************/
#include “1602.h”
#include “fun.h”
uint8 code hot[8]={ //攝氏溫度字模
0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00
};
uint8 code pi[8]={
0x00,0x1f,0x0a,0x0a,0x0a,0x13,0x00,0x00 //pai
};
uint8 code strMCU[]=“I love MCU!”;
uint8 code strTest[]=“LCD1602 Test!”;
uint8 code blog[]=“Welcome to my blog!”;
uint8 i;
void main()
{
Init1602(); //初始化1602
//自定義CGRAM
Write1602_Str(0x40,8,hot); //攝氏溫標
Write1602_Str(0x48,8,pi); //pai
Write1602_Str(0x80,strlen(strMCU),strMCU);
//“I love MCU!”
Write1602_Str(0x80+0x40,strlen(strTest),strTest);
//“LCD1602 Test!”
for(i=0;i《50;i++) //延時一段時間
Delay(10000);
Write1602_Com(CLEAR); //指令執(zhí)行時間較長
Delay(500); //多加一些延時
for(i=0;i《16;i++)
Write1602_Dat(0);
Write1602_Com(0xc0); //設(shè)置DDRAM地址
for(i=0;i《16;i++)
Write1602_Dat(1);
for(i=0;i《50;i++) //延時一段時間
Delay(10000);
Write1602_Com(CLEAR); //指令執(zhí)行時間較長
Delay(500); //多加一些延時
Write1602_Str(0x80+0x10,strlen(blog),blog);
//寫在顯示之外
while(1)
{
Write1602_Com(0x18); //左移
for(i=0;i《20;i++) //延時
Delay(10000);
? ? ? ?責任編輯:pj
評論
查看更多