S3C2440 framebuffer编程

          我们的目标是在LCD上画出简单的图形,而framebuffer就是我们与LCD打交道的一个工具。网上有一段话解释得很详细:

         Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。

          但Framebuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池.CPU将运算后的结果放到这个水池,水池再将结果流到显示器.中间不会对数据做处理. 应用程序也可以直接读写这个水池的内容.在这种机制下,尽管Framebuffer需要真正的显卡驱动的支持,但所有显示任务都有CPU完成,因此CPU负担很重.

         framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等等。

         Framebuffer结构图示意:

S3C2440 framebuffer编程_第1张图片

         其中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);

}

         整个背后的结构是这样的:

S3C2440 framebuffer编程_第2张图片

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。

你可能感兴趣的:(S3C2440 framebuffer编程)