Linux Frame Buffer(Linux 底层的帧缓冲设备)
????????可以显示一帧一帧的图像(屏幕的显示)
? ? ?帧缓冲是Linux系统为显示设备提供的一个接口,把显示设备抽象成一个设备文件,它可以让上层的图像应用程序不需要关心具体的硬件的实现细节,上层的图像应用程序只需要操作对应的"文件",往文件中写入"数据",经过帧缓冲驱动,就可以在硬件设备(LCD)上面显示对应的图像 ?
如何让LCD设备显示颜色呢?????????Linux中一切都是文件,只需要利用文件IO的接口去操作LCD设备就可以了
操作文件的大概步骤:
? ? ? ? open?
? ? ? ? read/write?
? ? ? ? close?
操作文件的时候需要知道文件名:
????????lcd的文件名 ?"/dev/fb0" ?是一个绝对路径(Frame Buffer)lcd上图像的显示,是由一个一个的像素点组成的
? ? ? ? 像素点:可以描述一个点的颜色
? ? ? ? 将LCD上面每一个像素点"描绘"成不同的颜色,就可以显示一副图画颜色的组成:RGB三原色(三基色)
? ? ? ? 每一个基色使用一个byte(8bits)表示,给基色中的每一个bit赋值为不同的值,就可以表示不同的颜色。????????问题:
? ? ? ? ? ? ? ? 一个基色可以表示多少种颜色? ? 0~255 ------>256
? ? ? ? ? ? ? ? ????????8个bit的值不同,就可以表示不同的颜色?
? ? ? ? ? ? ? ? 三个基色可以表示多少种颜色? ? 2^24 ------>1670万种
? ? ? ? ? ? ? ? ? ? ? ? 24个bit的值不同,就可以表示不同的颜色?LCD上面一个像素点的表示:ARGB ? A:透明度(1byte)
????????在LCD上面一个像素点使用几个字节表示? ? 4byte
? ? ? ? 使用4byte就可以表示一个像素点的颜色如果使用一种数据类型来表示一个像素点的颜色的话,可以使用什么数据类型?
????????int ?刚好4个字节
? ? ? ? int color; // color就是一个int类型的变量,占用4byte
? ? ? ? 给color赋值为不同的值,color就表示不同的颜色? ? ? ? ????????红色:0x00FF0000
? ? ? ? ? ? ? ? 绿色:0x0000FF00
? ? ? ? ? ? ? ? 蓝色:0x000000FF
? ? ? ? ? ? ? ? 黑色:0x0
? ? ? ? ? ? ? ? 白色:0x00FFFFFF? ? ? ? ? ? ? ? ......
只需要把颜色数据写入到屏幕对应的文件中,就可以让屏幕显示对应的颜色
? ? ? ? ? ? 像素点在"文件"中的排列顺序是从上至下,从左至右
? ? ? ? ? ? 理论上就可以通过文件去操作屏幕上面每一个像素点的颜色
?屏幕上有多少个像素点:800*480
每一行有800个像素点,有480行注意:
????????有的开发板上面开机就会自动运行一个iot的程序
? ? ? ? 关闭方法:
? ? ? ? ????????killall iot?
? ? ? ? ? ? ? ? or?
? ? ? ? ? ? ? ? 设置 /etc/profile 文件练习:
????????使用基本的文件IO操作函数,在屏幕上面显示简单的颜色
? ? ? ? ? ? ? ? 先显示红色
? ? ? ? ? ? ? ? 1s后(sleep(1))
? ? ? ? ? ? ? ? 显示绿色
? ? ? ? ? ? ? ? 1s后?
? ? ? ? ? ? ? ? 显示蓝色? ? ? ? 注意光标的位置!!!
代码实现:write函数会直接去访问硬件资源,占用总线,效率是非常低的
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main() { // 打开屏幕 int fd = open("/dev/fb0", O_RDWR); if (-1 == fd) { perror("open lcd error"); return -1; } printf("open success!\n"); // 写入颜色数据 int color[480][800] = {0}; for (int i = 0; i < 480; i++) { for (int j = 0; j < 800; j++) { color[i][j] = 0x0000FF00; } } int ret = write(fd, color, 800 * 480 * 4); if (-1 == ret) { perror("write_1 lcd fail"); close(fd); // 关闭屏幕 } sleep(1); // 注意:移光标!!! lseek(fd, 0, SEEK_SET); for (int i = 0; i < 480; i++) { for(int j = 0; j < 800; j++) { color[i][j] = 0x0000FFFF; } } ret = write(fd, color, 800 * 480 * 4); if (-1 == ret) { perror("write_2 lcd fail"); } // 关闭屏幕 close(fd); return 0; }
ioctl函数是用来对文件/设备进行除了读写以外的其他控制操作,每一个设备的控制操作都是不一样的,这些设备具体有哪些操作是由设备的驱动程序决定的
? ? NAME
? ? ? ? ioctl - control device
? ? ? ? ????????控制设备(具体的操作由驱动程序决定)
? ? ? ? ????????此处我们可以使用函数去获取LCD屏幕的一些基本信息
? ? SYNOPSIS
? ? ? ? #include <sys/ioctl.h>? ? ? ? int ioctl(int fd, unsigned long request, ...);
? ? ? ? ????????fd:文件描述符,表示你要操作哪一个设备
? ? ? ? ????????request:命令号码,在驱动实现的时候,一般会把某些特定的操作取一个命令号,命令号的具体含义是由提供该命令号的驱动程序决定
? ? ? ? ????????...:可变参数,具体的操作由命令号决定
? ? ? ? 返回值:
? ? ? ? ????????成功返回0
? ? ? ? ? ? ????失败返回-1,同时errno被设置? ? 例子:
? ? ? ? 如果程序需要知道帧缓冲设备的相关信息,可以使用ioctl来完成
? ? ? ? 对于帧缓冲设备(/usr/include/linux/fb.h)? ? ? ? ????????vim?/usr/include/linux/fb.h
????????最常用的有两条命令:
? ? ? ? ? ? #define FBIOGET_VSCREENINFO?? ?0x4600
? ? ? ? ? ? ? ? 返回和设备相关的可变信息
? ? ? ? ? ? ? ? 如:帧缓冲设备的大小(宽度,高度),以及颜色显示等信息
? ? ? ? ? ? #define FBIOGET_FSCREENINFO?? ?0x4602
? ? ? ? ? ? ? ? 返回和设备相关的固定信息
? ? ? ? ? ? ? ? 如:设备本身的一些信息,硬件加速度等信息? ? ? ? ? ? 对应的结构体:
? ? ? ? ? ? ????????struct fb_var_screeninfo{} ? ?
? ? ? ? ? ? ????????和
? ? ? ? ? ? ????????struct fb_fix_screeninfo{}? ? ? ? ? ? 获取方法:
? ? ? ? ? ? // 定义一个结构体用来保存即将获取到的数据
? ? ? ? ? ? struct fb_var_screeninfo ?vinfo; ? ? ?
? ? ? ? ? ? // 把获取到的信息保存到结构体中
? ? ? ? ? ? ioctl(fd,FBIOGET_VSCREENINFO,&vinfo);struct fb_var_screeninfo {
????????__u32 xres;?? ??? ??? ?/* 可视分辨率?? ?*/
? ? ? ? __u32 yres;
? ? ? ? ?__u32 xres_virtual;?? ??? ?/* virtual resolution */
? ? ? ? ?__u32 yres_virtual;
? ? ? ???__u32 xoffset;?? ??? ??? ?/* offset from virtual to visible */
? ? ? ? ?__u32 yoffset;?? ??? ??? ?/* resolution?*/? ? ? ? ?__u32 bits_per_pixel;?? ??? ?/* 每一个像素点占用的bit数量 */
? ? ? ? ?__u32 grayscale;?? ??? ?/* 0 = color, 1 = grayscale,?>1 = FOURCC??*/
? ? ? ? ?struct fb_bitfield red;? ? ? ?// Red
? ? ? ? ?struct fb_bitfield green;? ?// Green
? ? ? ? ?struct fb_bitfield blue;? ? // Blue
? ? ? ? ??struct fb_bitfield transp;?? ?/* transparency?*/?? ?
};? ? ???
struct fb_bitfield {
????????__u32 offset;?? ??? ??? ?/* beginning of bitfield??*/
? ? ? ? __u32 length;?? ??? ??? ?/* length of bitfield??*/
? ? ? ? __u32 msb_right;?? ??? ?/* != 0 : Most significant bit is right? */?
};代码实现:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/fb.h> // 注意!!! /* 包含 struct fb_var_screeninfo{}、struct fb_fix_screeninfo{}、 #define FBIOGET_VSCREENINFO 0x4600、 #define FBIOGET_FSCREENINFO 0x4602 的头文件 */ int main() { // 打开屏幕 int fd = open("/dev/fb0", O_RDWR); if (-1 == fd) { perror("open lcd error"); return -1; } printf("open success!\n"); /* 获取屏幕的信息 */ // 定义一个结构体用来保存即将获取到的数据 struct fb_var_screeninfo vinfo; //把获取到的信息保存到结构体中 int r = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); if (-1 == r) { perror("ioctl failed"); close(fd); // 关闭屏幕 return -1; } /* 解析结构体,打印获取到的屏幕的信息 */ // 分辨率 printf("resolution:%d*%d\n", vinfo.xres, vinfo.yres); // 800*480 // 每一个像素点占用的bit数量(位) printf("bits_per_pixel:%d\n", vinfo.bits_per_pixel); // 32 // 每一种颜色的信息 printf("transp.offset:%d\n", vinfo.transp.offset); // 24 printf("transp.length:%d\n", vinfo.transp.length); // 8 printf("red.offset:%d\n", vinfo.red.offset); // 16 printf("red.length:%d\n", vinfo.red.length); // 8 printf("green.offset:%d\n", vinfo.green.offset); // 8 printf("green.length:%d\n", vinfo.green.length); // 8 printf("blue.offset:%d\n", vinfo.blue.offset); // 0 printf("blue.length:%d\n", vinfo.blue.length); // 8 // 关闭屏幕 close(fd); return 0; }
每一次操作屏幕的图像,都需要使用write函数
但是write函数会直接去访问硬件资源,占用总线,效率是非常低的?========>
内存映射,把屏幕文件映射到内存中(操作内存就相当于操作屏幕)
映射:把文件和一段内存一一对应(建立一个联系),操作文件的时候就不需要使用write函数,只需要使用指针操作对应的内存就可以了(C语言中指针可以直接操作内存),内存内容的改变会由具体的映射驱动同步到文件中去
在LCD中,如果把屏幕映射到内存中,就不需要使用write函数了(也不需要移动光标),只需要去操作映射之后的内存即可
NAME mmap, munmap - map or unmap files or devices into memory SYNOPSIS #include <sys/mman.h> mmap是把指定的文件映射到进程地址空间(堆和栈中间) void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); addr:表示映射地址,你要把文件映射到内存的哪一个位置 一般为NULL,表示让OS自行选择一个合适的地址 length:表示你要映射的长度(字节),你的文件多大 prot:应用程序对映射之后的内存的操作权限 PROT_READ 可读 PROT_WRITE 可写 PROT_READ | PROT_WRITE flags:映射标记 MAP_SHARED:共享映射,内存的改变会同时同步到文件 MAP_PRIVATE:私有映射,内存数据的改变对其他程序是不可见的 fd:你要映射的文件的文件描述符 offset:偏移量,表示你要从文件的哪一个位置开始映射,一般为0,表示从文件的开头开始映射 返回值: 成功返回映射之后的内存的首地址(相当于文件的开头) 失败返回MAP_FAILED,errno被设置 munmap是解除内存的映射关系 int munmap(void *addr, size_t length); addr:你要接映射的首地址,是mmap的返回值 length:你要接映射的长度
代码实现:把1.txt映射到内存
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> int main(int argc,char *argv[]) { // 打开文件 int fd = open("1.txt", O_RDWR | O_CREAT, 0777); if (-1 == fd) { perror("open 1.txt"); return -1; } // 把文件映射到内存 char *plcd = (char*)mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (plcd == MAP_FAILED) { perror("mmap failed"); close(fd); return -1; } /* 注意!!!:1.txt内容不能为空,并且执行*(plcd + i) = x; 或者 plcd[i] = x;时, 只有当*(plcd + i) 或者plcd[i]在文件中有值时才能修改(空格也可以),否则无效 */ *plcd = 'A'; plcd[1] = 'B'; *(plcd+20) = 'H'; // 解除映射 munmap(plcd, 1024); // 关闭文件 close(fd); return 0; }
流程:
? ? ? ? 打开文件?
? ? ? ? 映射文件到内存
? ? ? ? ==============
? ? ? ? 操作内存就是操作文件
? ? ? ? ============== ? ? ?
? ? ? ? 解除映射
? ? ? ? 关闭文件 ?假设映射成功后,映射后的像素点在内存中的对应关系是从左至右,从上至下
?????????也就是说,plcd指向的位置对应屏幕上面第0行的第0个点
plcd+1对应屏幕上面第0行的第1个点(指针做偏移,是偏移单位个指向类型的长度)如果想要通过plcd把屏幕的第y行的第x个点设置为红色,应该如何操作?
? ? ? ? 把0x00ff0000这个数据放到第y行的第x个点对应的内存地址
? ? ? ? 第y行的第x个点对应的内存地址就是(plcd+800*y+x)
? ? ? ? *(plcd+800*y+x) = 0x00FF0000?我们可以通过plcd去操作屏幕上面的每一个像素点
????????注意:因为内存是连续的,plcd向后只能偏移800*480*4个字节有权限使用代码实现:使用内存映射,把屏幕设置为红色
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> int main() { // 打开屏幕 int fd = open("/dev/fb0", O_RDWR); if (-1 == fd) { perror("open lcd error"); return -1; } printf("open success!\n"); // 把屏幕映射到内存 int *plcd = (int*)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (plcd == MAP_FAILED) { perror("mmap failed"); close(fd); // 关闭屏幕 return -1; } // 通过plcd把屏幕设置为红色 for (int y = 0; y < 480; y++) { for (int x = 0; x < 800; x++) { *(plcd + 800 * y + x) = 0x00FF0000; } } // 解除映射 munmap(plcd, 1024); // 关闭屏幕 close(fd); return 0; }
练习:
? ? ? ? 使用内存映射的形式,在屏幕上面显示颜色?
? ? ? ? 要求:
? ? ? ? ? ? 设计一个函数,在屏幕上指定的位置(x,y)的像素点显示指定的颜色
? ? ? ? ? ? ????????void lcd_draw_point(......);? ? ? ? ? ? 设计一个函数,在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的矩形
? ? ? ? ? ? ????????void lcd_draw_rect(....);? ? ? ? ? ? 设计一个函数,在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的圆
? ? ? ? ? ? ????????void lcd_draw_cir(....);? ? ? ? ? ? 设计一个函数,可以把屏幕刷成指定的颜色
? ? ? ? ? ? ????????void lcd_clear(....);代码实现:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> // 设计一个函数,在屏幕上指定的位置(x,y)的像素点显示指定的颜色 void lcd_draw_point(int *plcd, int x, int y, int color) { *(plcd + 800 * y + x) = color; } // 设计一个函数,在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的矩形 w:长 h:宽 void lcd_draw_rect(int *plcd, int x, int y, int w, int h, int color) { for (int i = x, j = y; i >= 0 && i < 800 && i - x <= w && j > 0 && j < 480; i++) { *(plcd + 800 * j + i) = color; } for (int i = x, j = y + h; i >= 0 && i < 800 && i - x <= w && j > 0 && j < 480; i++) { *(plcd + 800 * j + i) = color; } for (int i = x, j = y; i >= 0 && i < 800 && j - y <= h && j > 0 && j < 480; j++) { *(plcd + 800 * j + i) = color; } for (int i = x + w, j = y; i >= 0 && i < 800 && j - y <= h && j > 0 && j < 480; j++) { *(plcd + 800 * j + i) = color; } } // 判断点是否在圆内 int is_within(int x, int y, int i, int j, int r) { int a = (x - i) * (x - i) + (y - j) * (y - j); int b = r * r; if (a <= b) { return 1; } return 0; } // 设计一个函数,在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的圆 void lcd_draw_cir(int *plcd, int x, int y, int r, int color) { for (int i = 0; i < 800; i++) { for (int j = 0; j < 480; j++) { if (is_within(x, y, i, j, r)) { *(plcd + 800 * j + i) = color; } } } } // 设计一个函数,可以把屏幕刷成指定的颜色 void lcd_clear(int *plcd, int color) { for (int i = 0; i < 480; i++) { for (int y = 0; y < 800; y++) { *(plcd + 800 * i + y) = color; } } } int main(int argc, char *argv[]) { // 打开屏幕 int fd = open("/dev/fb0", O_RDWR); if (-1 == fd) { perror("open fail"); return -1; } // 映射 int *plcd =(int*)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (plcd == MAP_FAILED) { perror("mmap fail"); close(fd); // 关闭屏幕 return -1; } // 设计一个函数,可以把屏幕刷成指定的颜色 lcd_clear(plcd, 0x00ffffff); // 设计一个函数,在屏幕上指定的位置(x,y)的像素点显示指定的颜色 lcd_draw_point(plcd, 500, 400, 0x00ff0000); // 设计一个函数,在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的矩形 lcd_draw_rect(plcd, 100, 100, 200, 500, 0x00ff0000); // 设计一个函数,在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的圆 lcd_draw_cir(plcd, 840, 410, 250, 0x00ff0000); // 解映射 munmap(plcd, 800 * 480 *4); // 关闭屏幕 close(fd); return 0; }