12864是128*64点阵液晶模块的点阵数简称,业界约定俗成的简称。
一、液晶显示模块概述
12864A-1 汉字图形点阵液晶显示模块,可显示汉字及图形,内置 8192 个中文汉字(16X16 点阵,16*8=128,16*4=64,一行只能写 8 个汉字,4 行;、128 个字符(8X16 点阵)及 64X256 点阵显示 RAM(GDRAM))。
主要技术参数和显示特性:
电源:VDD 3.3V~+5V(内置升压电路,无需负压);
显示内容:128 列× 64 行(128 表示点数)
显示颜色:黄绿
显示角度:6:00 钟直视
LCD 类型:STN
与 MCU 接口:8 位或 4 位并行/3 位串行
配置 LED 背光
多种软件功能:光标显示、画面移位、自定义字符、睡眠模式等
引脚定义:D0-D7,RS,RES,RD,WR,CS,BL;具体头文件定义如下:
#ifndef LCD12864_H
#define LCD12864_H
#define LCD12864_MAJOR 242
#define DEV_NAME "lcd12864"
#define LCD12864_IOCTL 'l'
#define SET_FONT_SIZE _IOW(LCD12864_IOCTL,0 ,unsigned int)
#define SET_LCD_PAGE _IOW(LCD12864_IOCTL,1 ,unsigned int)
#define SET_LCD_COLUMN _IOW(LCD12864_IOCTL,2 ,unsigned int)
#define CLR_SCREEN _IOW(LCD12864_IOCTL,3 ,unsigned int)
#define BACKLIGHT_CTRL _IOW(LCD12864_IOCTL,4 ,unsigned int)
#define CONTRAST_SETTING _IOW(LCD12864_IOCTL,5 ,unsigned int)
#define LCD_DATA_0 S3C2410_GPC8
#define LCD_DATA_1 S3C2410_GPC9
#define LCD_DATA_2 S3C2410_GPC10
#define LCD_DATA_3 S3C2410_GPC11
#define LCD_DATA_4 S3C2410_GPC12
#define LCD_DATA_5 S3C2410_GPC13
#define LCD_DATA_6 S3C2410_GPC14
#define LCD_DATA_7 S3C2410_GPC15
#define LCD_E_RD S3C2410_GPD0
#define LCD_RW_WR S3C2410_GPD1
#define LCD_RS S3C2410_GPD2
#define LCD_RES S3C2410_GPD3
#define LCD_CS1 S3C2410_GPD4
#define LCD_BL S3C2410_GPD5
#define LCD_DATA_CFG0_OUTP S3C2410_GPC8_OUTP
#define LCD_DATA_CFG1_OUTP S3C2410_GPC9_OUTP
#define LCD_DATA_CFG2_OUTP S3C2410_GPC10_OUTP
#define LCD_DATA_CFG3_OUTP S3C2410_GPC11_OUTP
#define LCD_DATA_CFG4_OUTP S3C2410_GPC12_OUTP
#define LCD_DATA_CFG5_OUTP S3C2410_GPC13_OUTP
#define LCD_DATA_CFG6_OUTP S3C2410_GPC14_OUTP
#define LCD_DATA_CFG7_OUTP S3C2410_GPC15_OUTP
#define LCD_E_RD_CFG_OUTP S3C2410_GPD0_OUTP
#define LCD_RW_WR_CFG_OUTP S3C2410_GPD1_OUTP
#define LCD_RS_CFG_OUTP S3C2410_GPD2_OUTP
#define LCD_RES_CFG_OUTP S3C2410_GPD3_OUTP
#define LCD_CS1_CFG_OUTP S3C2410_GPD4_OUTP
#define LCD_BL_CFG_OUTP S3C2410_GPD5_OUTP
#define FONT_SIZE_8 8
#define FONT_SIZE_16 16
#endif
将引脚单独用头文件重新自己定义一次,以提高代码的可移植性。
下面看看模块初始化/释放函数:
static int __init lcd12864_init(void)
{
int ret;
dev_t devno;
DBPRINTF(KERN_ALERT "%s enter!\n",__func__);
devno = MKDEV(lcd12864_major,0);
if((ret = register_chrdev_region(devno,1,DEV_NAME))<0)
{
if((ret = alloc_chrdev_region(&devno, 0, 1, DEV_NAME))<0)
{
DBPRINTF(KERN_ALERT "chrdev region fail!ret=%d;\n",ret);
return ret;
}
else
{
lcd12864_major = MAJOR(devno);
}
}
cdev_init(&lcd_cdev, &lcd12864_ops);
lcd_cdev.owner=THIS_MODULE;
lcd_cdev.ops = &lcd12864_ops;
ret = cdev_add(&lcd_cdev, devno, 1);
if(ret<0)
{
goto fail_reg;
}
lcd_dev_class = class_create(THIS_MODULE, DEV_NAME);
if(IS_ERR(lcd_dev_class))
{
goto fail_cdev;
}
device_create(lcd_dev_class, NULL, devno, NULL, DEV_NAME);
lcd_bd_info = kmalloc(sizeof(struct lcd_board_info),GFP_KERNEL);
lcd_bd_info->font_size = 16;
#ifdef NORMAL
lcd_bd_info->pag = 0;
lcd_bd_info->column = 0;
#else
lcd_bd_info->pag = 6;
lcd_bd_info->column = 112;
#endif
lcd_bd_info->lcd_data_t = lcd_data_table;
lcd_bd_info->lcd_data_cfg_t = lcd_data_cfg_table;
spin_lock_init(&lock);
gpio_pin_init();
//gpio_pin_init();
ret = init_lcd12864();
if(ret<0)
{
goto fail_dev;
}
back_light_ctrl(1);
clear_lcd_screen(0x00);
DBPRINTF(KERN_ALERT "%s leave,init success!\n",__func__);
return ret;
fail_dev:
device_destroy(lcd_dev_class, devno);
class_destroy(lcd_dev_class);
fail_cdev:
cdev_del(&lcd_cdev);
fail_reg:
unregister_chrdev_region(devno, 1);
return ret;
}
static void __exit lcd12864_exit(void)
{
kfree(lcd_bd_info);
device_destroy(lcd_dev_class, MKDEV(lcd12864_major, 0));
class_destroy(lcd_dev_class);
cdev_del(&lcd_cdev);
unregister_chrdev_region(MKDEV(lcd12864_major, 0), 1);
}
module_init(lcd12864_init);
module_exit(lcd12864_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HOY");
MODULE_DESCRIPTION("gonsin 128x64 lcd driver");
以上主要工作是:通过register_chrdev_region静态分配设备号devno,如果分配失败,则通过系统alloc_chrdev_region动态分配;
此处通过cdev创建字符设备;通过cdev_add添加入系统中。通过class_create和device_create生成并注册一个逻辑设备,通过此工作,可以在/dev/下面看到设备名;
初始化lcd_board_info结构,lcd_board_info结构定义如下:
struct lcd_board_info{
unsigned int font_size;
unsigned int pag;
unsigned int column;
unsigned long* lcd_data_t;
unsigned long* lcd_data_cfg_t;
};
static struct lcd_board_info* lcd_bd_info;
其他变量和头文件定义如下:
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <mach/regs-gpio.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/delay.h>
#define DEBUG //调试状态
#ifdef DEBUG
#define DBPRINTF(fmt,args...) printk(KERN_ALERT "lcd:" fmt, ##args)
#else
#define DBPRINTF(fmt,args...)
#endif
#include "lcd_12864.h"
#define NORMAL //正常模式
static struct class* lcd_dev_class;
struct cdev lcd_cdev;
static int lcd12864_major = LCD12864_MAJOR;
static unsigned long lcd_data_table[]={
LCD_DATA_0,
LCD_DATA_1,
LCD_DATA_2,
LCD_DATA_3,
LCD_DATA_4,
LCD_DATA_5,
LCD_DATA_6,
LCD_DATA_7,
};
/**/
static unsigned long lcd_data_cfg_table[] =
{
LCD_DATA_CFG0_OUTP,
LCD_DATA_CFG1_OUTP,
LCD_DATA_CFG2_OUTP,
LCD_DATA_CFG3_OUTP,
LCD_DATA_CFG4_OUTP,
LCD_DATA_CFG5_OUTP,
LCD_DATA_CFG6_OUTP,
LCD_DATA_CFG7_OUTP,
};
/*
static unsigned char io_read_byte()
{
}
*/
spinlock_t lock;
然后调用以下函数进行gpio引脚的初始化、液晶屏的初始化:
static int gpio_pin_init(void)
{
int ii;
s3c2410_gpio_cfgpin(LCD_CS1, LCD_CS1_CFG_OUTP);
s3c2410_gpio_cfgpin(LCD_RES, LCD_RES_CFG_OUTP);
s3c2410_gpio_cfgpin(LCD_RS, LCD_RS_CFG_OUTP);
s3c2410_gpio_cfgpin(LCD_RW_WR, LCD_RW_WR_CFG_OUTP);
s3c2410_gpio_cfgpin(LCD_E_RD, LCD_E_RD_CFG_OUTP);
s3c2410_gpio_cfgpin(LCD_BL, LCD_BL_CFG_OUTP);
for(ii=0; ii<8; ii++)
{
s3c2410_gpio_cfgpin(lcd_bd_info->lcd_data_t[ii], lcd_bd_info->lcd_data_cfg_t[ii]);
}
return 0;
}
static unsigned char init_cmd[]={
0xe2,
0xa0,
0xc0,
0xA2,
0x2B,
0x2E,
0x2F,
0x27,
0xAF,
0,
};
static int init_lcd12864(void)
{
int ii;
s3c2410_gpio_setpin(LCD_CS1, 0);
udelay(500);
s3c2410_gpio_setpin(LCD_RES, 0);
udelay(500);
s3c2410_gpio_setpin(LCD_RES, 1);
udelay(500);
for(ii=0;init_cmd[ii]!=0;ii++)
{
send_cmd(init_cmd[ii]);
udelay(50);
}
return 0;
}
以下为写液晶屏和写命令的操作:
#ifdef NORMAL
/*往液晶中写入1个字符数据*/
static void io_write_byte(unsigned char data)
{
unsigned char ii;
spin_lock(&lock);
for(ii=0;ii<8;ii++)
{
if(data&0b1)
{
s3c2410_gpio_setpin(lcd_bd_info->lcd_data_t[ii], 1);
}
else
{
s3c2410_gpio_setpin(lcd_bd_info->lcd_data_t[ii], 0);
}
data = data>>1;
}
//__raw_writel(((__raw_readl(S3C2410_GPCDAT)&~(0xff<<8))|(data<<8)),S3C2410_GPCDAT);
spin_unlock(&lock);
s3c2410_gpio_setpin(LCD_RS, 1);
s3c2410_gpio_setpin(LCD_RW_WR, 0);
s3c2410_gpio_setpin(LCD_E_RD, 1);
s3c2410_gpio_setpin(LCD_CS1, 1);
udelay(1);
s3c2410_gpio_setpin(LCD_CS1, 0);
udelay(1);
}
#else
static void io_write_byte(unsigned char data)
{
unsigned char ii;
spin_lock(&lock);
for(ii=0;ii<8;ii++)
{
if(data&0x80)
{
//DBPRINTF(KERN_ALERT "[||||||||||||||||||||]\n");
s3c2410_gpio_setpin(lcd_bd_info->lcd_data_t[ii], 1);
}
else
{
//DBPRINTF(KERN_ALERT "[--------------------]\n");
s3c2410_gpio_setpin(lcd_bd_info->lcd_data_t[ii], 0);
}
data = data<<1;
}
spin_unlock(&lock);
//DBPRINTF(KERN_ALERT "\n");
s3c2410_gpio_setpin(LCD_RS, 1);
s3c2410_gpio_setpin(LCD_RW_WR, 0);
s3c2410_gpio_setpin(LCD_E_RD, 1);
s3c2410_gpio_setpin(LCD_CS1, 1);
udelay(1);
s3c2410_gpio_setpin(LCD_CS1, 0);
udelay(1);
}
#endif
//发送命令
static int send_cmd(unsigned char cmd)
{
int ii;
unsigned char tmp;
tmp = 0x00;
spin_lock(&lock);
for(ii=0;ii<8;ii++)
{
if(cmd&0x80)
{
s3c2410_gpio_setpin(lcd_bd_info->lcd_data_t[ii], 1);
}
else
{
s3c2410_gpio_setpin(lcd_bd_info->lcd_data_t[ii], 0);
}
cmd = cmd<<1;
}
/*******************/
/*
for(ii=0;ii<8;ii++)
{
tmp = tmp<<1;
if(cmd&0b1)
{
tmp|=0b1;
}
else
{
tmp &= 0b0;
}
cmd = cmd>>1;
}
__raw_writel(((__raw_readl(S3C2410_GPCDAT)&~(0xff<<8))|(tmp<<8)),S3C2410_GPCDAT);*/
/*******************/
spin_unlock(&lock);
s3c2410_gpio_setpin(LCD_RS, 0);
s3c2410_gpio_setpin(LCD_RW_WR, 0);
s3c2410_gpio_setpin(LCD_E_RD, 1);
s3c2410_gpio_setpin(LCD_CS1, 1);
udelay(1);
s3c2410_gpio_setpin(LCD_CS1, 0);
udelay(1);
return 0;
}
static int dev_write_data(const char* data)
{
unsigned char dl,dh;
int i,j;
spin_lock(&lock);
dh = lcd_bd_info->column/16;
dl = lcd_bd_info->column-dh*16;
for(i=0; i<2; i++)
{
send_cmd(0xB7-i-lcd_bd_info->pag);
send_cmd(0x10+dh);
send_cmd(dl);
#ifdef NORMAL
for(j=0;j<16;j++)
io_write_byte(data[lcd_bd_info->font_size*i+j]);
#else
for(j=0;j<16;j++)
io_write_byte(data[lcd_bd_info->font_size*(1-i)+(15-j)]);
#endif
}
spin_unlock(&lock);
return 0;
}
因为公司结构方面的需要,要将液晶倒转显示,所以此处做了两个不同的方案,如果定义了NORMAL,则正常显示,否则倒转显示;
其他对液晶屏控制的操作如下:
/*清除屏幕*/
static void clear_lcd_screen(unsigned char clr_data)
{
unsigned char page_addr = 0xb7;
unsigned int page_num;
int ii;
for(page_num=0; page_num<8; page_num++)
{
send_cmd(page_addr);
send_cmd(0x10);
send_cmd(0x00);
for(ii=0; ii<128; ii++)
io_write_byte(clr_data);
page_addr--;
}
}
static int lcd12864_open(struct inode* inode, struct file* filp)
{
return 0;
}
static int lcd12864_release(struct inode* inode, struct file* filp)
{
return 0;
}
static ssize_t lcd12864_write(struct file* filp, char __user* buf, size_t size, loff_t offset)
{
char* kbuf;
// int count;
kbuf = kmalloc(size, GFP_KERNEL);
copy_from_user(kbuf, buf, size);
dev_write_data(kbuf);
kfree(kbuf);
return 0;
}
static int contrast_setting(int val)
{
if((val&0xff)>0x3e)
return -1;
send_cmd(0x81);
send_cmd(val&0xff);
DBPRINTF(KERN_ALERT "contrast setting,val is%d\n",val);
return 0;
}
/*背光控制*/
static int back_light_ctrl(int bl)
{
if(bl<0)
{
return -1;
}
else if(bl==0)
{
s3c2410_gpio_setpin(LCD_BL, 0);
DBPRINTF(KERN_ALERT "back light set value is 0\n");
}
else
{
s3c2410_gpio_setpin(LCD_BL, 1);
DBPRINTF(KERN_ALERT "back light set value is 1\n");
}
return 0;
}
static int lcd12864_ioctl(struct inode* inode,struct file* filp, unsigned int cmd, unsigned long args)
{
int ret;
spin_lock(&lock);
ret = 0;
DBPRINTF(KERN_ALERT "%s",__func__);
switch(cmd)
{
case SET_FONT_SIZE:
{
int size = *(int*)args;
if(size == FONT_SIZE_8||size == FONT_SIZE_16)
{
lcd_bd_info->font_size = size;
}
else
{
ret = -1;
DBPRINTF(KERN_ALERT "set font size fail!\n");
goto ioctl_fail;
}
break;
}
case SET_LCD_PAGE:
{
int tmp = *(int*)args;
if(tmp>63)
return -1;
#ifdef NORMAL
lcd_bd_info->pag = tmp;
#else
lcd_bd_info->pag = 6-tmp;
#endif
DBPRINTF(KERN_ALERT "set pag is %d\n",lcd_bd_info->pag);
break;
}
case SET_LCD_COLUMN:
{
int tmp = *(int*)args;
if(tmp>127)
return -1;
#ifdef NORMAL
lcd_bd_info->column =tmp;
#else
lcd_bd_info->column =112-tmp;
#endif
DBPRINTF(KERN_ALERT "set column is %d\n",lcd_bd_info->column);
break;
}
case CLR_SCREEN:
{
unsigned char data = *(char*)args;
clear_lcd_screen(data);
break;
}
case BACKLIGHT_CTRL:
{
int bl = *(int*)args;
ret = back_light_ctrl(bl);
break;
}
case CONTRAST_SETTING:
{
int con = *(int*)args;
ret = contrast_setting(con);
break;
}
}
spin_unlock(&lock);
return ret;
ioctl_fail:
spin_unlock(&lock);
return ret;
}
最后看看file_operations结构体:
static struct file_operations lcd12864_ops={
.owner = THIS_MODULE,
.open = lcd12864_open,
.release = lcd12864_release,
.write = lcd12864_write,
//.read = lcd12864_read,
.ioctl = lcd12864_ioctl,
};
以上则是液晶屏的驱动程序,很简单吧?哈哈~~
Makefile如下所示:
obj-m:=lcd_12864.o
KDIR=/home/project/linux-res/linux-2.6.30.4/
all:
$(MAKE) -C $(KDIR) M=$(PWD)
.PHONY:clean
clean:
rm -f *.o *.ko *.mod.c *.mod.o *.tmp_versions
最后是液晶屏测试程序:
enum {
FONT_SIZE8,
FONT_SIZE16,
FONT_SIZE32,
};
/*将字符数组里面的每一位取反*/
void oppersite(const char* cin, __out char* cout,int len)
{
int i=0;
for(i=0;i<len;i++)
{
cout[i]=~cin[i];
}
}
/*由size_flag 解析出字体大小*/
int prase_size_flag(int size_flag)
{
int fsize;
switch(size_flag)
{
case FONT_SIZE8:
fsize = 8;
break;
case FONT_SIZE16:
fsize = 16;
break;
case FONT_SIZE32:
fsize = 32;
break;
default:
printf("size_flag is involid!\n");
return -1;
}
return fsize;
}
int prase_row(int pag)
{
return pag*2;
}
int prase_column(int col)
{
return col*16;
}
/*显示一个选中的字符
ch:字符的16进制数组
pag:第几页;0-7
col:第几列;0-127
size_flag:字体枚举
*/
int show_select_ch(int fd, const char* ch, int pag, int col, int size_flag)
{
char chtmp[50];
int fsize;
if(!ch)
return -1;
if((fsize=prase_size_flag(size_flag))<0)
return -1;
int size = fsize*fsize/8;
oppersite(ch, chtmp, size);
if(ioctl(fd, SET_LCD_PAGE, &pag)<0)
return -1;
if(ioctl(fd, SET_LCD_COLUMN, &col)<0)
return -1;
if(write(fd, chtmp, size)<0)
return -1;
return 0;
}
/*显示一个字符
ch:字符的16进制数组
pag:第几页;0-7
col:第几列;0-127
size_flag:字体枚举
*/
int show_ch(int fd, const char* ch, int pag, int col, int size_flag)
{
if(!ch)
return -1;
int fsize;
if((fsize=prase_size_flag(size_flag))<0)
return -1;
int size = fsize*fsize/8;
if(ioctl(fd, SET_LCD_PAGE, &pag)<0)
return -1;
if(ioctl(fd, SET_LCD_COLUMN, &col)<0)
return -1;
if(write(fd, ch, size)<0)
return -1;
return 0;
}
/*显示一行字符串*/
int show_str(int fd, const char** str, int count, int pag, int col, int size_flag)
{
int fsize;
int i;
if((fsize=prase_size_flag(size_flag))<0)
return -1;
for(i=0; i<count; i++)
{
if(pag<PAGE_COUNT && col<COLUMN_COUNT)
{
if(show_ch(fd, str[i], pag, col, size_flag)<0)
return -1;
col+=fsize;
}
}
return 0;
}
/*显示一行选中的字符串*/
int show_select_str(int fd, const char** str, int count, int pag, int col, int size_flag)
{
int fsize;
int i;
if((fsize=prase_size_flag(size_flag))<0)
return -1;
for(i=0; i<count; i++)
{
if(pag<PAGE_COUNT && col<COLUMN_COUNT)
{
if(show_select_ch(fd, str[i], pag, col, size_flag)<0)
return -1;
col+=fsize;
}
}
return 0;
}
/*对比度调节0-62*/
int contrast_ctrl(int fd, int val)
{
if(ioctl(fd,CONTRAST_SETTING,&val)<0)
return -1;
return 0;
}
/*背光调节 1:亮,0:灭*/
int backlight_ctrl(int fd,int val)
{
if(ioctl(fd, BACKLIGHT_CTRL, &val)<0)
return -1;
return 0;
}
/*清屏*/
int clear_screen(int fd, int val)
{
if(ioctl(fd, CLR_SCREEN, &val)<0)
return -1;
return 0;
}
/*字体大小*/
int font_size(int fd, int val)
{
if(ioctl(fd, SET_FONT_SIZE, &val)<0)
return -1;
return 0;
}
int main(void)
{
fd = open(LCD_DEV_NAME,O_RDWR);
show_ch(fd, zi, 0, 0, 1);
show_select_ch(fd, zi, 0, 32, 1);
show_str(fd, tmp, 8, 2, 0, 1);
show_select_str(fd, tmp, 8, 4, 0, 1);
close(fd);
}