开发板的参数:
芯片:三星S5P6818
处理器:ARM Cortex-A53 64bits
OS:Linux
lcd屏幕:800 * 480
Linux Frame Buffer(Linux 底层的帧缓冲设备)
可以显示一帧一帧的图像(屏幕的显示)
1. 基本操作
帧缓冲是Linux系统为显示设备提供的一个接口,把显示设备抽象成一个设备文件,它可以让上层的图像应用程序不需要关心具体的硬件实现细节,上层的图像应用程序只需要操作对应的"文件",往文件中写入"数据",经过帧缓冲驱动,就可以在硬件设备(LCD)上面显示对应的图像
如何让LCD设备显示颜色呢?
Linux中一切都是文件,只需要利用文件IO的接口去操作LCD设备就可以了
操作文件的大概步骤:
open
read/write
close
操作文件的时候需要知道文件名:
lcd的文件名 "/dev/fb0" 是一个绝对路径linux把LCD这种硬件设备抽象成 Frame Buffer(帧缓冲设备)
Lcd上图像的显示,本质上是由一个一个的像素点组成的
像素点:可以描述一个点的颜色
将LCD上面每一个像素点"描绘"成不同的颜色,就可以显示一副图画分辨率(描述屏幕的像素点信息):分辨率决定了位图细节的精细程度,通常情况下,分辨率越高包含的像素就越高,图像就越清晰
6818开发板 800 * 480
有480行,每一行有800个像素点
可以通过函数获取
颜色的组成:RGB三基(原)色
世界上所有的颜色都是由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就表示不同的颜色
使用RGB颜色查询对照表查找你所需要的颜色:
红色: 0x00FF0000
绿色: 0x0000FF00
蓝色: 0x000000FF
黑色: 0x0
白色: 0x00FFFFFF只需要把颜色数据写入到屏幕对应的文件中,就可以让屏幕显示对应的颜色
像素点在"文件"中的排列顺序是从上至下,从左至右
理论上就可以通过文件去操作屏幕上面每一个像素点的颜色屏幕上有多少个像素点:800*480
每一行有800个像素点,有480行写一个简单的代码,让开发板显示对应的颜色
打开屏幕------->(只能用系统IO打开,因为标准IO只能操作"普通文件")// 获取屏幕信息(分辨率,颜色分量)
写入颜色数据// lcd上面从上至下、从左至右每一个像素点都是独立的,每一个像素点都可以使 用一个int类型的颜色去描述
// 每一次操作都必须全部给一个颜色值
关闭屏幕注意:
有的开发板开机就会自动运行一个iot的程序
关闭方法:
killall iot
or
设置 /etc/profile 文件练习:
1. 画一个矩形,x0 = 100,y0 = 100,w = 200,h = 200;
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { // 打开lcd屏幕 int fd = open("/dev/fb0", O_RDWR); if (-1 == fd) { perror("open lcd faile"); return -1; } int colour[480][800] = {0}; for (int y = 100; y < 300; y++) { for (int x = 100; x < 300; x++) { colour[y][x] = 0xff0000; // 红色 } } ssize_t w = write(fd, colour, 800 * 480 * 4); if (-1 == w) { perror("write lcd faile"); // 关闭lcd屏幕 close(fd); return -1; } // 关闭屏幕 close(fd); return 0; }
2. 使用基本的文件IO操作函数,在屏幕上面显示简单的颜色
先显示红色
1s后(sleep(1))
显示绿色
1s后
显示蓝色注意光标的位置!!!
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { // 打开lcd屏幕 int fd = open("/dev/fb0", O_RDWR); if (-1 == fd) { perror("open lcd faile"); return -1; } // 操作lcd屏幕 // int colour = 0xff0000; // 红色 // for (int y = 0; y < 480; y++) { // for (int x = 0; x < 800; x++) { // ssize_t w = write(fd, &colour, 4); // if (-1 == w) { // perror("write lcd faile"); // // 关闭lcd屏幕 // close(fd); // return -1; // } // } // } int colour[480][800] = {0}; for (int y = 0; y < 480; y++) { for (int x = 0; x < 800; x++) { colour[y][x] = 0xff0000; // 红色 } } ssize_t w = write(fd, colour, 800 * 480 * 4); if (-1 == w) { perror("write lcd faile"); // 关闭lcd屏幕 close(fd); return -1; } sleep(1); lseek(fd, SEEK_SET, 0); // 将光标移到文件开头 for (int y = 0; y < 480; y++) { for (int x = 0; x < 800; x++) { colour[y][x] = 0x00ff00; // 绿色 } } w = write(fd, colour, 800 * 480 * 4); if (-1 == w) { perror("write lcd faile"); // 关闭lcd屏幕 close(fd); return -1; } sleep(1); lseek(fd, SEEK_SET, 0); // 将光标移到文件开头 for (int y = 0; y < 480; y++) { for (int x = 0; x < 800; x++) { colour[y][x] = 0x0000ff; // 蓝色 } } w = write(fd, colour, 800 * 480 * 4); if (-1 == w) { perror("write lcd faile"); // 关闭lcd屏幕 close(fd); return -1; } // 关闭屏幕 close(fd); return 0; }
2. 使用ioctl函数去获取设备信息
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): #define FBIOGET_VSCREENINFO 0x4600 返回和设备相关的可变信息 如:帧缓冲设备的大小(宽度,高度),以及颜色显示等信息 #define FBIOGET_FSCREENINFO 0x4602 返回和设备相关的固定信息 如:设备本身的一些信息,硬件加速度等信息 两个命令号分别有对应的结构体: 定义在内核的头文件中:/usr/include/linux/fb.h 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位数量 ... struct fb_bitfield red; // R 描述红色的信息 struct fb_bitfield green; // G struct fb_bitfield blue; // B struct fb_bitfield transp; // A ...... }; // 描述每一个颜色分量(A,R,G,B)占用的bit的区域信息 struct fb_bitfield { __u32 offset; // 颜色位从哪一个位开始 __u32 length; // 这个分量占用多少个bit __u32 msb_right; // true(!0)表示最高位在右边 };
代码实现:
#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> int main() { // 打开屏幕 int fd = open("/dev/fb0", O_RDWR); if (-1 == fd) { perror("open lcd error"); return -1; } // 获取屏幕的信息 // 定义一个结构体用来保存即将获取到的数据 struct fb_var_screeninfo vinfo; // 头文件:#include <linux/fb.h> // 把获取到的信息保存到结构体中 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); // 每一个像素点占用的bit数量 printf("bits_per_pixel: %d\n", vinfo.bits_per_pixel); // 每一种颜色的信息 printf("transp.offset: %d\n", vinfo.transp.offset); printf("transp.length: %d\n", vinfo.transp.length); printf("transp.msb_right: %d\n", vinfo.transp.msb_right); printf("red.offset: %d\n", vinfo.red.offset); printf("red.length: %d\n", vinfo.red.length); printf("red.msb_right: %d\n", vinfo.red.msb_right); printf("green.offset: %d\n", vinfo.green.offset); printf("green.length: %d\n", vinfo.green.length); printf("green.msb_right: %d\n", vinfo.green.msb_right); printf("blue.offset: %d\n", vinfo.blue.offset); printf("blue.length: %d\n", vinfo.blue.length); printf("blue.msb_right: %d\n", vinfo.blue.msb_right); // 关闭屏幕 close(fd); return 0; }
resolution: 800 * 480 bits_per_pixel: 32 transp.offset: 24 transp.length: 8 transp.msb_right: 0 red.offset: 16 red.length: 8 red.msb_right: 0 green.offset: 8 green.length: 8 green.msb_right: 0 blue.offset: 0 blue.length: 8 blue.msb_right: 0
3. 操作LCD屏幕的效率问题 (mmap)
每一次操作屏幕的图像,都需要使用write函数
但是write函数会直接去访问硬件资源,占用总线,效率是非常低的连续调用write函数,需要lseek函数的配合,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:表示你要映射的长度(字节),你的文件多大 如:映射lcd. 800*480*4 prot:应用程序对映射之后的内存的操作权限 PROT_EXEC 可执行 PROT_READ 可读 PROT_WRITE 可写 PROT_NONE 没有任何权限 可读可写: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:你要解映射的长度
流程:
打开文件
映射文件到内存
==============
操作内存就是操作文件
==============
解除映射
关闭文件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指向的位置对应屏幕上面第0行的第0个点
plcd+1对应屏幕上面第0行的第1个点(指针做偏移,是偏移单位个指向类型的长度)如果想要通过plcd把屏幕的第0行的第0个点设置为红色,应该如何操作?
把0x00ff0000这个数据放到第0行的第0个点对应的内存地址
第0行的第0个点对应的内存地址就是plcd
*plcd = 0x00FF0000如果想要通过plcd把屏幕的第0行的第1个点设置为红色,应该如何操作?
把0x00ff0000这个数据放到第0行的第1个点对应的内存地址
第0行的第1个点对应的内存地址就是(plcd+1)
*(plcd+1) = 0x00FF0000如果想要通过plcd把屏幕的第0行的第x个点设置为红色,应该如何操作?
把0x00ff0000这个数据放到第0行的第x个点对应的内存地址
第0行的第x个点对应的内存地址就是(plcd+x)
*(plcd+x) = 0x00FF0000如果想要通过plcd把屏幕的第y行的第x个点设置为红色,应该如何操作?
把0x00ff0000这个数据放到第y行的第x个点对应的内存地址
第y行的第x个点对应的内存地址就是(plcd+800*y+x)
*(plcd + 800 * y + x) = 0x00FF0000我们可以通过plcd去操作屏幕上面的每一个像素点
注意:因为内存是连续的,plcd向后只能偏移800*480*4个字节有权限使用
4. lcd代码实现
lcd.h
#ifndef __LCD_H__ #define __LCD_H__ #include <stdio.h> #include <string.h> #include <math.h> // arm-linux-gcc *.c -lm #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/fb.h> #include <sys/mman.h> // 定义一个结构体,描述一个打开的文件 typedef struct lcd { char name[256]; int fd; // 文件描述符 int *plcd; // 映射之后的首地址 int xres; int yres; int bits_per_pixel; // 每一个像素点占用的bit位数量 / 8 } lcd; /* init_lcd:初始化屏幕(打开文件/映射到内存) name:要初始化的屏幕的路径名,把初始化后的屏幕信息保存到lp指针指向的空间 */ void init_lcd(const char *name, lcd *lp); /* close_lcd:关闭屏幕(解映射/关闭文件) lp:要关闭的文件 */ void close_lcd(lcd *lp); /* lcd_draw_point:能够在指定的屏幕的指定的位置显示一个指定的颜色 */ void lcd_draw_point(lcd *lp, int x, int y, int colour); /* lcd_clear:把屏幕刷成指定的颜色 */ void lcd_clear(lcd *lp, int colour); /* 在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的矩形 */ void lcd_draw_rect(lcd *lp, int x, int y, int w, int h, int colour); /* 在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的圆 */ void lcd_draw_cir(lcd *lp, int x, int y, int r, int colour); #endif
lcd.c
#include "lcd.h" /* init_lcd:初始化屏幕(打开文件/映射到内存) name:要初始化的屏幕的路径名,把初始化后的屏幕信息保存到lp指针指向的空间 */ void init_lcd(const char *name, lcd *lp) { strcpy(lp->name, name); // 打开屏幕 lp->fd = open(name, O_RDWR); if (-1 == lp->fd) { perror("open lcd failed"); return ; } // 通过ioctl获取设备信息 struct fb_var_screeninfo vinfo; // 定义一个结构体用来保存即将获取到的信息 int r = ioctl(lp->fd, FBIOGET_VSCREENINFO, &vinfo); if (-1 == r) { perror("ioctl failed"); close(lp->fd); return ; } // 分辨率 lp->xres = vinfo.xres; lp->yres = vinfo.yres; lp->bits_per_pixel = vinfo.bits_per_pixel / 8; // 把屏幕映射到内存中去 lp->plcd = mmap(NULL, (lp->xres) * (lp->yres) * (lp->bits_per_pixel), PROT_READ | PROT_WRITE, MAP_SHARED, lp->fd, 0); if (lp->plcd == MAP_FAILED) { perror("mmap error"); close(lp->fd); return ; } } /* close_lcd:关闭屏幕(解映射/关闭文件) lp:要关闭的文件 */ void close_lcd(lcd *lp) { // 解映射 munmap(lp->plcd, (lp->xres) * (lp->yres) * (lp->bits_per_pixel)); // 关闭屏幕 close(lp->fd); } /* lcd_draw_point:能够在指定的屏幕的指定的位置显示一个指定的颜色 */ void lcd_draw_point(lcd *lp, int x, int y, int colour) { if (lp == NULL) return ; if (x >= lp->xres || x < 0 || y >= lp->yres || y < 0) { printf("brother,point out of the lcd\n"); return ; } *(lp->plcd + lp->xres * y + x) = colour; } /* lcd_clear:把屏幕刷成指定的颜色 */ void lcd_clear(lcd *lp, int colour) { for (int y = 0; y < lp->yres; y++) { for (int x = 0; x < lp->xres; x++) { lcd_draw_point(lp, x, y, colour); } } } /* 在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的矩形 */ void lcd_draw_rect(lcd *lp, int x, int y, int w, int h, int colour) { if (lp == NULL) return ; for (int i = x; i <= x + w; i++) { for (int j = y; j <= y + h; j++) { if (i >= 0 && i < lp->xres && j >= 0 && j < lp->yres) { lcd_draw_point(lp, i, j, colour); } } } } /* 在屏幕上指定的位置(x,y)显示一个指定大小,指定颜色的圆 */ void lcd_draw_cir(lcd *lp, int x, int y, int r, int colour) { if (lp == NULL) return ; for (int i = x - r; i <= x + r; i++) { for (int j = y - r; j <= y + r; j++) { if (i >= 0 && i < lp->xres && j >= 0 && j < lp->yres && (i - x) * (i - x) + (j - y) * (j - y) <= r * r) { lcd_draw_point(lp, i, j, colour); } } } }
5. 模块化思想:把大问题分解成小问题
1. 建立一个源代码工程
src/ 源代码 (.c,.h,Makefile)
libs/ 项目用到的第三方库 (jpg解析)
inc/ 第三方库的头文件
readme.txt 项目介绍,功能,作者,编译方法,bugs......
changelog.txt 修改日志
2. 确定程序的大致框架
思路流程