Framebuffer 字面意思就是帧缓存的意思,即显存,里面保存着一帧图像。事实上,对于嵌入式系统而言,没有真正意义上的显存,Framebuffer 是通过内存模拟出来的。
LCD FrameBuffer 里的若干字节表示(具体根据驱动适配),LCD 屏幕上的一个像素点。
假设LCD屏幕分辨率是800x600,每个像素占4字节,那么framebuffer 大小就是:
800 x 600 x 4 = 960000 字节
framebuffer 显示原理,如下图所示:
假设需要设置 LCD 中坐标(x,y)处像素的颜色,首要要找到这个像素对应的内存,然后根据它的 BPP 值设置颜色。
假设 fb_base 是 APP 执行 mmap 后得到的 Framebuffer 地址,如下图所示:
(x,y)像素起始地址=fb_base+(xres*bpp/8)y + xbpp/8
思考一个问题,为什么要用Framebuffer?
上图为LCD 驱动框架图:
从软件层面分析:framebuffer 起着承上启下的作用,向上,为应用层提供通用系统调用(open(),ioctl(),mmap());向下,联接LCD控制器,之前对硬件进行操作。
从硬件层面分析:用户只需要将数据写到framebuffer,硬件会自动刷新到屏幕上。
通过 man 2 查看如下:
函数说明:
函数原型:
int ioctl(int fd, unsigned long request, ...);
函数说明:
fd :表示文件描述符;
request:表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据。
… :表示可变参数arg,根据request命令,设备驱动程序返回输出的数据。
返回值:打开成功返回文件描述符,失败将返回-1。
3)mmap 系统调用:
函数说明:
fb_var_screeninfo:包含xres, yres, bits_per_pixel等信息,在后续会经常用到。
当我们需要显示一个字母‘A’时,是通过判断点阵的每一个位数值状态,来填充颜色,达到显示字符效果。其中‘1’表示一种颜色,‘0’表示填充另一种颜色。
如下图[1]8*16的点阵,只要有这个点阵,我们就可以在LCD上面描点,达到显示字符的效果。
framebuffer 操作如下流程:
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;
int main(int argc, char *argv[])
{
/*Step1: 打开设备 */
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
/*Step2:获取设备参数信息
* xres:x 方向总像素
* yres:y 方向总像素
* bits_per_pixel:每个像素占多少位
*/
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("can't get var\n");
return -1;
}
/* Step3: 计算线宽,分配显存 */
/* line_width 每行占的字节 */
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fbmem == (unsigned char *)-1)
{
printf("can't mmap\n");
return -1;
}
/* Step4: 清屏: 全部设为黑色 */
memset(fbmem, 0, screen_size);
/* Step5: 描点 */
lcd_put_ascii(var.xres/2, var.yres/2, 'A'); /*在屏幕中间显示8*16的字母A*/
/* Step6:释放资源*/
munmap(fbmem , screen_size);
/* Step7:关闭设备 */
close(fd_fb);
return 0;
}
描点的关键是计算点(x,y)位置对应的地址,然后直接指向fbmem即可向frambuffer 写入数据。
/**********************************************************************
* 函数名称:lcd_put_pixel
* 功能描述: 在LCD指定位置上输出指定颜色(描点)
* 输入参数:x坐标,y坐标,颜色
* 输出参数: 无
* 返 回 值: 会
***********************************************************************/
void lcd_put_pixel(int x, int y, unsigned int color)
{
/*
* 最主要的就是fbmem
* 计算(x,y)位置的偏移,然后指向fbmem,这块直接映射到framebuffer 内存里
*/
unsigned char *pen_8 = fbmem+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)
{
/* 8bpp*/
case 8:
{
*pen_8 = color;
break;
}
/*16 bpp */
case 16:
{
/* 565 */
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:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
写入英文的前提:
/**********************************************************************
* 函数名称:lcd_put_ascii
* 功能描述: 在LCD指定位置上显示一个8*16的字符
* 输入参数:x坐标,y坐标,ascii码
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void lcd_put_ascii(int x, int y, unsigned char c)
{
/* fontdata_8x16 8x16 英文点阵数据 */
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i, b;
unsigned char byte;
/* 8 x 16 的点阵, 16行 8列*/
for (i = 0; i < 16; i++)
{
byte = dots[i];
for (b = 7; b >= 0; b--)
{
if (byte & (1<<b))
{
/* show */
lcd_put_pixel(x+7-b, y+i, 0xffffff); /* 白 */
}
else
{
/* hide */
lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */
}
}
}
}
END
→点关注,不迷路←
文章引用微信公众号"嵌入式微处理器",如有侵权,请联系管理员删除!