16×2 LCD 模塊的驅(qū)動
點陣字符型LCD-TC1602A
點陣字符型液晶顯示器是專門用于顯示數(shù)字、字母、圖形符號及少量自定義符號的顯示
器。由于其具有功耗低、體積小、重量輕、超薄等優(yōu)點,自問世以來LCD 就得到了廣泛應(yīng)
用。字符型液晶顯示器模塊在國際上已經(jīng)規(guī)范化,在市場上內(nèi)核為HD44780 的較常見(可
以參考配套光盤上的數(shù)據(jù)手冊)。本章以TC1602A型LCD為例介紹其驅(qū)動程序的編寫方法。
1. TC1602A 液晶顯示器與DP-51PROC 實驗儀的連接
DP-51PROC 實驗儀上有一標(biāo)準(zhǔn)的LCD 液晶顯示器接口J106,標(biāo)注為LCD1602。
它與P87C52X2 以總線方式連接,其硬件連接如圖2.57 所示。
2. 驅(qū)動程序的使用
本驅(qū)動程序可以在沒有Small RTOS51 的情況下使用。此時,要使用本驅(qū)動程序只需
要配置設(shè)置讀寫液晶模塊LCD1602 的數(shù)據(jù)、命令、狀態(tài)的方法。定義它們的例子見程序
清單4.1。因為在驅(qū)動程序的主文件lcd1602.c 僅包含一個文件config.h,所以用戶必須
把它們放在文件config.h 中。如果用戶單獨使用lcd1602.c,還要在config.h 包含
lcd1602.h 文件和其它必須的文件如reg51 等;并定義宏TRUE、FALSE 和與編譯器無關(guān)
的數(shù)據(jù)類型。在使用Small RTOS51 的情況下,如果用戶只有一個任務(wù)使用液晶模塊
LCD1602 總線,用戶只要在config.h 定義這些方法就可以了。 如果用戶不止一個任務(wù)需要訪問液晶模塊LCD1602,則驅(qū)動程序需要使用信號量保證各個任務(wù)對液晶模塊
LCD1602 的互斥操作。這時,需要將宏LCD1602_SEM 定義為分配給液晶模塊LCD1602
驅(qū)動程序的信號量的索引,并在使用驅(qū)動程序前建立這個信號量。
在使用液晶模塊LCD1602 驅(qū)動程序前應(yīng)該調(diào)用函數(shù)Lcd1602Init()初始化液晶模塊。
單獨使用或單任務(wù)使用本驅(qū)動程序時,使用函數(shù)Lcd1602DispStr()在屏幕指定位置顯示
字符串,使用函數(shù)Lcd1602Clr()清除指定行。如果有特殊字符需要寫入液晶模塊,則可以
調(diào)用函數(shù)Lcd1602LoadC()。如果有多個任務(wù)需要對使用本驅(qū)動程序,則是分別調(diào)用宏
OSLcd1602DispStr()、OSLcd1602Clr()和OSLcd1602LoadC()。
當(dāng)有多個任務(wù)需要對液晶模塊LCD1602 操作時,還要注意驅(qū)動程序的重入問題。如果
用戶不是在Keil C51 中使用Small RTOS51,可能不需要關(guān)心這個問題。但在Small
RTOS51 中,因為液晶驅(qū)動程序使用了通用指針,導(dǎo)致函數(shù)Lcd1602DispStr()和
Lcd1602LoadC()不可重入。幸好這個問題并不嚴(yán)重,只要禁止所有使用了液晶的任務(wù)與
Lcd1602DispStr()和Lcd1602LoadC()進行覆蓋分析就對程序沒有影響了。這是因為使
用了信號量使各個任務(wù)互斥調(diào)用函數(shù)Lcd1602DispStr()和Lcd1602LoadC()。如果只有
一個任務(wù)對器件寫,則不需要禁止對它們進行覆蓋分析。
程序清單4.1 DP-51PROC 中讀寫液晶模塊LCD1602 的數(shù)據(jù)、命令、狀態(tài)的方法
/* 定義LCD1602 操作地址 */
#define LCD1602_WR XBYTE[0x2001] /* 寫數(shù)據(jù)操作地址 */
#define LCD1602_RD XBYTE[0x2002] /* 讀狀態(tài)操作地址 */
#define LCD1602_WC XBYTE[0x2000] /* 寫命令操作地址 */
//寫命令
#define LCD1602_SEND_COMMAND(a)
LCD1602_WC = a; /* 寫命令 */
//寫數(shù)據(jù)
#define LCD1602_SEND_DATA(a)
LCD1602_WR = a; /* 寫數(shù)據(jù) */
#ifdef IN_LCD17602
//返回狀態(tài)
uint8 LCD1602_GET_FLAG(void)
{
return (LCD1602_RD); /* 返回液晶狀態(tài) */
}
#endif
3. 對TC1602A 操作的基本函數(shù)
1) 等待TC1602A 操作完成
液晶模塊TC1602A 的控制器HD44780 速度較慢,每次進行讀寫操作時,應(yīng)首先檢測
上次操作是否完成,或在每次讀寫操作后延時1ms 等待讀寫完成。這是通過調(diào)用函數(shù)
Lcd1602Delay()來完成的。程序Lcd1602Delay()的代碼見程序清單4.2。
程序清單4.2 等待TC1602A 空閑
void Lcd1602Delay(void)
{
uint8 i;
i = 100; (1)
do
{
if ((LCD1602_GET_FLAG() & 0x80) == 0) (2)
{
break; (3)
}
} while (--i != 0); (4)
}
程序首先設(shè)置循環(huán)次數(shù)(程序清單4.2(1)),然后在循環(huán)中檢測液晶模塊是否空閑(程
序清單4.2(2))。如果空閑,函數(shù)結(jié)束。設(shè)置循環(huán)上限目的是為了避免液晶損壞而使程序進
入無限循環(huán)。
2) 向TC1602A 發(fā)送命令
驅(qū)動程序使用函數(shù)Lcd1602SendComm()向液晶模塊TC1602A 發(fā)送命令,它的唯
一參數(shù)為將要發(fā)送的命令字。函數(shù)Lcd1602SendComm()代碼見程序清單4.3。代碼很
簡單,不作介紹。
程序清單4.3 向TC1602A 發(fā)送命令
void Lcd1602SendComm(uint8 Command)
{
Lcd1602Delay(); /* 等待任務(wù)lcd1602 空閑 */
LCD1602_SEND_COMMAND(Command); /* 寫命令字 */
}
3) 向TC1602A 發(fā)送數(shù)據(jù)
驅(qū)動程序使用函數(shù)Lcd1602SendDate ()向液晶模塊TC1602A 發(fā)送數(shù)據(jù),它的唯一
參數(shù)為將要發(fā)送的數(shù)據(jù)。函數(shù)Lcd1602SendDate ()代碼見程序清單4.4。代碼很簡單,
不作介紹。
程序清單4.4 向TLC1602A 發(fā)送數(shù)據(jù)
void Lcd1602SendDate(uint8 Data)
{
Lcd1602Delay(); /* 等待任務(wù)lcd1602 空閑 */
LCD1602_SEND_DATA(Data); /* 寫數(shù)據(jù) */
}
4. 初始化TC1602A 液晶顯示器
在使用TC1602A液晶顯示器前必須對它進行初始化,這是通過調(diào)用函數(shù)Lcd1602Init()
實現(xiàn),其代碼見程序清單4.5。
程序清單4.5 初始化TC1602A
void Lcd1602Init(void)
{
Lcd1602SendComm(LCD1602_MODE); (1)
Lcd1602SendComm(LCD1602_NO_FLASH); (2)
Lcd1602SendComm(LCD1602_NO_SHIFT); (3)
Lcd1602SendComm(LCD1602_SH); (4)
Lcd1602Clr(1); (5)
Lcd1602Clr(2); (6)
}
程序首先設(shè)置液晶模塊控制器HD44780 的工作模式(程序清單4.5(1)),其中
LCD1602_MODE 的值在文件lcd1602.h 中定義,為0x3c。從前面介紹可知,這是把
HD44780 設(shè)置為8 位總線、兩行顯示、5*10 點陣字體。然后打開顯示(程序清單4.5(2)),
其中LCD1602_NO_FLASH 的值在文件lcd1602.h 中定義,為0x0c。從前面介紹可知,
這是使液晶開始顯示、不顯示光標(biāo)、光標(biāo)不閃爍。接著設(shè)置液晶模塊的輸入方式(程序清單
4.5(3)),其中LCD1602_NO_SHIFT 的值在文件lcd1602.h 中定義,為0x06。從前面
介紹可知,這使模塊數(shù)據(jù)輸入為增量方式,顯示內(nèi)容不移動(光標(biāo)移動)。接下來設(shè)置光標(biāo)位
移方式(程序清單4.5(4));其中LCD1602_SH 的值在文件lcd1602.h 中定義,為0x14。
從前面介紹可知,這是使顯示一個字符時光標(biāo)左移,并且光標(biāo)在下一個要顯示的字符的位置。
最后是清屏(程序清單4.5(5)、(6))。
4. 清除指定行
如果有多個任務(wù)需要操作液晶模塊TC1602A,則使用OSLcd1602Clr()清除顯示模塊
的某一行。如果僅一個任務(wù)需要操作操作液晶模塊TC1602A,則使用Lcd1602Clr()清除
顯示模塊的某一行。OSLcd1602Clr()是一個宏,代碼見程序清單4.6。
程序清單4.6 多任務(wù)中清除指定行
#define OSLcd1602Clr(y)
{
OSSemPend(LCD1602_SEM, 0); (1)
Lcd1602Clr(y); (2)
OSSemPost(LCD1602_SEM); (3)
}
程序通過在液晶模塊TC1602A 上清除指定行之前等待信號量(程序清單4.6(1))和
在液晶模塊TC1602A 上清除指定行之后發(fā)送信號量(程序清單4.6(3))來實現(xiàn)對器件的
互斥操作。這樣做的原因可以參見4.1 節(jié)。在宏中調(diào)用函數(shù)Lcd1602Clr()在液晶模塊
TC1602A 清除指定行。而函數(shù)Lcd1602Clr()就是單任務(wù)情況下在液晶模塊TC1602A 清
除指定行的函數(shù),所以兩者的參數(shù)相同。
函數(shù)Lcd1602Clr()的代碼見程序清單4.7。函數(shù)Lcd1602Clr()的流程圖見圖4.1。 函
數(shù)Lcd1602Clr()有唯一參數(shù)指示需要清除的行。
程序清單4.7 單任務(wù)中清除指定行
void Lcd1602Clr(uint8 y)
{
uint8 i;
i = 0; (1)
if (y == 1) (2)
{
Lcd1602SendComm(LCD1602_LINE1); (3)
i = 16; (4)
}
else if (y == 2) (5)
{
Lcd1602SendComm(LCD1602_LINE2); (6)
i = 16; (7)
}
if (i != 0) (8)
{
do
{
Lcd1602SendDate(' '); (9)
} while (--i != 0); (10)
}
}
函數(shù)Lcd1602Clr()首先要根據(jù)清除的行號設(shè)置相應(yīng)的行顯示首地址(程序清單
4.7(3)、(6))。LCD1602_LINE1 和LCD1602_LINE2 的值在文件lcd1602.h 中定義,
分別為0x80 和0xc0,為各行的顯示首地址+0x80(0x80 為設(shè)置顯示地址命令)。然后
函數(shù)Lcd1602Clr()判斷行號是否有效(程序清單4.7(8))。這里利用了變量i 作為標(biāo)志來
判斷。變量i 同時也存儲需要清除的字符的個數(shù)。真正的清除行是通過顯示16(一行的字
符數(shù))個空格來實現(xiàn)的(程序清單4.7(9)、(10))。
圖4.1 單任務(wù)清除指定行流程圖
6.在指定位置顯示字符串
如果有多個任務(wù)需要操作液晶模塊TC1602A,則使用OSLcd1602DispStr()來顯示
一個字符串。如果僅一個任務(wù)需要操作操作液晶模塊TC1602A,則使用Lcd1602DispStr()
來顯示一個字符串。OS Lcd1602DispStr()是一個宏,代碼見程序清單4.8。
程序清單4.8 多任務(wù)中在指定位置顯示字符串
#define OSLcd1602DispStr(x, y, Data)
{
OSSemPend(LCD1602_SEM, 0); (1)
Lcd1602DispStr((x), (y), (Data)); (2)
OSSemPost(LCD1602_SEM); (3)
}
程序通過在液晶模塊TC1602A 上顯示字符串行之前等待信號量(程序清單4.8(1))
和在液晶模塊TC1602A 上顯示字符串之后發(fā)送信號量(程序清單4.8(3))來實現(xiàn)對器件
的互斥操作。這樣做的原因可以參見前面的敘述。在宏中調(diào)用函數(shù)Lcd1602DispStr()在
液晶模塊TC1602A 上顯示字符串。而函數(shù)Lcd1602DispStr())就是單任務(wù)情況下在液晶
模塊TC1602A 顯示字符串的函數(shù),所以兩者的參數(shù)相同。
函數(shù)Lcd1602DispStr()的代碼見程序清單4.4。函數(shù)Lcd1602DispStr()的流程圖
見圖4.2,該圖作了簡化。 函數(shù)Lcd1602DispStr()的參數(shù)中x,y 指示字符串開始顯示
的位置坐標(biāo),其中液晶模塊TC1602A 的左上角坐標(biāo)定義為1,1。而參數(shù)Data 指向?qū)⒁@
示的字符串(以’\0’作為結(jié)束標(biāo)志)。該函數(shù)會自動換行,即當(dāng)?shù)谝恍酗@示不完整個字符串
則從第二行開始處繼續(xù)顯示;但如果第二行顯示不完則剩余的字符不再顯示。
程序清單4.9 單任務(wù)中在指定位置顯示字符串
void Lcd1602DispStr(uint8 x, uint8 y, char *Data)
{
if (y == 1) (1)
{
if (x < (16 + 1)) (2)
{
Lcd1602SendComm(LCD1602_LINE1 - 1 + x); (3)
for( ; x < (16 + 1) && *Data != '\0'; x++) (4)
{
Lcd1602SendDate(*Data++); (5)
}
if (*Data != '\0') (6)
{
x = 1; (7)
y = 2; (8)
}
}
}
if (y == 2) (9)
{
Lcd1602SendComm(LCD1602_LINE2 - 1 + x); (10)
for( ; x < (16 + 1) && *Data != '\0'; x++) (11)
{
Lcd1602SendDate(*Data++); (12)
}
}
}
函數(shù)Lcd1602DispStr()首先判斷字符串是否在第一行顯示(程序清單4.9(1));是否
超過行尾( 程序清單4.9(2) )。如果在第一行的顯示范圍內(nèi)開始顯示, 函數(shù)
Lcd1602DispStr()將設(shè)置顯示開始的地址(程序清單4.9(3))),并開始寫入顯示字符(程
序清單4.9(5))直到行尾或字符串結(jié)束(程序清單4.9(4))。接著判斷顯示字符串是否結(jié)束(程序清單4.9(6)),如果沒有,重新設(shè)置顯示的開始地址為第二行(程序清單4.9(8))
第一列(程序清單4.9(7))。由于需要支持自動換行,函數(shù)Lcd1602DispStr()直接使用if
判斷是否在第二行顯示(程序清單4.9(9))使字符串在第一行顯示不完時可以在第二行開
始處接著顯示。如果在第二行顯示,則也需要設(shè)置顯示開始的地址(程序清單4.9(10))),
并接著寫入顯示字符(程序清單4.9(12))直到行尾或字符串結(jié)束(程序清單4.9(11))。
因為液晶模塊僅兩行,所以不需要再次判斷字符串是否顯示完畢。
圖4.2 單任務(wù)在指定位置顯示字符串流程圖
7. 在指定地址向液晶模塊寫多個字符
如果用戶需要把任意字符寫入液晶模塊TC1602A 的任意地址, 可以調(diào)用
OSLcd1602LoadC()或Lcd1602LoadC()實現(xiàn)。當(dāng)用戶有多個任務(wù)需要操作液晶模塊
TC1602A,使用OSLcd1602LoadC()來寫多個字符。當(dāng)用戶僅一個任務(wù)需要操作操作液
晶模塊TC1602A,則使用Lcd1602LoadC()來寫多個字符。OSLcd1602LoadC()是一個
宏,代碼見程序清單4.10。
程序清單4.10 多任務(wù)中在指定地址寫多個字符
#define OSLcd1602LoadC(addr, dstr, no)
{
OSSemPend(LCD1602_SEM, 0); (1)
Lcd1602LoadC ((addr), (dstr), (no)); (2)
OSSemPost(LCD1602_SEM); (3)
}
程序通過對液晶模塊TC1602A 寫字符之前等待信號量(程序清單4.10(1))和對液
晶模塊TC1602A 寫字符之后發(fā)送信號量(程序清單4.10(3))來實現(xiàn)對器件的互斥操作。
這樣做的原因可以參見4.1 節(jié)。在宏中調(diào)用函數(shù)Lcd1602LoadC()對液晶模塊TC1602A
寫字符。而函數(shù)Lcd1602LoadC()就是單任務(wù)情況下對液晶模塊TC1602A 寫字符的函數(shù),
所以兩者的參數(shù)相同。
函數(shù)Lcd1602LoadC()的代碼見程序清單4.11。函數(shù)Lcd1602LoadC()的第一個參
數(shù)Addr 為將要寫入字符的開始地址;第二個參數(shù)Data 為指向?qū)⒁獙懭氲淖址坏谌齻€參
數(shù)NChar 為將要寫入的字符數(shù)目。程序比較簡單,這里不再說明。
程序清單4.11 單任務(wù)中在指定地址寫多個字符
void Lcd1602LoadC(uint8 Addr, uint8 *Data, uint8 NChar)
{
Lcd1602SendComm(Addr | 0x80); // 設(shè)置寫入地址
do
{
Lcd1602SendDate(*Data++);
} while (--NChar != 0);
}
8. 驅(qū)動程序在DP-51PROC 上使用的例子
在DP-51PROC 上運行本程序后,液晶TC1602A 的第一行閃動顯示字符串"Small
RTOS51",第二行滾動顯示另一個長字符串。(接線可以參考實驗26 上的接法)
例子的主要代碼見程序清單4.12。程序比較簡單,這里不再說明。
程序清單4.12 驅(qū)動程序使用的例子主要代碼
/*************************************************************
** 函數(shù)名稱: main
** 功能描述: 主函數(shù),用戶程序從這里執(zhí)行
** 輸 入: 無
** 輸 出: 無
** 全局變量: 無
** 調(diào)用模塊: init(),OSStart(),LCMIni(),LCMClr();
*************************************************************/
void main(void)
{
init();
Lcd1602Init();
OSStart();
}
/*************************************************************
** 函數(shù)名稱: LcdDisplay1
** 功能描述: 一個任務(wù),在液晶第一行閃動字符串“Small RTOS51”
** 輸 入: 無
** 輸 出: 無
** 全局變量: 無
** 調(diào)用模塊: OSSemCreate(),OSLcd1602DispStr(),OSWait(),Lcd1602Clr()
*************************************************************/
void LcdDisplay1(void)
{
OSSemCreate(LCD1602_SEM, 1);
while (1)
{
OSLcd1602Clr(1); // 第一行清屏
OSWait(K_TMO, OS_TICKS_PER_SEC / 2); // 延時0.5S
OSLcd1602DispStr(4, 1, "Small RTOS51");
// 第一行顯示" Small RTOS51"
OSWait(K_TMO, (OS_TICKS_PER_SEC + 1) / 2); // 延時0.5S
}
}
/*************************************************************
** 函數(shù)名稱: LcdDisplay2
** 功能描述: 一個任務(wù),在液晶第二行滾動顯示一個字符串
** 輸 入: 無
** 輸 出: 無
** 全局變量: 無
** 調(diào)用模塊: OSLcd1602DispStr(),OSWait()
*************************************************************/
char xdata LogoStr[] = " Hello,World! Down it from www.zlgmcu.com";
void LcdDisplay2(void)
{
uint8 *cp;
cp = LogoStr;
while(1)
{
OSLcd1602Clr(2); // 第二行清屏
OSLcd1602DispStr(1, 2, cp); // 顯示字符串
OSWait(K_TMO, OS_TICKS_PER_SEC / 4); // 延時0.25S
cp++;
if (*cp == '\0')
{
cp = LogoStr;
}
}
}
代碼的其它部分參見本書配套光盤中的源代碼。
評論
查看更多