ARM-驱动-09-LCD FrameBuffer
2026/4/16 21:18:41 网站建设 项目流程

一、核心概念:Frame Buffer 是什么

裸机 vs Linux 的显示方式

方式做法
裸机直接向 LCD 控制器物理地址(如0x56000000)写像素数据
Linux通过/dev/fb0设备,用mmap映射显存到用户空间,再写虚拟地址

Linux 的内存保护机制禁止应用层直接访问物理地址,Frame Buffer 就是内核提供的合法通道。

mmap 的本质

open("/dev/fb0") ↓ mmap() → 用户空间拿到虚拟地址指针 p_mem ↓ 写 p_mem → 内核页表 → 真实显存物理地址 → LCD 自动刷新

mmap之后,写内存 = 写屏幕,不需要任何驱动调用,效率极高。


二、初始化流程(三步固定写法)

代码(来自 pro1/framebuffer.c 的 fb_init)

intfb_init(void){// 第一步:打开设备intfd_fb=open("/dev/fb0",O_RDWR);// 第二步:获取屏幕参数ioctl(fd_fb,FBIOGET_VSCREENINFO,&info);// 第三步:映射显存intlen=info.xres_virtual*info.yres_virtual*info.bits_per_pixel/8;pfb=(unsignedchar*)mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd_fb,0);}

关键参数说明(fb_var_screeninfo 结构体)

字段含义例子
xres/yres屏幕实际可见分辨率800 / 600
xres_virtual/yres_virtual显存虚拟分辨率(通常更大)1024 / 600
bits_per_pixel每个像素占多少位32

为什么要用xres_virtual而不是xres
显存实际宽度是xres_virtual,不是屏幕可见宽度xres。用错了偏移计算就会错位。
两个地方必须用xres_virtual:① mmap 大小计算;② 像素偏移计算。

退出时释放

voidfb_deinit(void){munmap(pfb,len);close(fd_fb);}

三、画点——所有绘图的基础

代码(pro1/framebuffer.c)

voiddraw_point(unsignedshortx,unsignedshorty,unsignedintcol){if((x>info.xres_virtual)||(y>info.yres_virtual))return;// 越界保护unsignedint*p=(unsignedint*)(pfb+(y*info.xres_virtual+x)*info.bits_per_pixel/8);*p=col;}

偏移公式推导

显存是一维内存,按行存储: 第 0 行:像素(0,0) (1,0) (2,0) ... (xres_virtual-1, 0) 第 1 行:像素(0,1) (1,1) ... 像素(x, y) 的字节偏移 = (y × xres_virtual + x) × (bits_per_pixel / 8) ↑ 把位转成字节,32位色 = 4字节

颜色格式(32位 ARGB)

0xFF0000// 红色0x00FF00// 绿色0x0000FF// 蓝色0xFFFFFF// 白色0x000000// 黑色0xFFFF00// 黄色

四、想换背景色怎么做

用 lcd_fill 填充整个屏幕

// 把屏幕全部填成黑色(用来清屏)lcd_fill(0,0,799,599,0x000000);// 把屏幕全部填成白色lcd_fill(0,0,799,599,0xFFFFFF);// 函数原型(来自 framebuffer.h)voidlcd_fill(unsignedshortx0,unsignedshorty0,unsignedshortx1,unsignedshorty1,unsignedintcolor);

用 draw_bmp 显示图片背景(来自 mouse.c)

// 把 bg.bmp 从 (0,0) 开始铺满屏幕draw_bmp(0,0,"./bg.bmp");

典型用法:切换背景时先清屏再画

// main.c 里的写法lcd_fill(0,0,799,599,0xffff);// 先清屏lcd_show_string(100,200,200,50,32,"hello",0xffffff);// 再显示文字sleep(3);lcd_fill(0,0,799,599,0xffff);// 清屏(换背景)lcd_show_string(100,200,200,50,32,"abcde",0xffffff);// 显示新内容

五、想显示字符串怎么做

函数原型(framebuffer.h)

voidlcd_show_string(unsignedshortx,// 起始 X 坐标unsignedshorty,// 起始 Y 坐标unsignedshortwidth,// 显示区域宽度(超出自动换行)unsignedshortheight,// 显示区域高度(超出截断)unsignedcharsize,// 字体大小:12/16/24/32char*p,// 字符串内容unsignedintcol);// 字体颜色

示例

// 在 (100, 100) 位置显示 "hello",字体 16,白色lcd_show_string(100,100,200,50,16,"hello",0xffffff);// 在 (100, 200) 位置显示 "world",字体 32,红色lcd_show_string(100,200,200,50,32,"world",0xff0000);

字体大小对应像素

size字宽字高适用场景
126px12px小字,密集信息
168px16px普通正文
2412px24px标题
3216px32px大标题

注意:字符堆叠问题

连续显示不同字符串时,如果不清屏,新字符串会和旧字符串叠在一起。原因是lcd_showchar里只画"有笔画"的点,不画背景。

解决方法:显示前先用lcd_fill清屏(或用copy_mem局部恢复背景,见第七节)。


六、想显示图片怎么做

版本一:简单版(fb/fb.c,硬编码尺寸 120×120)

intdraw_bmp(intx0,inty0,constchar*bmp_name){intfd=open(bmp_name,O_RDWR);unsignedcharhead[54]={0};read(fd,head,sizeof(head));// 跳过 54 字节 BMP 文件头for(j=0;j<120;j++){for(i=0;i<120;i++){unsignedcharc[3]={0};read(fd,c,sizeof(c));// BMP 存储顺序是 BGR,转换为 RGBunsignedintcol=(c[2]<<16)|(c[1]<<8)|c[0];draw_point(i+x0,120-j-1+y0,col);// BMP 是倒序,需要翻转}}}// 调用:把图片显示在 (679, 0)draw_bmp(679,0,"./123.bmp");

版本二:通用版(mouse.c,自动读取宽高)

// 解析 BMP 文件头,自动获取图片宽高BitMapFileHeader file_head;BitMapInfoHeader info_head;read(fd,&file_head,sizeoffile_head);read(fd,&info_head,sizeofinfo_head);// info_head.biWidth → 图片宽度// info_head.biHeight → 图片高度(正数 = 倒序存储)

BMP 文件头结构

// 文件头 14 字节typedefstruct{unsignedcharbfType[2];// "BM" 标识unsignedintbfSize;// 文件总大小unsignedshortbfReserved1;// 保留,必须为 0unsignedshortbfReserved2;// 保留,必须为 0unsignedintbfOffBits;// 像素数据相对文件头的偏移量}BitMapFileHeader;// 共 14 字节(需 #pragma pack(1))// 信息头 40 字节typedefstruct{unsignedintbiSize;// 信息头大小(40)intbiWidth;// 图片宽度(像素)intbiHeight;// 图片高度,正数=倒序存储,负数=正序unsignedshortbiPlanes;// 位面数,恒为 1unsignedshortbiBitCount;// 每像素位数:24=RGB,32=ARGBunsignedintbiCompression;// 压缩类型:0=不压缩...}BitMapInfoHeader;

为什么 BMP 是上下颠倒的?
BMP 标准规定图像数据从底部向上存储(最后一行数据在文件最前面),所以显示时需要height - j - 1翻转 y 坐标。


七、想显示鼠标并让鼠标移动怎么做

核心思路:移动前先恢复背景

鼠标移动 = 在新位置画鼠标图片,但旧位置的鼠标覆盖了背景,需要先恢复。

每次移动: ① copy_mem(旧x, 旧y, 16, 16) ← 从 p_save 恢复旧位置的背景 ② draw_bmp(新x, 新y, mouse.bmp) ← 在新位置画鼠标

save_fb 和 copy_mem(来自 mouse.c)

// save_fb:把当前整个显存复制到 p_save(程序开始时调用一次)intsave_fb(){unsignedint*pdst=(unsignedint*)p_save;unsignedint*psrc=(unsignedint*)p_mem;for(j=0;j<info.yres_virtual;j++)for(i=0;i<info.xres_virtual;i++)*pdst++=*psrc++;}// copy_mem:从 p_save 把指定矩形区域恢复到 p_mem(每次移动前调用)intcopy_mem(intx0,inty0,intw,inth){for(j=y0;j<y0+h;j++)for(i=x0;i<x0+w;i++)*(pdst+j*xres_virtual+i)=*(psrc+j*xres_virtual+i);}

内存分配(fb_init 里额外 malloc)

// p_save 是额外分配的内存,和显存等大,用来保存背景快照p_save=malloc(info.xres_virtual*info.yres_virtual*info.bits_per_pixel/8);

读取鼠标坐标(来自 mouse.c,使用绝对坐标设备)

intfd=open("/dev/input/event2",O_RDWR);// 触摸屏/绝对鼠标structinput_eventevent;read(fd,&event,sizeofevent);if(event.type==EV_ABS){if(event.code==ABS_X)x=event.value/65535.0*800;// 原始值 0~65535 → 屏幕 0~800elseif(event.code==ABS_Y)y=event.value/65535.0*600;// 原始值 0~65535 → 屏幕 0~600}

读取相对鼠标(来自 fb/main.c,使用 /dev/input/mice)

intfd=open("/dev/input/mice",O_RDWR);chardata[3]={0};read(fd,data,3);// data[0]:按键状态(9=左键按下,10=右键按下,8=左键松开)// data[1]:X 轴位移(有符号,负=向左)// data[2]:Y 轴位移(有符号,负=向上)x+=data[1];// 累加位移y+=data[2];if(x<0)x=0;// 边界保护if(x>799)x=799;

注意:save_fb 的局限性

save_fb只保存调用那一刻的画面。如果之后又画了字符串,再copy_mem会把字符串也擦掉,因为快照里没有这些字符串。全屏重绘(清屏 + 重画所有内容)是最简单但效率最低的解决方案。


八、基本图形绘制(pro1/framebuffer.c)

函数速查表

函数用途参数
lcd_fill(x0,y0,x1,y1,col)填充矩形区域左上角、右下角坐标、颜色
lcd_drawline(x1,y1,x2,y2,col)画直线起点、终点坐标、颜色
lcd_draw_rectangle(x1,y1,x2,y2,col)画矩形边框左上角、右下角坐标、颜色
lcd_draw_Circle(x0,y0,r,col)画圆形边框圆心坐标、半径、颜色
lcd_showchar(x,y,ch,size,mode,col)显示单个字符坐标、字符、大小、叠加模式、颜色
lcd_show_string(x,y,w,h,size,str,col)显示字符串坐标、区域宽高、大小、内容、颜色
lcd_shownum(x,y,num,len,size,col)显示整数(高位0不显示)坐标、数值、位数、大小、颜色
lcd_showxnum(x,y,num,len,size,mode,col)显示整数(可控制高位0)同上+模式

lcd_showchar 的 mode 参数

mode=0;// 非叠加:背景色为黑色(字符区域黑底)mode=1;// 叠加:背景透明,只画有笔画的点,不覆盖底色

九、三色条纹测试(最简单的全屏渲染,验证 FB 是否工作)

来自 pro1/main.c 的 DEBUG 版本,验证 Frame Buffer 初始化是否正常:

for(j=0;j<info.yres;j++){for(i=0;i<info.xres;i++){if(j<info.yres/3)draw_point(i,j,0xff);// 上 1/3:蓝色elseif(j<info.yres*2/3)draw_point(i,j,0xff00);// 中 1/3:绿色elsedraw_point(i,j,0xff0000);// 下 1/3:红色}}

十、编译运行

# PC 上测试(需要 root 权限,Ctrl+Alt+F2 切到纯文本终端)gcc fb.c-ofb_testsudo./fb_test# 交叉编译(开发板运行)arm-linux-gnueabihf-gcc fb.c-ofb_test# 传到开发板,直接运行(开发板不需要切终端)./fb_test# pro1 多文件编译arm-linux-gnueabihf-gcc main.c framebuffer.c-omain

PC 运行注意:图形界面(X11/Wayland)独占显存,必须先Ctrl+Alt+F2切换到 TTY 纯文本终端后再运行,否则没有效果或权限报错。


十一、常见问题

问题原因解决
运行没有任何显示在图形界面下运行Ctrl+Alt+F2切换到 TTY
颜色显示不对bpp 不是 32,颜色格式不同ioctl查看bits_per_pixel,RGB565 需要位操作转换
图片显示上下颠倒忘记翻转 y 坐标height - j - 1 + y0代替j + y0
图片颜色偏BMP 是 BGR 存储,直接用成了 RGB用 `(c[2]<<16)
字符显示堆叠没清屏就切换内容显示前先调用lcd_fill清屏
鼠标移动有残影没有恢复旧位置背景移动前先调用copy_mem恢复背景

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询