1.1 LCD Framebuffer操作原理
LCD Framebuffer 就是一块显存,在嵌入式系统中,显存是被包含在内存中。LCD Framebuffer里的若干字节(根据驱动程序对LCD控制器的配置而定)表示LCD屏幕中的一个像素点,一一对应整个LCD屏幕。举个例子,LCD屏幕是800* 600的分辨率,即LCD屏幕存在480000个像素点,若每个像素点4个字节表示,那么LCD Framebuffer显存大小为480000 * 4=960000字节,即1.92MB。因此我们的内存将会分割至少1.92MB的空间用作显存。具体地址在哪里,这个就是又驱动程序去定,应用程序只需直接使用即可,硬件相关操作已由驱动程序封装好。
如上图,我们只需要往Framebuffer中填入不同的值,驱动程序和硬件控制器就会把这些数据传输到对应LCD屏幕上的像素点,从而显示不同的颜色。由此可知,我们应用程序只需要针对Framebuffer操作即可,其他交给驱动程序和硬件。
#1.2 Framebuffer API接口
#1.2.1 open系统调用
头文件:#include <sys/types.h>,#include <sys/stat.h>,#include <fcntl.h>
函数原型:
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode\_t mode);
函数说明:
- pathname 表示打开文件的路径;
Flags表示打开文件的方式,常用的有以下6种,
①:O\_RDWR表示可读可写方式打开;
②:O\_RDONLY表示只读方式打开;
③:O\_WRONLY表示只写方式打开;
④:O\_APPEND 表示如果这个文件中本来是有内容的,则新写入的内容会接续到原来内容的后面;
⑤:O\_TRUNC表示如果这个文件中本来是有内容的,则原来的内容会被丢弃,截断;
⑥:O\_CREAT表示当前打开文件不存在,我们创建它并打开它,通常与O\_EXCL结合使用,当没有文件时创建文件,有这个文件时会报错提醒我们;
Mode表示创建文件的权限,只有在flags中使用了O\_CREAT时才有效,否则忽略。
返回值:打开成功返回文件描述符,失败将返回-1。
#1.2.2 ioctl系统调用
头文件:#include <sys/ioctl.h>
函数原型:
- int ioctl(int fd, unsigned long request, ...);
函数说明:
- fd 表示文件描述符;
- request表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据;
- … 表示可变参数arg,根据request命令,设备驱动程序返回输出的数据。
返回值:打开成功返回文件描述符,失败将返回-1。
#1.2.3 mmap系统调用
头文件:#include <sys/mman.h>
函数原型:
- void *mmap(void *addr, size\_t length, int prot, int flags,int fd, off\_t offset);
函数说明:
- addr表示指定映射的內存起始地址,通常设为 NULL表示让系统自动选定地址,并在成功映射后返回该地址;
- length表示将文件中多大的内容映射到内存中;
prot 表示映射区域的保护方式,可以为以下4种方式的组合
①PROT\_EXEC 映射区域可被执行
②PROT\_READ 映射区域可被读写
③PROT\_WRITE 映射区域可被写入
④PROT\_NONE 映射区域不能存取
Flags 表示影响映射区域的不同特性,常用的有以下两种
①MAP\_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文件会改变。
②MAP\_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,对此区域的任何修改都不会写回原来的文件内容中。
返回值:若成功映射,将返回指向映射的区域的指针,失败将返回-1。
#1.3 在LCD上描点操作
#1.3.1 在LCD上显示点阵理论基础
如上图,当我们需要显示一个字母‘A’时,是通过判断点阵的每一个位数值状态,来填充颜色,达到显示字符效果。其中‘1’表示一种颜色,‘0’表示填充另一种颜色。上图的是8*16的点阵,我们也可以用其他不同大小点阵,只要有这个点阵,我们就可以在LCD上面描点,达到显示字符的效果。
#1.3.2 获取fb\_var\_screeninfo结构体
在用点阵显示字符之前,我们需要先从设备fb0中获取相关的LCD信息,下图截取我们将用到的fb\_info结构体部分内容。
通过系统调用ioctl,获取xres(x方向总像素点),yres(y方向总像素点),bits\_per\_pixel(每个像素点占据的位数),根据获取的三个资源,外加点阵,根据这四个资源,我们就可以显示一个字符。
程序文件:show\_ascii.c
4718 fd_fb = open("/dev/fb0", O_RDWR);
4719 if (fd_fb < 0)
4720 {
4721 printf("can't open /dev/fb0\n");
4722 return -1;
4723 }
4724 if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
4725 {
4726 printf("can't get var\n");
4727 return -1;
4728 }
先打开LCD设备(fb0),获得文件描述符,再通过ioctl获取fb\_var\_screeninfo信息并保存在var变量,后续只需访问var这个结构体,就可以获得xres(x方向总像素点),yres(y方向总像素点),bits\_per\_pixel(每个像素点占据的位数)这三个关于fb0的资源。
#1.3.3 根据fb\_var\_screeninfo计算变量
fb\_var\_screeninfo已保存在var结构体变量中,接着来访问var结构体变量即可
根据xres与bits\_per\_pixel算出每行像素点所占据的字节数
程序文件:show\_ascii.c
4730 line_width = var.xres * var.bits_per_pixel / 8;
根据bits\_per\_pixel算出每个像素点所占据的字节数
程序文件:show\_ascii.c
4731 pixel_width = var.bits_per_pixel / 8;
根据xres,yres,bits\_per\_pixel算出全部像素点所占据的字节总和
程序文件:show\_ascii.c
4732 screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
#1.3.4 使用mmap系统调用,映射内存
程序文件:show\_ascii.c
4733 fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
4734 if (fbmem == (unsigned char *)-1)
4735 {
4736 printf("can't mmap\n");
4737 return -1;
4738 }
4739
4740 /* 清屏: 全部设为黑色 */
4741 memset(fbmem, 0, screen_size);
调用mmap将显存映射在内存中,以可读可写(PROT\_READ | PROT\_WRITE)及内存回写(MAP\_SHARED)的方式映射,从而获得一个指向映射在内存空间的首地址fbmem,后续操作就是在这个首地址的基础上计算各种不同的偏移量,填充颜色值。
#1.3.5 描点函数编写
程序文件:show\_ascii.c
4641 void lcd_put_pixel(int x, int y, unsigned int color)
描点函数有3个参数,x坐标,y坐标,像素点颜色值。
程序文件:show\_ascii.c
4643 unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
4644 unsigned short *pen_16;
4645 unsigned int *pen_32;
4646
4647 unsigned int red, green, blue;
4648
4649 pen_16 = (unsigned short *)pen_8;
4650 pen_32 = (unsigned int *)pen_8;
在此处函数参数x与y表示的是像素点的坐标,而单个像素点所占据的显存大小可能会有不同的情况出现,如1字节表示一个像素点,2字节表示一个像素点,4字节表示一个像素点等,为了更多的兼容不同的情况,因此申请3个指针,pen\_8指向的是占据1个字节的像素点空间, pen\_16指向的是占据2个字节的像素点空间,pen\_32指向的是占据4个字节的像素点空间。
fbmem是系统调用mmap返回的显存首地址,根据fbmem计算填充颜色的内存空间。
当像素点占据1个字节空间时
对应描点地址= fbmem+Y * 一行所占据的字节数 + x * 每个像素点所占据的字节数
程序文件:show\_ascii.c
4652 switch (var.bits_per_pixel)
4653 {
4654 case 8:
4655 {
4656 *pen_8 = color;
4657 break;
4658 }
4659 case 16:
4660 {
4661 /* 565 */
4662 red = (color >> 16) & 0xff;
4663 green = (color >> 8) & 0xff;
4664 blue = (color >> 0) & 0xff;
4665 color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
4666 *pen_16 = color;
4667 break;
4668 }
4669 case 32:
4670 {
4671 *pen_32 = color;
4672 break;
4673 }
4674 default:
4675 {
4676 printf("can't surport %dbpp\n", var.bits_per_pixel);
4677 break;
4678 }
4679 }
4680 }
根据设备fb0实际的bits\_per\_pixel值,选择对应的pen(pen\_8,pen\_16,pen\_32其中一个),最后把color颜色变量传入选择的pen中。