本章学习 Linux 下的 Framebuffer 应用编程,通过对本章内容的学习,大家将会了解到 Framebuffer 设备究竟是什么?以及如何编写应用程序来操控 FrameBuffer 设备。
本章将会讨论如下主题。
⚫ 什么是 Framebuffer 设备?
⚫ LCD 显示的基本原理;
⚫ 使用存储映射 I/O 方式编写 LCD 应用程序。
⚫ 在 LCD 上打点、画线;
⚫ BMP 图片格式详解;
⚫ 在 LCD 上显示图片;
什么是 FrameBuffer
Frame 是帧的意思,buffer 是缓冲的意思,所以 Framebuffer 就是帧缓冲,这意味着 Framebuffer 就是一块内存,里面保存着一帧图像。帧缓冲(framebuffer)是 Linux 系统中的一种显示驱动接口,它将显示设备(譬如 LCD)进行抽象、屏蔽了不同显示设备硬件的实现,对应用层抽象为一块显示内存(显存),它允许上层应用程序直接对显示缓冲区进行读写操作,而用户不必关心物理显存的位置等具体细节,这些都由Framebuffer 设备驱动来完成。
所以在 Linux 系统中,显示设备被称为 FrameBuffer 设备(帧缓冲设备),所以 LCD 显示屏自然而言就是 FrameBuffer 设备。FrameBuffer 设备对应的设备文件为/dev/fbX(X 为数字,0、1、2、3 等),Linux下可支持多个 FrameBuffer 设备,最多可达 32 个,分别为/dev/fb0到/dev/fb31,开发板出厂系统中,/dev/fb0设备节点便是 LCD 屏。
应用程序读写/dev/fbX 就相当于读写显示设备的显示缓冲区(显存),譬如 LCD 的分辨率是 800*480,每一个像素点的颜色用 24 位(譬如 RGB888)来表示,那么这个显示缓冲区的大小就是 800 x 480 x 24 / 8 =1152000 个字节。譬如执行下面这条命令将 LCD 清屏,也就是将其填充为黑色(假设 LCD 对应的设备节点是/dev/fb0,分辨率为 800*480,RGB888 格式):
dd if=/dev/zero of=/dev/fb0 bs=1024 count=1125
这条命令的作用就是将 1125x1024 个字节数据全部写入到 LCD 显存中,并且这些数据都是 0x0。
LCD 的基础知识
关于 LCD 相关的基础知识,本书不再介绍,开发板配套提供的驱动教程中已经有过详细的介绍,除此之外,网络上也能找到相关内容。
可参考:
51单片机外设篇:点阵式LCD_单片机lcd-CSDN博客
STM32实战总结:HAL之FSMC控制TFT-LCD_stm32hal库fsmc-CSDN博客
基于ILI9341的TFT-LCD屏幕显示要点总结_ili9341显示屏怎么接线-CSDN博客
LCD 应用编程介绍
本小节介绍如何对 FrameBuffer 设备(譬如 LCD)进行应用编程,通过上面的介绍,相信大家应该已经知道如何操作 LCD 显示设备了,应用程序通过对 LCD 设备节点/dev/fb0(假设 LCD 对应的设备节点是/dev/fb0)进行 I/O 操作即可实现对 LCD 的显示控制,实质就相当于读写了 LCD 的显存,而显存是 LCD 的显示缓冲区,LCD 硬件会从显存中读取数据显示到 LCD 液晶面板上。
在应用程序中,操作/dev/fbX 的一般步骤如下:
①、首先打开/dev/fbX 设备文件。
②、使用 ioctl()函数获取到当前显示设备的参数信息,譬如屏幕的分辨率大小、像素格式,根据屏幕参数计算显示缓冲区的大小。
③、通过存储映射 I/O 方式将屏幕的显示缓冲区映射到用户空间(mmap)。
④、映射成功后就可以直接读写屏幕的显示缓冲区,进行绘图或图片显示等操作了。
⑤、完成显示后,调用 munmap()取消映射、并调用 close()关闭设备文件。
从上面介绍的操作步骤来看,LCD 的应用编程还是非常简单的,这些知识点都是在前面的入门篇中给大家介绍过。
使用 ioctl()获取屏幕参数信息
当打开 LCD 设备文件之后,需要先获取到 LCD 屏幕的参数信息,譬如 LCD 的 X 轴分辨率、Y 轴分辨率以及像素格式等信息,通过这些参数计算出 LCD 显示缓冲区的大小。
通 过 ioctl() 函 数 来 获 取 屏 幕 参 数 信息, 对 于 Framebuffer 设备来说, 常 用 的 request 包 括FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO。
⚫ FBIOGET_VSCREENINFO:表示获取 FrameBuffer 设备的可变参数信息,可变参数信息使用 struct fb_var_screeninfo 结 构 体 来 描 述 , 所 以 此 时 ioctl() 需 要 有 第 三 个 参 数 , 它 是 一 个 struct fb_var_screeninfo *指针,指向 struct fb_var_screeninfo 类型对象,调用 ioctl()会将 LCD 屏的可变参数信息保存在 struct fb_var_screeninfo 类型对象中,如下所示:
struct fb_var_screeninfo fb_var; ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
⚫ FBIOPUT_VSCREENINFO:表示设置 FrameBuffer 设备的可变参数信息,既然是可变参数,那说明应用层可对其进行修改、重新配置,当然前提条件是底层驱动支持这些参数的动态调整,譬如在我们的 Windows 系统中,用户可以修改屏幕的显示分辨率,这就是一种动态调整。同样此时 ioctl()需要有第三个参数,也是一个 struct fb_var_screeninfo *指针,指向 struct fb_var_screeninfo 类型对象,表示用 struct fb_var_screeninfo 对象中填充的数据设置 LCD,如下所示:
struct fb_var_screeninfo fb_var = {0}; /* 对 fb_var 进行数据填充 */ ...... ...... /* 设置可变参数信息 */ ioctl(fd, FBIOPUT_VSCREENINFO, &fb_var);
⚫ FBIOGET_FSCREENINFO:表示获取 FrameBuffer 设备的固定参数信息,既然是固定参数,那就意味着应用程序不可修改。固定参数信息使用struct fb_fix_screeninfo结构体来描述,所以此时ioctl()需要有第三个参数,它是一个 struct fb_fix_screeninfo *指针,指向 struct fb_fix_screeninfo 类型对象,调用 ioctl()会将 LCD 的固定参数信息保存在 struct fb_fix_screeninfo 对象中,如下所示:
struct fb_fix_screeninfo fb_fix; ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
上面所提到的三个宏定义 FBIOGET_VSCREENINFO 、 FBIOPUT_VSCREENINFO 、FBIOGET_FSCREENINFO 以及 2 个数据结构 struct fb_var_screeninfo 和 struct fb_fix_screeninfo 都定义在<linux/fb.h>头文件中,所以在我们的应用程序中需要包含该头文件。
#define FBIOGET_VSCREENINFO 0x4600 #define FBIOPUT_VSCREENINFO 0x4601 #define FBIOGET_FSCREENINFO 0x4602
struct fb_var_screeninfo 结构体
struct fb_var_screeninfo 结构体内容如下所示:
通过 xres、yres 获取到屏幕的水平分辨率和垂直分辨率,bits_per_pixel 表示像素深度 bpp,即每一个像素点使用多少个 bit 位来描述它的颜色,通过 xres * yres * bits_per_pixel / 8 计算可得到整个显示缓存区的大小。
red、green、blue 描述了 RGB 颜色值中 R、G、B 三种颜色通道分别使用多少 bit 来表示以及它们各自的偏移量,通过 red、green、blue 变量可知道 LCD 的 RGB 像素格式,譬如是 RGB888 还是 RGB565,亦或者是 BGR888、BGR565 等。struct fb_bitfield 结构体如下所示:
struct fb_fix_screeninfo 结构体
struct fb_fix_screeninfo 结构体内容如下所示:
smem_start 表示显存的起始地址,这是一个物理地址,当然在应用层无法直接使用;smem_len 表示显存的长度,这个长度并一定等于 LCD 实际的显存大小。line_length 表示屏幕的一行像素点有多少个字节,通常可以使用 line_length * yres 来得到屏幕显示缓冲区的大小。
通过上面介绍,接下来我们编写一个示例代码,获取 LCD 屏幕的参数信息,示例代码如下所示:本例程源码对应的路径为:开发板光盘->11、Linux C 应用编程例程源码->19_lcd->lcd_info.c。
首先打开 LCD 设备文件,开发板出厂系统,LCD 对应的设备文件为/dev/fb0;打开设备文件之后得到文件描述符 fd,接着使用 ioctl()函数获取 LCD 的可变参数信息和固定参数信息,并将这些信息打印出来。
在测试之前,需将 LCD 屏通过软排线连接到开发板(掉电情况下连接),连接好之后启动开发板。
使用交叉编译工具编译上述示例代码,将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下,并直接运行它,如下所示:
笔者使用的是 7 寸 800*480 RGB 屏,与上图打印显示的分辨率 800*480 是相符的;像素深度为 16,也就意味着一个像素点的颜色值将使用 16bit(也就是 2 个字节)来表示;一行的字节数为 1600,一行共有 800个像素点,每个像素点使用 16bit 来描述,一共就是 800*16/8=1600 个字节数据,这也是没问题的。
打印出像素格式为 R<11 5> G<5 6> B<0 5>,分别表示 R、G、B 三种颜色分量对应的偏移量和长度,第一个数字表示偏移量,第二个参数为长度,从打印的结果可知,16bit 颜色值中高 5 位表示 R 颜色通道、中间 6 位表示 G 颜色通道、低 5 位表示 B 颜色通道,所以这是一个 RGB565 格式的显示设备。
Tips:正点原子的 RGB LCD 屏幕,包括 4.3 寸 800*480、4.3 寸 480*272、7 寸 800*480、7 寸 1024*600 以及 10.1 寸 1280*800 硬件上均支持 RGB888,但 ALPHA/Mini I.MX6U 开发板出厂系统中,LCD 驱动程序将其实现为一个 RGB565 格式的显示设备,用户可修改设备树使其支持 RGB888,或者通过 ioctl 修改。
前面我们提到可以通过 ioctl()去设置 LCD 的可变参数,使用 FBIOPUT_VSCREENINFO 宏,但不太建议大家去改这些参数,如果 FrameBuffer 驱动程序支持不够完善,改完之后可能会出现一些问题!这里就不再演示了。
使用 mmap()将显示缓冲区映射到用户空间
在入门篇 13.5 小节中给大家介绍了存储映射 I/O 这种高级 I/O 方式,它的一个非常经典的使用场景便是用在 Framebuffer 应用编程中。通过 mmap()将显示器的显示缓冲区(显存)映射到进程的地址空间中,这样应用程序便可直接对显示缓冲区进行读写操作。
为什么这里需要使用存储映射 I/O 这种方式呢?其实使用普通的 I/O 方式(譬如直接 read、write)也是可以的,只是,当数据量比较大时,普通 I/O 方式效率较低。假设某一显示器的分辨率为 1920 * 1080,像素格式为 ARGB8888,针对该显示器,刷一帧图像的数据量为 1920 x 1080 x 32 / 8 = 8294400 个字节(约等于 8MB),这还只是一帧的图像数据,而对于显示器来说,显示的图像往往是动态改变的,意味着图像数据会被不断更新。
在这种情况下,数据量是比较庞大的,使用普通 I/O 方式必然导致效率低下,所以才会采用存储映射I/O 方式。
LCD 应用编程练习之 LCD 基本操作
本小节编写应用程序,在 LCD 上实现画点(俗称打点)、画线、画矩形等基本 LCD 操作,示例代码如下所示:
本例程源码对应的路径为:开发板光盘->11、Linux C 应用编程例程源码->19_lcd->lcd_test.c。
在示例代码中定义了一个宏 argb8888_to_rgb565,用于实现将 unsigned int 类型的颜色(也就是ARGB8888 颜色)转换为 RGB565 颜色。
程序中自定义了 4 个函数:
lcd_draw_point:用于实现画点、打点操作,参数 x 和 y 指定像素点的位置,参数 color 表示颜色。
lcd_draw_line:用于实现画线操作,参数 x 和 y 指定线的起始位置;参数 dir 表示方向,水平方向(dir!=0)还是垂直方向(dir=0),不支持斜线画法,画斜线需要一些算法去操作,这不是本章内容需要去关注的知识点;参数 length 表示线的长度,以像素为单位;参数 color 表示线条的颜色。
lcd_draw_rectangle:用于实现画矩形操作,参数 start_x 和 start_y 指定矩形左上角的位置;参数 end_x和 end_y 指定矩形右下角的位置;参数 color 指定矩形 4 个边的线条颜色。
lcd_fill:将一个指定的矩形区域填充为参数 color 指定的颜色,参数 start_x 和 start_y 指定矩形左上角的位置;参数 end_x 和 end_y 指定矩形右下角的位置;参数 color 指定矩形区域填充的颜色。
具体代码的实现各位读者自己去看,非常简单,来看下 main()中做了哪些事情:
⚫ 首先调用 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 设备文件,退出程序。
编译应用程序:
将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下,执行应用程序(在测试之前,先将出厂系统对应的 Qt GUI 应用程序退出):
此时 LCD 屏上将会显示程序中绘制的方块、矩形、以及线条:
忽略手机拍摄的问题,实际效果各位读者运行程序便知。
LCD应用编程练习之显示BMP图片
本小节介绍如何在 LCD 上显示一张 BMP 图片,在编写程序之前,首先需要对 BMP 格式图片进行简单地介绍,自行百度,此处不赘述了,可参考:
图形图像基础 之 bmp介绍_bmp图片-CSDN博客
更多内容参考正点原子的应用开发手册吧。
暂略。
在LCD上显示jpeg图像
我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP。上一章给大家介绍了如何在 LCD 上显示 BMP 图片,详细介绍了 BMP 图像的格式;BMP 图像虽然没有失真、并且解析简单,但是由于图像数据没有进行任何压缩,因此,BMP 图像文件所占用的存储空间很大,不适合存储在磁盘设备中。
而 JPEG(或 JPG)、PNG 则是经过压缩处理的图像格式,将图像数据进行压缩编码,大大降低了图像文件的大小,适合存储在磁盘设备中,所以很常用。本章我们就来学习如何在 LCD 屏上显示 jpeg 图像,下一章将向大家介绍如何在 LCD 屏上显示 png 图像。
本章将会讨论如下主题。
⚫ JPEG 简介;
⚫ libjpeg 库简介;
⚫ libjpeg 库移植;
⚫ 使用 libjpeg 库函数对 JPEG 图像进行解码;
JPEG 简介
JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准。
JPEG 由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度;JPEG 虽然是有损压缩,但这个损失的部分是人的视觉不容易察觉到的部分,它充分利用了人眼对计算机色彩中的高频信息部分不敏感的特点,来大大节省了需要处理的数据信息。
JPEG 压缩文件通常以.jpg 或.jpeg 作为文件后缀名,关于 JPEG 压缩标准就给大家介绍这么多,这些内容都是笔者从网络上截取下来的,对此感兴趣的读者可以自行从网络上查阅这些信息。
libjpeg 简介
JPEG 压缩标准使用了一套压缩算法对原始图像数据进行了压缩得到.jpg 或.jpeg 图像文件,如果想要在LCD 上显示.jpg 或.jpeg 图像文件,则需要对其进行解压缩、以得到图像的原始数据,譬如 RGB 数据。
既然压缩过程使用了算法,那对.jpg 或.jpeg 图像文件进行解压同样也需要算法来处理,当然,笔者并不会教大家如何编写解压算法,这些算法的实现也是很复杂的,笔者肯定不会,自然教不了大家!但是,我们可以使用别人写好的库、调用别人写好的库函数来解压.jpg 或.jpeg 图像文件,也就是本小节要向大家介绍的 libjpeg 库。
libjpeg 是一个完全用 C 语言编写的函数库,包含了 JPEG 解码(解压缩)、JPEG 编码(创建压缩)和其他的 JPEG 功能的实现。可以使用 libjpeg 库对.jpg 或.jpeg 压缩文件进行解压或者生成.jpg 或.jpeg 压缩文件。
libjpeg 是一个开源 C 语言库,我们获取到它的源代码。
更多内容参考正点原子的应用开发手册吧。
暂略。
在LCD上显示png图片
上一章介绍了如何使用 libjpeg 库对 jpeg 图像进行解码、并显示到 LCD 屏上,除了 jpeg 图像之外,png图像也很常见,那本章我们就来学习如何对 png 图像进行解码、并显示到 LCD 屏上。
本章将会讨论如下主题。
⚫ PNG 简介;
⚫ libpng 库简介;
⚫ libpng 库移植;
⚫ 使用 libpng 库函数对 PNG 图像进行解码;
PNG 简介
以下的这些内容都是从网络上截取下来的。
PNG(便携式网络图形格式 PortableNetwork Graphic Format,简称 PNG)是一种采用无损压缩算法的位图格式,其设计目的是试图替代 GIF 和 TIFF 文件,同时增加一些 GIF 文件所不具备的特性。PNG 使用从LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小,并且支持透明效果,所以被广泛使用。
特点
⚫ 无损压缩:PNG 文件采用 LZ77 算法的派生算法进行压缩,其结果是获得高的压缩比,不损失数据。它利用特殊的编码方法标记重复出现的数据,因而对图像的颜色没有影响,也不可能产生颜色的损失,这样就可以重复保存而不降低图像质量。
⚫ 体积小:在保证图片清晰、逼真、不失真的前提下,PNG 使用从 LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小;
⚫ 索引彩色模式:PNG-8 格式与 GIF 图像类似,同样采用 8 位调色板将 RGB 彩色图像转换为索引彩色图像。图像中保存的不再是各个像素的彩色信息,而是从图像中挑选出来的具有代表性的颜色编号,每一编号对应一种颜色,图像的数据量也因此减少,这对彩色图像的传播非常有利。
⚫ 更优化的网络传输显示:PNG 图像在浏览器上采用流式浏览,即使经过交错处理的图像会在完全下载之前提供浏览者一个基本的图像内容,然后再逐渐清晰起来。它允许连续读出和写入图像数据,这个特性很适合于在通信过程中显示和生成图像。
⚫ 支持透明效果:PNG 可以为原图像定义 256 个透明层次,使得彩色图像的边缘能与任何背景平滑地融合,从而彻底地消除锯齿边缘。这种功能是 GIF 和 JPEG 没有的。
关于 PNG 格式就介绍这么多。
libpng 简介
对于 png 图像,我们可以使用 libpng 库对其进行解码,跟 libjpeg 一样,它也是一套免费、开源的 C 语言函数库,支持对 png 图像文件解码、编码等功能。
zlib 移植
zlib 其实是一套包含了数据压缩算法的函式库,此函数库为自由软件,是一套免费、开源的 C 语言函数库,所以我们可以获取到它源代码。
libpng 依赖于 zlib 库,所以要想移植 libpng 先得移植 zlib 库才可以,zlib 也好、libpng 也好,其实移植过程非常简单,无非就是下载源码、编译源码这样的一些工作,那本小节就向大家介绍如何移植 zlib。
在移植之前,先给大家说明一下,我们的开发板出厂系统都是已经移植好了这些库,其实是可以直接使用的,但是作为学习,必须要自己亲自把这些库给移植到开发板,这是非常重要的!
更多内容参考正点原子的应用开发手册吧。
暂略。
LCD 横屏切换为竖屏
之前在技术交流群里边有人提到了 LCD 横屏切换为竖屏的问题,笔者觉得还是很有必要给大家讲一下,所以这里单独做一章内容来讲一讲怎么样实现 LCD 横屏切换为竖屏,其实个人觉得还是非常简单地。首先给大家普及一个基本的知识点,这种横屏、竖屏的切换与驱动程序无关,是应用层需要去解决的一个问题!
本章将会讨论如下主题。
⚫ 横屏显示如何切换为竖屏显示;
⚫ 编写代码验证;
更多内容参考正点原子的应用开发手册吧。
暂略。
在 LCD 上显示字符
前面几个章节向大家介绍了如何在 LCD 屏上显示图像,本章我们就来学习下,如何在 LCD 屏上显示字符,譬如数字、字母以及中文字符等,相信很多读者都已经迫不及待的想要了解了。OK,废话不多说直接开干!
本章将会讨论如下主题。
⚫ 使用原始的方式:自己取模显示字符
⚫ 使用 freetype 访问字体文件;
⚫ freetype 简介;
⚫ freetype 移植;
⚫ freetype 的使用介绍。
更多内容参考正点原子的应用开发手册吧。
暂略。