您当前的位置: 首页 >  嵌入式

正点原子

暂无认证

  • 3浏览

    0关注

    382博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【正点原子Linux连载】第十八章 FrameBuffer应用编程-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1

正点原子 发布时间:2021-08-18 11:19:33 ,浏览量:3

1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 在这里插入图片描述

第十八章 FrameBuffer应用编程

本章学习Linux下的Framebuffer应用编程,通过对本章内容的学习,大家将会了解到Framebuffer设备究竟是什么?以及如何编写应用程序来操控FrameBuffer设备。 本章将会讨论如下主题。 什么是Framebuffer设备? LCD显示的基本原理; 使用存储映射I/O方式编写LCD应用程序。 在LCD上显示不同的颜色; 在LCD上显示图片;

20.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,譬如阿尔法开发板出厂烧录的Linux系统中,/dev/fb0设备节点便是对应LCD屏。 应用程序读写/dev/fbX就相当于读写显示设备的显示缓冲区(显存),譬如LCD的分辨率是800480,每一个像素点的颜色用24位(譬如RGB888)来表示,那么这个显示缓冲区的大小就是800 x 480 x 24 / 8 = 1152000个字节。譬如执行下面这条命令将LCD清屏,也就是将其填充为黑色(假设LCD对应的设备节点是/dev/fb0,分辨率为800480,RGB888格式): dd if=/dev/zero of=/dev/fb0 bs=1024 count=1125 这条命令的作用就是将1125x1024个字节数据全部写入到LCD显存中,并且这些数据都是0x0。 20.2LCD的基础知识 关于LCD相关的基础知识,本书不再介绍,在阿尔法I.MX6U开发板配套提供的驱动教程中已经有过详细的介绍,除此之外,网络上也能找到相关内容。 20.3LCD应用编程介绍 本小节介绍如何对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的应用编程还是非常简单的,这些知识点都是在前面的入门篇中给大家介绍过。 20.3.1使用ioctl()获取屏幕参数信息 当打开LCD设备文件之后,需要先获取到LCD屏幕的参数信息,譬如LCD的X轴分辨率、Y轴分辨率以及像素格式等信息,通过这些参数计算出LCD显示缓冲区的大小。 通过ioctl()函数来获取屏幕参数,3.10.2小节给大家介绍过该函数,ioctl()是一个文件IO操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或设备文件,为了方便讲解,再次把ioctl()函数的原型列出: #include

int ioctl(int fd, unsigned long request, …); 第一个参数fd对应文件描述符;第二个参数request与具体要操作的对象有关,没有统一值;此函数是一个可变参函数,第三个参数需要根据request参数来决定,配合request来使用。 譬如对于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()调用成功之后会将可变参数信息保存在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 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()调用成功之后会将固定参数信息保存在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都定义在头文件中,所以在我们的应用程序中需要包含该头文件。 #define FBIOGET_VSCREENINFO 0x4600 #define FBIOPUT_VSCREENINFO 0x4601 #define FBIOGET_FSCREENINFO 0x4602 struct fb_var_screeninfo结构体 struct fb_var_screeninfo结构体内容如下所示: 示例代码 20.3.1 struct fb_var_screeninfo结构体

struct fb_var_screeninfo {
    __u32 xres; 			/* 可视区域,一行有多少个像素点,X分辨率 */
    __u32 yres; 			/* 可视区域,一列有多少个像素点,Y分辨率 */
    __u32 xres_virtual; 	/* 虚拟区域,一行有多少个像素点 */
    __u32 yres_virtual; 	/* 虚拟区域,一列有多少个像素点 */
    __u32 xoffset; 		/* 虚拟到可见屏幕之间的行偏移 */
    __u32 yoffset; 		/* 虚拟到可见屏幕之间的列偏移 */

    __u32 bits_per_pixel; 	/* 每个像素点使用多少个bit来描述,也就是像素深度bpp */
    __u32 grayscale; 		/* =0表示彩色, =1表示灰度, >1表示FOURCC颜色 */

    /* 用于描述R、G、B三种颜色分量分别用多少位来表示以及它们各自的偏移量 */
    struct fb_bitfield red;  		/* Red颜色分量色域偏移 */
    struct fb_bitfield green;   	/* Green颜色分量色域偏移 */
    struct fb_bitfield blue;    	/* Blue颜色分量色域偏移 */
    struct fb_bitfield transp;  	/* 透明度分量色域偏移 */

    __u32 nonstd; 	/* nonstd等于0,表示标准像素格式;不等于0则表示非标准像素格式 */
    __u32 activate;

    __u32 height; 	/* 用来描述LCD屏显示图像的高度(以毫米为单位) */
    __u32 width; 		/* 用来描述LCD屏显示图像的宽度(以毫米为单位) */

    __u32 accel_flags;

    /* 以下这些变量表示时序参数 */
    __u32 pixclock; 		/* pixel clock in ps (pico seconds) */
    __u32 left_margin; 	/* time from sync to picture */
    __u32 right_margin; 	/* time from picture to sync */
    __u32 upper_margin; 	/* time from sync to picture */
    __u32 lower_margin;
    __u32 hsync_len; 		/* length of horizontal sync */
    __u32 vsync_len; 		/* length of vertical sync */
    __u32 sync; 			/* see FB_SYNC_* */
    __u32 vmode; 		/* see FB_VMODE_* */
    __u32 rotate; 			/* angle we rotate counter clockwise */
    __u32 colorspace; 		/* colorspace for FOURCC-based modes */
    __u32 reserved[4]; 	/* Reserved for future compatibility */
};

通过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结构体如下所示: 示例代码 20.3.2 struct fb_bitfield结构体

struct fb_bitfield {
    __u32 offset;    	/* 偏移量 */
    __u32 length;   	/* 长度 */
    __u32 msb_right; 	/* != 0 : Most significant bit is right */ 
};
struct fb_fix_screeninfo结构体
struct fb_fix_screeninfo结构体内容如下所示:
示例代码 20.3.3 struct fb_fix_screeninfo结构体
struct fb_fix_screeninfo {
    char id[16];        		/* 字符串形式的标识符 */
    unsigned long smem_start; 	/* 显存的起始地址(物理地址) */

    __u32 smem_len; 			/* 显存的长度 */
    __u32 type;
    __u32 type_aux;
    __u32 visual;
    __u16 xpanstep;
    __u16 ypanstep;
    __u16 ywrapstep;
    __u32 line_length;  		/* 一行的字节数 */
    unsigned long mmio_start; 	/* Start of Memory Mapped I/O(physical address) */
    __u32 mmio_len; 			/* Length of Memory Mapped I/O */
    __u32 accel; 				/* Indicate to driver which specific chip/card we have */
    __u16 capabilities;
    __u16 reserved[2];
};

smem_start表示显存的起始地址,这是一个物理地址,当然在应用层无法直接使用;smem_len表示显存的长度。line_length表示屏幕的一行像素点有多少个字节,等价于xres * bits_per_pixel / 8;通常可以使用line_length * yres来得到屏幕显示缓冲区的大小。 通过上面介绍,接下来我们编写一个示例代码,获取LCD屏幕的参数信息,示例代码如下所示: 本例程源码对应的路径为:开发板光盘->11、Linux C应用编程例程源码->19_lcd->lcd_info.c。 示例代码 20.3.4 获取屏幕的参数信息

#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;

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_WRONLY))) {
        perror("open error");
        exit(-1);
    }

    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    printf("分辨率: %d*%d\n"
        "像素深度bpp: %d\n"
        "一行的字节数: %d\n"
        "像素格式: R G B\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);
}

首先打开LCD设备文件,阿尔法开发板出厂系统中,LCD对应的设备文件为/dev/fb0;打开设备文件之后得到文件描述符fd,接着使用ioctl()函数获取LCD的可变参数信息和固定参数信息,并将这些信息打印出来。 在测试之前,需将LCD屏通过软排线连接到阿尔法开发板(掉电情况下连接),连接好之后启动开发板,如图 17.4.1所示,为了测试方便,可以将出厂系统的GUI应用程序退出。 使用交叉编译工具编译上述示例代码,将编译得到的可执行文件拷贝到开发板出厂系统的家目录下,并直接运行它,如下所示: 在这里插入图片描述

图 20.3.1 获取到屏幕参数 笔者测试用的阿尔法开发板连接的LCD屏是正点原子的7寸800480 RGB屏,与上图打印显示的分辨率800480是相符的;像素深度为16,也就意味着一个像素点的颜色值将使用16bit(也就是2个字节)来表示;一行的字节数为1600,一行共有800个像素点,每个像素点使用16bit来描述,一共就是80016/8=1600个字节数据,这也是没问题的。 打印出像素格式为R G B,分别表示R、G、B三种颜色分量对应的偏移量和长度,第一个数字表示偏移量,第二个参数为长度,从打印的结果可知,16bit颜色值中高5位表示R颜色分量、中间6位表示G颜色分量、低5位表示B颜色分量,所以这是一个RGB565格式的显示设备。 Tips:正点原子的RGB LCD屏幕,包括4.3寸800480、4.3寸480272、7寸800480、7寸1024600以及10.1寸1280800硬件上均支持RGB888,但阿尔法I.MX6开发板出厂烧录的Linux系统,其LCD驱动程序实现支持的是RGB565格式,用户可修改驱动程序或设备树使其支持RGB888。 20.3.2使用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方式。 20.4LCD应用编程练习之刷LCD背景颜色 本例程源码对应的路径为:开发板光盘->11、Linux C应用编程例程源码->19_lcd->lcd_background.c。 本小节编写一个简单的应用程序,将LCD的背景颜色修改为指定的颜色,示例代码如下所示: 示例代码 20.4.1 刷LCD的背景颜色

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/**** RGB888颜色定义 ****/
typedef struct rgb888_type {
    unsigned char blue;
    unsigned char green;
    unsigned char red;
} __attribute__ ((packed)) rgb888_t;

static int width;                 	//LCD X分辨率
static int height;                  	//LCD Y分辨率
static void *screen_base = NULL;   	//映射后的显存基地址
static int bpp;                    	//像素深度

/********************************************************************
 * 函数名称: set_background_color
 * 功能描述: 将LCD背景颜色设置为指定的颜色
 * 输入参数: 颜色
 * 返 回 值: 无
 ********************************************************************/
static void set_background_color(unsigned int color)
{
    int size = height * width;  //计算出像素点个数
    int j;

    switch (bpp) {
    case 16: {  //RGB565
        unsigned short *base = screen_base;
        unsigned short rgb565_color =
            ((color & 0xF80000UL) >> 8) |
            ((color & 0xFC00UL) >> 5) |
            ((color & 0xF8UL) >> 3); //得到RGB565颜色

        /* 向每一个像素点填充颜色 */
        for (j = 0; j > 8,
            .red = (color & 0xFF0000UL) >> 16,
        };

        for (j = 0; j  (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        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;
    bpp = fb_var.bits_per_pixel;

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == screen_base) {
        perror("mmap error");
        close(fd);
        exit(-1);
    }

    /* 刷背景 */
    set_background_color(strtoul(argv[1], NULL, 16));

    /* 退出 */
    munmap(screen_base, screen_size);  //取消映射
    close(fd);  //关闭文件
    exit(0);    //退出进程
}

先来看main()函数,在main()函数中,首先第一步是校验传参,执行程序时需要传入一个参数,这个参数就是RGB颜色值,并且格式为RGB888,程序的最终目的是将LCD的背景色设置为该指定颜色。 调用open()函数打开LCD设备节点得到文件描述符fd,使用ioctl()获取到LCD屏幕的可变参数信息和固定参数信息,计算出LCD的显示缓冲区的大小、获取到LCD屏幕的X分辨率和Y分辨率以及像素深度。接着调用mmap()将LCD的显示缓冲区映射到进程的地址空间中,此时显示缓冲区的基地址为screen_base。mmap()函数在13.5小节中已有详细介绍,这里不再重述! 接着调用自定义函数set_background_color()刷LCD的背景颜色,调用strtoul()函数将外部传入的参数(字符串形式)转换为整形数据得到rgb颜色值,将颜色值作为参数传递给set_background_color()函数。在set_background_color()函数中,如果像素深度为16,默认为RGB565格式;如果像素深度为24,默认为RGB888格式;如果像素深度为32,默认为ARGB8888格式。 如果是RGB565,则需要将颜色值转换为RGB565格式,因为传入的颜色值为RGB888格式,怎么转换呢?其实非常简单,只需取R、G、B每个颜色通道的最高位即可,譬如R通道和B通道取高5位数据,G通道取高6位数据,然后在组合成一个16位的RGB565颜色值,最后通过一个for循环将RGB565颜色值写入到每一个像素点即可! 如果是RGB888,因为C语言中没有3个字节大小的整形数据类型,为了方便操作,程序中自定义了一个数据类型rgb888_t,其实就是一个结构体,该结构体占用内存空间大小为3个字节,包括3个无符号char类型变量red、green、blue,分别用于存放R、G、B三个通道颜色,然后写入到LCD显存中。 如果是ARGB8888,直接写入颜色值即可! 前面已经给大家提到过,阿尔法开发板出厂烧录的Linux系统,其LCD驱动实现支持的是RGB565格式,所以自然会进入到case 16分支。 编译测试 使用交叉编译工具编译上述示例代码,得到可执行文件: 在这里插入图片描述

图 20.4.1 编译示例代码 将编译得到的可执行文件拷贝到开发板家目录下,执行程序: 在这里插入图片描述

图 20.4.2 刷新LCD背景颜色 上述命令中,我们将LCD背景色变成RGB 0xFF00FF颜色,RGB颜色的对照表,大家自己百度解决。执行命令之后,可以看到LCD此时显示出的效果为: 在这里插入图片描述

图 20.4.3 显示效果 由于手机拍摄的问题,实际的效果与图片展示的效果可能存在些许差异。对于其它颜色,大家自己动手测试,本文不再演示。 20.5LCD应用编程练习之LCD打点 本例程源码对应的路径为:开发板光盘->11、Linux C应用编程例程源码->19_lcd->point_color.c。 本小节编写一个打点的函数,在LCD指定位置上输出指定颜色(描点),对于LCD应用编程来说,这是一个非常基本的操作,示例代码如下所示: 示例代码 20.5.1 LCD打点

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/**** RGB888颜色定义 ****/
typedef struct rgb888_type {
    unsigned char blue;
    unsigned char green;
    unsigned char red;
} __attribute__ ((packed)) rgb888_t;

static int width;              		//LCD X分辨率
static int height;                  	//LCD Y分辨率
static void *screen_base = NULL;   	//映射后的显存基地址
static int bpp;                  	//像素深度

/********************************************************************
 * 函数名称: set_point_color
 * 功能描述: 在LCD指定位置(某个像素点)上输出指定颜色(描点)
 * 输入参数: x坐标, y坐标, 颜色
 * 返 回 值: 无
 ********************************************************************/
static void set_point_color(int x, int y, unsigned int color)
{
    /* 校验(x,y)坐标的合法性 */
    if ((x >= width) || (y >= height))
        return;

    switch (bpp) {
    case 16: {  //RGB565
        unsigned short *base = screen_base;
        unsigned short rgb565_color =
            ((color & 0xF80000UL) >> 8) |
            ((color & 0xFC00UL) >> 5) |
            ((color & 0xF8UL) >> 3); //得到RGB565颜色

        base[y * width + x] = rgb565_color;
    }
        break;

    case 24: {  //RGB888
        rgb888_t *base = screen_base;
        rgb888_t rgb888_color = {
            .blue = color & 0xFFUL,
            .green = (color & 0xFF00UL) >> 8,
            .red = (color & 0xFF0000UL) >> 16,
        };

        base[y * width + x] = rgb888_color;
    }
        break;

    case 32: {  //ARGB8888
        unsigned int *base = screen_base;

        base[y * width + x] = color;
    }
        break;

    default:
        fprintf(stderr, "can't surport %dbpp\n", bpp);
        break;
    }
}

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;
    int j;

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        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;
    bpp = fb_var.bits_per_pixel;

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == screen_base) {
        perror("mmap error");
        close(fd);
        exit(-1);
    }

    /* 清屏 */
    memset(screen_base, 0xFF, screen_size);

    /* 利用打点函数画线 */
    for (j = 0; j 19_lcd->bmp_show.c。 通过上小节对BMP图像的介绍之后,相信大家对BMP文件的格式已经非常了解了,那么本小节我们将编写一个示例代码,在阿尔法I.MX6U开发板上显示一张指定的BMP图像,示例代码笔者已经完成了,如下所示。 示例代码 20.6.1 显示BMP图像

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/**** RGB888颜色定义 ****/
typedef struct rgb888_type {
    unsigned char blue;
    unsigned char green;
    unsigned char red;
} __attribute__ ((packed)) rgb888_t;

/**** BMP文件头数据结构 ****/
typedef struct {
    unsigned char type[2];     	//文件类型
    unsigned int size;        	//文件大小
    unsigned short reserved1;  	//保留字段1
    unsigned short reserved2;   	//保留字段2
    unsigned int offset;     	//到位图数据的偏移量
} __attribute__ ((packed)) bmp_file_header;

/**** 位图信息头数据结构 ****/
typedef struct {
    unsigned int size;      	//位图信息头大小
    int width;              	//图像宽度
    int height;             	//图像高度
    unsigned short planes;  		//位面数
    unsigned short bpp;     	//像素深度 
    unsigned int compression;	//压缩方式
    unsigned int image_size;   	//图像大小
    int x_pels_per_meter;     	//像素/米
    int y_pels_per_meter;     	//像素/米 
    unsigned int clr_used;
    unsigned int clr_omportant;
} __attribute__ ((packed)) bmp_info_header;

/**** 静态全局变量 ****/
static int width;                       //LCD X分辨率
static int height;                      //LCD Y分辨率
static void *screen_base = NULL;        //映射后的显存基地址
static int bpp;                         //像素深度

/********************************************************************
 * 函数名称: show_bmp_image
 * 功能描述: 在LCD上显示指定的BMP图片
 * 输入参数: 文件路径
 * 返 回 值: 无
 ********************************************************************/
static void show_bmp_image(const char *path)
{
    bmp_file_header file_h;
    bmp_info_header info_h;
    int fd = -1;

    /* 打开文件 */
    if (0 > (fd = open(path, O_RDONLY))) {
        perror("open error");
        return;
    }

    /* 读取BMP文件头 */
    if (sizeof(bmp_file_header) !=
        read(fd, &file_h, sizeof(bmp_file_header))) {
        perror("read error");
        close(fd);
        return;
    }

    if (0 != memcmp(file_h.type, "BM", 2)) {
        fprintf(stderr, "it's not a BMP file\n");
        close(fd);
        return;
    }

    /* 读取位图信息头 */
    if (sizeof(bmp_info_header) !=
        read(fd, &info_h, sizeof(bmp_info_header))) {
        perror("read error");
        close(fd);
        return;
    }

    /* 打印信息 */
    printf("文件大小:%d\n"
         "到位图数据的偏移量:%d\n"
         "位图信息头大小:%d\n"
         "图像分辨率:%d*%d\n"
         "像素深度:%d\n", file_h.size, file_h.offset,
         info_h.size, info_h.width, info_h.height,
         info_h.bpp);

    /* 将文件读写位置移动到图像数据开始处 */
    if (-1 == lseek(fd, file_h.offset, SEEK_SET)) {
        perror("lseek error");
        close(fd);
        return;
    }

    /**** 读取图像数据显示到LCD ****/
    /*******************************************
     * 为了软件处理上方便,这个示例代码便不去做兼容性设计了
     * 我们默认传入的bmp图像是RGB565格式
     * bmp图像分辨率大小与LCD屏分辨率一样
     * 并且是倒向的位图
     *******************************************/
    unsigned short *base = screen_base;
    unsigned int line_bytes = info_h.width * info_h.bpp / 8;

    for (int j = info_h.height - 1; j >= 0; j--)
        read(fd, base + j * width, line_bytes);

    close(fd);
}

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 (2 != argc) {
        fprintf(stderr, "usage: %s \n", argv[0]);
        exit(-1);
    }

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        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;
    bpp = fb_var.bits_per_pixel;

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == screen_base) {
        perror("mmap error");
        close(fd);
        exit(-1);
    }

    /* 显示BMP图片 */
    memset(screen_base, 0xFF, screen_size);
    show_bmp_image(argv[1]);

    /* 退出 */
    munmap(screen_base, screen_size);  //取消映射
    close(fd);  //关闭文件
    exit(0);    //退出进程
}

代码中有两个自定义结构体bmp_file_header和bmp_info_header,描述bmp文件头的数据结构bmp_file_header、以及描述位图信息头的数据结构bmp_info_header。 当执行程序时候,需要传入参数,指定一个bmp文件。main()函数中会调用show_bmp_image()函数在LCD上显示bmp图像,show_bmp_image()函数的参数为bmp文件路径,在show_bmp_image()函数中首先会打开指定路径的bmp文件,得到对应的文件描述符fd,接着调用read()函数读取bmp文件头和位图信息头。 获取到信息之后使用printf将其打印出来,接着使用lseek()函数将文件的读写位置移动到图像数据起始位置处,也就是bmp_file_header结构体中的offset变量指定的地址偏移量, 最后读取图像的数据,写入到LCD显存中,为了在软件处理上的方便,上述示例代码没有做兼容性设计,默认用户指定的文件都满足下面三个条件: 是16位色的RGB565格式位图文件; 是倒立的位图; 图像的分辨率与LCD屏分辨率相同。 笔者使用图 18.6.1所示的bmp图像作为本次测试的示例图。 读取图像数据、写入LCD显存对应的代码如下: unsigned short *base = screen_base; unsigned int line_bytes = info_h.width * info_h.bpp / 8;

for (int j = info_h.height - 1; j >= 0; j--)
	read(fd, base + j * width, line_bytes);

这是按照倒向位图方式进行解析的。 关于本示例代码就介绍这么多,接下来使用交叉编译工具编译上述示例代码,如下: 在这里插入图片描述

图 20.6.11 编译示例代码 20.6.3在开发板上测试 将上小节编译得到的可执行文件testApp以及测试使用的bmp图像文件拷贝到开发板根文件系统用户家目录下: 在这里插入图片描述

图 20.6.12 可执行文件和bmp文件 接着执行程序,并且传入参数、指定bmp文件路径,执行之后将会在串口终端打印相应的信息,如下: 在这里插入图片描述

图 20.6.13 打印信息 此时LCD上将会显示bmp图像: 在这里插入图片描述

图 20.6.14 LCD上显示出bmp图像 Tips:请忽略手机拍摄问题,大家自己动手测试! 20.7练习 在本章的最后,给大家出一个练习题,要求在LCD上显示中文字符或英文字符,这个任务交给大家自己完成!下一章我们会向大家介绍,如何在LCD上显示中文或英文字符,那么本章我们的内容到这里就结束了,谢谢大家!

关注
打赏
1665308814
查看更多评论
0.0843s