在Linux系统中显示设备被归类为framebuffer设备即帧缓冲设备。对于应用层来说,Linux系统把LCD等显示设备抽象为一块显存缓冲区,允许上层应用程序直接对该显存缓冲区进行读写操作,不必关心硬件的物理地址,大大简化了读写速度和方便性。
一、使用ioctl函数读取屏幕的参数信息想要操作一块LCD屏幕,首先得明确了解屏幕的参数信息,比如分辨率等等。这样才能计算出显示缓冲区应该设置的大小。读取信息采用ioctl函数。
ioctl函数
#includeint ioctl(int fd, unsigned long request, ...); 第一个参数fd为文件描述符 第二个参数为request,对于LCD屏幕来说常用的request有 FBIOGET_VSCREENINFO:获取LCD的可变参数信息 FBIOPUT_VSCREENINFO: 设置LCD的可变参数信息 FBIOGET_FSCREENINFO: 获取LCD的不变参数信息 第三个参数为保存信息的结构体。
宏 定 义 FBIOGET_VSCREENINFO 、 FBIOPUT_VSCREENINFO 、
FBIOGET_FSCREENINFO 以及 2 个数据结构 struct fb_var_screeninfo 和 struct fb_fix_screeninfo 都定义在
头文件中
获取LCD屏幕信息:
#include#include #include #include #include #include #include #include int main(int argc, char *argv[]) { struct fb_fix_screeninfo fb_fix; //保存屏幕的固定参数 struct fb_var_screeninfo fb_var; //保存屏幕的可变参数 int fd; //保存文件描述符 //检查命令行参数是否正确 if(argc != 2) { fprintf(stderr, "Error used!n"); exit(-1); } //打开LCD文件 fd = open(argv[1], O_RDWR); //读写方式打开 if(fd < 0) { perror("open failed!n"); exit(-1); } ioctl(fd, FBIOGET_VSCREENINFO, &fb_var); ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix); printf("分辨率: %d*%dn" "像素深度 bpp: %dn" "一行的字节数: %dn" "像素格式: R<%d %d> G<%d %d> B<%d %d>n", fb_var.xres, fb_var.yres, fb_var.bits_per_pixel, fb_fix.line_length, fb_var.red.offset, fb_var.red.length, fb_var.green.offset, fb_var.green.length, fb_var.blue.offset, fb_var.blue.length); close(fd); exit(0); }
运行结果如下:
二、使用mmap函数将显示缓冲区映射到用户空间对于Framebuffer编程普通的I/O读写方式,对于数据量比较大的时候效率低并且很麻烦。因此通常采用存储映射I/O的方式将显示器的显示缓冲区(显存)映射到进程的地址空间中,这样应用程序便可直接对显示缓冲区进行读写操作。由系统调用 mmap()来实现。 其函数原型如下所示:
#include三、LCD应用编程基本操作------水平方向void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 使用该函数需要包含头文件 。 函数参数和返回值含义如下: addr: 参数 addr 用于指定映射到内存区域的起始地址。通常将其设置为 NULL,这表示由系统选择该 映射区的起始地址,这是最常见的设置方式;如果参数 addr 不为 NULL,则表示由自己指定映射区的起始 地址,此函数的返回值是该映射区的起始地址。 length: 参数 length 指定映射长度, 表示将文件中的多大部分映射到内存区域中,以字节为单位,譬如 length=1024 * 4,表示将文件的 4K 字节大小映射到内存区域中。 offset: 文件映射的偏移量,通常将其设置为 0,表示从文件头部开始映射;所以参数 offset 和参数 length就确定了文件的起始位置和长度, 将文件的这部分映射到内存区域中 fd: 文件描述符,指定要映射到内存区域中的文件。 prot: 参数 prot 指定了映射区的保护要求,可取值如下: PROT_EXEC: 映射区可执行; PROT_READ: 映射区可读; PROT_WRITE: 映射区可写; PROT_NONE: 映射区不可访问。 flags: 参数 flags 可影响映射区的多种属性, 参数 flags 必须要指定以下两种标志之一:⚫ MAP_SHARED: 此标志指定当对映射区写入数据时,数据会写入到文件中,也就是会将写入到映 射区中的数据更新到文件中,并且允许其它进程共享。 MAP_PRIVATE: 此标志指定当对映射区写入数据时,会创建映射文件的一个私人副本(copy-onwrite),对映射区的任何操作都不会更新到文件中,仅仅只是对文件副本进行读写。 除此之外,还可将以下标志中的 0 个或多个组合到参数 flags 中,通过按位或运算符进行组合: MAP_FIXED: 在未指定该标志的情况下,如果参数 addr 不等于 NULL,表示由调用者自己指定 映射区的起始地址,但这只是一种建议、而并非强制,所以内核并不会保证使用参数 addr 指定的 值作为映射区的起始地址;如果指定了 MAP_FIXED 标志,则表示要求必须使用参数 addr 指定的 值作为起始地址,如果使用指定值无法成功建立映射时,则放弃!通常,不建议使用此标志,因为 这不利于移植。 MAP_ANONYMOUS: 建立匿名映射, 此时会忽略参数 fd 和 offset,不涉及文件,而且映射区域 无法和其它进程共享。 MAP_ANON: 与 MAP_ANONYMOUS 标志同义,不建议使用。 MAP_DENYWRITE: 该标志被忽略。 MAP_EXECUTABLE: 该标志被忽略。 MAP_FILE: 兼容性标志,已被忽略。 MAP_LOCKED: 对映射区域进行上锁。 除了以上标志之外,还有其它一些标志,这里便不再介绍,可通过 man 手册进行查看。 在众多标志当 中,通常情况下,参数 flags 中只指定了 MAP_SHARED。 返回值: 成功情况下,函数的返回值便是映射区的起始地址;发生错误时,返回(void *)-1, 通常使用 MAP_FAILED 来表示, 并且会设置 errno 来指示错误原因。
#include#include #include #include #include #include #include #include #include static int width; //LCD X 分辨率 static int height; //LCD Y 分辨率 static unsigned short *screen_base = NULL; //映射后的显存基地址 static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color); //画点函数 static void lcd_draw_line(unsigned int x, unsigned int y, int dir,unsigned int length, unsigned int color);//画线函数 static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,unsigned int start_y, unsigned int end_y,unsigned int color); //画矩形 static void lcd_fill(unsigned int start_x, unsigned int end_x,unsigned int start_y, unsigned int end_y,unsigned int color); //填充矩形 unsigned int argb8888_to_rgb565(unsigned int color); //RGB88 to RGB565 int main(int argc, char *argv[]) { struct fb_fix_screeninfo fb_fix; //保存屏幕的不变参数 struct fb_var_screeninfo fb_var; //保存屏幕的可变参数 unsigned int screen_size; int fd; if(argc != 2) { fprintf(stderr, "Erroe used!n"); exit(-1); } fd = open(argv[1], O_RDWR); if(fd < 0) { perror("open failed!n"); exit(-1); } ioctl(fd, FBIOGET_VSCREENINFO, &fb_var); ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix); screen_size = fb_fix.line_length * fb_var.yres; width = fb_var.xres; height = fb_var.yres; printf("width = %d,height = %dn",width,height); screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0); if ((void *)screen_base == MAP_FAILED) { perror("mmap error"); close(fd); exit(-1); } lcd_draw_line(0, height * 0.5, 1, width, 0xFFFFFF);//白色线 lcd_draw_line(width * 0.5, 0, 0, height, 0xFFFFFF);//白色线 munmap(screen_base, screen_size); //取消映射 close(fd); //关闭文件 exit(0); //退出进程 } unsigned int argb8888_to_rgb565(unsigned int color) { unsigned int temp = (color); temp = (temp & 0xF80000UL) >> 8 | (temp & 0xFC00UL) >> 5 | (temp & 0xF8UL) >> 3; return temp; } static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color) { unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值 if (x >= width) x = width - 1; if (y >= height) y = height - 1; screen_base[y * width + x] = rgb565_color; } static void lcd_draw_line(unsigned int x, unsigned int y, int dir,unsigned int length, unsigned int color) { unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值 unsigned int end; unsigned long temp; if (x >= width) x = width - 1; if (y >= height) y = height - 1; temp = y * width + x;//定位到起点 if (dir) { //水平线 end = x + length - 1; if (end >= width) end = width - 1; for ( ; x <= end; x++, temp++) screen_base[temp] = rgb565_color; } else { //垂直线 end = y + length - 1; if (end >= height) end = height - 1; for ( ; y <= end; y++, temp += width) screen_base[temp] = rgb565_color; } } static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x, unsigned int start_y, unsigned int end_y, unsigned int color) { int x_len = end_x - start_x + 1; int y_len = end_y - start_y - 1; lcd_draw_line(start_x, start_y, 1, x_len, color);//上边 lcd_draw_line(start_x, end_y, 1, x_len, color); //下边 lcd_draw_line(start_x, start_y + 1, 0, y_len, color);//左边 lcd_draw_line(end_x, start_y + 1, 0, y_len, color);//右边 } static void lcd_fill(unsigned int start_x, unsigned int end_x, unsigned int start_y, unsigned int end_y, unsigned int color) { unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值 unsigned long temp; unsigned int x; if (end_x >= width) end_x = width - 1; if (end_y >= height) end_y = height - 1; temp = start_y * width; //定位到起点行首 for ( ; start_y <= end_y; start_y++, temp+=width) { for (x = start_x; x <= end_x; x++) screen_base[temp + x] = rgb565_color; } }
代码流程:
首先调用 open()打开 LCD 设备文件得到文件描述符 fd;
接着使用 ioctl 函数获取 LCD 的可变参数信息和固定参数信息,通过得到的信息计算 LCD 显存大小、得到 LCD 屏幕的分辨率,从图 19.3.1 可知, ALPHA/Mini I.MX6U 开发板出厂系统将 LCD 实现为一个 RGB565 显示设备,所以程序中自定义的 4 个函数在操作 LCD 像素点时、都是以 RGB565的格式写入颜色值。
接着使用 mmap 建立映射;
映射成功之后就可以在应用层直接操作 LCD 显存了,调用自定义的函数在 LCD 上画线
操作完成之后,调用 munmap 取消映射,调用 close 关闭 LCD 设备文件,退出程序。
有时候需要进行垂直显示也就是将本屏幕变为600 * 1024分辨率,其实很简单只需要在计算坐标的时候改变一下公式即可实现。
水平方向像素点位置计算公式:
screen_base[y * width + x]
垂直方向像素点位置计算公式:
screen_base[(height - 1- x) * width + y)];



