我们的目标是在LCD上画出简单的图形,而framebuffer就是我们与LCD打交道的一个工具。网上有一段话解释得很详细:
Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
但Framebuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池.CPU将运算后的结果放到这个水池,水池再将结果流到显示器.中间不会对数据做处理. 应用程序也可以直接读写这个水池的内容.在这种机制下,尽管Framebuffer需要真正的显卡驱动的支持,但所有显示任务都有CPU完成,因此CPU负担很重.
framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等等。
Framebuffer结构图示意:
其中fbmem.c中的函数供用户调用,而XXXfb.c与硬件相连。与硬件相连的部分因硬件不同而不同,但是与用户相连的部分是抽象出来的通用的方法。
Fb_info当中有两个结构体fb_var_screeninfo和fb_fix_screeninfo,前者的内容是用户空间可以更改的,而后者不可以通过用户空间更改。他们的详细内容如下:
struct fb_var_screeninfo{
__u32xres; /*可见屏幕一行有多少个像素点*/
__u32yres; /*可见屏幕一列有多少个像素点*/
__u32xres_virtual; /*虚拟屏幕一行有多少个像素点*/
__u32yres_virtual; /*虚拟屏幕一列有多少个像素点*/
__u32xoffset; /*虚拟到可见屏幕之间的行偏移*/
__u32yoffset; /*虚拟到可见屏幕之间的列偏移*/
__u32bits_per_pixel; /*每个像素的位数即BPP*/
__u32grayscale; /*非0时,指的是灰度*/
structfb_bitfield red; /*fb缓存的R位域*/
structfb_bitfield green; /*fb缓存的G位域*/
structfb_bitfield blue; /*fb缓存的B位域*/
structfb_bitfield transp; /*透明度*/
__u32nonstd; /* != 0 非标准像素格式*/
__u32activate;
__u32height; /*高度*/
__u32width; /*宽度*/
__u32accel_flags;
/*定时:除了pixclock本身外,其他的都以像素时钟为单位*/
__u32pixclock; /*像素时钟(皮秒)*/
__u32left_margin; /*行切换,从同步到绘图之间的延迟*/
__u32right_margin; /*行切换,从绘图到同步之间的延迟*/
__u32upper_margin; /*帧切换,从同步到绘图之间的延迟*/
__u32lower_margin; /*帧切换,从绘图到同步之间的延迟*/
__u32hsync_len; /*水平同步的长度*/
__u32vsync_len; /*垂直同步的长度*/
__u32sync;
__u32vmode;
__u32rotate;
__u32reserved[5]; /*保留*/
};
对于入门者来说,先关注xres、yres和bits_per_pixel这三个值。
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; /*看FB_VISUAL_* */
__u16 xpanstep; /*如果没有硬件panning就赋值为0 */
__u16 ypanstep; /*如果没有硬件panning就赋值为0 */
__u16 ywrapstep; /*如果没有硬件ywrap就赋值为0 */
__u32 line_length; /*一行的字节数 */
unsigned long mmio_start; /*内存映射IO的开始位置*/
__u32 mmio_len; /*内存映射IO的长度*/
__u32 accel;
__u16 reserved[3]; /*保留*/
};
Fix结构体中我们主要关注line_length这个值。
下面介绍如何编写用户空间的程序来操作LCD画图(首先确保LCD已经移植成功):
只给出main函数:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#define XRES 480
char *fb_addr;
unsigned int fb_size;
void main()
{
int x1, y1,\
x2, y2,\
c;
int fbd = 0;
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
fbd = open("/dev/fb0", O_RDWR);
ioctl(fbd, FBIOGET_FSCREENINFO, &fb_fix);
ioctl(fbd, FBIOGET_VSCREENINFO, &fb_var);
fb_size = fb_var.yres * fb_fix.line_length;
printf("xres:%d, yres:%d\n"\
"bits_per_pixel:%d\n"\
"height:%d, width:%d\n",\
fb_var.xres, fb_var.yres,\
fb_var.bits_per_pixel,\
fb_var.height, fb_var.width);
printf("smem_start:%ld\n"\
"smem_len:%d\n"\
"line_length:%d\n",\
fb_fix.smem_start,\
fb_fix.smem_len,\
fb_fix.line_length);
fb_addr=(char *)mmap(0, fb_size, PROT_READ|PROT_WRITE, MAP_SHARED, fbd,0);
Line(0, 0, 480, 272, 0xF800);
Rectangle(100, 100, 200, 200, 0x7E00);
FilledRectangle(300, 5, 480, 10, 0x1F);
//drawline(136, 100, 136, 300, 0xFFFF);
close(fbd);
}
整个背后的结构是这样的:
char *fb_addr 用来指向framebuffer首地址。
unsigned int fb_size 用来保存framebuffer的大小。
struct fb_fix_screeninfo fb_fix 用来保存从内核空间中传递出来的fb_fix_screeninfo结构体,因为用户空间要用到里面的参数。
struct fb_var_screeninfo fb_var 同上。
fbd = open("/dev/fb0", O_RDWR) 以读写的方式打开fb0设备。
ioctl(fbd, FBIOGET_FSCREENINFO,&fb_fix)这句就是上面所说的,将内核空间中的fb_fix_screeninfo结构体传递到用户空间中。第一个参数为所要操作的文件标识符,第二个参数表示要进行的操作,即cmd,ioctl根据这个cmd来进行具体的操作,该操作由驱动程序给出,FBIOGET_FSCREENINFO定义在源代码的include/linux/fb.h中。
用户空间的ioctl会调用驱动中的do_fb_ioctl函数,该函数在内核代码的drivers/video/fbmem.c中。里面有一个判断,switch(cmd),ioctl就是这样根据用户空间的cmd来判断干什么事情的。里面除了FBIOGET_FSCREENINFO还有FBIOGET_VSCREENINFO 等等。后者就是获取fb_var_screeninfo 结构体的。
第三个参数为用户空间的指针,指向要保存的位置,就是我们之前声明的fb_fix。
ioctl(fbd, FBIOGET_VSCREENINFO,&fb_var)同上。
fb_size = fb_var.yres *fb_fix.line_length这一句就是根据上面获取到的结构体中的参数,计算出framebuffer缓冲区的大小。yres保存的是 一列有多少个像素,换一种说法就是LCD有多少行像素。line_length 表示一行的字节数。两者相乘就是缓冲区大小了。
fb_addr=(char *)mmap(0, fb_size,PROT_READ|PROT_WRITE, MAP_SHARED, fbd, 0) 获取缓冲区首地址。Mmap就是将一个文件映射进内存,然后用户对这片内存的操作就是对这个文件的操作。这里的文件就是fb0,fb0代表着LCD屏幕,所以用户只要往这片内存区域里面写进内容,LCD中对应的像素点就会亮起来。具体用法如下:
用法:
#include
void *mmap(void *start, size_t length, int prot, int flags,intfd, off_t offset);
返回说明:
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值
EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区
参数:
start:映射区的开始地址。
length:映射区的长度。
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE //这个标志被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE //兼容标志,被忽略。
MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
offset:被映射对象内容的起点。
有映射就有解除映射,解除映射函数为munmap。
int munmap( void * addr, size_t len )
该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。
上面所说的就是framebuffer编程的基本既定步骤,目的就是将硬件资源映射到内存中,然后用户直接对内存操作。比如现在fb_addr表示了缓冲区的首地址,那么(unsignedshort *)fb_addr = 0xFFFF,就是将屏幕的第一个像素点亮为白色。赋予的值为RGB值,按照5:6:5分配两个字节。因为一个像素代表两个字节,所以赋值时要强制转换为 unsigned short *。这里的“一个像素代表两个字节”是我们在移植LCD驱动时设置的。那如果我们操作的是别人移植的LCD,我怎么知道是多少字节呢?上面说到fb_var_screeninfo中有一个bits_per_pixel参数,这就是每个像素代表几个字节。
我的屏幕时480*272的,xres=480,yres=272。具体的画图函数在另一片文章中:
http://blog.csdn.net/zq979999/article/details/47103969。