LCD屏的定义和分类
LCD的定义
LCD是Liquid Crystal Display的简称,也就是经常所说的液晶显示器。
LCD能够支持彩色图像的显示和视频的播放,是一种非常重要的输出设备。
LCD的分类(按驱动方式)
依据驱动方式可分为静态驱动、简单矩阵驱动以及主动矩阵驱动3 种。其中,简单矩阵型又可再细分扭转向列型(TN)和超扭转式向列型(STN)两种,而主动矩阵型则以薄膜式晶体管型(TFT)为主流。如表,列出了它们的区别:
类别 | TN | STN | TFT |
---|---|---|---|
原理 | 液晶分子,扭转 90° | 扭转 180°~270° | 液晶分子,扭转 90° |
特性 | 黑白、单色低对比 | 黑白、彩色,低对比 | 彩色(1667 万色),可媲美 CRT 显示器的全彩,高对比 |
动画显示 | 否 | 否 | 是 |
视角 | 30°以下 | 40°以下 | 80°以下 |
面板尺寸 | 1~3 英寸 | 1~12 英寸 | 37 英寸 |
TN、STN 和 TFT 显示器的区别
- TN 型液晶显示技术是 LCD 中最基本的,其他种类的 LCD 都以 TN 型为基础改进而得。TN 型 LCD显示质量很差,色彩单一,对比度低,反映速度很慢,故主要用于简单的数字符与文字的显示,如电子表及电子计算器等。
- STN LCD 的显示原理与 TN 类似,区别在于 TN 型的液晶分子将入射光旋转 90°,而 STN 则可将入射光旋转 180°~270°。STN 改善了 TN 视角狭小的缺点,并提高了对比度,显示品质较 TN 高。STN 搭配彩色滤光片,将单色显示矩阵的任一像素分成 3 个子像素,分别透过彩色滤光片显示红、绿、蓝三原色,再经由三原色按比例调和,显示出逼近全彩模式的色彩。STN 显示的画面色彩对比度仍较小,反应速度也较慢,可以作为一般的操作显示接口。
- 在 TFT 型 LCD 中,晶体管矩阵依显示信号开启或关闭液晶分子的电压,使液晶分子轴转向而成“亮”或“暗”的对比,避免了显示器对电场效应的依靠。因此,TFT LCD 的显示质量较 TN/STN 更佳,画面显示对比度可达 150:1 以上,反应速度逼近 30ms 甚至更快,适用于 PDA、笔记本电脑、数码相机、MP4 等。
一块 LCD 屏显示图像不但需要 LCD 驱动器,还需要有相应的 LCD 控制器。通常 LCD 驱动器会以 COF/COG 的形式与 LCD 玻璃基板制作在一起,而 LCD 控制器则由外部电路来实现。许多 MCU内部直接集成了 LCD 控制器,通过 LCD 控制器可以方便地控制 STN 和 TFT 屏。TFT 屏是目前嵌入式系统应用的主流。
Frambuffer(帧缓冲)
Framebuffer 是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行操作。
- Framebuffer又叫帧缓冲,用户应用程序可以通过Framebuffer透明地访问不同类型的显示设备。
- Framebuffer是硬件设备显示缓冲区的抽象。Linux抽象出Framebuffer这个帧缓冲区可以供用户应用程序直接读写,通过更改Framebuffer中的内容,就可以立刻显示在LCD显示屏上。
Framebuffer 的特点
- Framebuffer是显卡硬件的抽象
Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象为一系列的数据结构,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反映在屏幕上。这些操作是由抽象的,统一的。用户不必关心物理显存的位置、换页机制等具体细节。这些都是由Framebuffer设备驱动完成的。 - Frambuffer 是标准的字符设备
Framebuffer 是一个标准的字符设备,主设备号是29,次设备号根据缓冲区的数目而定。
Framebuffer 对应 /dev/fb%d 设备文件。根据显卡的多少,设备文件可能是/dev/fb0、/dev/sb1等。缓冲区设备也是一种普通的内存设备,可以直接对其进行读写。
例如:对屏幕进行抓屏,可以使用下面的命令:
cp /dev/fb0 myfile.png
一个系统上有多个显示设备。例如一个系统上,又有一个独立的显卡,那么就有两个缓冲区设备文件/dev/fb1 和 /dev/fb2,应用程序利用它们来工作,向其中写入数据,就能够在屏幕上立刻看到显示的变化。
Framebuffer 与应用程序的交互
有了Framebuffer,用户程序不需要对底层的驱动深入了解就能够作出很好的图形。
对用户程序而言,它和 /dev 下面的其他设备没有什么区别,用户可以把FrameBuffer看成一块内存,既可以写,又可以读。显示器将根据内存数据显示对应的图像界面。这一切都由LCD控制器和响应的驱动程序来完成
Framebuffer的显示缓冲区位于Linux的内核态地址空间。而在Linux中,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区的。为此,Linux在文件操作file_operations结构中提供了mmap()函数,可将文件的内容映射到用户空间。对应帧缓冲设备,则可以通过映射操作,将屏幕缓冲区(Framebuffer)的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图。
Framebuffer与应用程序的交互如下图:
图1 Framebuffer与应用程序的交互图
Framebuffer显示原理:
通过Framebuffer,应用程序用mmap()把显存映射到应用程序虚拟地址空间。帧缓冲设备主设备号29,次设备号从0到31.分别对应/dev/fd0 ~ /dev/fd31。
Framebuffer 驱动的重要数据结构
Framebuffer驱动的功能就是分配一块内存作为显存,然后LCD控制器的寄存器做一些设置。LCD显示器会不断地从显存中获取数据,并将其显示在LCD显示器上。LCD显示器可以显示显存中的一个区域或者整个区域。framebuffer驱动程序提供了操作显存的功能,例如复制显存、向显存中写入数据(画园,画方型等)。
实现这些操作的方法是:填充一个fbinfo结构,用register_framebuffer(fbinfo *)将fbinfo结构注册到内核,对于fbinfo结构,最主要的它的fs_ops成员,需要针对具体设备实现fs_ops中的接口。
Framebuffer驱动主要位于/include/linux/fb.h 、/drivers/video/fbmem.c这两个文件中,它们处于驱动体系结构的中间层,为上层用户程序提供系统调用,也为底层特定硬件驱动提供了接口。
fb.h中定义了一些主要的数据结构,Frameuffer设备在很大程度上依靠了下面的3个数据结构:
- struct fb_var_screeninfo结构体
用来描述图形卡的特性,通常是被用户设置的。它描述了一种显示模式的所有信息,如宽、高、颜色深度等,不同显示模式对应不同的信息。它存储了用户可以修改的显示器控制器的参数。例如屏幕分辨率、每个像素的比特数、透明度等:
struct fb_var_screeninfo {
__u32 xres; //xres和yres表示可见解析度,即分辨率。xres表示一行有多少个像素点,yres表示屏幕一列有多少个像素点
__u32 yres;
__u32 xres_virtual; //这两行表示虚拟解析度
__u32 yres_virtual;
__u32 xoffset; //不可见部分与可见部分的偏移地址
__u32 yoffset;
__u32 bits_per_pixel; //每一个像素所在的位数。每个 像素点占用多少字节
__u32 grayscale; //非0时的灰度值
struct fb_bitfield red; //下面是fb缓存的R、G、B位域
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
__u32 nonstd; //非标准像素格式
__u32 activate;
__u32 height; //屏幕的高度
__u32 width; //屏幕的宽度
__u32 accel_flags; //fb_info的标志
__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]; //保留
};
- struct fb_fix_screeninfo结构体
定义了图形卡的硬件特性,是不能改变的,用户选定了LCD控制器和显示器后,那么硬件特性也就定下来了。它定义了显卡信息,如framebuffer内存的起始地址,地址的长度。它记录了用户不能修改的固定显示器控制参数。这些固定参数如缓冲区的物理地址、缓冲区的长度、显示色彩模式、内存映射的开始位置等。这个结构体的成员都需要在驱动程序初始化时设置:
struct fb_fix_screeninfo {
char id[16]; //字符串形式的标志符
unsigned long smem_start; //fb缓冲区的开始位置
__u32 smem_len; //fb缓冲区的长度
__u32 type; //FB_TYPE_*类型
__u32 type_aux; //分界
__u32 visual; //屏幕使用色彩模式
__u16 xpanstep; //如果没有硬件panning,赋值0
__u16 ypanstep; //如果没有硬件panning,赋值0
__u16 ywrapstep; //如果没有硬件panning,赋值0
__u32 line_length; //一行的字节数
unsigned long mmio_start; //内存映射I/O的开始位置
__u32 mmio_len; //内存映射I/O的长度
__u32 accel; //特定的芯片
__u16 reserved[3]; //保留
};
- struct fb_info结构体
定义了当前图形卡Framebuffer设备的独立状态,一个图形卡可能有两个Framebuffer,在这种情况下,就需要两个fb_info结构。它包含当前显示卡的状态信息,struct fb_info结构体只对内核可见。它包含了驱动实现的底层函数和记录设备状态的数据。一个帧缓冲区对应一个fb_info结构体:
struct fb_info {
int node;
int flags;
struct mutex lock; //为 open/release/ioctl 等函数准备的互斥锁
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 list_head modelist; //模块列表t
struct fb_videomode *mode; //当前视频模式
#ifdef CONFIG_FB_BACKLIGHT //如果配置了LCD支持背光灯
struct backlight_device *bl_dev; //对应的背光灯设备,设置bl_dev这个变量,应该在注册Framebuffer之前
struct mutex bl_curve_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 *fbops; //帧缓冲操作函数集合
struct device *device; //指向父设备结构体
struct device *dev; //内嵌的Framebuffer设备自身
int class_flag; //私有sysfs标志
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; //图块Blitting
#endif
char __iomem *screen_base; //虚拟基地址
unsigned long screen_size; //ioremap的虚拟内存大小
void *pseudo_palette; //伪16位调色板
#define FBINFO_STATE_RUNNING 0 // 运行状态
#define FBINFO_STATE_SUSPENDED 1 // 挂起状态
u32 state; //硬件的状态
void *fbcon_par; //fbcon使用的私有数据
void *par;
};
其他数据结构:
① struct fb_cmap结构体:用来定义帧缓存区设备的颜色表(colormap)信息,可以通过ioctl()函数的FBIOGETCMAP和FBIOPUTCMAP命令来设置colormap。结构为:
struct fb_cmap
{
_ _u32 start; /* 第 1 个元素入口 */
_ _u32 len; /* 元素数量 */
/* R、G、B、透明度*/
_ _u16 * red;
_ _u16 * green;
_ _u16 * blue;
_ _u16 * transp;
};
② struct fb_ops结构体:应用程序使用这些函数操作底层的LCD硬件,fb_ops结构中定义的方法用于支持这些操作。这些操作需要驱动开发人员来实现。具体结构为:
struct fb_ops
{
struct module * owner;
/* 打开/释放 */
int(*fb_open) (struct fb_info * info, int user);
int(*fb_release) (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);
/* 矩形填充 */
void(*fb_fillrect) (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 空闲 (可选) */
int(*fb_sync) (struct fb_info * info);
/* fb 特定的 ioctl (可选) */
int(*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_restore_state) (struct fb_info * info);
};
fb_ops 的 fb_check_var()成员函数用于检查可以修改的屏幕参数并调整到合适的值; fb_set_par()则使得用户设置的屏幕参数在硬件上有效。
③ fb_bitfield 结构体
fb_bitfield 结构体描述每一像素显示缓冲区的组织方式,包含位域偏移、位域长度和 MSB 指示,结构为:
struct fb_bitfield
{
_ _u32 offset; /* 位域偏移 */
_ _u32 length; /* 位域长度 */
_ _u32 msb_right; /* MSB */
};
④ 文件操作结构体
作为一种字符设备,帧缓冲设备的文件操作结构体定义于/linux/drivers/vedio/fbmem.c 文件中,具体代码:
static struct file_operations fb_fops =
{
.owner = THIS_MODULE,
.read = fb_read, //读函数
.write = fb_write, //写函数
.ioctl = fb_ioctl, //I/O 控制函数
#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
};
在Framebuffer设备驱动程序中,这些结构是互相关联,互相配合使用的。只有每一个结构体起到自己的作用,才能使整个Framebuffer设备驱动程序正常工作。
它们之间的联系如下图:
图2 Framebuffer数据结构之间的联系图
注册与注销帧缓冲设备
Linux 内核提供了 register_framebuffer()和 unregister_framebuffer()函数分别注册和注销帧缓冲设备,这两个函数都接受 FBI 指针为参数,原型为:
int register_framebuffer(struct fb_info *fb_info); //注册帧缓冲设备
int unregister_framebuffer(struct fb_info *fb_info); //注销帧缓冲设备
对于 register_framebuffer()函数而言,如果注册的帧缓冲设备数超过了 FB_MAX(目前定义为 32),则函数返回-ENXIO,注册成功则返回 0。
Linux 帧缓冲设备驱动的实现
Linux 帧缓冲设备驱动的主要结构
下图是Linux 帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的 file_operations 结构体由fbmem.c 中的 file_operations 提供,而特定帧缓冲设备 fb_info 结构体的注册、注销以及其中成员的维护,尤其是 fb_ops 中成员函数的实现则由对应的 xxxfb.c 文件实现,fb_ops 中的成员函数最终会操作 LCD 控制器硬件寄存器。
图3 帧缓冲设备驱动的程序结构
帧缓冲设备驱动的模块加载与卸载函数
在帧缓冲设备驱动的模块加载函数中,应该完成如下 4 个工作:
- 申请 FBI 结构体的内存空间,初始化 FBI 结构体中固定和可变的屏幕参数,即填充 FBI 中fb_var_screeninfo var 和 struct fb_fix_screeninfo fix 成员。
- 根据具体 LCD 屏幕的特点,完成 LCD 控制器硬件的初始化。
- 申请帧缓冲设备的显示缓冲区空间。
- 注册帧缓冲设备。
由于 LCD 控制器经常被集成在 SoC 上作为一个独立的硬件模块而存在(platform_device),因此,LCD驱动中也经常包含平台驱动,这样,在帧缓冲设备驱动的模块加载函数中完成的工作只是注册平台驱动,而初始化 FBI 结构体中的固定和可变参数、LCD 控制器硬件的初始化、申请帧缓冲设备的显示缓冲区空间和注册帧缓冲设备的工作则移交到平台驱动的探测函数中完成。
在帧缓冲设备驱动的模块卸载函数中,应该完成相反的工作,包括释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备。
同样地,在使用平台驱动的情况下,释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备的工作也移交到平台驱动的移除函数中完成。
帧缓冲设备驱动的模块加载和卸载以及平台驱动的探测和移除函数中的模板:
/* 平台驱动结构体 */
static struct platform_driver xxxfb_driver =
{
.probe = xxxfb_probe, //平台驱动探测函数
.remove = xxxfb_remove, //平台驱动移除函数
.suspend = xxxfb_suspend, .resume = xxxfb_resume, .driver =
{
.name = "xxx-lcd", //驱动名
.owner = THIS_MODULE,
}
};
/* 平台驱动探测函数 */
static int _ _init xxxfb_probe(...)
{
struct fb_info * info;
/*分配 fb_info 结构体*/
info = framebuffer_alloc(...);
info->screen_base = framebuffer_virtual_memory;
info->var = xxxfb_var; //可变参数
info->fix = xxxfb_fix; //固定参数
/*分配显示缓冲区*/
alloc_dis_buffer(...);
/*初始化 LCD 控制器*/
lcd_init(...);
/*检查可变参数*/
xxxfb_check_var(&info->var, info);
/*注册 fb_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); //注销 fb_info
dealloc_dis_buffer(...); //释放显示缓冲区
framebuffer_release(info); //注销 fb_info
}
return 0;
}
/* 帧缓冲设备驱动模块加载与卸载函数 */
int __devinit xxxfb_init(void)
{
return platform_driver_register(&xxxfb_driver); //注册平台设备
}
static void __exit xxxfb_cleanup(void)
{
platform_driver_unregister(&xxxfb_driver); //注销平台设备
}
module_init(xxxfb_init);
module_exit(xxxfb_cleanup);
上面出现的register_framebuffer()和 unregister_framebuffer()分别用于注册和注销帧缓冲设备。
帧缓冲设备显示缓冲区的申请与释放
在嵌入式系统中,一种常见的方式是直接在 RAM 空间中分配一段显示缓冲区,在分配显示缓冲区时一定要考虑 cache 的一致性问题,因为系统往往会通过 DMA方式搬移显示数据。合适的方式是使用 dma_alloc_writecombine()函数分配一段writecombining区域,对应的writecombining区域由dma_free_writecombine()函数释放,帧缓冲设备显示缓冲区的分配与释放具体模板为:
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->map_cpu)
{
memset(fbi->map_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; //赋值 fix 的 smem_start
}
return fbi->map_cpu ? 0: -ENOMEM;
}
static inline void xxxfb_unmap_video_memory(struct s3c2410fb_info * fbi)
{
//释放显示缓冲区
dma_free_writecombine(fbi->dev, fbi->map_size, fbi->map_cpu, fbi->map_dma);
}
帧缓冲设备的参数设置
-
定时参数
FBI 结构体可变参数 var 中的 left_margin、right_margin、upper_margin、lower_margin、hsync_len 和vsync_len 直接查 LCD 的数据手册就可以得到。 -
像素时针
FBI 可变参数 var 中的 pixclock 意味着像素时钟,例如,如果为 28.37516 MHz,那么画 1 个像素需要35242 ps(皮秒):
1/(28.37516E6 Hz) = 35.242E-9 s
如果屏幕的分辨率是 640×480,显示一行需要的时间是:
640*35.242E-9 s = 22.555E-6 s
每条扫描线是 640,但是水平回扫和水平同步也需要时间,假设水平回扫和同步需要 272 个像素时钟,因此,画一条扫描线完整的时间是:
(640+272)*35.242E-9 s = 32.141E-6 s
可以计算出水平扫描率大约是 31kHz:
1/(32.141E-6 s) = 31.113E3 Hz
完整的屏幕有 480 线,但是垂直回扫和垂直同步也需要时间,假设垂直回扫和垂直同步需要 49 个象素时钟,因此,画一个完整的屏幕的时间是:
(480+49)*32.141E-6 s = 17.002E-3 s
可以计算出垂直扫描率大约是 59kHz:
1/(17.002E-3 s) = 58.815 Hz
这意味着屏幕数据每秒钟大约刷新 59 次。 -
颜色位域
FBI 可变参数 var 中的 red、green 和 blue 位域的设置直接由显示缓冲区与显示点的对应关系决定,例如:对于 RGB565 模式,查表 18.4,red 占据 5 位,偏移为 11 位;green 占据 6 位,偏移为 5 位;blue 占据 5 位,偏移为 0 位,即:
fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
- 固定参数
FBI 固定参数 fix 中的 smem_start 指示帧缓冲设备显示缓冲区的首地址,smem_len 为帧缓冲设备显示 缓冲区的大小,计算公式为:
smem_len = max_xres * max_yres * max_bpp
即:帧缓冲设备显示缓冲区的大小 = 最大的 x 解析度 * 最大的 y 解析度 * 最大的 BPP。
Linux 帧缓冲设备的操作函数
虽然帧缓冲设备的 file_operations 中的成员函数,即文件操作函数已经由内核在 fbmem.c 文件中实现,一般不再需要驱动工程师修改,但分析这些函数对于巩固字符设备驱动的知识以及加深对帧缓冲设备驱动的理解是大有作用的。
Linux 帧缓冲设备的读写函数
LCD 设备驱动的文件操作读写函数的的源代码,从代码结构及习惯而言,与字符设备驱动完全一致,因此不做过多的介绍。
Linux 帧缓冲设备的mmap函数
file_operations 中的 mmap()函数非常关键,它将显示缓冲区映射到用户空间,从而使得用户空间可以直接操作显示缓冲区而省去一次用户空间到内核空间的内存复制过程,提高效率。其源代码为:
static int fb_mmap(struct file * file, struct vm_area_struct * vma)
{
int fbidx = iminor(file->f_dentry->d_inode);
struct fb_info * info = registered_fb[fbidx];
struct fb_ops * fb = info->fbops;
unsigned long off;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return - EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
if (!fb)
return - ENODEV;
if (fb->fb_mmap) //FBI 中实现了 mmap,则调用 FBI 的 mmap
{
int res;
lock_kernel();
res = fb->fb_mmap(info, vma);
unlock_kernel();
return res;
}
lock_kernel();
/* 映射帧缓冲设备的显示缓冲区 */
start = info->fix.smem_start; //开始地址
len = PAGE_ALIGN((start & ~PAGE_MASK) +info->fix.smem_len); //长度
if (off >= len)
{
/* 内存映射的 I/O */
off -= len;
if (info->var.accel_flags)
{
unlock_kernel();
return - EINVAL;
}
start = info->fix.mmio_start;
len = PAGE_ALIGN((start & ~PAGE_MASK) +info->fix.mmio_len);
}
unlock_kernel();
start &= PAGE_MASK;
if ((vma->vm_end - vma->vm_start + off) > len)
return - EINVAL;
off += start;
vma->vm_pgoff = off >> PAGE_SHIFT;
/* 这是 1 个 I/O 映射 */
vma->vm_flags |= VM_I / O | VM_RESERVED;
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot)) //映射
return - EAGAIN;
return 0;
}
Linux 帧缓冲设备ioctl函数
fb_ioctl()函数最终实现对用户 I/O 控制命令的执行,这些命令包括 FBIOGET_VSCREENINFO(获得可变的屏幕参数)、FBIOPUT_VSCREENINFO(设置可变的屏幕参数)、FBIOGET _FSCREENINFO(获得固定的屏幕参数设置,注意,固定的屏幕参数不能由用户设置)、FBIOPUTCMAP(设置颜色表)、FBIOGETCMAP(获得颜色表)等。简单使用为:
static int fb_ioctl(struct inode * inode, struct file * file, unsigned int cmd,
unsigned long arg)
{
...;
if (!fb)
return - ENODEV;
switch (cmd)
{
case FBIOGET_VSCREENINFO: // 获得可变的屏幕参数
return copy_to_user(argp, &info->var, sizeof(var)) ? -EFAULT: 0;
case FBIOPUT_VSCREENINFO: //设置可变的屏幕参数
...;
case FBIOGET_FSCREENINFO: //获得固定的屏幕参数设置
return copy_to_user(argp, &info->fix, sizeof(fix)) ? - EFAULT: 0;
case FBIOPUTCMAP: //设置颜色表
...;
}
}
Linux 帧缓冲设备其他函数
FBI 中的 fp_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/video/cfbfillrect.c文件中, cfb_copyarea() 定义在 drivers/video/cfbcopyarea.c 文件中, cfb_imageblit() 定义在drivers/video/cfbimgblt.c 文件中。
- fb_ops 中 的 fb_setcolreg() 成员函数实现伪颜色表(针对 FB_VISUAL_TRUECOLOR 、 FB_ VISUAL_DIRECTCOLOR 模式)和颜色表的填充。
帧缓冲设备的用户空间访问
通过/dev/fbns,应用程序可进行的针对帧缓冲设备的操作主要有如下几种:
- 读/写 dev/fbn:相当于读/写屏幕缓冲区。例如用 cp /dev/fb0 tmp 命令可将当前屏幕的内容复制到一个文件中,而命令 cp tmp > /dev/fb0 则将图形文件 tmp 显示在屏幕上。
- 映射操作:对于帧缓冲设备,可通过 mmap()映射操作将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读/写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图了。而且若干个进程可以映射到同一 个显示缓冲区。实际上,使用帧缓冲设备的应用程序都是通过映射操 作来显示图形的。
- I/O 控制:对于帧缓冲设备,对设备文件的 ioctl()操作可读取 /设置显示设备及屏幕的参数,如分辨率、显示颜色数、屏幕大小等。
如下图所示,在应用程序中,操作/dev/fbn 的一般步骤如下:
- 打开/dev/fbn 设备文件。
- 用 ioctl()操作取得当前显示屏幕的参数,如屏幕分辨率、每 个像素点的比特数和偏移。根据屏幕参数可计算屏幕缓冲区的大小。
- 将屏幕缓冲区映射到用户空间。
- 映射后就可以直接读/写屏幕缓冲区,进行绘图和图片显示 了。
图4 用户空间访问帧缓冲设备流程图