👏Linux发烧友
🌹0.FrameBuffer的引入
FrameBuffer是出现在 2.2.xx 内核当中的一种驱动程序接口。在Linux系统中通过Framebuffer驱动程序来控制LCD。Frame是帧的意思,buffer是缓冲的意思,这意味着Framebuffer就是一块内存,里面保存着一帧图像。我们可以理解位每一个点就是一个像素,这个像素的单位可以是24位,16位,32位,假设这块lcd屏幕分辨率大小为55100,每一个像素用32位表示,那么一共有55100*32个字节。
☕1.LCD如何显示的原理
在内存中有一块专门的地址位framebuffer使用,我们只需要往这块地址写入数据就可以了,所以我们要先获取到这块内存的基础地址,比如说,一个像素有5个字节,那么我们只需要修改这五个字节的值就可以往LCD里面写入数据了,用韦东山老师画的图表示更清晰一些。
⌛2.如何修改LCD指定的像素呢
比如说,我们要修改第(x,y)个像素的值,有以下几个步骤
- 获取framebuffer的基地址
- 得到这个像素的绝对地址。
♐3.获取framebuffer基地址
使用mmap磁盘映射函数就可以获取基地址了,
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
🚀4.像素绝对地址的计算
在基地址的基础上先算出有多少行,在算出偏离x轴多少个,bpp是像素的意思,一个像素可以理解位一个点
公式总结:(x,y)像素起始地址=fb_base+(xres*bpp/8)y + xbpp/8
🌸5.像素的颜色的表示
像素是由RGB三原色(红、绿、蓝)来表示的,在不同的BPP格式中,用不同的位来分别表示R、G、B,
- 对于32BPP,一般只设置其中的低24位,高8位表示透明度,一般的LCD都不支持
- 对于24BPP,硬件上为了方便处理,在Framebuffer中也是用32位来表示,效果跟32BPP是一样的。
- 对于16BPP,常用的是RGB565;很少的场合会用到RGB555,这可以通过ioctl读取驱动程序中的RGB位偏移来确定使用哪一种格式。
💯6,LCD参数的获取
LCD在每一块开发板上都含有一个LCD驱动程序,我们主要是通过人家提供的驱动程序进行代码编写,主要有两类。可变的参数fb_var_screeninfo、固定的参数fb_fix_screeninfo。编写应用程序时主要关心可变参数。
我们可以调用ioctl这个函数来获取lcd的参数,并把它放入你指定的结构体里,如下代码所示
static struct fb_var_screeninfo var;
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("can't get var\n");
return -1;
}
👑7.设备的打开
在linux系统上,有一个特有的设备结点,他是负责写入framebuffer数据的,我们需要调用open函数打开该设备结点
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
🐟8.framebuffer完整代码演示
功能:在LCD屏幕中间画一条红线
里面加了很多注释,应该不难理解
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
//LCD驱动程序给APP提供2类参数:可变的参数fb_var_screeninfo、固定的参数fb_fix_screeninfo。
static int fd_fb;
static struct fb_var_screeninfo var; /* Current var */
static int screen_size;//屏幕大小
static unsigned char *fb_base;//显存的及地址
static unsigned int line_width;
static unsigned int pixel_width;//像素
void lcd_put_pixel(int x, int y, unsigned 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;
//在不同的BPP格式中,用不同的位来分别表示R、G、B,由8位,16位,32位,在这里选择十六位
switch (var.bits_per_pixel)
{
case 8:
{
//8bpp,color就不再表示RBG三原色了,这涉及调色板的概念,color是调色板的值。
*pen_8 = color;
break;
}
case 16:
{
/* RGB888转RGB565 把red、green、blue这三种8位颜色值,根据RGB565的格式,只保留red中的高5位、green中的高6位、blue中的高5位,组合成一个新的16位颜色值。*/
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;
}
}
}
int main()
{
int i;
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
//获取可变的参数fb_var_screeninfo的值
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("can't get var\n");
return -1;
}
//计算行,每个点多少字节
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
//lcd的大小计算公式为x轴*y轴 var.bits_per_pixeld代表像素,除8是因为像素是以位位单位
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
//通过mmap获取显存地址
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1)
{
printf("can't mmap\n");
return -1;
}
/* 清屏: 全部设为白色 */
memset(fb_base, 0xff, screen_size);
/* 随便设置出100个为红色 */
for (i = 0; i < 100; i++)
lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);//写入像素
//释放磁盘映射
munmap(fb_base , screen_size);
close(fd_fb);
return 0;
}