Linux Framebuffer应用编程
LCD控制原理
Linux系统通过Framebuffer(帧缓冲)驱动程序控制LCD。显示设备也被称为帧缓冲设备。Frame表示帧,buffer表示缓冲。这就是说,Framebuffer是一块内存,里面存放着一帧图像,每帧图像包含每个像素颜色值。BPP,像素深度,指存储每个像素所用的位数,通常值为16,24,或32。
假设LCD分辨率1024 x 768,每个像素颜色用32bit表示,那么Framebuffer大小为1024 * 768 * 32 / 8 = 3145728 byte。
Framebuffer应用编程的本质:修改指定位置像素点颜色值。
控制LCD主要步骤:
1)驱动程序设置好LCD控制器
根据LCD参数设置LCD控制器的时序、信号极性;
根据LCD分辨率、BPP分配Framebuffer。
2)APP使用ioctl获得LCD分辨率、BPP。
3)APP通过mmap映射Framebuffer,彺Framebuffer写入数据。
假设需要设置LCD中坐标(x,y)处像素的颜色,首先要找到这个像素对应的内存(地址),然后根据它的BPP值设置颜色。假设fb_base是APP执行mmap后得到的Framebuffer地址,如下图所示:
可以用下面公式算出(x,y)坐标处像素对应的Framebuffer地址:
(x,y)像素起始地址 = fb_base + (xres * bpp / 8) * y + x * bpp / 8
xres 表示x轴像素个数, yres表示y轴像素个数,bpp表示像素深度。
像素的颜色如何表示?
用RGB三原色(红、绿、蓝)表示,在不同的BPP格式中,用不同的位来表示R、G、B。如下图所示:
对于32BPP,一般只设置其中的低24bit,高8bit表示透明度,一般LCD不支持。
对于24BPP,硬件上为方便处理,在Framebuffer中也是用32bit表示,效果跟32BPP一样。
对于16BPP,常用RGB565;很少用到RGB555,这可以通过ioctl读取驱动程序中的RGB位偏移来确定使用哪种格式。
Framebuffer编程有关API
通过一个示例程序,进行Framebuffer编程。目的:打开LCD设备节点,获取分辨率等参数,映射Framebuffer,实现描点函数。
open
open用于打开设备文件。
#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种:
a. O_RDWR 可读可写方式打开;
b. O_RDONLY 只读方式打开;
c. O_WRONLY 只写方式打开;
d. O_APPEND 如果文件原来有内容,则新写入内容会接续到原内容后面;
e. O_TRUNC 如果这个文件中本来有内容,则原内容截断;
f. O_CREAT 当前打开文件不存在,就创建并打开它,通常与O_EXCL结合使用;当没有文件时创建文件,有文件时报错并提醒我们。
- mode 表示创建文件的权限,只有在flags中使用了O_CREAT时才有效;否则,忽略。
返回值:成功打开文件,返回文件描述符;打开失败,返回-1,并且errno被设置。
ioctl
ioctl用于获取设备参数。ioctl功能非常强大、灵活,不同驱动程序内部会实现不同ioctrl,APP可以使用各种ioctl跟驱动程序交互,如传数据给驱动程序,也可以从驱动程序中读取数据。
#include <sys/ioctl.h>
int ioctl(int fd, int request, ...);
函数参数说明:
- fd 文件描述符
- request 表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据
对于LCD设备来说,常用request有
(1)FBIOGET_VSCREENINFO:获取LCD的可变参数信息;
(2)FBIOPUT_VSCREENINFO:设置LCD的可变参数信息;
(3)FBIOGET_FSCREENINFO:获取LCD的不变参数信息
宏定义FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO及保存结果的数据结构struct fb_var_screeninfo 和 struct fb_fix_screeninfo,都位于<linux/fb.h>。
- ... 表示可变参数arg,根据request命令,保存设备驱动程序返回输出的数据
返回值:通常是成功时,返回0,也有少部分request,返回非负数;失败时,返回-1,并且errno被设置。
mmap
mmap用于建立内存映射。
munmap用于取消mmap建立的内存映射。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
函数参数说明:
-
addr 表示指定映射的内存起始地址,通常设为NULL表示让系统自动选择地址,在成功建立映射后返回改地址
-
length 表示将文件中多大的内容映射到内存中
-
prot 表示映射区域的保护方式,可以为以下4种方式组合:
a. PROT_EXEC 映射区域可被执行
b. PROT_READ 映射区域可被读出
c. PROT_WRITE 映射区域可被写入
d. PROT_NONE 映射区域不能存取 -
flags 表示影响映射区域的不同特性,常用2种:
a. MAP_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文件会改变。
b. MAP_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,对此区域的任何修改都不会写回原来的文件内容中。
返回值:若成功映射,则返回指向映射的区域指针;失败返回-1,errno被设置。
Framebuffer程序
1.open打开设备
首先,打开设备节点
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0) {
printf("Can't open /dev/fb0\n");
return -1;
}
2.ioctl获取LCD参数
LCD驱动程序给APP提供2类参数:可变的参数fb_var_screeninfo,固定的参数fb_fix_screeninfo。编写APP时,主要关系可变参数,定义如下:
#include <linux/fb.h>
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */
__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 */
};
fb_bar_screeninfo关键参数:
分辨率:xres, yres;
bpp:bits_per_pixel,像素深度;
red、green、blue成员,表示RGB分别用多少位表示从哪位开始。
如何获取当前fb_bar_screeninfo类型的var?
可以使用ioctl函数:
static struct fb_var_screeninfo var; /* current var */
...
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) {
printf("Can't get var\n");
return -1;
}
注意:这里ioctl用到的命令是FBIOGET_VSCREENINFO,表示get var screen info,即获得屏幕的可变信息。也可以用FBIOPUT_VSCREENINFO来调整这些参数,但很少用到。
对于固定参数fb_fix_screeninfo,APP编程中很少用到,定义如下:
#include <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 */
};
可以用ioctl FBIOGET_FSCREENINFO读出这些信息,但很少用到。
3.mmap映射Framebuffer
通过mmap调用,来映射一块内存。需要知道它的地址,由驱动程序来设置;需要知道它的大小(占用多少byte),由APP设置。
Framebuffer占用多少byte内存(screen_size),取决于LCD分辨率(xres * yres)+ 像素深度(bpp)。
line_width = var.xres * var.bits_per_pixel / 8; // 一行多少byte
pixel_width = var.bits_per_pixel / 8; // 一个像素多少byte
screen_size = var.xres * var.yres * var.bits_per_pixel / 8; // 一屏多少byte
fb_base = (unsigned char*)mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char*)MAP_FAILED) {
printf("Can't mmap\n");
return -1;
}
screen_size是整个Framebuffer大小(bytes);PROT_READ | PROT_WRITE表示该区域可读、可写;MAP_SHARED表示该区域共享的,APP写入数据时,会直达驱动程序。
4.定义描点函数
所谓描点函数,就是根据指定lcd坐标(x, y),修改Framebuffer对应内存的值,从而修改颜色值。
能在LCD上描绘指定像素后,就可以写字、画图,描点函数是基础。
void lcd_put_pixel(int x, int y, unsiged int color)
{
unsigned char *pen_8 = fb_base + y * line_width + x * pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch(var.bits_per_pixel) { /* bpp 像素深度 */
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* RGB565 */
red = (color >> 16) & 0xFF;
green = (color >> 8) & 0xFF;
blue = (color >> 0) & 0xFF;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
/* RGB888 */
*pen_32 = color;
break;
}
default:
{
printf("Can't support %d bpp\n", var.bits_per_pixel);
break;
}
}
}
传入的color表示颜色,格式是0x00RRGGBB,即RGB888。当LCD是16bpp时,要把color变量中的R、G、B抽出来,再合并成RGB565格式。
pen_8的计算公式,实际上也是计算(x, y)坐标像素对应的Framebuffer地址。
pen_8 = fb_base + y * line_width + x * pixel_width
对于8bpp,color不再表示RGB三原色,这涉及到调色板的概念,color是调色板的值。
将RGB888转换为RGB565时,要从color变量中把R、G、B抽取出来,然后以RGB565格式合成新的color值。
通过赋值语句*pen_16 = color ,将16bit颜色值写入Framebuffer。
对于32bpp,颜色格式跟color参数一致,可以直接写入Framebuffer。
5. 示例:lcd_put_pixel画点
main函数中,在最后简单画几个点:
/* 清屏: 全部设为白色 */
memset(fbmem, 0xFF, screen_size);
/* 设置100个像素为红色 */
for (i = 0; i < 100; i++) {
lcd_put_pixel(var.xres / 2 + i, var.yres / 2, 0xFF0000);
}
完整程序:show_pixel.c | gitee
参考
嵌入式Linux应用开发完全手册V4.0_韦东山全系列视频文档-IMX6ULL开发板, 韦东山.