Bootstrap

【Linux学习】LCD开发(1)

以下是关于Linux LCD的介绍和基本使用,博客结尾有LCDdemo,希望对您有所帮助。

如果我的文章让你感兴趣,可以点赞收藏关注一波。

作者:小海编程心语录-CSDN博客

目录

1.FrameBuffer

2. lcd屏基本概念

2.1像素

 2.2分辨率

2.3色深

 3.LCD 应用编程介绍

4.使用 ioctl()获取屏幕参数信息

5.存储映射

5.1 mmap()函数

5.2munmap 

6.颜色显示demo


1.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 的分辨率是 1024*600,每一个像素点的颜色用 24 位(譬如RGB888)来表示,那么这个显示缓冲区的大小就是1024 x 600 x 24 / 8 =1843200 个字节。

2. lcd屏基本概念

2.1像素

像素(pixel)是屏幕上显示颜色的最小单位,一个像素点就相当于一个RGB 小灯,通过控制R、G、B 这三种颜色的亮度就 可以显示出各种各样的色彩,而图像实际上是由非常多的像素点拼凑而成的,每个像素点各司其职,分别显示自己的颜色,这样也就构成了我们看到的一副完整的图像

像素点由红绿蓝(R\G\B)三个子像素构成,通过混合三个子像素的颜色比例,来合成一切想要的颜色

 2.2分辨率

分辨率=画面水平方向的像素值 * 画面垂直方向的像素值,指的是像素点的数目,我们平常所说的1080p就是水平1920个像素点,竖直1080个像素点,也就是1920 * 1080, 2k是2560 * 1440,4k则是3840*2160,分辨率越高,所需要的显存越大。

分辨率与屏幕尺寸没有任何关系,有23寸的1080p屏幕,也有16寸的4k屏。只不过在不同尺寸的屏幕塞下更多的像素点罢了。

2.3色深

色深指在位图或视频帧缓冲区中用于表示单个像素的颜色的比特数,或者是用于单个像素的每个颜色分量的比特数,简单来说就是用来描述色彩丰富程度的参数。

色深越高,色彩数量就越多,所以颜色过渡越自然、丝滑,条纹状的等高线(或者说「色彩断层」)也越少。当色深足够高的时候,颜色和颜色之间可以非常平滑地过渡。

比如色深为1bit表示只能显示2^1=2个色阶,也就是指的是红/绿/蓝三者的任何一个原色都能被平均分成2份。以红色举例,一份是最暗的黑,一份是最亮的红。 色深为2bit表示红/绿/蓝能被平均分成2^2=4份,这时候除了最亮和最暗,就会有中间的过度。色深为8bit表示红/绿/蓝能被平均分成2^8=256份。

一般一个R、G、B 这三部分分别使用8bit 的数据,那么一个像素点就是8bit*3=24bit,也就是说一个像素点3 个字节,这种像素格式称为RGB888。如果在加入8bit 的Alpha(透明)通道的话一个像素点就是32bit,也就是4 个字节,这种像素格式称ARGB8888

 3.LCD 应用编程介绍

本节介绍如何对 FrameBuffer 设备(譬如 LCD)进行应用编程,应用程序通过对 LCD 设备节点/dev/fb0(假设 LCD 对应的设备节点是 /dev/fb0)进行 I/O 操作即可实现对 LCD 的显示控制,实质就相当于读写了 LCD 的显存,而显存是 LCD 的 显示缓冲区, LCD 硬件会从显存中读取数据显示到 LCD 液晶面板上。

在应用程序中,操作/dev/fbX 的一般步骤如下:

①、首先打开/dev/fbX 设备文件。

②、 使用 ioctl()函数获取到当前显示设备的参数信息,譬如屏幕的分辨率大小、像素格式,根据屏幕参数计算显示缓冲区的大小。

③、通过存储映射 I/O 方式将屏幕的显示缓冲区映射到用户空间(mmap)。

④、映射成功后就可以直接读写屏幕的显示缓冲区,进行绘图或图片显示等操作了。

⑤、完成显示后, 调用 munmap()取消映射、并调用 close()关闭设备文件。

4.使用 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);

5.存储映射

存储映射 I/O(memory-mapped I/O) 是一种基于内存区域的高级 I/O 操作,它能将一个文件映射到进程 地址空间中的一块内存区域中, 当从这段内存中读数据时,就相当于读文件中的数据(对文件进行 read 操 作) ,将数据写入这段内存时,则相当于将数据直接写入文件中(对文件进行 write 操作) 。这样就可以在不使用基本 I/O 操作函数 read()和 write()的情况下执行 I/O 操作。

5.1 mmap()函数

为了实现存储映射 I/O 这一功能,我们需要告诉内核将一个给定的文件映射到进程地址空间中的一块 内存区域中,这由系统调用 mmap()来实现。 其函数原型如下所示:

//函数原型
#include <sys/mman.h>
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:映射区不可访问。

对指定映射区的保护要求不能超过文件 open()时的访问权限,譬 如,文件是以只读权限方式打开的,那么对映射区的不能指定为 PROT_WRITE。

flags:

参数 flags 可影响映射区的多种属性, 参数 flags 必须要指定以下两种标志之一:

MAP SHARED:
此标志指定当对映射区写入数据时,数据会写入到文件中,也就是会将写入到映射区中的数据更新到文件中,并且允许其它进程共享。
MAP PRIVATE:
此标志指定当对映射区写入数据时,会创建映射文件的一个私人副本(copy-onwrite),对映射区的任何操作都不
会更新到文件中,仅仅只是对文件副本进行读写。

返回值:

成功情况下,函数的返回值便是映射区的起始地址;发生错误时,返回(void *)-1, 通常使用 MAP_FAILED 来表示, 并且会设置 errno 来指示错误原因。

5.2 munmap()函数

通过 open()打开文件,需要使用 close()将将其关闭;同理,通过 mmap()将文件映射到进程地址空间中的一块内存区域中,当不再需要时,必须解除映射,使用munmap()解除映射关系,其函数原型如下所示:

//函数原型
#include <sys/mman.h>
int munmap(void *addr, size_t length);

 函数参数含义如下:

munmap()系统调用解除指定地址范围内的映射, 参数 addr 指定待解除映射地址范围的起始地址,它必须是系统页大小的整数倍;

参数 length 是一个非负整数,指定了待解除映射区域的大小(字节数),被解除映射的区域对应的大小也必须是系统页大小的整数倍

需要注意的是,当进程终止时也会自动解除映射(如果程序中没有显式调用 munmap()),但调用 close() 关闭文件时并不会解除映射。

通常将参数 addr 设置为 mmap()函数的返回值,将参数 length 设置为 mmap()函数的参数 length,表示解除整个由 mmap()函数所创建的映射。

6.颜色显示demo

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

int main()
{
    struct fb_var_screeninfo fb_var;    //可变参数信息
    struct fb_fix_screeninfo fb_fix;    //固定参数信息
    unsigned int size;     //定义无符号屏幕缓冲区大小
    int *screen = NULL;    //定义内存映射到内存区域的起始地址为NULL

    int fd = open("/dev/fb0", O_RDWR);   //以可读、可写方式打开文件   fd文件描述符
    if(fd < 0)   //判断一下,打开失败
    {
        perror("open fd error");   
        exit(-1);
    }

    //ioctl函数获取显示屏信息并存储
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);  
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    size = fb_fix.line_length * fb_var.yres;   //屏幕显示缓冲区的大小
    int width = fb_var.xres;        //屏幕的水平分辨率
    int height = fb_var.yres;       //屏幕的垂直分辨率
    int i = 0; 

    //printf("%d,    %d,   %d", size, width, height);


    //内存映射
    screen = mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);  
    if(screen == NULL)                                        
    {
        perror("mmap error");
        exit(-1);
    }

    for(i = 0; i < size/4; i++)
    {
        //rgb 
        //red = 11111111 = 0xFF
        //green = blue = 0xFF
        //
        screen[i] = 0x4caeb1;
    }
    

    munmap(screen, size); // 取消映射

    close(fd);            // 关闭文件

    return 1;
}

参数解析:

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 等。

line_length 表示屏幕的一行像素点有多少个字节, 通常可以使用 line_length * yres 来得到屏幕显示缓冲区的大小

使用交叉编译工具编译上述示例代码,将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下,并直接运行它,便可得到结果!

感谢阅读我的博客,如果你喜欢的话,不妨点赞收藏关注一波!

作者:小海编程心语录-CSDN博客

;