FrameBuffer帧缓冲区驱动详解(操作流程,自定义函数接口等) 目录一. FrameBuffer帧缓冲区1.1 什么是FrameBuffer帧缓冲区1.2 FrameBuffer的一般操作流程1.2.1 打开设备1.2.2 获取设备的各种信息1、分辨率2、 Linux中屏幕的显示坐标信息3、 色深BPP(Bits Per Pixel)4、显存5、ioctl函数1可视尺寸 800×6002虚拟显存远大于可视1176 × 885332bits per pixel1.2.3 建立显存与用户空间内存的映射关系1.2.4 解除映射关闭文件1.3 自定义FrameBuffer操作函数1.3.1 画点函数接口1.3.2 清屏函数接口1.3.3 画线函数接口1.3.4 画矩形函数接口1.3.5 画圆函数接口1.3.6 根据字模显示文字函数接口1.3.7 根据字库中保存的字模显示自定义文本函数接口二. 字模2.1 什么是字模2.2 字模的生成与显示2.2.1 字模的生成2.2.2 字模显示代码三. 字库3.1 什么是字库3.2 字模与字库大小关系3.3 字库的生成与显示3.3.1 字库的生成3.3.2 字库的显示四. bmp图片的显示4.1 什么是bmp图片4.2 显示函数接口一. FrameBuffer帧缓冲区1.1 什么是FrameBuffer帧缓冲区Linux FrameBuffer帧缓冲区是内核提供的标准化底层图形抽象接口它将屏幕硬件的物理显存封装为统一字符设备/dev/fb*抹平不同 LCD、OLED、显卡之间的硬件差异。开发者可通过内存映射直接读写显存内存遵循行优先像素排布规则完成画面绘制无需操作硬件寄存器。它是嵌入式无桌面设备屏幕开发的核心基础点阵汉字渲染、开机画面、简易 GUI 等场景均基于它实现FrameBuffer 接口简单轻量化资源占用极低广泛运用于工业显示屏、小型嵌入式终端。1.2 FrameBuffer的一般操作流程1.2.1 打开设备在linux中framebuffer对应的设备文件在/dev/fb0故可以使用文件IO的操作来直接打开它/打开显示设备 int fd open(/dev/fb0, O_RDWR); /以读写的方式打开 if (-1 fd) { perror(open fb error); return -1; }1.2.2 获取设备的各种信息1、分辨率分辨率本质就是屏幕横向、纵向一共有多少个像素点宽 水平一行有多少个像素点x 轴高 垂直一共有多少行像素点y 轴总像素数量 宽度 × 高度2、 Linux中屏幕的显示坐标信息坐标左上角是(0, 0)点x轴往右y轴往下3、 色深BPP(Bits Per Pixel)色深(BPP,Bits Per Pixel)每个像素占用多少个二进制比特位一个像素点需要几个二进制位来描述常用的两个BPP标准BPP标准简介RGB5651 个像素共 16 位拆分5bit红(R) 6bit绿(G) 5bit蓝(B)二进制排布R R R R R G G G G G G B B B B B单像素仅占2 字节显存占用小例480×272 屏幕 总显存 480 × 272 × 2 261120 字节色彩总数32 × 64 × 32 65536 种颜色俗称 65 万色RGB8881 个像素共 24 位拆分8bit红 8bit绿 8bit蓝二进制排布连续 3 字节依次存放 R、G、B。单像素占3 字节显存开销更大例 480×272 屏幕总显存 480 × 272 × 3 391680 字节色彩总数256×256×256 ≈ 1670 万种彩色4、显存显存显存全称显示存储器Video RAMVRAM是一块独立的高速内存专门用来完整存放当前屏幕一整帧画面的全部像素数据总显存容量 横向像素 × 纵向像素 × BPP ÷ 8eg1920×1080 RGB88824bpp高清屏的显存容量 1920×1080×24/8 ≈ 6MB5、ioctl函数想要获取以上信息可以通过函数ioctl来进行获取作用ioctl 是设备驱动程序中设备控制接口函数一个字符设备驱动通常会实现设备打开、关闭、读、写等功能在一些需要细分的情境下如果需要扩展新的功能通常以增设 ioctl() 命令的方式实现。参数fd打开的设备文件描述符/dev/fb0、串口、触摸屏、摄像头、声卡等request命令码内核预定义宏告诉内核要执行什么操作可变参数...可选传递 / 接收参数结构体、数值、缓冲区指针等返回值成功0 失败-1且errno示例代码此处使用FBIOGET_VSCREENINFO这个宏来获取FB的一些参数分辨率BPP虚拟分辨率等fb_var_screeninfo结构体该结构体来自内核头文件linux/fb.h全称可变屏幕信息存储屏幕可动态修改的配置参数通过ioctl(fd, FBIOGET_VSCREENINFO, vinfo)读取FBIOPUT_VSCREENINFO写入修改。struct fb_var_screeninfo vinf; /获取显示设备相关参数 分辨率 位深度 int ret ioctl(fd, FBIOGET_VSCREENINFO, vinf); if (-1 ret) { perror(fail ioctl); return -1; } printf(xres %d, yres %d\n, vinf.xres, vinf.yres);/屏幕物理可视大小 printf(xres_virtual %d, yres_virtual %d\n, vinf.xres_virtual, vinf.yres_virtual);/屏幕显存真实大小 printf(bits_per_pixel : %d\n, vinf.bits_per_pixel);/屏幕色深结果1可视尺寸 800×600屏幕硬件真实可视区域横向 800 像素、纵向 600 像素2虚拟显存远大于可视1176 × 885xres_virtual1176 800横向超长虚拟画布显存每行分配 1176 个像素单元比屏幕可见宽度多出一截用于画面左右滚动滑动 UI、长页面拖拽yres_virtual885 600纵向显存高度多出 285 行不是标准上下双缓冲标准双缓冲 yvirtual 应该≈2*6001200 这个 885 高度是显卡 / 虚拟机分配的冗余显存行预留滚动、桌面 UI 缓冲空间无法完整存第二张 800×600 画面不支持标准双缓冲寻址必须用xres_virtual计算行偏移 任意像素地址公式y * 1176 x如果误用 800 做行宽所有画面会严重错位、花屏。332bits per pixel32 位色深每个像素占用 4 字节内存格式 RGBA8888红 / 绿 / 蓝 / 透明通道各 8bit1.2.3 建立显存与用户空间内存的映射关系使用函数mmap即可完成内存映射在这篇文章中对mmap做了详细的介绍mmap函数详解部分代码示例这里(vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel / 8)计算的就是总的显存大小/8是因为mmap函数第二个参数单位是字节size_t len vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel / 8; //建立显存和用户空间的映射关系 pmem mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if ((void *)-1 pmem) { perror(fail mmap); return -1; }1.2.4 解除映射关闭文件函数munmap详解munmap函数详解部分代码示例void uninit_fb(int fd) { size_t len vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel / 8; munmap(pmem, len); close(fd); }1.3 自定义FrameBuffer操作函数1.3.1 画点函数接口注意屏幕看似是一个二维但在内存中还是一维存储就行一维数组一样所有也就有了这样的坐标与内存位置对应的计算公式p y * vinf.xres_virtual xp是映射内存的起始地址代码中的pmem是一个全局变量注意以下函数接口中的unsigned int col这个参数是四字节除了三字节的RGB888还有最高一字节的Alpha 通道void draw_point(int x, int y, unsigned int col) { if (x vinf.xres || y vinf.yres)/判断坐标是否合理 { return; } if (vinf.bits_per_pixel RGB888_FMT)/判断屏幕使用哪种色深 { unsigned int *p pmem; *(p y * vinf.xres_virtual x) col; } else if (vinf.bits_per_pixel RGB565_FMT) { unsigned short *p pmem; *(p y * vinf.xres_virtual x) col; } return; }1.3.2 清屏函数接口void draw_clear(unsigned int col) { for (int j 0; j vinf.yres_virtual; j) { for (int i 0; i vinf.xres_virtual; i) { draw_point(i, j, col); } } }1.3.3 画线函数接口/起始位置(x,y)画一条横线长len void draw_h_line(int x, int y, int len, unsigned int col) { for (int i x; i x len; i) { draw_point(i, y, col); } } /起始位置(x,y)画一条竖线长len void draw_s_line(int x, int y, int len, unsigned int col) { for (int i y; i y len; i) { draw_point(x, i, col); } }/画斜线 void draw_x_line(int x1, int y1, int x2, int y2, unsigned int col) { int x 0; int y 0; /竖线情况由于竖线斜率无穷大要先行判断 if (x1 x2) { if (y2 y1) { draw_s_line(x1, y1, y2 - y1, col); } else { draw_s_line(x2, y2, y1 - y2, col); } } /计算斜率 double k (double)(y2 - y1) / (double)(x2 - x1); /计算b的值 double b y1 - k * x1; /循环计算从x坐标小的点到x坐标大的点途中的每一个点与其y坐标然后画点 for (int x (x1 x2 ? x2 : x1); x (x1 x2 ? x1 : x2); x) { y x * k b; draw_point(x, y, col); } return; }1.3.4 画矩形函数接口/(x,y)为矩形的左上坐标 (xw,yh)为矩形的右下坐标 /以点(x,y)为起始点画一个宽w高h的矩形 void draw_rectangle(int x, int y, int w, int h, unsigned int col) { draw_h_line(x, y, w, col); draw_s_line(x, y, h, col); draw_s_line(x w, y, h, col); draw_h_line(x, y h, w, col); }1.3.5 画圆函数接口注意在c语言中sin/cos函数传进去为弧度要转为角度角度转弧度 (π/180)×角度弧度变角度 (180/π)×弧度画圆抗锯齿算法画法一次画五个点计算出来的点再加上他的上下左右四个点这样画出来的圆更加平滑void draw_circle(int x0, int y0, int r, unsigned int col) { int x 0; int y 0; for (double si 0; si 360; si 0.01) { x r * cos(2 * 3.14159 / 360 * si) x0; y r * sin(2 * 3.14159 / 360 * si) y0; draw_point(x, y, col); draw_point(x - 1, y, col); draw_point(x 1, y, col); draw_point(x, y - 1, col); draw_point(x, y 1, col); } }1.3.6 根据字模显示文字函数接口注意在FB中图像操作数据类型都是unsigned char因为有符号时左移右移会有影响unsigned char pu[24 * 24 / 8] { /*-- 文字: 普 --*/ /*-- 仿宋18; 此字体下对应的点阵为宽x高24x24 --*/ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0xC3, 0x80, 0x00, 0xE3, 0x00, 0x00, 0x77, 0x00, 0x00, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x00, 0x7E, 0xE0, 0x06, 0x7E, 0xE0, 0x03, 0x7F, 0xC0, 0x03, 0xFF, 0x80, 0x01, 0x7F, 0xFC, 0x7F, 0xFF, 0xE0, 0x3E, 0x00, 0x80, 0x03, 0xFF, 0xC0, 0x03, 0xFF, 0xC0, 0x03, 0x81, 0xC0, 0x03, 0xFF, 0xC0, 0x01, 0xFF, 0x80, 0x01, 0x81, 0x80, 0x01, 0xFF, 0x80, 0x03, 0xFF, 0x80, 0x01, 0x81, 0x80, 0x00, 0x00, 0x00}; /在屏幕上显示文字 void draw_word(int x, int y, unsigned char *word, int w, int h, unsigned int col) { for (int j 0; j h; j) { for (int i 0; i w; i) { unsigned char tmp word[i j * w];/将一维数组当作二维数组看 for (int k 0; k 8; k) { if (tmp 0x80)/从最高位判断每一个像素点是否要进行显示 { draw_point(i * 8 k x, j y, col);/从左往右从上往下生成 } else { /文字的背景色 } tmp tmp 1; } } } }以24x24字模大小为例画图解释1.3.7 根据字库中保存的字模显示自定义文本函数接口int draw_utf8(UTF8_INFO *info, int x, int y, char *zi, unsigned int col, unsigned int col1) { unsigned long out 0; int ret enc_utf8_to_unicode_one((unsigned char *)zi, out); unsigned char *data get_utf_data(info, out); unsigned char temp 0; unsigned int i, j, k; unsigned int num 0; for (i 0; i info-height; i) { for (j 0; j info-width / 8; j) { temp data[num]; for (k 0; k 8; k) { if (0x80 temp) { draw_point(x k j * 8, y i, col); } else { // draw_point( xkj*8, yi, col1); } temp temp 1; } } } return ret; } int draw_utf8_str(UTF8_INFO *info, int arg_x, int arg_y, char *zi, unsigned int col, unsigned int col1) { char *temp zi; unsigned int x arg_x; unsigned int y arg_y; while (*temp ! \0) { int ret draw_utf8(info, x, y, temp, col, col1); x info-width;//加字模的宽度 if (x vinf.xres) { x 0; y info-height; if (y vinf.yres) { y 0; } } temp ret; } return 0; }二. 字模2.1 什么是字模字模就是文字的像素模板把汉字/字母用固定尺寸的黑白点阵表示用二进制 bit记录每个像素亮/灭存成数组就是字模数据在单片机、液晶显示或LED显示系统中字模是将字符或汉字转换为点阵形式的二进制编码。每个字符由一组像素点组成每个点对应一个二进制位1表示点亮0表示不亮。例如24×24点阵的汉字“普”需要72个字节来表示其字模2.2 字模的生成与显示2.2.1 字模的生成例如使用字模生成器生成一个字的字模/*-- 文字: 雪 --*/ /*-- 宋体26; 此字体下对应的点阵为宽x高35x35 --*/ /*-- 宽度不是8的倍数现调整为宽度x高度40x35 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00, 0x00,0x00,0x78,0x00,0x07,0xFF,0xFF,0xFC,0x00,0x03,0x00,0xE0,0x00,0x00,0x0C,0x00, 0xE0,0x00,0x00,0x0C,0x00,0xE0,0x07,0x00,0x0F,0xFF,0xFF,0xFF,0x80,0x1C,0x00,0xE0, 0x0F,0x80,0x1C,0x00,0xE0,0x0E,0x00,0x3D,0xFE,0xEF,0xFC,0x00,0x3C,0x00,0xE0,0x00,2.2.2 字模显示代码unsigned char xue[35 * 40 / 8] { /*-- 文字: 雪 --*/ /*-- 宋体26; 此字体下对应的点阵为宽x高35x35 --*/ /*-- 宽度不是8的倍数现调整为宽度x高度40x35 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00, 0x00,0x00,0x78,0x00,0x07,0xFF,0xFF,0xFC,0x00,0x03,0x00,0xE0,0x00,0x00,0x0C,0x00, 0xE0,0x00,0x00,0x0C,0x00,0xE0,0x07,0x00,0x0F,0xFF,0xFF,0xFF,0x80,0x1C,0x00,0xE0, 0x0F,0x80,0x1C,0x00,0xE0,0x0E,0x00,0x3D,0xFE,0xEF,0xFC,0x00,0x3C,0x00,0xE0,0x00, 0x00,0x10,0x01,0xE0,0x00,0x00,0x00,0x01,0xE0,0x00,0x00,0x03,0xFF,0xEF,0xF0,0x00, 0x00,0x01,0xE0,0x00,0x00,0x00,0x01,0xE0,0x00,0x00,0x00,0x01,0x80,0x70,0x00,0x07, 0xFF,0xFF,0xF8,0x00,0x03,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x70,0x00,0x00,0x00, 0x00,0x70,0x00,0x00,0x00,0x00,0x70,0x00,0x03,0xFF,0xFF,0xF0,0x00,0x01,0x80,0x00, 0x70,0x00,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x70, 0x00,0x00,0x00,0x00,0x70,0x00,0x0F,0xFF,0xFF,0xF0,0x00,0x07,0x00,0x00,0x70,0x00, 0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; /在屏幕上显示文字 /h代表行数单位是bitw代表列数但是单位是字节byte传参示例40/8 void draw_word(int x, int y, unsigned char *word, int w, int h, unsigned int col) { for (int j 0; j h; j) { for (int i 0; i w; i) { unsigned char tmp word[i j * w];/将一维数组当作二维数组看 /每次取一字节对这一字节的每一位进行判断 for (int k 0; k 8; k) { if (tmp 0x80)//循环判断最高位是否需要显示 { /这是显示函数从左往右从上往下显示 /j y表示在第几行进行显示 /i * 8 k x表示在第几列进行显示 draw_point(i * 8 k x, j y, col); } else { /文字的背景色 } tmp tmp 1; } } } }三. 字库3.1 什么是字库字库把大量字符的字模集合打包在一起统一存放在文件里就是字库3.2 字模与字库大小关系字库就是所有常见文字字模的集合3.3 字库的生成与显示3.3.1 字库的生成使用点阵字库生成器生成3.3.2 字库的显示字库文件缓冲结构体/***字模文件缓存**/ typedef struct { char path[256];/字模库文件路径 unsigned width; /字模宽度 unsigned height;/字模高度 unsigned zimo_size;/每个字模字节数 unsigned char* g_ziku_data;/字模库文件缓存区 /堆内存指针用来存放整个字库文件全部二进制数据 }UTF8_INFO;函数init_utf8作用该函数是UTF8 点阵字库的初始化加载函数打开磁盘上存储点阵字库的二进制文件读取整个字库文件全部数据到堆内存计算单个字模占用字节大小把字库数据缓存到UTF8_INFO结构体中/***************************************************************************** * 函数名init_utf8 * 将字库文件读取到缓存区 * * 参数: * info 缓存区数据结构 * * 返回值: * 无 * * 注意事项 * 无 * ****************************************************************************/ void init_utf8(UTF8_INFO *info) { int ret 0 ; int fd open(info-path,O_RDONLY); if(-1 fd) { exit(1); } struct stat st; ret stat(info-path,st);/不打开文件仅读取文件/目录的元信息属性信息 if(-1 ret) { printf(get zi ku file size error); exit(1); } if(NULL info-g_ziku_data) { info-g_ziku_data malloc(st.st_size);/开堆空间读取字库文件 } ret read(fd,info-g_ziku_data,st.st_size); if(ret0) { printf(read utf-8 info error!); exit(1); } // info-height heigh; // info-width width; info-zimo_size st.st_size /65536;/求单个字模大小单位字节 /总文件大小除65536 close(fd); }四. bmp图片的显示4.1 什么是bmp图片BMPBitmap位图文件是原生无压缩点阵位图图片格式后缀.bmp。核心特点不压缩、完整存储每个像素原始 RGB 数据文件体积大但解析逻辑极简非常适合嵌入式 / Linux framebuffer 直接读取显示。4.2 显示函数接口注意bmp文件有54字节的文件头bmp header记录一般的属性大小色深等等在显示时需跳过屏幕的色深是RGB格式而bmp图片的色深是BGR格式注意转换一次性 malloc 缓存整张图片像素是用少量内存开销换取极少磁盘 IO极快内存遍历速度bmp的像素存储规则遵循“底部优先”Bottom-Up的准则即BMP 图片像素数据是上下颠倒存储的和屏幕绘图顺序相反//宽w 高h void draw_bmp(int x, int y, char *picname, int w, int h) { int fd open(picname, O_RDONLY);//打开图片文件 if (-1 fd) { perror(fail open bmp); return; } lseek(fd, 54, SEEK_SET);//指定偏移字节跳过头部 unsigned char r, g, b;//注意数据类型是无符号char unsigned char *buff malloc(w * h * 3); read(fd, buff, w * h * 3); unsigned char *p buff; for (int j h - 1; j 0; j--)//从下往上画buff存照片是从下往上存的 //for (int j 0; j h; j) { for (int i 0; i w; i) { b *p; p;//一次跳过一个字节bmp图片是BGR存储而屏幕是RGB显示故要进行转换 g *p; p; r *p; p; if (vinf.bits_per_pixel RGB888_FMT)//判断当前屏幕的色深 { unsigned int col (r 16) | (g 8) | (b 0);//移位时发生整形提升 draw_point(i x, j y, col); } else if (vinf.bits_per_pixel RGB565_FMT) { unsigned short col ((r 3) 11) | ((g 2) 5) | ((b 3) 0); draw_point(i x, j y, col); } } } free(buff); close(fd); }