# 双Framebuffer驱动 该章节请参考【20.lcd】工程,需要恢复系统自带的驱动程序如下: 1. 修改`drivers/video/fbdev/Makefile`,删掉自己的驱动`imx6uLL_fb.o` ![](media/image-20210918083749526.png) 2. 修改`arch/arm/boot/dts/Makefile`,删除掉自己的`imx6ull-14x14-xym.dtb` ![](media/image-20210918084237766.png) 实验验证: ```bash cd 20.lcd # 切换到工程目录 make clean # 清除 make # 编译 make download # 拷贝应用程序app 到nfs共享文件夹 ``` 启动开发板 ```bash ./app s # 单buffer运行,可以看见屏幕有撕裂感 ./app d # 双buffer运行,可以看见屏幕切换流畅 ``` ## 单Buffer的缺点 * 如果APP速度很慢,可以看到它在LCD上缓慢绘制图案 * 即使APP速度很高,LCD控制器不断从Framebuffer中读取数据来显示,而APP不断把数据写入Framebuffer * 假设APP想把LCD显示为整屏幕的蓝色、红色 * 很大几率出现这种情况: * LCD控制器读取Framebuffer数据,读到一半时,在LCD上显示了半屏幕的蓝色 * 这是APP非常高效地把整个Framebuffer的数据都改为了红色 * LCD控制器继续读取数据,于是LCD上就会显示半屏幕蓝色、半屏幕红色 * 人眼就会感觉到屏幕闪烁、撕裂 ![image-20210203172823180](media/048_singble_buffer.png) ## 使用多Buffer来改进 上述两个缺点的根源是一致的:Framebuffer中的数据还没准备好整帧数据,就被LCD控制器使用了。 使用双buffer甚至多buffer可以解决这个问题: * 假设有2个Framebuffer:FB0、FB1 * LCD控制器正在读取FB0,此时APP写FB1 * 写好FB1后,让LCD控制器切换到FB1 * 然后APP写FB0 * 写好FB0后,让LCD控制器切换到FB0,如此反复即可。 ## 内核驱动程序、APP互相配合使用多buffer 假如我们有两个buffer,在fb_info里面的固定信息存放的数据描述如下: ![](media/image-20210913203326453.png) **流程如下**: ![](media/049_drv_app_use_double_buff.png) * 驱动:分配多个buffer ```c // 这里比较狠,直接分配了32M内存作为fb,正确的做法应该是x*y* fb_bpp/8 *N // 其中 x、y是分辨率,fb_bpp是一个像素多少bit,除以8得到字节数,N是FB的数量 fb_info->fix.smem_len = SZ_32M; fbi->screen_base = dma_alloc_writecombine(fbi->device, fbi->fix.smem_len, (dma_addr_t *)&fbi->fix.smem_start, GFP_DMA | GFP_KERNEL); ``` * 驱动:保存buffer信息 ```c fb_info->fix.smem_len // 含有总buffer大小 fb_info->var // 含有单个buffer信息 ``` * APP:读取buffer信息 ```c ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix); ioctl(fd_fb, FBIOGET_VSCREENINFO, &var); // 计算是否支持多buffer,有多少个buffer screen_size = var.xres * var.yres * var.bits_per_pixel / 8; nBuffers = fix.smem_len / screen_size; // 得到fb数量 ``` * APP:使能多buffer ```c var.yres_virtual = nBuffers * var.yres; ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var); ``` * APP:写buffer ```c fb_base = (unsigned char *)mmap(NULL , fix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0); /* get buffer */ pNextBuffer = fb_base + nNextBuffer * screen_size; /* set buffer */ lcd_draw_screen(pNextBuffer, colors[i]); ``` * APP:开始切换buffer ```c /* switch buffer */ var.yoffset = nNextBuffer * var.yres; ioctl(fd_fb, FBIOPAN_DISPLAY, &var); ``` * 驱动:切换buffer ```c // fbmem.c fb_ioctl do_fb_ioctl fb_pan_display(info, &var); err = info->fbops->fb_pan_display(var, info) // 调用硬件相关的函数 ``` 示例: ![image-20210203180357860](media/050_mxsfb_pan_display.png) * APP:等待切换完成(在驱动程序中已经等待切换完成了,所以这个调用并无必要) ```c ret = 0; ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret); ```