18. 屏幕显示(framebuffer)¶
18.1. LubanCat-RK系列板卡¶
该教程适用于LubanCat-RK系列板卡, 可以接入野火mipi屏以及HDMI屏幕进行使用。
本章的示例代码目录为:base_linux/screen/framebuffer
18.2. framebuffer介绍¶
FrameBuffer中文译名为帧缓冲驱动,它是出现在2.2.xx内核中的一种驱动程序接口。 主设备号为29,次设备号递增。
Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。 FrameBuffer机制模仿显卡的功能,将显卡硬件结构抽象掉, 可以通过FrameBuffer的读写直接对显存进行操作。 用户可以将FrameBuffer看成是显示内存的一个映像, 将其映射到进程地址空间之后,就可以直接进行读写操作, 而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。
用户不必关心物理显存的位置、换页机制等等具体细节, 这些都是由FrameBuffer设备驱动来完成的。
FrameBuffer实际上就是嵌入式系统中专门为GPU所保留的一块连续的物理内存, LCD通过专门的总线从framebuffer读取数据,显示到屏幕上。
FrameBuffer本质上是一块显示缓存, 往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。 所以说FrameBuffer就是一块白板。
屏幕位置从上到下,从左至右与内存地址是顺序的线性关系
屏幕除了RGB888的格式外,还具有其他的格式
1 2 3 4 | ARGB888:|AAAAAAAAA|RRRRRRRRR|GGGGGGGG|BBBBBBBB|
RGB888: | |RRRRRRRRR|GGGGGGGG|BBBBBBBB|
RGB565: |RRRRRRGGG|GGGBBBBB|
RGB555: | RRRRRGGG|GGGBBBBB|
|
18.3. framebuffer应用程序¶
前言:
注意
如果你使用的是带桌面版的镜像,在使用framebuffer前要注意先把图形界面关闭, 不然会出现触摸后屏幕不断闪烁的情况
1 2 3 4 5 6 7 8 9 | #关闭用户图形界面
sudo systemctl set-default multi-user.target
sudo reboot
#开启用户图形界面
sudo systemctl set-default graphical.target
sudo reboot
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | #include <stdio.h>
#include <sys/types.h> //open需要的头文件
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> //write
#include <sys/types.h>
#include <sys/mman.h> //mmap 内存映射相关函数库
#include <stdlib.h> //malloc free 动态内存申请和释放函数头文件
#include <string.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
//32位的颜色
#define Black 0x00000000
#define White 0xffFFFFFF
#define Red 0xffFF0000
#define Green 0xff00ff00
#define Blue 0xff99ffff
int fd;
unsigned int *fb_mem = NULL; //设置显存的位数为32位
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
int main(void)
{
unsigned int i;
int ret;
/*--------------第一步--------------*/
fd = open("/dev/fb0",O_RDWR); //打开framebuffer设备
if(fd == -1){
perror("Open LCD");
return -1;
}
/*--------------第二步--------------*/
//获取屏幕的可变参数
ioctl(fd, FBIOGET_VSCREENINFO, &var);
//获取屏幕的固定参数
ioctl(fd, FBIOGET_FSCREENINFO, &fix);
//打印分辨率
printf("xres= %d,yres= %d \n",var.xres,var.yres);
//打印总字节数和每行的长度
printf("line_length=%d,smem_len= %d \n",fix.line_length,fix.smem_len);
printf("xpanstep=%d,ypanstep= %d \n",fix.xpanstep,fix.ypanstep);
/*--------------第三步--------------*/
fb_mem = (unsigned int *)mmap(NULL, var.xres*var.yres*4, //获取显存,映射内存
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(fb_mem == MAP_FAILED){
perror("Mmap LCD");
return -1;
}
memset(fb_mem,0xff,var.xres*var.yres*4); //清屏
sleep(1);
/*--------------第四步--------------*/
//将屏幕全部设置成蓝色
for(i=0;i< var.xres*var.yres ;i++)
fb_mem[i] = Blue;
sleep(2);
memset(fb_mem,0x00,var.xres*var.yres*4); //清屏
munmap(fb_mem,var.xres*var.yres*4); //映射后的地址,通过mmap返回的值
close(fd); //关闭fb0设备文件
return 0;
}
|
编译运行
1 2 3 4 5 6 7 8 | gcc framebuffer.c -o framebuffer
./framebuffer
#运行结果
#打印屏幕信息
#屏幕会先变白
#等待1s
#屏幕变蓝
|
18.3.1. 代码分析¶
操作屏幕一共需要四步就可以操作整个屏幕
1 2 3 4 5 6 | /*--------------第一步--------------*/
fd = open("/dev/fb0",O_RDWR); //打开framebuffer设备
if(fd == -1){
perror("Open LCD");
return -1;
}
|
这一步是打开framebuffer设备,这一步成功后会返回文件描述符– fd, 我们后面可以操作这个文件描述符操作该设备。
1 2 3 4 5 6 7 8 9 10 11 12 | /*--------------第二步--------------*/
//获取屏幕的可变参数
ioctl(fd, FBIOGET_VSCREENINFO, &var);
//获取屏幕的固定参数
ioctl(fd, FBIOGET_FSCREENINFO, &fix);
//打印分辨率
printf("xres= %d,yres= %d \n",var.xres,var.yres);
//打印总字节数和每行的长度
printf("line_length=%d,smem_len= %d \n",fix.line_length,fix.smem_len);
printf("xpanstep=%d,ypanstep= %d \n",fix.xpanstep,fix.ypanstep);
|
这一步获取屏幕的参数以及设置屏幕
获取屏幕的可变参数 ioctl(fd, FBIOGET_VSCREENINFO, &var);
获取屏幕的固定参数 ioctl(fd, FBIOGET_FSCREENINFO, &fix);
这两个目的是获取fb_var_screeninfo,fb_fix_screeninfo结构体
我们看一下结构体所包含的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | //固定参数
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* mmap后内存的首地址*/
/* (physical address) */
__u32 smem_len; /* framebuffer mmap的最大长度*/
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* 每行像素的长度 */
unsigned long mmio_start; /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 capabilities; /* see FB_CAP_* */
__u16 reserved[2]; /* Reserved for future compatibility */
};
//可变参数
struct fb_var_screeninfo {
__u32 xres; /* x轴的可视区域--x轴实际像素*/
__u32 yres; /* y轴的可视区域--y轴实际像素*/
__u32 xres_virtual; /* x轴的虚拟区域,可用于双缓冲设计*/
__u32 yres_virtual; /* y轴的虚拟区域,可用于双缓冲设计*/
__u32 xoffset; /* x轴偏移量-默认为零,可用于双缓冲设计*/
__u32 yoffset; /* x轴偏移量-默认为零,可用于双缓冲设计*/
__u32 bits_per_pixel; /* 每个像素占用的字节*/
__u32 grayscale; /* 0 = color, 1 = grayscale,>1 = FOURCC*/
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm */
__u32 width; /* width of picture in mm */
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 colorspace; /* colorspace for FOURCC-based modes */
__u32 reserved[4]; /* Reserved for future compatibility */
};
|
更多详细的内容可以阅读内核源码以及查看手册位置kernel/Documentation/fb/api.txt
ioctl :在framebuffer里除了 FBIOGET_VSCREENINFO, FBIOGET_FSCREENINFO 还有其他的选项进行配置
选项名 |
功能 |
用法 |
---|---|---|
FBIOGET_VSCREENINFO |
ioctl(fd, FBIOGET_VSCREENINFO, &var); |
获取屏幕可变参数 |
FBIOPUT_VSCREENINFO |
ioctl(fd, FBIOPUT_VSCREENINFO, &var); |
修改屏幕的可变参数 |
FBIOGET_FSCREENINFO |
ioctl(fd, FBIOGET_FSCREENINFO, &fix); |
获取屏幕的固定参数 |
FBIOPAN_DISPLAY |
ioctl(fd, FBIOPAN_DISPLAY, &var); |
平移显示,设置屏幕的显示,可用于双缓冲framebuffer设计 |
其他 |
ioctl(fd, xxxxxx, &xxx); |
其他参数可以阅读内核源码:/kernel/drivers/video/fbdev/core/fbmem.c 1113行~1267行 |
1 2 3 4 5 6 7 8 9 10 | unsigned int *fb_mem = NULL; //设置显存的位数为32位
/*--------------第三步--------------*/
fb_mem = (unsigned int *)mmap(NULL, var.xres*var.yres*4, //获取显存,映射内存
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(fb_mem == MAP_FAILED){
perror("Mmap LCD");
return -1;
}
|
这一步操作是把设备的内存映射到用户空间里, 用户只需要对着这块内存操作就可以改变屏幕的内容, 与传统的write函数相比, 数据传输的速度以及便捷性都得到了大幅的提高
第一行设置fb_mem为32位, 可以让我能够方便的使用32位的数据操作24位屏幕数据RGB888, 如果使用想要节省空间的话,可以使用unsigned char型, 不过颜色分量就需要解析,然后分别改写。
1 2 | mmap的原型如下
caddr_t mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);
|
返回值为地址
addr :申请的地址,如果为空,系统会自动分配地址空间
len :申请内存空间的大小,单位为字节
prot :prot参数指定访问权限,可取如下几个值的“或”:PROT_READ(可读)、 PROT_WRITE(可写)、PROT_EXEC(可执行)和PROT_NONE(不可访问)
flags :两个参数可以选择:MAP_PRIVATE和MAP_SHARED 1. MAP_PRIVATE - 创建一个私人写时复制映射,映射的更新对映射相同文件的其他进程不可见,而且并不是通向底层文件。 2. MAP_SHARED - 分享该映射,映射的更新对映射此文件的其他进程可见,并被带到底层文件。
fd : 文件描述符,fd=-1,匿名映射
offset :映射文件的偏移量,从文件的哪里开始操作
注意
更详细的操作可以前往pc上的Ubuntu在命令行里输入 man 2 mmap 获得
与之相对应的是第64行的 munmap(fb_mem,var.xres*var.yres*4);
1 2 | #函数功能:释放内存空间
int munmap(void *addr, size_t length);
|
addr :mmap返回的地址
length :要回收的内存空间大小
1 2 3 4 | /*--------------第四步--------------*/
//将屏幕全部设置成蓝色
for(i=0;i< var.xres*var.yres ;i++)
fb_mem[i] = Blue;
|
该操作就可以直接通过操作framebuffer就可以控制屏幕了。 i对应的数值就是屏幕中像素点的位置
18.4. 双缓冲framebuffer设计¶
18.4.1. 前言¶
本小结仅提供双缓冲framebuffer的应用程序设计思路, 想在LubanCat-RK系列板卡中实现这个功能需要自行修改内核源码适配
双缓冲的设计在drm中有较高的支持,我们推荐使用drm应用编程替代framebuffer, 如果不想修改内核源码,但是想体验双缓冲framebuffer,可以前往下一章节学习
18.4.2. 双缓冲¶
在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。 计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。 如果所需要绘制的图形很简单,那么这样也没什么问题。 但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。 让我们把计算机想象成一个画图比较快的人,假如他直接在屏幕上画图,而图形比较复杂, 则有可能在他只画了某幅图的一半的时候就被观众看到。而后面虽然他把画补全了, 但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。 也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。 如何解决这一问题呢?我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。 这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。 即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域, 在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。
以横向扩充为例
18.4.3. 实现思路¶
内核修改:
修改 fb_fix_screeninfo.smem_len 这个参数,而固定参数不能在应用层修改,所以需要在内核里扩充它的长度
修改内核中mmap函数的分配内存的长度,dma的传输范围
修改内核中fb_pan_display,以适配显示
应用层修改:
在应用层需要把xres_virtual扩充到xres的两倍
如果想在主屏幕上显示的话,需要设置 fb_var_screeninfo.xoffset=0;在副屏幕上显示的话,需要设置 fb_var_screeninfo.xoffset=xres;
通过ioctl(fd, FBIOPUT_VSCREENINFO, &var);修改参数
通过ioctl(fd, FBIOPAN_DISPLAY, &var);把参数传入到内核里,就可以切换屏幕了
本次设计思路参考文章:https://blog.csdn.net/louiswangbing/article/details/6606849