一、液晶显示模块概述
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); }