【Linux应用编程】framebuffer设备应用编程实例


1 前言

  LCD设备是嵌入式设备中常用的外设,具有良好的可视化功能,不仅仅是消费电子,工控、医疗、汽车等行业的电子设备也越来越倾向于使用LCD作为显示介质。本文描述linux系统LCD设备的应用,LCD设备在linux系统下称为”帧缓冲区设备“( framebuffer device)。


2 framebuffer

  帧缓冲设备(framebuffer device)是linux系统抽针对显示设备抽象出来的一个字符设备,向上为用户层提供统一的访问接口,向下屏蔽底层各类硬件显示设备的差异,提高framebuffer设备应用程序的兼容性和可移植性。


2.1 framebuffer特点

  framebuffer对于应用层和驱动层来说,优势是毋庸置疑的。

  • 对于应用编程,应用程序无需关心显示设备的底层实现、内存换页、数据流向,通过标准framebuffer接口即可控制显示设备。
  • 对于应用可移植性,framebuffer屏蔽底层差异,应用程序可以无缝移植到支持framebuffer的linux设备上,即使cpu平台不一样、显示设备不一样;如qt、minigui等ui程序的可移植性。
  • 对于多显示设备,linux系统支持多个framebuffer设备,显示界面可以快速切换到指定显示设备,只需把底层内存数据重定向到一个framebuffer设备即可。
  • 对于framebuffer驱动,遵循标准的驱动接口,更换或新增显示设备时只需实现标准回调接口,简化开发过程。

2.2 framebuffer设备

  framebuffer设备是一个标准的字符设备,与其他设备一样位于“/dev”目录下,一般命名为fbX("/dev/fb0"),X表示显示设备序号,一般从0开始,linux系统最大支持32个framebuffer设备。

  如下,本人的Hi3520D板子支持5个framebuffer设备。

#ls /dev/fb*
/dev/fb0  /dev/fb1  /dev/fb2  /dev/fb3  /dev/fb4

  framebuffer设备为标准字符设备,主设备号为29,次设备号则从0到31,分别对应fb0—fb31。


3 framebuffer接口

  对应framebuffer应用程序开发,不需关心底层的具体实现,只需知道具体访问接口、数据结构以及显示设备的一些必要信息,即可开发应用程序。


3.1 framebuffer设备描述信息

  framebuffer设备信息包括两部分,分别是不可更改和可更改信息。设备信息描述数据结构位于“include/uaip/linux/fb.h”中声明。

  不可更改信息描述的是显示设备的固有属性、物理内存地址、物理内存大小等等;对于应用层来说,该部分信息可以用来查询显示设备固有属性信息,对于具体应用过程关联性不大。

struct fb_fix_screeninfo{
    
    
	char id[16];			/* identification string eg "TT Builtin" */
	unsigned long smem_start;	/* Start of frame buffer mem */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem */
	__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;		/* length of a line in bytes    */
	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;			/* 可视分辨率(物理分辨率) */
	__u32 yres;
    
	__u32 xres_virtual;	/* 虚拟分辨率 */
	__u32 yres_virtual;
	__u32 xoffset;		/* 虚拟分辨率相对可视分配率偏移值 */
	__u32 yoffset;			

	__u32 bits_per_pixel;	/* 像素位宽,每个像素用多少未表示 */
	__u32 grayscale;		/* 灰度等级,0表示彩色,1表示灰度(黑白屏)*/
	
    /* 缓存RGB位域 */
	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 */

	/* 实际显示屏(LCD)参数,根据LCD手册设置,用户层一般不用更改 */
	__u32 pixclock;			/* 像素时钟 */
	__u32 left_margin;		/* 显示设备水平方向前肩,单位:时钟 */
	__u32 right_margin;		/* 显示设备水平方向后肩,单位:时钟 */
	__u32 upper_margin;		/* 显示设备垂直方式前肩,单位:行 */
	__u32 lower_margin;		/* 显示设备垂直方向后肩,单位:行 */
	__u32 hsync_len;		/* 显示设备水平方向有效区域,单位:时钟 */
	__u32 vsync_len;		/* 显示设备垂直方向有效区域,单位:行 */
    
	__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 */
}

  framebuffer应用程序开发,主要关注的的成员参数是物理分辨率、虚拟分辨率、像素位宽。物理分辨率和像素位宽决定了映射内存的大小,如一个LCD分辨率为1024*768、像素位宽为32位;则需要的内存空间大小为:1024*768*32/8=3145728字节。


  • 物理分辨率和虚拟分辨率

  物理分辨率易于理解,即是一个显示设备的实际分辨率大小;虚拟分辨率则是驱动层实现的一个比物理分辨率低或者高的虚拟分辨率。比如电脑显示器物理分辨率为1920*1080,可以设置显示分辨率为低于或者高于该分辨率,只是显示画质降低或者图形扭曲。


  • 像素位宽

  像素位宽指的是用多少位(bit)表示一个显示像素。常见的色彩模式是24bit RGB,即是RGB颜色分别用8bit表示,称为RGB888。后来引入了透明度(Alpha)元素,同样是用8bit描述,此时用32bit表示一个像素,称为ARGB32。显示设备源于颜色标准,因此也增加了透明度参数。实际上,嵌入式设备中为了节约cpu和内存资源,在色彩画质要求不高的场合,还常用到RGB565、RGB555等格式。更多的颜色格式以及区别,可以参考文章末尾的文章


  显示设备参数一般是在驱动层设置好,无需用户层更改。如图,是一个1024*768的LCD屏幕参数的截图部分。

在这里插入图片描述

  图中虽然没有给出前肩参数,但可以根据总有区域计算出来,计算公式:总区域 = 有效区域 + 前肩 +后肩。该部分属于framebuffer驱动开发的范畴了,不多赘述,后面在framebuffer驱动文章详细描述。


3.2 framebuffer访问接口

  framebuffer设备是一个标准的linux字符设备,可以通过标准虚拟文件接口open/read/write/ioctl/close访问。设备访问分为两部分数据,分别是控制数据流和显示数据流传。对控制数据流通过ioctl结合指定命令实现;对于显示流数据,由于数据量比较大,结合mmap函数,将framebuffer物理内存映射到用户态,直接访问物理内存。

ioctl(fd, cmd, param); /* 文件描述符, 命令字, 参数信息 */

注:
关于mmap函数的使用方法,参考“mmap内存映射”文章。


  framebuffer设备ioctl命令位于“include/uapi/linux/fb.h”定义。

/* include/uaip/linux/fb.h */
/* ioctls
   0x46 is 'F'			*/
#define FBIOGET_VSCREENINFO	0x4600	/* 查询显示设备可更改信息 */
#define FBIOPUT_VSCREENINFO	0x4601	/* 设置显示设备可更改信息 */
#define FBIOGET_FSCREENINFO	0x4602	/* 查询显示设备不可更改信息 */
#define FBIOGETCMAP			0x4604
#define FBIOPUTCMAP			0x4605
#define FBIOPAN_DISPLAY		0x4606
......

3.2.1 查询/设置可更改信息

函数原型:

int ioctl(int fd, int cmd, struct fb_var_screeninfo *var);
  • cmd,命令字:FBIOGET_VSCREENINFO

  • var,设备可更改信息数据结构

  • 返回,成功返回0,失败返回-1,错误码存于errno


  获取显示设备可更改信息伪代码:

int fb = 0;
struct fb_var_screeninfo var;

fb = open("/dev/fb0", O_RDWR);
if (fb < 0)
{
    
    
	printf("open fb device failed:%s\n",strerror(errno));
	return -1;
}
if (ioctl(fb, FBIOGET_VSCREENINFO, &var))
{
    
    
	printf("read fb device param failed:%s\n",strerror(errno));
    return -1;
}	

3.2.2 查询不可更改信息

函数原型:

int ioctl(int fd, int cmd, struct  fb_fix_screeninfo  *var);
  • cmd,命令字:FBIOGET_FSCREENINFO

  • var,设备不可更改信息数据结构

  • 返回,成功返回0,失败返回-1,错误码存于errno


  获取显示设备不可更改信息伪代码:

int fb = 0;
struct fb_fix_screeninfo fix;

fb = open("/dev/fb0", O_RDWR);
if (fb < 0)
{
    
    
	printf("open fb device failed:%s\n",strerror(errno));
	return -1;
}
if (ioctl(fb, FBIOGET_FSCREENINFO, &fix))
{
    
    
	printf("read fb device param failed:%s\n",strerror(errno));
    return -1;
}	

3.2.3 显示设备描点

  面由线组成,线又由点组成,实现一个显示画面,最底层本质是描点。用户层将framebuffer设备将物理缓存映射到用户态,应用程序直接将像素点数据填充到“映射内存”,framebuffer驱动器即会将数据输出至显示设备显示。


描点函数实现步骤:

【1】获取显示设备物理分辨率、像素位宽
【2】根据目标XY坐标计算偏移内存
【3】根据像素位宽写内存,16bit、32bit位宽等

  描点伪代码:

void draw_pixel(int x, int y, uint32_t color)
{
    
    
	uint8_t *poffset_buf = NULL;

	poffset_buf = fb_base + (x*var.bits_per_pixel/8) 
				  + (y*fb->var.xres*var.bits_per_pixel/8);	/* 计算内存偏移地址 */
	*(uint32_t*)poffset_buf = color;	/* ARGB32格式 */
}

4 framebuffer应用编程

  应用程序访问一个framebuffer设备的的总体流程如下图。

在这里插入图片描述

4.1 实例

  • 获取framebuffer设备信息
  • “红—绿—蓝” 1秒周期循环刷新输出
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/fb.h>

struct _fb_info
{
    
    
	int fd;	/* framebuffer 文件描述符 */
	uint8_t *pbuf; /* 映射内存 */
	struct fb_fix_screeninfo fix;
	struct fb_var_screeninfo var;/* framebuffer设备信息*/
};

struct _fb_info fb_app = {
    
    0};

/* 画点函数 */
static void draw_pixel(struct _fb_info *fb, int x, int y, uint32_t color)
{
    
    
	uint8_t *poffset_buf = NULL;

	poffset_buf = fb->pbuf + (x*fb->var.bits_per_pixel/8) 
				  + (y*fb->var.xres*fb->var.bits_per_pixel/8);	/* 计算内存偏移地址 */
	*(uint32_t*)poffset_buf = color;	/* ARGB32格式 */

}

/* 全屏画点函数 */
static void fill_pixel(struct _fb_info *fb, uint32_t color)
{
    
    
    int i, j;

    for (i=0; i<fb->var.xres; i ++) 
	{
    
    
        for (j=0; j<fb->var.yres; j ++) 
		{
    
    
			draw_pixel(fb, i, j, color);
        }
    }
}

int main(int argc, char *argv[])
{
    
    
	int ret = 0;
	int mem_size = 0;
	
	if (argc < 2)
	{
    
    
		printf("parameter invalid\n");
		return -1;
	}
	
	fb_app.fd = open(argv[1], O_RDWR);
	if (fb_app.fd < 0)
	{
    
    
		printf("open device [%s] failed:%s\n", argv[1], strerror(errno));
		return -1;
	}
	printf("framebuffer device:%s\n", argv[1]);
	
	/* 读取不可更改信息 */
	ret = ioctl(fb_app.fd, FBIOGET_FSCREENINFO, &fb_app.fix);    
	if (ret < 0)
	{
    
    
		printf("read fb device fscreeninfo failed:%s\n", strerror(errno));
		close(fb_app.fd);
		return -1;
	}
	printf("device id:%s\n", fb_app.fix.id);
	printf("smem_start:0x%x, smem_len:%u\n", fb_app.fix.smem_start, fb_app.fix.smem_len);    

	/* 读取可更改信息 */
	ret = ioctl(fb_app.fd, FBIOGET_VSCREENINFO, &fb_app.var);
	if (ret < 0)
	{
    
    
		printf("read fb device vscreeninfo failed:%s\n", strerror(errno));
		close(fb_app.fd);
		return -1;
	}
	printf("visible resolution:%d*%d\n", fb_app.var.xres, fb_app.var.yres);
	printf("virtual resolution:%d*%d\n", fb_app.var.xres_virtual, fb_app.var.yres_virtual);
	printf("pixel bits wide:%d\n", fb_app.var.bits_per_pixel);
	printf("grayscale:%d\n", fb_app.var.grayscale);
	
	mem_size = fb_app.var.xres * fb_app.var.yres * fb_app.var.bits_per_pixel / 8;	/* 计算内存 */
	fb_app.pbuf = (uint8_t *)mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, fb_app.fd, 0);
	if (fb_app.pbuf == NULL)
	{
    
    
		printf("fb device mmap failed:%s\n", strerror(errno));
		close(fb_app.fd);
		return -1;
	}
	memset(fb_app.pbuf, 0, mem_size);	/* 清屏操作(黑屏)*/
	for (;;)
	{
    
    
		fill_pixel(&fb_app, 0xffff0000);/* 红 */
		sleep(1);
		fill_pixel(&fb_app, 0xff00ff00);/* 绿 */
		sleep(1);
		fill_pixel(&fb_app, 0xff0000ff);/* 蓝 */
		sleep(1);
	}
	munmap(fb_app.pbuf, mem_size);
	close(fb_app.fd);
	
	return 0;	
}

编译测试

  • 平台:Hid3520D
  • framebuffer:fb0,1024*768LCD
/* 编译 */
# arm-hisiv500-linux fb.c -o fb

/* 执行 */
# ./fb /dev/fb0
framebuffer device:/dev/fb0
device id:hifb
smem_start:0x90082000, smem_len:24883200
visible resolution:1024*768
virtual resolution:1024*1536
pixel bits wide:32
grayscale:0

5 参考文章

【1】fb_var_screeninfo 和fb_fix_screeninfo

【2】图解RGB565、RGB555、RGB16、RGB24、RGB32、ARGB32等格式的区别

【3】mmap内存映射

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/107882790
今日推荐