1602液晶驱动<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
在写1602液晶驱动之前,先要熟悉1602的控制过程:
字符型LCD(1602)液晶显示器的控制与显示
液晶显示器以其微功耗、小体积、使用灵活等诸多优点在袖珍式仪表和低功耗应用系统中得到越来越广泛的应用。液晶显示器通常可分为两大类,一类是点阵型,另一类是字符型。点阵型液晶通常面积较大,可以显示图形;而一般的字符型液晶只有两行,面积小,只能显示字符和一些很简单的图形,简单易控制且成本低。目前市面上的字符型液晶绝大多数是基于HD44780液晶芯片的,所以控制原理是完全相同的,为HD44780写的控制程序可以很方便地应用于市面上大部分的字符型液晶。
首先我们来熟悉下字符型LCD的各个引脚
字符型LCD通常有14条引脚线(市面上也有很多16条引脚线的LCD,多出来的2条线是电源线VCC(15脚)和地线GND(16脚),其控制原理与14脚的LCD完全一样),定义如下表所示:
字符型LCD的引脚定义
引脚号 | 引脚名 | 电平 | 输入/输出 | 作用 |
1 | Vss |
|
| 电源地 |
2 | Vcc |
|
| 电源(+5V) |
3 | Vee |
|
| 对比调整电压 |
4 | RS | 0/1 | 输入 | 0=输入指令 1=输入数据 |
5 | Rw | 0/1 | 输入 | 0=向LCD写入指令或数据 1=从LCD读取信息 |
6 | E | 1,1→0 | 输入 | 使能信号,1时读取信息, 1→0(下降沿)执行指令 |
7 | DB0 | 0/1 | 输入/输出 | 数据总线line0(最低位) |
8 | DB1 | 0/1 | 输入/输出 | 数据总线line1 |
9 | DB2 | 0/1 | 输入/输出 | 数据总线line2 |
10 | DB3 | 0/1 | 输入/输出 | 数据总线line3 |
11 | DB4 | 0/1 | 输入/输出 | 数据总线line4 |
12 | DB5 | 0/1 | 输入/输出 | 数据总线line5 |
13 | DB6 | 0/1 | 输入/输出 | 数据总线line6 |
14 | DB7 | 0/1 | 输入/输出 | 数据总线line7(最高位) |
HD44780内置了192个常用字符,存于字符产生器CGROM(Character Generator ROM)中,另外还有几个允许用户自定义的字符产生RAM,称为CGRAM(Character Generator RAM)。下图说明了CGROM和CGRAM与字符的对应关系(字符码"指CGROM的字符号,"地址"指DDRAM的地址)
字符码0x00~0x0F为用户自定义的字符图形RAM(对于5X8点阵的字符,可以存放8组,5X10点阵的字符,存放4组),0x20~0x7F为标准的ASCII码,0xA0~0xFF为日文字符和希腊文字符,其余字符码(0x10~0x1F及0x80~0x9F)没有定义
除了CGROM和CGRAM外,LCD内部还有一个DDRAM(Display Data RAM),用于存放待显示内容,LCD控制器的指令系统规定,在送待显示字符代码的指令之前,先要送DDRAM的地址(即待显示的字符显示位置)。16×2的字符型LCD的DDRAM地址与显示位置的对应关系如下:
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
00H 01H 02H 03H 04H 05H 06H 07H 08H 09H 0AH 0BH 0CH 0DH 0EH 0FH
40H 41H 42H 43H 44H 45H 46H 47H 48H 49H 4AH 4BH 4CH 4DH 4EH 4FH
为了更好的理解,这边我们举个例子:
假设要在第1行第2列写入字符"A",这时先写入第1行第2列对应的DDRAM的地址:01H(参见上图),然后再往DDRAM中写入"A"的字符码0x41(参见字符与字符码对照表),这样LCD的第1行第2列就会出现字符A了。也就是说,DDRAM的内容对应于把要显示的字符地址,而DDRAM的地址就对应于显示字符的位置。总而言之,希望在LCD的某一特定位置显示某一特定字符,一般要遵循"先指定地址,后写入内容"的原则;但如果希望在LCD上显示一串连续的字符(如单词等),并不需要每次写字符码之前都指定一次地址,这是因为液晶控制模块中有一个计数器叫地址计数器AC(Address Counter)。地址计数器的作用是负责记录写入DDRAM数据的地址,或从DDRAM读出数据的地址。该计数器的作用不仅仅是"写入"和"读出"地址,它还能根据用户的设定自动进行修改。比如,如果规定地址计数器在"写入DDRAM内容"这一操作完成后自动加1,那么在第1行第1列定写入一个字符后,如果不对字符显示位置(DDRAM地址)重新设置,再写入一个字符,则这个新的字符会出现在第1行第2列
那么如何对DDRAM的内容和地址进行操作呢,下面是HD44780的指令集及其设置说明,请浏览该指令集,并找出对DDRAM的内容和地址进行操作的指令。
清屏指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
清屏 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1.64 |
功能:<1> 清除液晶显示器,即将DDRAM的内容全部填入"空白"的ASCII码20H;
<2> 光标归位,即将光标撤回液晶显示屏的左上方;
<3> 将地址计数器(AC)的值设为0。
光标归位指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
光标归位 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | 1.64 |
功能:<1> 把光标撤回到显示器的左上方;
<2> 把地址计数器(AC)的值设置为0;
<3> 保持DDRAM的内容不变。
进入模式设置指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
进入模式设置 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | I/D | S | 40 |
功能:设定每次定入1位数据后光标的移位方向,并且设定每次写入的一个字符是否移动。参数设定的
情况如下所示:
I/D 0=写入新数据后光标左移 1=写入新数据后光标右移
S 0=写入新数据后显示屏不移动 1=写入新数据后显示屏整体右移1个字符
显示开关控制指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
显示开关控制 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | D | C | B | 40 |
功能:控制显示器开/关、光标显示/关闭以及光标是否闪烁。参数设定的情况如下:
位名 设置
D 0=显示功能关 1=显示功能开
C 0=无光标 1=有光标
B 0=光标闪烁 1=光标不闪
设定显示屏或光标移动方向指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
设定显示屏或 光标移动方向 | 0 | 0 | 0 | 0 | 0 | 1 | S/C | R/L | X | X | 40 |
功能:使光标移位或使整个显示屏幕移位。参数设定的情况如下:
S/C R/L 设定情况
0 0 光标左移1格,且AC值减1
0 1 光标右移1格,且AC值加1
1 0 显示器上字符全部左移一格,但光标不动
1 1 显示器上字符全部右移一格,但光标不动
功能设定指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
功能设定 | 0 | 0 | 0 | 0 | 1 | DL | N | F | X | X | 40 |
功能:设定数据总线位数、显示的行数及字型。参数设定的情况如下:
位名 设置
DL 0=数据总线为4位 1=数据总线为8位
N 0=显示1行 1=显示2行
F 0=5×7点阵/每字符 1=5×10点阵/每字符
设定CGRAM地址指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
设定CGRAM 地址 | 0 | 0 | 0 | 1 | CGRAM的地址(6位) | 40 | |||||
功能:设定下一个要存入数据的DDRAM的地址
设定DDRAM地址指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
设定DDRAM地址 | 0 | 0 | 1 | DDRAM的地址(7位) | 40 | ||||||
功能:设定下一个要存入数据的DDRAM的地址
读取忙信号或AC地址指令
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
读取忙碌信号或AC地址 | 0 | 1 | FB | AC内容(7位) | 40 | ||||||
功能:<1> 读取忙碌信号BF的内容,BF=1表示液晶显示器忙,暂时无法接收单片机送来的数据或指令;
当BF=0时,液晶显示器可以接收单片机送来的数据或指令;
<2> 读取地址计数器(AC)的内容
数据写入DDRAM或CGRAM指令一览
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
数据写入到DDRAM或CGRAM | 1 | 0 |
要写入的数据 D7~D0 | 40 | |||||||
功能:<1> 将字符码写入DDRAM,以使液晶显示屏显示出相对应的字符;
<2> 将使用者自己设计的图形存入CGRAM。
从CGRAM或DDRAM读出数据的指令一览
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
从CGRAM或DDRAM读出 数据 | 1 | 1 |
要读出的数据 D7~D0 | 40 | |||||||
功能:读取DDRAM或CGRAM中的内容
在上面的指令集中,有RS、R/W和8位数据总线,却小了一个使能位E。使能位E对执行LCD指令起着关键作用,E有两个有效状态,高电平(1)和下降沿(1→0)。当E为高电平时,如果R/W为0,则LCD从单片机读入指令或者数据;如果R/W为1,则单片机可以从LCD中读出状态字(BF忙状态)和地址。而E的下降沿指示LCD执行其读入的指令或者显示其读入的数据。
基本操作时序:
读状态 输入:RS=L,RW=H,E=H 输出:DB0~DB7=状态字
写指令 输入:RS=L,RW=L,E=下降沿脉冲,DB0~DB7=指令码 输出:无
读数据 输入:RS=H,RW=H,E=H 输出:DB0~DB7=数据
写数据 输入:RS=H,RW=L,E=下降沿脉冲,DB0~DB7=数据 输出:无
有了上面的基础我们就开始来编写驱动了:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
//#include <linux/interrupt.h>
#include <linux/delay.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware.h>
#define KEY_MAJOR 210 /* 主设备号*/
#define wcmd_0 0
#define wcmd_1 1
#define wcmd_2 2
#define wcmd_3 3
#define wcmd_4 4
#define wcmd_5 5
unsigned int i="0";
struct yj_dev
{
struct cdev cdev;
unsigned char value;
};
struct yj_dev *yjdev;
int sep4020_yj_open(struct inode *inode, struct file *filp)
{
return 0;
}
int sep4020_yj_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t sep4020_yj_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
return 0;
}
static ssize_t sep4020_yj_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
return 0;
}
int sep4020_yj_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case wcmd_0:
{
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0138);
for(i=0;i<4000;i++);//延迟
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0038); //设置8位格式,2行,5x7
break;
}
case wcmd_1:
{
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />010c);
for(i=0;i<4000;i++);
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x000c); //整体显示,关光标,不闪烁
break;
}
case wcmd_2:
{
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0106);// 设定输入方式,增量不移位
for(i=0;i<4000;i++);
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0006);
break;
}
case wcmd_3:
{
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0101);
for(i=0;i<4000;i++);
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0001); //清除屏幕显示
break;
}
case wcmd_4:
{
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0180);
for(i=0;i<4000;i++);
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0080); //第一行开始显示
break;
}
case wcmd_5:
{
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0565);
for(i=0;i<4000;i++);
*(volatile unsigned long*)GPIO_PORTB_DATA_V=((*(volatile unsigned long*)GPIO_PORTB_DATA_V) & (0xf800)) | (0x0465);//显示小写字母e
break;
}
default:
return -ENOTTY;
}
return 0;
}
static const struct file_operations sep4020_yj_fops =
{
.owner = THIS_MODULE,
.read = sep4020_yj_read,
.write = sep4020_yj_write,
.ioctl = sep4020_yj_ioctl,
.open = sep4020_yj_open,
.release = sep4020_yj_release,
};
static void sep4020_yj_setup(void)
{
*(volatile unsigned long*)GPIO_PORTB_SEL_V |= (0x07ff); //作为通用用途
*(volatile unsigned long*)GPIO_PORTB_DIR_V &= (0xf800);//输出
}
static int __init sep4020_yj_init(void)
{
int err,result;
dev_t devno = MKDEV(KEY_MAJOR, 0);
if(KEY_MAJOR)
result = register_chrdev_region(devno, 1, "sep4020_yj");
else
{
result = alloc_chrdev_region(&devno, 0, 1, "sep4020_yj");
}
if(result < 0)
return result;
/*动态申请设备结构体的内存*/
yjdev = kmalloc(sizeof(struct yj_dev),GFP_KERNEL);
if (!yjdev)
{
result = -ENOMEM;
goto fail_malloc;
}
memset(yjdev,0,sizeof(struct yj_dev));
/*注册中断函数*/
/*对键盘进行初始化*/
sep4020_yj_setup();
cdev_init(&(yjdev->cdev), &sep4020_yj_fops);
yjdev->cdev.owner = THIS_MODULE;
err = cdev_add(&yjdev->cdev, devno, 1);
if(err)
printk("adding err\r\n");
return 0;
fail_malloc: unregister_chrdev_region(devno,1);
return result;
}
static void __exit sep4020_yj_exit(void)
{
printk("sep4020_yj_exit\n");
unregister_chrdev_region(KEY_MAJOR,1);
}
module_init(sep4020_yj_init);
module_exit(sep4020_yj_exit);
MODULE_AUTHOR("scx");
MODULE_LICENSE("GPL");
将这段代码以模块的形式加载
一下是我写的一个应用程序:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEVICE_NAME "/dev/sep4020_yj"
#define wcmd_0 0
#define wcmd_1 1
#define wcmd_2 2
#define wcmd_3 3
#define wcmd_4 4
#define wcmd_5 5
int main(void)
{
int fd;
printf("\n start sep4020_yj test\n\n");
fd=open("/dev/sep4020_yj",O_RDONLY);
printf("fd=%d\n",fd);
if(fd==-1)
{
printf("wrong\r\n");
exit(-1);
}
else
{
while(1)
{
ioctl(fd,wcmd_0);
sleep(1);
ioctl(fd,wcmd_1);
sleep(1);
ioctl(fd,wcmd_2);
sleep(1);
ioctl(fd,wcmd_3);
sleep(1);
ioctl(fd,wcmd_4);
sleep(1);
ioctl(fd,wcmd_5);
sleep(1);
}
}
close(fd);
printf("\n close sep4020_yj test!!!\n\n");
return 0;
}
用arm-linux-gcc 交叉编译器将上面的应用程序编译成可执行文件
在secureCRT里面执行会出现如下情况
这个时候你的1602液晶上会出现如下的情况:
文章评论(0条评论)
登录后参与讨论