基于AT91RM9200的LCD驱动程序设计
扫描二维码
随时随地手机看文章
1 引言
嵌入式系统应用于工控领域越来越普及,对于传统工控产品升级换代发挥重要作用,随着由此带来的工控产品性能的大幅提高,与之对应的较高档次、友好的人机界面需求也不断增大。为此,Linux也出现了许多图形界面软件包,在其开发和移植过程种都涉及到底层LCD的驱动。本文针对一款基于AT91RM9200芯片的工业级嵌入式系统开发板,加上可扩展外围控制器SLD13506,在Linux2.4.19操作系统下,通过编写其驱动程序,再用arm-linux-gcc进行编译,使ARM9开发板添加12.1英寸TFT彩色LCD显示功能。
2 硬件介绍
AT91RM9200是一款基于ARM920T内核的高性价比、低功耗、32位的ARM 芯片,拥有独立的16K指令和16K数据cache,写缓存,全功能的MMU虚拟内存管理单元,内部的16KB SDRAM和128KB ROM,在180MHz工作频率下运行速度为200MIPS。AT91RM9200集成了EBI, PMC,I/O,Ethernet,USB,MCI,SSC,UASRT, SPI,RTC,TWI等接口及其控制器。却没有针对LCD显示的控制器,所以本系统添加了SLD13506作为显示控制器,来实现LCD的显示。
SLD13506是EPSON公司一款用于LCD/CRT/TV的显示控制芯片,其体系结构应低成本、低功耗的嵌入式市场的需求而设计,多用于移动通讯工具,手提电脑和办公自动化。它可支持4/8位单色或4/8/16位彩色的单板单显示接口,直接支持9/12位TFT/D-TFD彩色显示,在18位TFT/D-TFD下可显示65536种颜色,最大分辨率可为18bpp800×600。通过编写SLD13506的设备驱动程序,读写一系列的寄存器来产生驱动信号,就可以驱动LCD的显示。
3设备驱动程序
Linux是Unix操作系统的一种变种,类似于大部分Unix系统,Linux应用程序独立于底层硬件运行,用户无需关心硬件问题,但需要为每一款硬件编写驱动程序【²】,从而构成完整的运行系统。模块化驱动程序后,用户操作只需要通过一组标准化的调用来完成。把这些调用映射到设备特定的操作上,则是设备驱动程序的任务【2】。
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。这种机制可称为“文件层-驱动层”接口方式。
应用程序是通过设备文件操作硬件,实际上是通过如 open,read,write,close系统调用来实现的。而file_operations这一关键的数据结构就把系统调用和驱动程序关联起来,它的形式如下:
struct file_operations {
struct module *owner;
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
}
这个结构的每一个成员的名字都对应着一个系统调用,应用程序利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是linux的设备驱动程序工作的基本原理。所以编写LCD驱动程序的主要工作就是编写子函数来填充file_operations的各个域。[!--empirenews.page--]
4 linux下的帧缓冲区
Linux操作系统为LCD等显示设备提供了帧缓冲区,它是一种驱动程序接口【3】。帧缓冲区为图像硬件设备提供了一种抽象化处理,那么应用软件无需关心硬件设备的细节,就可以通过定义明确的界面来访问图像硬件设备。所以为LCD硬件设备编写驱动程序,实际上就是为帧缓冲区编写驱动程序, 它们的关系如下图1-1所示。
500)this.style.width=500;" border="0" />
把硬件设备抽象化为帧缓冲区设备,首先要指定LCD的帧缓冲区,在fb.h文件中,其结构体fb_info为帧缓冲设备定义了驱动层接口,它不仅包含了底层函数,而且还可以记录设备状态的数据。每个帧缓冲设备都与一个fb_info结构相对应。其中成员变量modename为设备名称,fontname为显示字体,node为指向底层操作的函数的指针:
struct fb_info {
char modename[40];
kdev_t node; int open;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_cmap cmap;
struct fb_ops *fbops; …};
??1)Struct fb_fix_screeninfo:定义了显示输出设备自身的属性,如屏幕缓冲区的物理地址和长度。
??2)Struct fb_var_screeninfo:记录了帧缓冲设备和指定显示模式的可修改信息。它包括显示屏幕的分辨率、每个像素的比特数和一些时序变量。其中变量xres定义了屏幕一行所占的像素数,yres定义了屏幕一列所占的像素数,bits_per_pixel定义了每个像素用多少个位来表示。
帧缓冲设备也属于字符设备(文件设备的一种,还有块设备),要实现“文件层-驱动层”的接口方式来对LCD进行驱动就必须定义一个类似于File_operationes可实现文件设备操作数据结构fb_ops,然后编写子函数对fb_ops的各个域进行填充:
struct fb_ops {
struct module *owner;
int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con, struct fb_info *info);
int (*fb_get_var)(struct fb_var_screeninfo *var, int con, struct fb_info *info);
int (*fb_set_var)(struct fb_var_screeninfo *var, int con, struct fb_info *info);
int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info);
int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info);
int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma); …};
这个结构中的每一个字段都必须指向驱动程序中实现特定操作的函数,对于不支持的操作,对应的字段可以被置为NULL,或留到后续开发时载添加。
5 帧缓冲区驱动程序的编写
为LCD显示设备指定了帧缓冲区后,要实现LCD驱动实际上就是编写帧缓冲区的驱动程序。在编写帧缓冲区的驱动程序时,首先要对指出与fb_info中fb_ops结构相对应的成员函数,然后分别实现它们,再对LCD的显示进行初始化。只有实现了这些成员子函数才能在“文件层-驱动层”实现系统调用【4】,从而使应用程序可直接操作显示硬件。现分述如下:
5.1 编写结构体fb_info中fb_ops及其对应的成员函数
本系统中定义了特定操作所对应的成员函数,代码如下:
static struct fb_ops s1d13xxxfb_ops = {
owner: THIS_MODULE,
fb_get_fix: s1d13xxxfb _get_fix,
fb_get_var: s1d13xxxfb _get_var,
fb_set_var: s1d13xxxfb _set_var,
fb_get_cmap: s1d13xxxfb _get_cmap,
fb_set_cmap: s1d13xxxfb _set_cmap,
fb_mmap: s1d13xxx_mmap, …};[!--empirenews.page--]
这些函数都是用来设置和获取驱动层接口fb_info结构体中的成员变量的,在第4小节中已提到,当应用程序对设备文件进行操作或读取设备文件状态时会调用这些函数。如fb_get_fix和fb_get_var函数得到的是fb_info中变量fix和var,fb_set_var则是对var变量进行设置。这些函数都要根据实际的操作来进行实现,下面以s1d13xxxfb_set_var函数为例来说明这些子函数都是如何实现的。它的作用是设置fb_info里的结构体fb_var_screeninfo变量var的值:
static int s1d13xxxfb_set_var(struct fb_var_screeninfo *var,){
memset(var, 0, sizeof(struct fb_var_screeninfo));
var->xres = 800; //显示800×600分辨率
var->yres = 600;
var->bits_per_pixel = 16; //定义16位颜色数
… //其他与LCD硬件有关的参数}
5.2 LCD初始化
LCD控制器是通过产生显示驱动信号来驱动LCD的。在驱动程序里,用户只需要通过读写一系列的寄存器,就可以完成配置和显示控制。而Linux下驱动程序总是先调用module_init()函数,括号里的参数是所要初始化的文件设备的初始化函数。因此在本系统中,通过调用module_init(s1d13xxxfb_init)初始化函数来实现对一系列寄存器的设置。s1d13xxxfb_init初始化函数部分代码如下:
int __init s1d13xxxfb_init(char *dummy){
S1D_INDEX s1dReg; //定义寄存器数组
S1D_VALUE s1dValue; //设置所对应寄存器的值
plateform_init_video(); //LCD显示电压寄存器的初始化
for (i = 0; i < sizeof(aS1DRegs)/sizeof(aS1DRegs[0]); i++) {
s1dReg = aS1DRegs[i].Index; //把设定的值写入寄存器
s1dValue = aS1DRegs[i].Value;… }
local_s1d13xxxfb_open(); //打开sld13506控制器
strcpy(fb_info.modename, "s1d13xxx"); //复制modename
fb_info.node = -1; //指向底层函数指针赋初值为-1
fb_info.fbops = &s1d13xxxfb_ops; //对结构体fb_info.fbops初始化
fbgen_get_var(&disp.var, -1, &fb_info.gen.info); //获取当前的显示参数
fbgen_do_set_var(&disp.var, 1, &fb_info.gen); //设置显示控制器参数
fbgen_install_cmap(0, &fb_info.gen); //根据LCD硬件参数开辟显存空间
if (register_framebuffer(&fb_info.gen.info) < 0) {//注册显示驱动程序,不成功则报错
return -EINVAL; }
printk("Installed sld31506 frame buffer \n”); };
首先对LCD的背光灯进行点亮,LCD显示是一种被动的显示模式,它不能发光,只能依靠控制透射或反射周围环境的光来达到显示的目的,因此,必须通过写电压寄存器,用高电平对LCD显示器加3.2伏电压来实现背光灯的点亮。其函数的部分代码如下:[!--empirenews.page--]
void plateform_init_video(void) {
AT91_SYS->PIOC_PER |= 0x00004000; //对LCD加3.2伏的背光电压[5]
AT91_SYS->PIOC_OER |= 0x00004000;
AT91_SYS->PIOC_SODR |= 0x00004000; …}
本文系统采用的12.1寸TFT彩色LCD最佳分辨率是800×600,但通过前面对结构Struct fb_var_screeninfo的赋值并不能真正设定其分辨率,因为结构Struct fb_var_screeninfo的值只是作为一个显示记录来用,必须通过设定寄存器的值,才能达到需要的分辨率。其中最主要的几个寄存器及其代表意义如图1-2所示:
500)this.style.width=500;" border="0" />
本系统通过一个数组对寄存器进行赋值,在初始化函数中利用s1dReg和s1dValue这两个实参写入:
static S1D_REGS aS1DRegs[] = {
…//前一个值为寄存器标识,后一个为写入寄存器的值[6]
{0x0032,0x63}, // 分辨率水平象素 =) ((032h)bit6-0)+ 1) × 8为800
{0x0038,0x57}, // 分辨率垂直象素0 = (038h)bit7-0 +分辨率垂直象素1为600
{0x0039,0x02}, // 分辨率垂直象素1 = ((039h)bit1-0) + 1
{0x0042,0x00}, // (042h)bit7-0帧缓冲区开始地址
{0x0043,0x00}, // (043h)bit7-0
{0x0044,0x00}, // (044h)bit3-0
{0x0046,0x20}, // (046h)bit7-0帧缓冲区宽度偏移量
{0x0047,0x03}, // (047h)bit2-0
{0x0048,0x00}, // lcd显示图像的起始位置地址0x00
}
至此,彩色LCD的驱动程序框架基本上已经完成了,通过5.1小节中已实现的成员函数调用就可以对帧缓冲区的显示内容进行控制调试,步骤是把编写好驱动程序用arm-linux-gcc进行交叉编译,然后通过串口在目标板上运行insmod/remod命令动态加载/卸载来调试。若程序已调试好就可以把它编译到Linux内核,烧录进目标板的flash就可以完成LCD的显示了。
6 结束语
文中介绍了Linux操作系统下的LCD驱动程序基本原理和框架,以及帧缓冲设备的作用。以基于AT91RM9200芯片的开发板和SLD13506控制器为例,编写了一个典型的帧缓冲设备驱动程序。LCD显示器的型号虽然很多,但其驱动的编写基本上是类似的,可以通过本文介绍的步骤对其它彩色LCD进行编写。
本文作者创新点:本文是介绍了基于ARM9技术的芯片的LCD显示屏驱动控制程序,给出实际项目中已实现了的操作,对同类芯片中的LCD驱动或其他硬件驱动有很好的参考价值。