16×2 LCD 模块的驱动
点阵字符型LCD-TC1602A
点阵字符型液晶显示器是专门用于显示数字、字母、图形符号及少量自定义符号的显示
器。由于其具有功耗低、体积小、重量轻、超薄等优点,自问世以来LCD 就得到了广泛应
用。字符型液晶显示器模块在国际上已经规范化,在市场上内核为HD44780 的较常见(可
以参考配套光盘上的数据手册)。本章以TC1602A型LCD为例介绍其驱动程序的编写方法。
1. TC1602A 液晶显示器与DP-51PROC 实验仪的连接
DP-51PROC 实验仪上有一标准的LCD 液晶显示器接口J106,标注为LCD1602。
它与P87C52X2 以总线方式连接,其硬件连接如图2.57 所示。
2. 驱动程序的使用
本驱动程序可以在没有Small RTOS51 的情况下使用。此时,要使用本驱动程序只需
要配置设置读写液晶模块LCD1602 的数据、命令、状态的方法。定义它们的例子见程序
清单4.1。因为在驱动程序的主文件lcd1602.c 仅包含一个文件config.h,所以用户必须
把它们放在文件config.h 中。如果用户单独使用lcd1602.c,还要在config.h 包含
lcd1602.h 文件和其它必须的文件如reg51 等;并定义宏TRUE、FALSE 和与编译器无关
的数据类型。在使用Small RTOS51 的情况下,如果用户只有一个任务使用液晶模块
LCD1602 总线,用户只要在config.h 定义这些方法就可以了。 如果用户不止一个任务需要访问液晶模块LCD1602,则驱动程序需要使用信号量保证各个任务对液晶模块
LCD1602 的互斥操作。这时,需要将宏LCD1602_SEM 定义为分配给液晶模块LCD1602
驱动程序的信号量的索引,并在使用驱动程序前建立这个信号量。
在使用液晶模块LCD1602 驱动程序前应该调用函数Lcd1602Init()初始化液晶模块。
单独使用或单任务使用本驱动程序时,使用函数Lcd1602DispStr()在屏幕指定位置显示
字符串,使用函数Lcd1602Clr()清除指定行。如果有特殊字符需要写入液晶模块,则可以
调用函数Lcd1602LoadC()。如果有多个任务需要对使用本驱动程序,则是分别调用宏
OSLcd1602DispStr()、OSLcd1602Clr()和OSLcd1602LoadC()。
当有多个任务需要对液晶模块LCD1602 操作时,还要注意驱动程序的重入问题。如果
用户不是在Keil C51 中使用Small RTOS51,可能不需要关心这个问题。但在Small
RTOS51 中,因为液晶驱动程序使用了通用指针,导致函数Lcd1602DispStr()和
Lcd1602LoadC()不可重入。幸好这个问题并不严重,只要禁止所有使用了液晶的任务与
Lcd1602DispStr()和Lcd1602LoadC()进行覆盖分析就对程序没有影响了。这是因为使
用了信号量使各个任务互斥调用函数Lcd1602DispStr()和Lcd1602LoadC()。如果只有
一个任务对器件写,则不需要禁止对它们进行覆盖分析。
程序清单4.1 DP-51PROC 中读写液晶模块LCD1602 的数据、命令、状态的方法
/* 定义LCD1602 操作地址 */
#define LCD1602_WR XBYTE[0x2001] /* 写数据操作地址 */
#define LCD1602_RD XBYTE[0x2002] /* 读状态操作地址 */
#define LCD1602_WC XBYTE[0x2000] /* 写命令操作地址 */
//写命令
#define LCD1602_SEND_COMMAND(a)
LCD1602_WC = a; /* 写命令 */
//写数据
#define LCD1602_SEND_DATA(a)
LCD1602_WR = a; /* 写数据 */
#ifdef IN_LCD17602
//返回状态
uint8 LCD1602_GET_FLAG(void)
{
return (LCD1602_RD); /* 返回液晶状态 */
}
#endif
3. 对TC1602A 操作的基本函数
1) 等待TC1602A 操作完成
液晶模块TC1602A 的控制器HD44780 速度较慢,每次进行读写操作时,应首先检测
上次操作是否完成,或在每次读写操作后延时1ms 等待读写完成。这是通过调用函数
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)
}
程序首先设置循环次数(程序清单4.2(1)),然后在循环中检测液晶模块是否空闲(程
序清单4.2(2))。如果空闲,函数结束。设置循环上限目的是为了避免液晶损坏而使程序进
入无限循环。
2) 向TC1602A 发送命令
驱动程序使用函数Lcd1602SendComm()向液晶模块TC1602A 发送命令,它的唯
一参数为将要发送的命令字。函数Lcd1602SendComm()代码见程序清单4.3。代码很
简单,不作介绍。
程序清单4.3 向TC1602A 发送命令
void Lcd1602SendComm(uint8 Command)
{
Lcd1602Delay(); /* 等待任务lcd1602 空闲 */
LCD1602_SEND_COMMAND(Command); /* 写命令字 */
}
3) 向TC1602A 发送数据
驱动程序使用函数Lcd1602SendDate ()向液晶模块TC1602A 发送数据,它的唯一
参数为将要发送的数据。函数Lcd1602SendDate ()代码见程序清单4.4。代码很简单,
不作介绍。
程序清单4.4 向TLC1602A 发送数据
void Lcd1602SendDate(uint8 Data)
{
Lcd1602Delay(); /* 等待任务lcd1602 空闲 */
LCD1602_SEND_DATA(Data); /* 写数据 */
}
4. 初始化TC1602A 液晶显示器
在使用TC1602A液晶显示器前必须对它进行初始化,这是通过调用函数Lcd1602Init()
实现,其代码见程序清单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)
}
程序首先设置液晶模块控制器HD44780 的工作模式(程序清单4.5(1)),其中
LCD1602_MODE 的值在文件lcd1602.h 中定义,为0x3c。从前面介绍可知,这是把
HD44780 设置为8 位总线、两行显示、5*10 点阵字体。然后打开显示(程序清单4.5(2)),
其中LCD1602_NO_FLASH 的值在文件lcd1602.h 中定义,为0x0c。从前面介绍可知,
这是使液晶开始显示、不显示光标、光标不闪烁。接着设置液晶模块的输入方式(程序清单
4.5(3)),其中LCD1602_NO_SHIFT 的值在文件lcd1602.h 中定义,为0x06。从前面
介绍可知,这使模块数据输入为增量方式,显示内容不移动(光标移动)。接下来设置光标位
移方式(程序清单4.5(4));其中LCD1602_SH 的值在文件lcd1602.h 中定义,为0x14。
从前面介绍可知,这是使显示一个字符时光标左移,并且光标在下一个要显示的字符的位置。
最后是清屏(程序清单4.5(5)、(6))。
4. 清除指定行
如果有多个任务需要操作液晶模块TC1602A,则使用OSLcd1602Clr()清除显示模块
的某一行。如果仅一个任务需要操作操作液晶模块TC1602A,则使用Lcd1602Clr()清除
显示模块的某一行。OSLcd1602Clr()是一个宏,代码见程序清单4.6。
程序清单4.6 多任务中清除指定行
#define OSLcd1602Clr(y)
{
OSSemPend(LCD1602_SEM, 0); (1)
Lcd1602Clr(y); (2)
OSSemPost(LCD1602_SEM); (3)
}
程序通过在液晶模块TC1602A 上清除指定行之前等待信号量(程序清单4.6(1))和
在液晶模块TC1602A 上清除指定行之后发送信号量(程序清单4.6(3))来实现对器件的
互斥操作。这样做的原因可以参见4.1 节。在宏中调用函数Lcd1602Clr()在液晶模块
TC1602A 清除指定行。而函数Lcd1602Clr()就是单任务情况下在液晶模块TC1602A 清
除指定行的函数,所以两者的参数相同。
函数Lcd1602Clr()的代码见程序清单4.7。函数Lcd1602Clr()的流程图见图4.1。 函
数Lcd1602Clr()有唯一参数指示需要清除的行。
程序清单4.7 单任务中清除指定行
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)
}
}
函数Lcd1602Clr()首先要根据清除的行号设置相应的行显示首地址(程序清单
4.7(3)、(6))。LCD1602_LINE1 和LCD1602_LINE2 的值在文件lcd1602.h 中定义,
分别为0x80 和0xc0,为各行的显示首地址+0x80(0x80 为设置显示地址命令)。然后
函数Lcd1602Clr()判断行号是否有效(程序清单4.7(8))。这里利用了变量i 作为标志来
判断。变量i 同时也存储需要清除的字符的个数。真正的清除行是通过显示16(一行的字
符数)个空格来实现的(程序清单4.7(9)、(10))。
图4.1 单任务清除指定行流程图
6.在指定位置显示字符串
如果有多个任务需要操作液晶模块TC1602A,则使用OSLcd1602DispStr()来显示
一个字符串。如果仅一个任务需要操作操作液晶模块TC1602A,则使用Lcd1602DispStr()
来显示一个字符串。OS Lcd1602DispStr()是一个宏,代码见程序清单4.8。
程序清单4.8 多任务中在指定位置显示字符串
#define OSLcd1602DispStr(x, y, Data)
{
OSSemPend(LCD1602_SEM, 0); (1)
Lcd1602DispStr((x), (y), (Data)); (2)
OSSemPost(LCD1602_SEM); (3)
}
程序通过在液晶模块TC1602A 上显示字符串行之前等待信号量(程序清单4.8(1))
和在液晶模块TC1602A 上显示字符串之后发送信号量(程序清单4.8(3))来实现对器件
的互斥操作。这样做的原因可以参见前面的叙述。在宏中调用函数Lcd1602DispStr()在
液晶模块TC1602A 上显示字符串。而函数Lcd1602DispStr())就是单任务情况下在液晶
模块TC1602A 显示字符串的函数,所以两者的参数相同。
函数Lcd1602DispStr()的代码见程序清单4.4。函数Lcd1602DispStr()的流程图
见图4.2,该图作了简化。 函数Lcd1602DispStr()的参数中x,y 指示字符串开始显示
的位置坐标,其中液晶模块TC1602A 的左上角坐标定义为1,1。而参数Data 指向将要显
示的字符串(以’\0’作为结束标志)。该函数会自动换行,即当第一行显示不完整个字符串
则从第二行开始处继续显示;但如果第二行显示不完则剩余的字符不再显示。
程序清单4.9 单任务中在指定位置显示字符串
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)
}
}
}
函数Lcd1602DispStr()首先判断字符串是否在第一行显示(程序清单4.9(1));是否
超过行尾( 程序清单4.9(2) )。如果在第一行的显示范围内开始显示, 函数
Lcd1602DispStr()将设置显示开始的地址(程序清单4.9(3))),并开始写入显示字符(程
序清单4.9(5))直到行尾或字符串结束(程序清单4.9(4))。接着判断显示字符串是否结束(程序清单4.9(6)),如果没有,重新设置显示的开始地址为第二行(程序清单4.9(8))
第一列(程序清单4.9(7))。由于需要支持自动换行,函数Lcd1602DispStr()直接使用if
判断是否在第二行显示(程序清单4.9(9))使字符串在第一行显示不完时可以在第二行开
始处接着显示。如果在第二行显示,则也需要设置显示开始的地址(程序清单4.9(10))),
并接着写入显示字符(程序清单4.9(12))直到行尾或字符串结束(程序清单4.9(11))。
因为液晶模块仅两行,所以不需要再次判断字符串是否显示完毕。
图4.2 单任务在指定位置显示字符串流程图
7. 在指定地址向液晶模块写多个字符
如果用户需要把任意字符写入液晶模块TC1602A 的任意地址, 可以调用
OSLcd1602LoadC()或Lcd1602LoadC()实现。当用户有多个任务需要操作液晶模块
TC1602A,使用OSLcd1602LoadC()来写多个字符。当用户仅一个任务需要操作操作液
晶模块TC1602A,则使用Lcd1602LoadC()来写多个字符。OSLcd1602LoadC()是一个
宏,代码见程序清单4.10。
程序清单4.10 多任务中在指定地址写多个字符
#define OSLcd1602LoadC(addr, dstr, no)
{
OSSemPend(LCD1602_SEM, 0); (1)
Lcd1602LoadC ((addr), (dstr), (no)); (2)
OSSemPost(LCD1602_SEM); (3)
}
程序通过对液晶模块TC1602A 写字符之前等待信号量(程序清单4.10(1))和对液
晶模块TC1602A 写字符之后发送信号量(程序清单4.10(3))来实现对器件的互斥操作。
这样做的原因可以参见4.1 节。在宏中调用函数Lcd1602LoadC()对液晶模块TC1602A
写字符。而函数Lcd1602LoadC()就是单任务情况下对液晶模块TC1602A 写字符的函数,
所以两者的参数相同。
函数Lcd1602LoadC()的代码见程序清单4.11。函数Lcd1602LoadC()的第一个参
数Addr 为将要写入字符的开始地址;第二个参数Data 为指向将要写入的字符;第三个参
数NChar 为将要写入的字符数目。程序比较简单,这里不再说明。
程序清单4.11 单任务中在指定地址写多个字符
void Lcd1602LoadC(uint8 Addr, uint8 *Data, uint8 NChar)
{
Lcd1602SendComm(Addr | 0x80); // 设置写入地址
do
{
Lcd1602SendDate(*Data++);
} while (--NChar != 0);
}
8. 驱动程序在DP-51PROC 上使用的例子
在DP-51PROC 上运行本程序后,液晶TC1602A 的第一行闪动显示字符串"Small
RTOS51",第二行滚动显示另一个长字符串。(接线可以参考实验26 上的接法)
例子的主要代码见程序清单4.12。程序比较简单,这里不再说明。
程序清单4.12 驱动程序使用的例子主要代码
/*************************************************************
** 函数名称: main
** 功能描述: 主函数,用户程序从这里执行
** 输 入: 无
** 输 出: 无
** 全局变量: 无
** 调用模块: init(),OSStart(),LCMIni(),LCMClr();
*************************************************************/
void main(void)
{
init();
Lcd1602Init();
OSStart();
}
/*************************************************************
** 函数名称: LcdDisplay1
** 功能描述: 一个任务,在液晶第一行闪动字符串“Small RTOS51”
** 输 入: 无
** 输 出: 无
** 全局变量: 无
** 调用模块: 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
}
}
/*************************************************************
** 函数名称: LcdDisplay2
** 功能描述: 一个任务,在液晶第二行滚动显示一个字符串
** 输 入: 无
** 输 出: 无
** 全局变量: 无
** 调用模块: 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;
}
}
}
代码的其它部分参见本书配套光盘中的源代码。
评论
查看更多