- 一、LCD硬件原理
利用液晶制成的显示器LCD,依据驱动方式可分为静态驱动、简单矩阵驱动以及主动矩阵驱动3中。其中,简单矩阵型又可再区分扭转向列型(TN)和超扭转式向列型(STN)两种,而主动矩阵型则以薄膜式晶体管型(TFT)为主流。
一块LCD屏显示图像不但需要LCD驱动器,还需要有相应的LCD控制器。通常LCD驱动器会议COF/COG与LCD玻璃基板制作在一起,而LCD控制则由外部电路来实现。许多MCU北部直接集成了LCD控制,通过LCD控制器可以方便地控制STN和TFT屏。
下图给出了LCD控制器中应该设置的TFT屏的参数,其中的上边界和下边界即为帧切换的回归时间,左边界和右便捷即为行切换的回归时间,水平同步和垂直同步分别是行和帧同步本身需要的时间。xres和yres则分别是屏幕的水平和垂直分辨率,常见的嵌入式设备的LCD分辨率主要为320*240、640*480等。
二、帧缓冲
2.1 帧缓冲的概念
帧缓冲(framebutter)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显示缓冲区的具体位置与存放方式。这些都由真缓冲设备驱动本身来完成。对于振缓冲设备而言,只要在现实缓冲区终于显示点对应的区域写入颜色值,对应的颜色会自动的在屏幕上显示,后续会讲解显示缓冲区与显示点的对应关系。
帧缓冲设备为标准的字符设备,主设备号位29,对应/dev/fbn设备文件。帧缓冲驱动的应用非常广泛,在Linux的桌面系统中,X Window服务器就是利用帧缓冲进行窗口的绘制,嵌入式系统中的Qt/Embedded等图形用户界面环境也基于帧缓冲而设计。
2.2 显示缓冲区与显示点
在帧缓冲设备中,对屏幕现实点的操作通过读写显示缓冲区来完成,在不同的色彩模式下,显示缓冲区和屏幕上的现实点有不同的对应关系,下表分别给出了16级灰度、8位色和16位情况下显示缓冲区与显示点的对应关系。
16级灰度显示缓冲区与显示点的对应关系
位 | 31~28 | 27~24 | 23~20 | 19~16 | 15~12 | 11~8 | 7~4 | 3~0 |
0x00 | 点7 | 点6 | 点5 | 点4 | 点3 | 点2 | 点1 | 点0 |
0x04 | 点15 | 点14 | 点13 | 点12 | 点11 | 点10 | 点9 | 点8 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
8位色时显示缓冲区与显示点的对应关系
RGB | BGR | ||||
7~5 | 4~2 | 1~0 | 7~5 | 4~2 | 1~0 |
R | G | B |
16位色时显示缓冲区与显示点的对应关系
位 | 15~11 | 10~5 | 4~0 |
RGB565 | R | G | B |
RGB555 | R | G | B |
2.3 Linux帧缓冲相关数据结构与函数
2.3.1 fb_info结构体
struct fb_info { int node; int flags; struct mutex lock; struct fb_var_screeninfo var; /* 可变参数 */ struct fb_fix_screeninfo fix; /* 固定参数 */ struct fb_monspecs monspecs; /* 显示器标准 */ struct work_struct queue; /* 帧缓冲事件队列 */ struct fb_pixmap pixmap; /* 图像硬件mapper */ struct fb_pixmap sprite; /* 光标硬件mapper */ struct fb_cmap cmap; /* 目前的颜色表 */ struct fb_vediomode *mode; /* 目前的vedio模式 */ struct list_head modelist; #ifdef CONFIG_FB_BACKLIGHT /* 对应的背光设备 */ struct backlight_device *bl_dev; /* 背光调整 */ struct mutex bl_mutex; u8 bl_curve[FB_BACKLIGHT_LEVELS];#endif #ifdef CONFIG_FB_DEFERRED_IO struct delayed_work deferred_work; struct fb_deferred_io *fbdefio;#endif struct fb_ops *fops; /* fb_ops,帧缓冲操作 */ struct device *device; /* 父设备 */ struct device *dev; /* fb设备 */ int class_flag; /* 私有sysfs标志 */#ifdef CONFIG_FB_TILEBLITING struct fb_tile_ops *tileops; /* 土块Blitting */#endif char __iomem *screen_base; unsigned long screen_size; void *pseudo_palette;#define FBINFO_STATE_RUNNING 0#define FBINFO_STATE_SUSPENDED 1 u32 state; void *fbcon_ptr; void *par;};
FBI中记录了帧缓冲的全部信息,包括设备的设置参数、状态以及操作函数指针。每一个帧缓冲设备都必须对应一个FBI。
2.3.2 fb_ops结构体
FBI的成员变量fbops为指向底层操作的函数指针,这些函数是需要驱动程序驱动程序人员编写的,其定义如下:
struct fb_ops { struct module *owner; /* 打开/关闭函数 */ int (*fb_open) (struct fb_info *info, int user); int (*fb_close) (struct fb_info *info, int user); /* 对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要 */ ssize_t (*fb_read) (struct file *file, char __user *buf, size_t count, loff_t *ppos); ssize_t (*fb_write) (struct file *file, const char __user *buf, size_t count, loff_t *ppos); /* 检测可变参数,并调整到支持的值 */ int (*fb_check_var) (struct fb_var_screeninfo *var, struct fb_info *info); /* 根据info->var设置video模式 */ int (*fb_set_par) (struct fb_info *info); /* 设置color寄存器 */ int (*fb_setcolreg) (unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info); /* 批量设置color寄存器,设置颜色表 */ int (*fb_setcmap) (struct fb_cmap *cmap, struct fb_info *info); /* 显示空白 */ int (*fb_blank) (int blank, struct fb_info *info); /* pan显示 */ int (*fb_pan_display) (struct fb_var_screeninfo *var, struct fb_info *info); /* 矩形填充 */ int (*fb_fillract) (struct fb_info *info, const struct fb_fillrect *rect); /* 数据复制 */ void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region); /* 图形填充 */ void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); /* 绘制光标 */ int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor); /* 旋转显示 */ void (*fb_rotate) (struct fb_info *info, int angle); /* 等待blit空闲(可选) */ void (*fb_sync) (struct fb_info *info); /* fb特定的ioctl(可选) */ void (*fb_ioctl) (struct fb_info *info, unsigned int cmd, unsigned long arg); /* 处理32位的compat ioctl(可选) */ int (*fb_compat_ioctl) (struct fb_info *info,unsigned cmd, unsigned long arg); /* fb特定的mmap */ int (*fb_mmap) (struct fb_info *info,struct vm_area_struct *vma); /* 保存目前的硬件状态 */ void (*fb_save_state) (struct fb_info *info); /* 恢复被保存的硬件状态 */ void (*fb_get_state) (struct fb_info *info, struct fb_blit_caps *caps, struct fb_var_screeninfo *var);};
fb_ops的fb_check_var()成员函数用于检查可以修改的屏幕参数并调整到合适的值,而fb_set_par()则是的用户设置的屏幕参数在硬件上有效。
2.3.3 fb_var_screeninfo和fb_fix_screeninfo结构体
FBI的fb_var_screeninfo和fb_fix_screeninfo成员也是结构体,fb_var_screeninfo记录用户可以修改的显示控制器参数,包括屏幕分辨率和每个像素点的比特殊。fb_fix_screeninfo中记录用户不能修改的显示控制器的参数,如屏幕缓冲区的物理地址、长度。当对帧缓冲设备进行映射操作的时候,就是从fb_fix_screeninfo中取得缓冲区物理地址的。上述数据成员都需要在驱动程序中初始化和设置。
fb_var_screeninfo结构体
struct fb_var_screeninfo { /* 可见解析度 */ u32 xres; u32 yres; /* 虚拟解析度 */ u32 xres_virtual; u32 yres_virtual; /* 虚拟到可见之间的偏移 */ u32 xoffset; u32 yoffset; u32 bits_per_pixel; /* 每像素位数,BPP */ u32 qrayscale; /* 非0时指灰度 */ /* fb缓存的R/G/B位域 */ struct fb_bitfield red; struct fb_bitfield green; struct fb_bitfield blue; struct fb_bitfield transp; /* 透明度 */
/* != 0 非标准像素格式 */ u32 nonstd; u32 activate; u32 height; /* 高度 */ u32 width; /* 宽度 */ u32 accel_flags; /* 看fb_info.flags */ /* 定时:除了pixclock本身外,其他的都以像素时钟为单位 */ u32 pixclock; /* 像素时钟 */ u32 left_margin; /* 行切换:从同步到绘图之间的延迟 */ u32 right_margin; /* 行切换:从绘图到同步之间的延迟 */ u32 upper_margin; /* 帧切换:从同步到绘图之间的延迟 */ u32 lower_margin; /* 帧切换:从绘图到同步之间的延迟 */ u32 hsync_len; /* 水平同步的长度 */ u32 vsync_len; /* 垂直同步的长度 */ u32 sync; u32 vmode; u32 rotate; u32 reserved[5]; /* 保留 */
};
fb_fix_screeninfo结构体
struct fb_fix_screeninfo { char ld[16]; /* 字符串形式的标识符 */ unsigned long smem_start; /* fb缓冲内存的开始位置 */ u32 smem_len; /* fb缓冲的场长度 */ u32 type; /* FB_TYPE_ */ u32 type_aux; /* Interleave */ u32 visual; /* FB_VISUAL_ */ u16 xpanstrp; /* 如果没有硬件panning,赋0 */ u16 ypanstep; u16 ywrapstep; u32 line_length; /* 1行的字节数 */ unsinged long mmio_start; /* 内存映射I/O的开始位置 */ u32 mmio_len; /* 内存映射I/O的长度 */ u32 accel; u16 reserved[3];};
fb_bitfield结构体
struct fb_bitfield { __u32 offset; /* 位域偏移 */ __u32 length; /* 位域长度 */ __u32 msb_right; /* !=0;MSB在右边 */};
fb_cmap结构体
fb_cmap结构体记录设备无关的颜色表信息,用户空间可以通过ioctl()的FBIOGETCMAP和FBIOPUTCMAP命令读取或设定颜色表。
struct fb_cmap{ u32 start; /* 第一个元素入口 */ u32 len; /* 元素数量 */ /* R,G,B 透明度 */ u16 *red; u16 *green; u16 *blue; u16 *transp;};
2.3.6 文件操作结构体
struct file_operations fb_ops = { .owner = THIS_MODULE, .read = fb_read, .write = fb_write, .ioctl = fb_ioctl,#ifdef CONFIG_COMPAT .compat_ioctl = fb_compat_ioctl,#endif .mmap = fb_mmap, .open = fb_open, .release = fb_release,#ifdef HAVE_ARCH_FB_UNMAPPED_AREA .get_unmapped_area = get_fb_unmapped_area,#endif#ifdef CONFIG_FB_DEFERRED_IO .fsync = fb_deferred_io_fsync,#endif};
帧缓冲设备的驱动文件操作接口函数已经fbmem.c中被统一实现,一般不需要由驱动工程师在编写。
2.3.7注册和主线帧缓冲设备
Linux内核提供了register_framebuffer()和unregister_framebuffer()函数分别注册和注销帧缓冲设备,这两个函数渡劫否FBI指针为参数。
int register_framebuffer(struct fb_info *info);int unregister_framebuffer(struct fb_info *info);
三、Linux帧缓冲设备驱动结构
Linux帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的file_operations结构由fbmem.c中的file_operation提供,而特定帧缓冲设别fb_info结构体的注册、注销以及其中成员的维护,尤其是fb_ops中成员函数的实现则由对应的xxxfb.c文件实现,fb_ops中的成员函数最终会操作LCD控制器硬件寄存器。
四、帧缓冲设备驱动的模块加载与卸载函数
在帧缓冲设备的模块加载函数中,应该完成如下4个工作:
1、申请FBI结构体的内存空间,初始化FBI结构体中固定和可变的屏幕参数,即填充FBI中的fb_var_screeninfo和fb_fix_screeninfo成员。
2、根据具体的LCD屏幕的特点,完成LCD控制器硬件的初始化。
3、申请帧缓冲设备的显示缓冲区空间。
4、注册帧缓冲设备。
在适用平台驱动的情况下,释放FBI结构体内存,关闭LCD、释放显示缓冲区以及注销帧缓冲区设备的工作也移交到平台驱动的移除函数中完成。
static struct platform_device { .proce = xxxfb_probe, .remove = xxxfb_remove, .suspend = xxxfb_suspend, .resume = xxxfn_resume, .driver = { .name = "xxx-lcd", .owner = THIS_MODULE, }}; /* 平台驱动探测函数 */static int __init xxxfb_probe(...){ struct fb_info *info; info = framebuffer_alloc(...); info->screen_var = xxxfb_var; info->fix = xxxfb->fix; alloc_dis_buffer(...); lcd_init(...); xxxfb_check_var(&info->var,info); if(register_framebuffer(info) < 0) { return -EINVAL; } return 0;} static void __exit xxxfb_remove(...){
struct fb_info *info = dev_get_drv_data(dev); if(info) { unregister_framebuffer(info); dealloc_dis_buffer(...); framebuffer_release(info); } return 0;} static int __init xxxfb_init(void){ return platfrom_driver_register(&xxxfb_driver);} static void __exit xxxfb_exit(void){ platform_driver_unregister(&xxxfb_driver);}
五、帧缓冲设备显示缓冲区的申请与释放
在嵌入式系统中,一种常见的方式是直接在RAM空间中分配一段显示缓冲区,典型的结构如下:
writecombining意味着写合并,它允许写入的数据被合并,兵临时保存在写合并缓冲区中,直到进行一次brust传输而不再需要多次single传输,通过dma_alloc_writeconbine()分配的显示缓冲区不会出现cache一致性问题,这一点类似于dma_alloc_coherent()。
static int __init xxxfb_map_video_memory(struct xxxfb_info *fbi){ fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len+PAGE_SIZE); fbi->map_cpu = dma_alloc_writecombine(fbi->dev,fbi->map_size,&fbi->map_dma,GFP_KERNEL); fbi->map_size = fbi->fb->fix.smem_len; if(fbi->mem_cpu) { memset(fbi->mem_cpu,0xf0,fbi->map_size); fbi->screen_dma = fbi->map_dma; fbi->fb->screen_base = fbi->map_cpu; fbi->fb->fix.smem_start = fbi->screen_dma; } return fbi->mem_cpu?0:-ENOMEM;} static inline void xxxfb_unmap_vedio_memory(struct s3c2410fb_info *fbi){ dma_free_writeconbine(fbi->dev, fbi->map_size, fbi->map_cpu, fbi->map_dma);}
六、帧缓冲设备的参数设置
6.1 定时参数
FBI结构体可变参数var中的left_margin、right_margin、upper_margin、lower_margin、hsync_len和vsync_len直接查LCD的数据手册就可以得到。
6.2 像素时钟
FBI可变参数var中的pixclock意味着像素时钟。
6.3 颜色位域
6.4 固定参数
FBI固定参数fix中的smem_start指示帧缓冲设备显示缓冲区的首地址,smem_len为帧缓冲设备显示缓冲区的大小。
smem_len = max_xres*max_yres*max_bpp。
七、帧缓冲设备驱动的fb_ops成员函数
FBI中的fb_ops是使得帧缓冲设备工作所需函数的集合,他们最终于LCD控制器硬件打交道。
fb_check_var()用于调整可变参数,并修正硬件所支持的值;fb_set_par()则根据屏幕参数设置具体读写LCD控制器的寄存器以使得LCD控制器进入相应的工作状态。对于fb_ops中的fb_fillrect()、fb_copyarea()和fb_imageblit()成员函数,通常直接使用对应的通用的cfb_fillrect()、cfb_copyarea()和cfb_imageblit()函数即可。cfb_fillrect()函数定义在drivers/vedio/cfbfill.c文件中。cfb_copyarea()函数定义在drivers/video/cfbcopyarea.c文件中。cfb_imageblit()函数定义在drivers/video/cfbimgblt.c文件中。
fb_ops中的fb_setcolreg()成员函数实现伪颜色表(针对FB_VISUAL_TRUECOLOR、FB_VISUAL_DIRECTCOLOR模式)和颜色表的填充。
static int xxxfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info){ struct xxxfb_info *fbi = info->par; unsigned int val; switch(fbi->fb->fix.visual) { case FB_VISUAL_TRUECOLOR: /* 真彩色,设置为伪颜色表 */ if(regno < 16) { u32 *pal = fbi->fb->pseudo_palette; val = chan_to_field(red,&fbi->fb->var.red); val |= chan_to_field(green,&fbi->fb->var.green); val |= chan_to_field(blue,&fbi->fb->var.blue); pal[reqno] = val; } break; case FB_VISUAL_PSEUDOCOLOR: if(regno < 256) { val = (red >> 0) & 0xf800; val |= (green >> 5) & 0x7a0; val |= (blue >> 11) & 0x1f; writel(val,XXX_TFTPAL(regno)); schedule_palette_update(fbi,regno,val); } break; } return 0;}
八、LCD设备驱动的读写、mmap和ioctl函数
虽然帧缓冲设备的file_operations中的成员函数,即文件操作函数已经由内核在fbmem.c文件中实现,一般不再需要驱动工程师修改,但分析这些函数对于巩固自读设备驱动的指示以及加深对真缓冲设备驱动的理解是大有脾益的。
static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){ unsigned long p = *ppos; struct inode *inode = file->f_path.dentry->d_inode; int fbidx = iminor(inode); struct fb_info *info = registered_fb(fbidx); u32 *buffer, *dst; u32 __iomem *src; int c,i,cnt = 0, err = 0; unsigned long total_size; ...... buffer = kmalloc((count > PAGE_SIZE)?PAGE_SIZE:count,GFP_KERNEL); if(!buffer) { return -ENOMEM; } src = (u32 __iomem *)(info->screen_base + p); if()}
九、帧缓冲设备的用户空间
通过/dev/dbn,应用程序可进行的针对真缓冲设备的操作主要有如下几种:
- 读/写dev/dbn:相当于读/写屏幕的缓冲区,例如cp /dev/fb0 tmp命令可将当前屏幕上的内容复制到一个文件中,而命令cp tmp>/dev/fb0 则将图形文件tmp显示在屏幕上。
- 映射操作,对于帧缓冲设备,可通过mmap()应设操作将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读/写这段虚拟地址访问缓冲区,在屏幕绘图了。而且若干个进程可以映射到同一个娴熟缓冲区。实际上,使用帧缓冲设备的应用程序都是通过映射操作来显示图形的。
- I/O控制:对于帧缓冲设备,对设备文件的ioctl()操作可读取/设置显示设备及屏幕的参数,分辨率、显示颜色数、屏幕大小等。
如图18.6所示,在应用程序中,操作/dev/fbn的一般步骤如下:
1、打开/dev/fbn设备文件
2、用ioctl()操作区的当前显示屏幕的参数,如分辨率、每个像素点的比特殊和偏移。根据屏幕参数可计算屏幕缓冲区的大小。
3、将屏幕缓冲区映射到用户空间。
4、映射后就可以直接读/写屏幕缓冲区,进行绘图和图片显示了。
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <fcntl.h>#include <linux/fb.h>#include <sys/mman.h> int main(void){ int fbfd = 0; struct fb_var_screeninfo vinfo; char *fbp = 0; fbfd = open("/dev/fb0",O_RDWR); if(fnfd == 0) { printf("Error:cannot open framebuffer device.\n"); return 1; } ptintf("The frambuffer device was opened successfully.\n"); if(ioctl(fbfd,FBIOGET_VSCEENINFO,&vinfo)) {
printf("Error: reading variable information.\n"); return 1; } printf("%dx%d,%dbpp\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel); if(vinfo.bits_per_pixel != 16) { printf("Error: not supported bits_per_pixel,it onlu supports 16 bit color.\n"); return 1; } fbp = (char *)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARD,fbfd,0); if((int)fbp == -1) { printf("Error: failed to map framebuffer device to memory.\n"); return 4; } printf("The framebuffer device was mapped to memory successfully.\n"); for(i = 0;i < 3;i++) { for(y = i*(vinfo.yres/3);y < (i+1)*(vinfo/yres/3);y++) { for(x = 0;x < vinfo.xres;x++) { long location = x*2 + y * vinfo.res*2; int r = 0,g = 0, b = 0; if(i == 0) { r = ((x*1.0)/vinfo.xres)*32; } if(i == 1) { g = ((x*1.0)/vinfo.xres)*64; } if(i == 2) { b = ((x*1.0)/vinfo.xres)*32; } rgb = (r<<11)|(q<<5)|b; *((unsigned short *)(fbp + location)) = rgb; } } } munmap(fbp,screensize); close(fbfd); return 0;}
总结
帧缓冲设备是一种典型的字符设备,他统一了显存,将显示缓冲区直接映射到用户空间。真缓冲设备驱动file_operations中VFS接口函数由fbmem.c文件统一实现。这样,驱动工程师的工作重点监视实现针对特定设备的fb_ops中的fb_ops的成员函数。另外,理解并能灵活地修改fb_info中的varhe fix参数非常关键。fb_info中的参数直接和LCD控制器的硬件设备以及LCD屏幕对应。
在用户空间,应用程序直接按照预先设置的R/G/B位数和偏移写经过mmap()映射后的显示缓冲区就可实现图形的显示,省去了内存从用户空间到内核空间的复制过程。