17. 屏幕显示(framebuffer)¶
17.1. LubanCat-sg200x系列板卡¶
该教程适用于LubanCat-sg200x系列板卡, 可以接入野火mipi屏进行使用。
本章的示例代码目录为:base_linux/screen/framebuffer
17.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|
|
17.3. framebuffer应用程序¶
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | #include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <errno.h>
#define ARGB8888
#define PAUSE() \
do { \
printf("---------------press Enter key to continue!---------------\n"); \
getchar(); \
} while (0)
#if defined(ARGB8888) // 32bits
#define RED 0xFFFF0000
#define GREEN 0xFF00FF00
#define BLUE 0xFF0000FF
#define YELLOW 0xFFFFFF00
#define WHITE 0xFFFFFFFF
#define BLACK 0xFF000000
void fill_color(uint32_t *fb_addr, uint32_t bit_map, int psize)
{
int i;
int pix_count = psize / 4;
for(i=0; i<pix_count; i++) {
*fb_addr = bit_map;
fb_addr++;
}
}
#else // 16bits
#define RED 0xFC00
#define GREEN 0x83E0
#define BLUE 0x801F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define BLACK 0x8000
void fill_color(short *fb_addr, short bit_map, int psize)
{
int i;
for(i=0; i<psize; i++) {
*fb_addr = bit_map;
fb_addr++;
}
}
#endif
void _fb_get_info(int fp, struct fb_fix_screeninfo *finfo, struct fb_var_screeninfo *vinfo)
{
long screensize=0;
if(ioctl(fp, FBIOGET_FSCREENINFO, finfo)){
printf("Error reading fixed information/n");
exit(2);
}
if(ioctl(fp, FBIOGET_VSCREENINFO, vinfo)){
printf("Error reading variable information/n");
exit(3);
}
screensize = finfo->line_length * vinfo->yres;
printf("The ID=%s\n", finfo->id);
printf("The phy mem = 0x%x, total size = %d(byte)\n", finfo->smem_start, finfo->smem_len);
printf("line length = %d(byte)\n", finfo->line_length);
printf("xres = %d, yres = %d, bits_per_pixel = %d\n", vinfo->xres, vinfo->yres, vinfo->bits_per_pixel);
printf("xresv = %d, yresv = %d\n", vinfo->xres_virtual, vinfo->yres_virtual);
printf("vinfo.xoffset = %d, vinfo.yoffset = %d\n", vinfo->xoffset, vinfo->yoffset);
printf("vinfo.vmode is :%d\n", vinfo->vmode);
printf("finfo.ypanstep is :%d\n", finfo->ypanstep);
printf("vinfo.red.offset=0x%x\n", vinfo->red.offset);
printf("vinfo.red.length=0x%x\n", vinfo->red.length);
printf("vinfo.green.offset=0x%x\n", vinfo->green.offset);
printf("vinfo.green.length=0x%x\n", vinfo->green.length);
printf("vinfo.blue.offset=0x%x\n", vinfo->blue.offset);
printf("vinfo.blue.length=0x%x\n", vinfo->blue.length);
printf("vinfo.transp.offset=0x%x\n", vinfo->transp.offset);
printf("vinfo.transp.length=0x%x\n", vinfo->transp.length);
printf("Expected screensize = %d(byte), using %d frame\n", screensize, finfo->smem_len/screensize);
}
int main ()
{
int fp=0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
void *fbp = NULL;
uint32_t *test_fbp = NULL;
int x = 0, y = 0;
long location = 0;
int i;
int num = 1;
int pix_size=0;
fp = open("/dev/fb0", O_RDWR);
if(fp < 0) {
printf("Error : Can not open framebuffer device/n");
exit(1);
}
printf("-- Default fb info --\n");
_fb_get_info(fp, &finfo, &vinfo);
fbp = mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fp, 0);
if (fbp == MAP_FAILED)
{
printf ("Error: failed to map framebuffer device to memory.\n");
exit (4);
}
printf("Get virt mem = %p\n", fbp);
pix_size = finfo.line_length * vinfo.yres;
/* using first frame, for FBIOPAN_DISPLAY
* 当刷新需要调用FBIOPAN_DISPLAY, 要告知驱动刷哪块帧, 用到下面两个参数
* 如果使用第二帧buffer -> vinfo.xoffset = 0; vinfo.yoffset = vinfo.yres;
*/
vinfo.xoffset = 0;
vinfo.yoffset = 0;
/* show color loop */
while(num--) {
printf("\ndrawing YELLOW......\n");
vinfo.xoffset = 0;
vinfo.yoffset = 0;
fill_color(fbp, YELLOW, pix_size);
if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
printf("\nPAN_DISPLAY err(%d)\n", errno);
}
sleep(1);
printf("\ndrawing BLUE......\n");
vinfo.xoffset = 0;
vinfo.yoffset = 0;
fill_color(fbp, BLUE, pix_size);
if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
printf("\nPAN_DISPLAY err(%d)\n", errno);
}
sleep(1);
printf("\ndrawing RED......\n");
vinfo.xoffset = 0;
vinfo.yoffset = 0;
fill_color(fbp, RED, pix_size);
if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
printf("\nPAN_DISPLAY err(%d)\n", errno);
}
sleep(1);
PAUSE();
}
munmap(fbp, finfo.smem_len); /*解除映射*/
close (fp);
return 0;
}
|
编译运行
1 2 | gcc framebuffer.c -o framebuffer
./framebuffer
|
运行结果:依次黄、蓝、红依次刷新,按下回车后退出。
17.3.1. 代码分析¶
操作屏幕一共需要四步就可以操作整个屏幕
1 2 3 4 5 6 | fp = open("/dev/fb0", O_RDWR);
if(fp < 0) {
printf("Error : Can not open framebuffer device/n");
exit(1);
}
|
这一步是打开framebuffer设备,这一步成功后会返回文件描述符– fd, 我们后面可以操作这个文件描述符操作该设备。
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 | void _fb_get_info(int fp, struct fb_fix_screeninfo *finfo, struct fb_var_screeninfo *vinfo)
{
long screensize=0;
if(ioctl(fp, FBIOGET_FSCREENINFO, finfo)){
printf("Error reading fixed information/n");
exit(2);
}
if(ioctl(fp, FBIOGET_VSCREENINFO, vinfo)){
printf("Error reading variable information/n");
exit(3);
}
screensize = finfo->line_length * vinfo->yres;
printf("The ID=%s\n", finfo->id);
printf("The phy mem = 0x%x, total size = %d(byte)\n", finfo->smem_start, finfo->smem_len);
printf("line length = %d(byte)\n", finfo->line_length);
printf("xres = %d, yres = %d, bits_per_pixel = %d\n", vinfo->xres, vinfo->yres, vinfo->bits_per_pixel);
printf("xresv = %d, yresv = %d\n", vinfo->xres_virtual, vinfo->yres_virtual);
printf("vinfo.xoffset = %d, vinfo.yoffset = %d\n", vinfo->xoffset, vinfo->yoffset);
printf("vinfo.vmode is :%d\n", vinfo->vmode);
printf("finfo.ypanstep is :%d\n", finfo->ypanstep);
printf("vinfo.red.offset=0x%x\n", vinfo->red.offset);
printf("vinfo.red.length=0x%x\n", vinfo->red.length);
printf("vinfo.green.offset=0x%x\n", vinfo->green.offset);
printf("vinfo.green.length=0x%x\n", vinfo->green.length);
printf("vinfo.blue.offset=0x%x\n", vinfo->blue.offset);
printf("vinfo.blue.length=0x%x\n", vinfo->blue.length);
printf("vinfo.transp.offset=0x%x\n", vinfo->transp.offset);
printf("vinfo.transp.length=0x%x\n", vinfo->transp.length);
printf("Expected screensize = %d(byte), using %d frame\n", screensize, finfo->smem_len/screensize);
}
|
这一步获取屏幕的参数以及设置屏幕
获取屏幕的可变参数 ioctl(fp, FBIOGET_VSCREENINFO, vinfo)
获取屏幕的固定参数 ioctl(fp, FBIOGET_FSCREENINFO, finfo)
这两个目的是获取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 | fbp = mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fp, 0);
if (fbp == MAP_FAILED)
{
printf ("Error: failed to map framebuffer device to memory.\n");
exit (4);
}
printf("Get virt mem = %p\n", fbp);
|
这一步操作是把设备的内存映射到用户空间里, 用户只需要对着这块内存操作就可以改变屏幕的内容, 与传统的write函数相比, 数据传输的速度以及便捷性都得到了大幅的提高
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 获得
与之相对应的是 munmap(fbp, finfo.smem_len);
1 2 | #函数功能:释放内存空间
int munmap(void *addr, size_t length);
|
addr :mmap返回的地址
length :要回收的内存空间大小
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 | while(num--) {
printf("\ndrawing YELLOW......\n");
vinfo.xoffset = 0;
vinfo.yoffset = 0;
fill_color(fbp, YELLOW, pix_size);
if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
printf("\nPAN_DISPLAY err(%d)\n", errno);
}
sleep(1);
printf("\ndrawing BLUE......\n");
vinfo.xoffset = 0;
vinfo.yoffset = 0;
fill_color(fbp, BLUE, pix_size);
if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
printf("\nPAN_DISPLAY err(%d)\n", errno);
}
sleep(1);
printf("\ndrawing RED......\n");
vinfo.xoffset = 0;
vinfo.yoffset = 0;
fill_color(fbp, RED, pix_size);
if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
printf("\nPAN_DISPLAY err(%d)\n", errno);
}
sleep(1);
PAUSE();
}
|
该操作就可以直接通过操作framebuffer就可以控制屏幕了。 i对应的数值就是屏幕中像素点的位置