丨budboool · 11月25日 · 广东

LCD Framebuffer应用开发 - 操作原理

1.1 LCD Framebuffer操作原理

LCD Framebuffer 就是一块显存,在嵌入式系统中,显存是被包含在内存中。LCD Framebuffer里的若干字节(根据驱动程序对LCD控制器的配置而定)表示LCD屏幕中的一个像素点,一一对应整个LCD屏幕。举个例子,LCD屏幕是800* 600的分辨率,即LCD屏幕存在480000个像素点,若每个像素点4个字节表示,那么LCD Framebuffer显存大小为480000 * 4=960000字节,即1.92MB。因此我们的内存将会分割至少1.92MB的空间用作显存。具体地址在哪里,这个就是又驱动程序去定,应用程序只需直接使用即可,硬件相关操作已由驱动程序封装好。

FramebufferAPP_Image00001

如上图,我们只需要往Framebuffer中填入不同的值,驱动程序和硬件控制器就会把这些数据传输到对应LCD屏幕上的像素点,从而显示不同的颜色。由此可知,我们应用程序只需要针对Framebuffer操作即可,其他交给驱动程序和硬件。

#1.2 Framebuffer API接口

#1.2.1 open系统调用

FramebufferAPP_Image00002

头文件:#include <sys/types.h>,#include <sys/stat.h>,#include <fcntl.h>

函数原型:

  • int open(const char *pathname, int flags);
  • int open(const char *pathname, int flags, mode\_t mode);

函数说明:

  • pathname 表示打开文件的路径;
  • Flags表示打开文件的方式,常用的有以下6种,

    ①:O\_RDWR表示可读可写方式打开;

    ②:O\_RDONLY表示只读方式打开;

    ③:O\_WRONLY表示只写方式打开;

    ④:O\_APPEND 表示如果这个文件中本来是有内容的,则新写入的内容会接续到原来内容的后面;

    ⑤:O\_TRUNC表示如果这个文件中本来是有内容的,则原来的内容会被丢弃,截断;

    ⑥:O\_CREAT表示当前打开文件不存在,我们创建它并打开它,通常与O\_EXCL结合使用,当没有文件时创建文件,有这个文件时会报错提醒我们;

Mode表示创建文件的权限,只有在flags中使用了O\_CREAT时才有效,否则忽略。

返回值:打开成功返回文件描述符,失败将返回-1。

#1.2.2 ioctl系统调用

FramebufferAPP_Image00003

头文件:#include <sys/ioctl.h>

函数原型:

  • int ioctl(int fd, unsigned long request, ...);

函数说明:

  • fd 表示文件描述符;
  • request表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据;
  • … 表示可变参数arg,根据request命令,设备驱动程序返回输出的数据。

返回值:打开成功返回文件描述符,失败将返回-1。

#1.2.3 mmap系统调用

FramebufferAPP_Image00004

头文件:#include <sys/mman.h>

函数原型:

  • void *mmap(void *addr, size\_t length, int prot, int flags,int fd, off\_t offset);

函数说明:

  • addr表示指定映射的內存起始地址,通常设为 NULL表示让系统自动选定地址,并在成功映射后返回该地址;
  • length表示将文件中多大的内容映射到内存中;
  • prot 表示映射区域的保护方式,可以为以下4种方式的组合

    ①PROT\_EXEC 映射区域可被执行

    ②PROT\_READ 映射区域可被读写

    ③PROT\_WRITE 映射区域可被写入

    ④PROT\_NONE 映射区域不能存取

  • Flags 表示影响映射区域的不同特性,常用的有以下两种

    ①MAP\_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文件会改变。

    ②MAP\_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,对此区域的任何修改都不会写回原来的文件内容中。

返回值:若成功映射,将返回指向映射的区域的指针,失败将返回-1。

#1.3 在LCD上描点操作

#1.3.1 在LCD上显示点阵理论基础

FramebufferAPP_Image00005

如上图,当我们需要显示一个字母‘A’时,是通过判断点阵的每一个位数值状态,来填充颜色,达到显示字符效果。其中‘1’表示一种颜色,‘0’表示填充另一种颜色。上图的是8*16的点阵,我们也可以用其他不同大小点阵,只要有这个点阵,我们就可以在LCD上面描点,达到显示字符的效果。

#1.3.2 获取fb\_var\_screeninfo结构体

在用点阵显示字符之前,我们需要先从设备fb0中获取相关的LCD信息,下图截取我们将用到的fb\_info结构体部分内容。

FramebufferAPP_Image00006

通过系统调用ioctl,获取xres(x方向总像素点),yres(y方向总像素点),bits\_per\_pixel(每个像素点占据的位数),根据获取的三个资源,外加点阵,根据这四个资源,我们就可以显示一个字符。

程序文件:show\_ascii.c

4718        fd_fb = open("/dev/fb0", O_RDWR);
4719        if (fd_fb < 0)
4720        {
4721            printf("can't open /dev/fb0\n");
4722            return -1;
4723        }
4724        if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
4725        {
4726            printf("can't get var\n");
4727            return -1;
4728        }    















先打开LCD设备(fb0),获得文件描述符,再通过ioctl获取fb\_var\_screeninfo信息并保存在var变量,后续只需访问var这个结构体,就可以获得xres(x方向总像素点),yres(y方向总像素点),bits\_per\_pixel(每个像素点占据的位数)这三个关于fb0的资源。

#1.3.3 根据fb\_var\_screeninfo计算变量

fb\_var\_screeninfo已保存在var结构体变量中,接着来访问var结构体变量即可

根据xres与bits\_per\_pixel算出每行像素点所占据的字节数

程序文件:show\_ascii.c

4730    line_width  = var.xres * var.bits_per_pixel / 8;





根据bits\_per\_pixel算出每个像素点所占据的字节数

程序文件:show\_ascii.c

4731    pixel_width = var.bits_per_pixel / 8;





根据xres,yres,bits\_per\_pixel算出全部像素点所占据的字节总和

程序文件:show\_ascii.c

4732    screen_size = var.xres * var.yres * var.bits_per_pixel / 8;





#1.3.4 使用mmap系统调用,映射内存

程序文件:show\_ascii.c

4733    fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ |             PROT_WRITE, MAP_SHARED, fd_fb, 0);
4734    if (fbmem == (unsigned char *)-1)
4735    {
4736        printf("can't mmap\n");
4737        return -1;
4738    }
4739
4740    /* 清屏: 全部设为黑色 */
4741    memset(fbmem, 0, screen_size);













调用mmap将显存映射在内存中,以可读可写(PROT\_READ | PROT\_WRITE)及内存回写(MAP\_SHARED)的方式映射,从而获得一个指向映射在内存空间的首地址fbmem,后续操作就是在这个首地址的基础上计算各种不同的偏移量,填充颜色值。

#1.3.5 描点函数编写

程序文件:show\_ascii.c

4641    void lcd_put_pixel(int x, int y, unsigned int color)





描点函数有3个参数,x坐标,y坐标,像素点颜色值。

程序文件:show\_ascii.c

4643        unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
4644        unsigned short *pen_16;    
4645        unsigned int *pen_32;    
4646
4647        unsigned int red, green, blue;    
4648
4649        pen_16 = (unsigned short *)pen_8;
4650        pen_32 = (unsigned int *)pen_8;












在此处函数参数x与y表示的是像素点的坐标,而单个像素点所占据的显存大小可能会有不同的情况出现,如1字节表示一个像素点,2字节表示一个像素点,4字节表示一个像素点等,为了更多的兼容不同的情况,因此申请3个指针,pen\_8指向的是占据1个字节的像素点空间, pen\_16指向的是占据2个字节的像素点空间,pen\_32指向的是占据4个字节的像素点空间。

fbmem是系统调用mmap返回的显存首地址,根据fbmem计算填充颜色的内存空间。

当像素点占据1个字节空间时

对应描点地址= fbmem+Y * 一行所占据的字节数 + x * 每个像素点所占据的字节数

程序文件:show\_ascii.c

4652        switch (var.bits_per_pixel)
4653        {
4654            case 8:
4655            {
4656                *pen_8 = color;
4657                break;
4658            }
4659            case 16:
4660            {
4661                /* 565 */
4662                red   = (color >> 16) & 0xff;
4663                green = (color >> 8) & 0xff;
4664                blue  = (color >> 0) & 0xff;
4665                color = ((red >> 3) << 11) | ((green >> 2) << 5) |                         (blue >> 3);
4666                *pen_16 = color;
4667                break;
4668            }
4669            case 32:
4670            {
4671                *pen_32 = color;
4672                break;
4673            }
4674            default:
4675            {
4676                printf("can't surport %dbpp\n", var.bits_per_pixel);
4677                break;
4678            }
4679        }
4680    }



































根据设备fb0实际的bits\_per\_pixel值,选择对应的pen(pen\_8,pen\_16,pen\_32其中一个),最后把color颜色变量传入选择的pen中。

推荐阅读
关注数
19
文章数
104
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息