1 LCD操作原理
linux中通过framebuffer驱动程序来控制LCD。framebuffer中包含LCD的参数,大小为LCD分辨率xbpp。framebuffer 是一块内存 内存中保存了一帧图像。
关于图像的帧指的是在图像处理中,一帧(Frame)是指图像序列中的单个静止图像。当连续的图像以每秒多帧的速度播放时,就可以产生动画、视频或电影。每一帧都是由像素组成的二维网格,每个像素都包含颜色和亮度信息。通过在连续的帧之间进行快速切换,人眼会感知到连续的动态效果。因此,一帧是构成动画和视频的基本单元。
LCD的操作过程
1、驱动程序设置好LCD控制器
根据 LCD 的参数设置 LCD 控制器的时序、信号极性;
根据 LCD 分辨率、BPP 分配 Framebuffer。
2、APP 使用 ioctl 获得 LCD 分辨率、BPP
3、APP 通过 mmap 映射 Framebuffer,在 Framebuffer 中写入数据
假设需要设置 LCD 中坐标(x,y)处像素的颜色,首要要找到这个像素对应的内存,然后根据它的 BPP 值设置颜色。也就是找到内存所对应的地址然后给他赋值。假设 fb_base 是 APP 执行 mmap 后得到的 Framebuffer 地址(首地址)
计算公式
(x,y)像素起始地址=fb_base+(xres*bpp/8)y + xbpp/8
xres:x方向分辨率bpp每个像素的位数
/8:将位数换算成字节
y y坐标这里从第0行开始所以不用减1
x x坐标
bpp :每个像素的位数bits per pixel
有RGB888 565 555格式
对于 32BPP,一般只设置其中的低 24 位,高 8 位表示透明度,一般的 LCD都不支持。
对于 24BPP,硬件上为了方便处理,在 Framebuffer 中也是用 32 位来表示,效果跟 32BPP 是一样的。
对于 16BPP,常用的是 RGB565;很少的场合会用到 RGB555,这可以通过ioctl 读取驱动程序中的 RGB 位偏移来确定使用哪一种格式
这里补充一下寻址方式一般为字节寻址也就是说一个地址对应的一个储存单元能储存8位数据。
实验一 并画一条直线
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
static struct fb_var_screeninfo var;
static int fd_fb;
static unsigned int screen_size;
static unsigned int line_width;
static unsigned int pixel_width;
static unsigned char *fb_base;
static void lcd_put_pixel(int x, int y,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;
switch (var.bits_per_pixel)
{
case 8:
{
*pen_8=color;
//printf("*pen_8= %d\n",*pen_8);
break;
}
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;
// printf("*pen_16= %d\n",*pen_16);
break;
}
case 32:
{
*pen_32 = color;
// printf("*pen_32= %d\n",*pen_32);
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
int main(char argc, char **argv)
{
int i;
//这里我先提个问题如何确定设备驱动文件的
/* 以只读方式打开驱动设备文件 */
fd_fb = open("/dev/fb0", O_RDWR);
if(fd_fb<0)
{
printf("cannot open /dev/fb0 \n");
return -1;
}
/* fd_fb:是一个打开的 Framebuffer 设备文件的文件描述符。
FBIOGET_VSCREENINFO:是一个常量宏,表示获取可见屏幕信息的操作。
&var:是一个指向 vscreeninfo 结构体的指针,用于接收获取到的可见屏幕信息。 */
// ioctl成功返回 失败-1
if (ioctl(fd_fb, FBIOGET_VSCREENINFO,&var))
{
printf("can not get var \n");
return -1;
}
/* 一行的字节数 */
line_width = var.xres *var.bits_per_pixel/8;
/* bpp转换成字节 */
pixel_width = var.bits_per_pixel/8;
screen_size = var.xres*var.yres*var.bits_per_pixel/8;
printf("var.bits_per_pixel= %d\n",var.bits_per_pixel);
/* MAP_SHARED 表示映射区域与其他进程共享。
0:表示映射区域在文件中的偏移量,此处为从文件开头开始映射。*/
fb_base=(unsigned char *)mmap(NULL ,screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
/* mmap 调用失败,它会返回 MAP_FAILED(通常定义为 (void *)-1) */
if(fb_base == (unsigned char *) -1)
{
printf("can not mmap \n");
return -1;
}
memset(fb_base, 0xff, screen_size);
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;
}
实验效果
在屏幕中间从左至右画了一条横线
终端打印出:var.bits_per_pixel= 32
这里我有几个问题
1、mmap返回值转换成uchar*类型: 这时注意地址并没有变地址这个指针操作系统是多少位他就永远是多少位,他指向的内容也不会变,只是寻址方式发生变化
就比如我们看datasheet stm32 的一个寄存器偏移地址为0x00而下一个为0x04 他就是以四个字节递增的。
再比如我们从flash中读取一段数据 要区分半字读还是字读。
2、这引入了第二个问题
x++后地址偏移的字节数是根据pixel_width决定的 pixel_width = var.bits_per_pixel/8; var.bits_per_pixel 为32位那么一次偏移4个字节刚好32位存储32位数据
也就是说2进制编码中一个地址存放1位数据0/1 0x1000 到0x1004 则跨了四个字节32位能存储32位数据。