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;
}
}
}
代码的其它部分参见本书配套光盘中的源代码。
中国汽车芯片创新联盟在京正式成立
新冠疫情期间医疗机构如何预防网络黑客的攻击
TCP/IP协议,TCP/IP协议内容和作用是什么?
使用安森美新型SiC模块构建25kW快速电动汽车充电桩
浅析3G通信网络技术在通信中的应用
16×2 LCD 模块的驱动
Realme在智能手机领域继续增长
如何改进士兰微的LED恒流驱动电源控制器
热继电器整定电流值的重要性
有利于验证未测试功能的RTL缓冲器插入和故障分级技术
无损检测常用词汇中英文对照
边缘人工智能蕴藏着物联网的崭新机遇
利用GPRS网络技术实现油田抽油机远程控制系统的设计
全固态电容的简单介绍
国人眼部健康需求日渐上涨,激光电视成市场黑马
中国集成电路产业将迎来什么样的机遇和挑战?
超声波流量计计量性能的主要影响因素
宁波大学图书馆使用机器人做管理员,不仅会借还书还能跟学生互动
将收购凌阳电视芯片片部门? 矽统涨停
废启辉器氖泡的巧用