版权声明

本文档所有内容文字资料由凌云实验室郭工编著,主要用于凌云嵌入式Linux教学内部使用,版权归属作者个人所有。任何媒体、网站、或个人未经本人协议授权不得转载、链接、转帖或以其他方式复制发布/发表。已经授权的媒体、网站,在下载使用时必须注明来源,违者本人将依法追究责任。

  • Copyright (C) 2022 凌云物网智科实验室·郭工

  • Author: Guo Wenxue Email: guowenxue@gmail.com QQ: 281143292

wechat_pub

2.12 LCD编程之Framebuffer

2.12.1 LCD简介

​ LCD 全称是Liquid Crystal Display,也就是液晶显示器。LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置TFT(薄膜晶体管),上基板璃上设置彩色滤光片,通过TFT 上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。

​ LCD的开发从使用角度需要去关注LCD的几个属性:

  1. 像素

    像素是组成图像的最基本单元要素,显示器的像素是指它成像最小的点,即LCD的最小一个显示单元。

  2. 分辨率

    一些嵌入式设备的显示器常常以“行像素值x列像素值”表示屏幕的分辨率。如分辨率1920x1080表示该显示器的每一行有1080个像素点,每一列有1920个像素点,也可理解为有1920列,1080行。如下图所示,,X 轴就是LCD 显示器的横轴,Y 轴就是显示器的竖轴。图中的小方块就是像素点,一共有1920*1080=2073600 个像素点。左上角的A 点是第一个像素点,右下角的C 点就是最后一个像素点。

    LCD_pixel_1920_1080_example

  3. 色彩深度(BPP)

    色彩深度指显示器的每个像素点能表示多少种颜色,一般用“位”(bit)来表示。如单色屏的每个像素点能表示亮或灭两种状态(即实际上能显示2种颜色),用1个数据位就可以表示像素点的所有状态,所以它的色彩深度为1bit,其它常见的显示屏色深为16bit、24bit,单位bit也可以用Bpp代替。

  4. 显示器尺寸

    显示器的大小一般以英寸表示,如5英寸、21英寸、24英寸等,这个长度是指屏幕对角线的长度, 通过显示器的对角线长度及长宽比可确定显示器的实际长宽尺寸。

  5. 像素格式

    一个像素点就相当于一个RGB 小灯,通过控制R、G、B 这三种颜色的亮度就可以显示出各种各样的色彩。像素格式与第3点色彩深度也是联系十分紧密,一般一个R、G、B 这三部分分别使用8bit 的数据,那么一个像素点就是8bit*3=24bit,也就是说一个像素点3 个字节,这种像素格式称为RGB888。如果再加入8bit 的Alpha (透明) 通道的话一个像素点就是32bit,也就是4 个字节,这种像素格式称为ARGB8888。还有一种RGB565格式对应16Bpp色深,使用2个字节表示一个像素,以及不常使用的RGB555格式,具体在各自字节中如何表达一个像素色彩,可以查看下图:

    LCD_RGB888_RGB565_RGB555_bit_example

2.12.2 Framebuffer简介

​ Frame是帧的意思,Buffer是缓冲的意思,Framebuffer就是帧缓冲,这意味着Framebuffer就是一块内存,里面保存着一帧图像。帧缓冲(framebuffer)是Linux系统中的一种显示驱动接口,它将显示设备(譬如LCD)进行抽象、屏蔽了不同显示设备硬件的实现,对应用层抽象为一块显示内存(显存),它允许上层应用程序直接对显示缓冲区进行读写操作,而用户不必关心物理显存的位置等具体细节,这些都由Framebuffer设备驱动来完成。

​ 简单来说Framebuffer把屏幕上的每个点映射成一段线性内存空间, 程序可以简单的改变这段内存的值来改变屏幕上某一点的颜色。

2.12.3 使能LCD设备树overlay

如果想使能IGKBoard的LCD,我们需要修改开发板的DToverlay配置文件,添加开发板的LCD的引脚支持。

具体方法为修改 eMMC 启动介质的 boot 分区下的 config.txt 文件,在 dtoverlay_lcd 选项中修改为 yes 即可。

root@igkboard:~# vi /run/media/mmcblk1p1/config.txt 
# Enable LCD overlay
dtoverlay_lcd=yes

修改完成后重启系统,系统会自动加载lcd驱动相关。待系统启动完成后,我们可以使用 dmesg 命令确认驱动是否成功加载。

root@igkboard:~# dmesg | grep lcd
[    1.809557] mxsfb 21c8000.lcdif: supply lcd not found, using dummy regulator
[    1.918758] sii902x bound to mxs-lcdif from 21c8000.lcdif
[    2.025003] mxsfb 21c8000.lcdif: initialized

并且在开机时候可以看到lcd显示的linux小企鹅logo,证明lcd驱动成功。

2.12.3.1 fbset工具

在开发板上使用 fbset命令查看显示屏相关参数。

root@igkboard:~# fbset 

mode "800x480-58"
        # D: 30.000 MHz, H: 30.738 kHz, V: 58.216 Hz
        geometry 800 480 800 480 16
        timings 33333 88 40 32 13 48 3
        accel false
        rgba 5/11,6/5,5/0,0/0
endmode

fbset工具很强大不仅可以显示设备相关参数,同时也能够在用户空间修改lcd参数。

2.12.4 Framebuffer相关数据结构与ioctl函数

LCD驱动程序给APP提供2类参数:可变的参数 fb_var_screeninfo、固定的参数 fb_fix_screeninfo。 编写应用程序时主要关心可变参数,其结构体定义如下:

2.12.4.1 fb_var_screeninfo结构体

//Linux内核源码: include/uapi/linux/fb.h 
//应用编程头文件: /usr/include/linux/fb.h
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;			/* see FB_ACTIVATE_*		*/

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

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.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:用于表示lcd的x坐标长度。

  • yres:用于表示lcd的y坐标长度。

  • bits_per_pixel:用于表示屏幕的像素深度bbp,通过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_bitfield {
	__u32 offset;			/* 偏移量	*/
	__u32 length;			/* 长度		*/
	__u32 msb_right;		/* != 0 : Most significant bit is right */ 
};

2.12.4.2 fb_fix_screeninfo结构体

//Linux内核源码: include/uapi/linux/fb.h 
//应用编程头文件: /usr/include/linux/fb.h
struct fb_fix_screeninfo {
	char id[16];			/* 字符串形式的标识符 */
	unsigned long smem_start;	/* 显存的起始地址(物理地址) */
					
	__u32 smem_len;			/* 显存的长度 */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__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;		/* see FB_CAP_*			*/
	__u16 reserved[2];		/* Reserved for future compatibility */
}

对于固定的参数 fb_fix_screeninfo,在应用编程中很少用到,主要包含比如图形硬件上实际的帧缓存空间的大小、 能否硬件加速等信息。

  • mem_start 表示显存的起始地址,这是一个物理地址,当然在应用层无法直接使用。

  • smem_len 表示显存的长度,这个长度并一定等于LCD实际的显存大小。

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

2.12.4.3 ioctl函数

需要使用ioctl函数来获取Framebuffer的结构体信息,其函数原型如下

 int ioctl(int fd, unsigned long request, ...);

request常用的参数如下

参数

功能

FBIOGET_VSCREENINFO

获取可变参数fb_var_screeninfo结构体

FBIOGET_FSCREENINFO

获取固定参数fb_fix_screeninfo结构体

FBIOPUT_VSCREENINFO

设置可变参数fb_var_screeninfo结构体

以下是三个功能的简易示例代码:

#include <linux/fb.h>
...
//FBIOGET_VSCREENINFO 获取FrameBuffer设备的可变参数信息 
struct fb_var_screeninfo fb_var; 
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);

//FBIOGET_FSCREENINFO 获取固定参数fb_fix_screeninfo结构体
struct fb_fix_screeninfo fb_fix;
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

//FBIOPUT_VSCREENINFO 设置可变参数fb_var_screeninfo结构体
struct fb_var_screeninfo fb_var = {0};
/* 对fb_var进行数据填充 */
...
/* 设置可变参数信息 */ 
ioctl(fd, FBIOPUT_VSCREENINFO, &fb_var);

2.12.4.4 mmap函数

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

​ 为了实现存储映射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就确定了文件的起始位置和长度,将文件的这部分映射到内存区域中。映射关系如下图所示:

mmap_sample_graph

fd:文件描述符,指定要映射到内存区域中的文件。

prot:参数prot指定了映射区的保护要求,可取值如下:

  • PROT_EXEC:映射区可执行;

  • PROT_READ:映射区可读;

  • PROT_WRITE:映射区可写;

  • PROT_NONE:映射区不可访问。

可将prot指定为PROT_NONE,也可将其设置为PROT_EXEC、PROT_READ、PROT_WRITE中一个或多个(通过按位或运算符任意组合)。对指定映射区的保护要求不能超过文件open()时的访问权限,譬如,文件是以只读权限方式打开的,那么对映射区的不能指定为PROT_WRITE。

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

  • MAP_SHARED:此标志指定当对映射区写入数据时,数据会写入到文件中,也就是会将写入到映射区中的数据更新到文件中,并且允许其它进程共享

  • MAP_PRIVATE:此标志指定当对映射区写入数据时,会创建映射文件的一个私人副本(copy-on-write),对映射区的任何操作都不会更新到文件中,仅仅只是对文件副本进行读写。

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

munmap()解除映射

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

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

munmap()系统调用解除指定地址范围内的映射,参数addr指定待解除映射地址范围的起始地址,它必须是系统页大小的整数倍;参数length是一个非负整数,指定了待解除映射区域的大小(字节数),被解除映射的区域对应的大小也必须是系统页大小的整数倍,即使参数length并不等于系统页大小的整数倍,与mmap()函数相似。

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

2.12.5 LCD显示控制编程实现

2.12.5.1 LCD应用编程之基本操作

LCD基本操作包含画点、画线、填充颜色等,下面我们将通过编程实现这些功能:

guowenxue@ubuntu20:~/igkboard/apps$ vim lcd_test.c
/*********************************************************************************
 *      Copyright:  (C) 2021 Guo Wenxue<Email:guowenxue@gmail.com QQ:281143292>
 *                  All rights reserved.
 *
 *       Filename:  lcd_test.c
 *    Description:  This file used to test LCD 
 *                 
 *        Version:  1.0.0(10/1/2022~)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "10/6/2022 17:46:18 PM"
 *                 
 ********************************************************************************/
 #include <stdio.h>  
#include <stdlib.h>  
#include <stdint.h>  
#include <string.h>
#include <errno.h>
#include <unistd.h>  
#include <fcntl.h>  
#include <getopt.h>
#include <libgen.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/fb.h>  
#include <sys/mman.h> 

/*程序版本*/
#define PROG_VERSION      "1.0.0"

/*RGB565 颜色*/
/*颜色对照表 https://blog.csdn.net/weixin_45020839/article/details/117781108 */
#define RED     0xF800       //红色
#define GREED   0x07E0       //绿色
#define BLUE    0x001f       //蓝色
#define WTITE   0xFFFF       //白色
#define BLACK   0x0000       //黑色


/*使用枚举定义常量列表*/
enum
{
    CMD_SHOW_INFO, /* show LCD info  */
    CMD_SHOW_RGB,  /* show RGB color */
    CMD_SHOW_BMP,  /* show BMP file  预留下面显示*/
};

/*定义framebuffer函数上下文结构体*/
typedef struct fb_ctx_s 
{
    int                       fd;       /* framebuffer file descriptor */
    char                      dev[64];  /* framebuffer device name */
    long                      fb_size;  /* framebuffer size 显存大小*/
    int                       pix_size; /* lcd screen pix size  像素数量*/
    struct fb_var_screeninfo  vinfo;    /* framebuffer var information */
    char                     *fbp;      /* framebuffer mmap() address */
} fb_ctx_t; 

int fb_init(fb_ctx_t *fb_ctx);/* framebuffer初始化*/
int fb_term(fb_ctx_t *fb_ctx);
int lcd_show_pixel_rgb565(fb_ctx_t *fb_ctx, unsigned int x, unsigned int y, unsigned short color);
int lcd_fill_rgb565(fb_ctx_t *fb_ctx, unsigned short color);
int show_example_line_fill(fb_ctx_t *fb_ctx, int times);

static void program_usage(char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is a program to show RGB color or BMP file on LCD screen\n", progname);

    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
    printf(" -d[device  ]  Specify framebuffer device, such as: /dev/fb0\n");
    printf(" -e[example ]  Display RGB corlor and draw rand line on LCD screen for some times, such as: -c 3\n");
    printf(" -b[bmp     ]  Display BMP file, such as: -b bg.bmp\n");
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");

    printf("\n%s version %s\n", progname, PROG_VERSION);
    return;
}

int main (int argc, char **argv)
{  
    fb_ctx_t                  fb_ctx;
    char                     *progname=NULL;
    char                     *bmp_file=NULL;
    char                     *fb_dev="/dev/fb0";
    int                       cmd = CMD_SHOW_INFO;
    int                       opt, times;

    struct option long_options[] = { 
        {"device", required_argument, NULL, 'd'},
        {"example", required_argument, NULL, 'c'},
        {"bmp", required_argument, NULL, 'b'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };

    progname = (char *)basename(argv[0]);

    /* Parser the command line parameters */
    while ((opt = getopt_long(argc, argv, "d:c:b:vh", long_options, NULL)) != -1)
    {
        switch (opt)
        {
            case 'd': /* Set framebuffer device */
                 fb_dev = optarg;
                break;

            case 'b': /* Set BMP file */
                /* 设置bmp 图片位置 */
                bmp_file = optarg;
                cmd = CMD_SHOW_BMP;
                break;

            case 'c': /* Show RGB color */
                /* 设置RGB颜色循环次数 */
                times = atoi(optarg);
                cmd = CMD_SHOW_RGB;
                break;

            case 'v':  /* Get software version */
                printf("%s version %s\n", progname, PROG_VERSION);
                return 0;

            case 'h':  /* Get help information */
                program_usage(progname);
                return 0;

            default:
                break;
        }

    }

    memset(&fb_ctx, 0, sizeof(fb_ctx));
    strncpy(fb_ctx.dev, fb_dev, sizeof(fb_ctx.dev));

    if( fb_init(&fb_ctx) < 0 )
    {
        printf("ERROR: Initial framebuffer device '%s' failure.\n", fb_ctx.dev);
        return 1;
    }

    switch( cmd )
    {
        case CMD_SHOW_RGB:
            show_example_line_fill(&fb_ctx, times);
            break;

        case CMD_SHOW_BMP:
            /*预留显示图片函数*/
            break;

        default:
            break;
    }

    fb_term(&fb_ctx);

    return 0;
}

/*获取LCD的可变参数函数*/
int fb_get_var_screeninfo(int fd, struct fb_var_screeninfo *vinfo)
{
    if( fd<0 || !vinfo )
    {
        printf("ERROR: Invalid input arguments\n");
        return -1;
    }
 
    if(ioctl(fd, FBIOGET_VSCREENINFO, vinfo))
    {
        printf("ERROR: ioctl() get variable info failure: %s\n", strerror(errno));  
        return -2;
    }  

    //显示LCD的信息,像素行和列,以及色深Bpp信息
    printf("LCD information : %dx%d, bpp:%d rgba:%d/%d,%d/%d,%d/%d,%d/%d\n", vinfo->xres, vinfo->yres, vinfo->bits_per_pixel,
            vinfo->red.length, vinfo->red.offset, vinfo->green.length, vinfo->green.offset, 
			vinfo->blue.length,vinfo->blue.offset, vinfo->transp.length, vinfo->transp.offset);

    return 0;
}

int fb_init(fb_ctx_t *fb_ctx)
{
    struct fb_var_screeninfo    *vinfo;

    if( !fb_ctx || !strlen(fb_ctx->dev) )
    {
        printf("ERROR: Invalid input arguments\n");
        return -1;
    }

    /*打开fb设备*/
    if( (fb_ctx->fd=open(fb_ctx->dev, O_RDWR)) < 0 )
    {
        printf("ERROR: Open framebuffer device '%s' failure: %s\n", fb_ctx->dev, strerror(errno));  
        return -2;
    } 

    fb_get_var_screeninfo(fb_ctx->fd, &fb_ctx->vinfo);

    vinfo = &fb_ctx->vinfo;
    fb_ctx->fb_size = vinfo->xres * vinfo->yres * vinfo->bits_per_pixel / 8;  
    fb_ctx->pix_size = vinfo->xres * vinfo->yres;

    fb_ctx->fbp =(char *)mmap(0, fb_ctx->fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_ctx->fd ,0);  
    if ( !fb_ctx->fbp )  
    {    
        printf ("ERROR: Framebuffer mmap() failure: %s\n", strerror(errno));  
        return -2;
    }

    vinfo->xoffset = 0;
    vinfo->yoffset = 0;

    return 0;
}

/* 解除mmap映射关系 */
int fb_term(fb_ctx_t *fb_ctx)
{
    if( !fb_ctx )
    {
        printf("ERROR: Invalid input arguments\n");
        return -1;
    }

    munmap(fb_ctx->fbp, fb_ctx->fb_size);
    close (fb_ctx->fd);
 
    return 0;
}

//画点函数
int lcd_show_pixel_rgb565(fb_ctx_t *fb_ctx, unsigned int x, unsigned int y, unsigned short color)
{
    char *color_addr;

    if( !fb_ctx || x > fb_ctx->vinfo.xres || y > fb_ctx->vinfo.yres)
    {
        printf("ERROR: Invalid input arguments\n");
        return -1;
    } 
    /*计算点的实际地址 起始地址 + (y*一行像素点数量 + x)*Bpp  */
    color_addr = fb_ctx->fbp + (fb_ctx->vinfo.xres * y + x) * (fb_ctx->vinfo.bits_per_pixel / 8);
    memcpy(color_addr, &color, fb_ctx->vinfo.bits_per_pixel / 8);
    return 0;
}

//填充函数
int lcd_fill_rgb565(fb_ctx_t *fb_ctx, unsigned short color)
{
    struct fb_var_screeninfo    *vinfo;
    int     i;
    int     bpp_bytes;
    char    *fb_addr;
    if( !fb_ctx )
    {
        printf("ERROR: Invalid input arguments\n");
        return -1;
    }  
    vinfo = &fb_ctx->vinfo;
    //rgb565 = 16 / 8 = 2
    bpp_bytes = vinfo->bits_per_pixel / 8;
    fb_addr = fb_ctx->fbp;
     /* 循环次数=像素点数量 */
    for(i=0; i<fb_ctx->pix_size; i++)
    {
        //对显存映射内存进行赋值,颜色为红色,2个字节进行拷贝
        memcpy(fb_addr, &color, bpp_bytes);
        //移动赋值地址 偏移为2byte
        fb_addr += bpp_bytes;
    }
    return 0;
}

//画线和填充函数示范
int show_example_line_fill(fb_ctx_t *fb_ctx, int times)
{
    int                     i;
    int                     j;
    unsigned int     line_y=0;
    if( !fb_ctx )
    {
        printf("ERROR: Invalid input arguments\n");
        return -1;
    }

    lcd_fill_rgb565(fb_ctx, WTITE);
    lcd_fill_rgb565(fb_ctx, RED);
    sleep(1);
    lcd_fill_rgb565(fb_ctx, BLUE);
    sleep(1);
    lcd_fill_rgb565(fb_ctx, GREED);
    sleep(1);
    lcd_fill_rgb565(fb_ctx, WTITE);

    for(i=0; i<times; i++)
    {
        line_y = rand()%(fb_ctx->vinfo.yres - 0 + 1) + 0;
        printf("draw line [y=%d]\n", line_y);
        for(j=0; j<200; j++)
        {
             lcd_show_pixel_rgb565(fb_ctx, fb_ctx->vinfo.xres/2+j, line_y, BLACK);
        }
    }   
    return 0;
}

编写Makefile如下

guowenxue@ubuntu20:~/igkboard/apps$ vim Makefile
CC=arm-linux-gnueabihf-gcc
APP_NAME=lcd_test

all:clean
	@${CC} ${APP_NAME}.c -o ${APP_NAME}

clean:
	@rm -f ${APP_NAME}

2.12.5.2 交叉编译测试运行

在ubuntu下的相关源码路径下执行make命令将会编译源码生成ARM开发板上的可执行文件。

guowenxue@ubuntu20:~/igkboard/apps$ make
guowenxue@ubuntu20:~/igkboard/apps$ ls
lcd_test  lcd_test.c  Makefile

现在我们在开发板上通过 tftp 命令 或其它方式将编译生成的测试程序下载到开发板上。

root@igkboard:~# tftp -gr lcd_test 192.168.2.2

接下来,我们给该程序加上执行权限并运行。

root@igkboard:~# ./lcd_test -c 5
LCD information : 800x480, bpp:16 rgba:5/11,6/5,5/0,0/0
draw line [y=182]
draw line [y=35]
draw line [y=375]
draw line [y=342]
draw line [y=352]

可以看到LCD屏幕红、蓝、绿颜色切换,最后切换为白色,并在中间产生随机的5条黑色横线,横线的y轴值打印在终端上。

2.12.6 LCD编程值BMP图片显示

2.12.6.1 BMP图像介绍

常用的图片格式有很多,一般最常用的有三种:JPEG(或JPG)、PNG、BMP和GIF。其中JPEG(或JPG)、PNG以及BMP都是静态图片,而GIF则可以实现动态图片。BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,图像数据没有进行任何压缩,因此,BMP图像文件所占用的空间很大,但是没有失真、并且解析BMP图像简单。

BMP文件的图像深度可选1bit、4bit、8bit、16bit、24bit以及32bit,典型的BMP图像文件由四部分组成:

  • BMP文件头(BMP file header),它包含BMP文件的格式、大小、位图数据的偏移量等信息;

  • 位图信息头(bitmap information),它包含位图信息头大小、图像的尺寸、图像大小、位平面数、压缩方式以及颜色索引等信息;

  • 调色板(color palette),这部分是可选的,如果使用索引来表示图像,调色板就是索引与其对应颜色的映射表;

  • 位图数据(bitmap data),也就是图像数据。

BMP文件头、位图信息头、调色板和位图数据,总结如下表所示:

数据段名称

大小(Byte)

说明

bmp 文件头 (bmp file header)

14

包含BMP 文件的格式、大小、到位图数据的偏移量等信息

位图信息头 (bitmap information)

通常为 40 或 56 字节

包含位图信息头大小、图像的尺寸、图像大小、位平面数、压缩方式以及颜色索引等信息;

调色板 (color palette)

由颜色索引数决定

可选,如果使用索引来表示图像的颜色,则调色板就是索引与其对应颜色的映射表;

位图数据 (bitmap data)

由图像尺寸决定

图像数据

下面使用一张实际的BMP格式图片在Ubuntu下分析其编码格式,图片(800x480 16Bpp)如下所示

bmp_image

linux使用hexdump命令查看该BMP文件的16进制格式。

guowenxue@ubuntu20:~/linux/tftp$ hexdump -C image.bmp | head  
00000000  42 4d 48 b8 0b 00 00 00  00 00 46 00 00 00 38 00  |BMH.......F...8.|
00000010  00 00 20 03 00 00 e0 01  00 00 01 00 10 00 03 00  |.. .............|
00000020  00 00 02 b8 0b 00 c2 0e  00 00 c2 0e 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 f8  00 00 e0 07 00 00 1f 00  |................|
00000040  00 00 00 00 00 00 00 48  60 68 60 78 00 60 00 58  |.......H`h`x.`.X|
00000050  00 68 00 68 00 68 00 70  00 90 00 80 00 80 00 98  |.h.h.h.p........|
00000060  00 88 00 70 a0 80 c0 90  20 88 00 88 00 90 00 90  |...p.... .......|
00000070  00 90 20 b0 40 c0 00 a8  cf 99 bc f5 57 fe 80 d8  |.. .@.......W...|
00000080  00 a8 00 a0 00 98 20 98  60 a8 20 a0 40 a0 60 b8  |...... .`. .@.`.|
00000090  a0 c0 60 c0 21 c0 e1 c0  2d e4 d5 fd 54 ec 65 e2  |..`.!...-...T.e.|
  • bmp 文件头

Windows下为bmp文件头定义了如下结构体:

typedef struct tagBITMAPFILEHEADER
{
    UINT16 bfType; 
    DWORD bfSize;
    UINT16 bfReserved1; 
    UINT16 bfReserved2; 
    DWORD bfOffBits;
} BITMAPFILEHEADER;

结构体中每一个成员说明如下:

变量名

地址偏移

大小

作用

bfType

00H

2bytes

说明 bmp 文件的类型,可取值为:
①BM – Windows
②BA – OS/2 Bitmap Array
③CI – OS/2 Color Icon
④CP – OS/2 Color Pointer
⑤IC – OS/2 Icon
⑥PT – OS/2 Pointer

bfSize

02H

4bytes

说明该文件的大小,以字节为单位。

bfReserved1

06H

2bytes

保留字段,必须设置为 0。

bfReserved2

08H

2bytes

保留字段,必须设置为 0。

bfOffBits

0AH

4bytes

说明从文件起始位置到图像数据之间的字节偏移量。这个参数非常有用,因为位图信息头和调色板的长度会根据不同的情况而变化,所以我们可以用这个偏移量迅速从文件中找到图像数据的偏移地址。

与刚才显示16进制文件的进行对比

guowenxue@ubuntu20:~/linux/tftp$ ls -l image.bmp 
-rw-r--r-- 1 guowenxue guowenxue 768072 2月  26  2022 image.bmp
guowenxue@ubuntu20:~/linux/tftp$ hexdump -C image.bmp | head  
00000000  42 4d 48 b8 0b 00 00 00  00 00 46 00 00 00 38 00  |BMH.......F...8.|

第1个字节0x42开始到第14个字节进行分析:

00~01H:0x42、0x4D对应的ASCII字符分别为为B、M,表示这是Windows所支持的位图格式,该字段必须是“BM”才是Windows位图文件。

02~05H:对应于文件大小,0x000BB848=768072字节,与image.bmp文件大小是相符的。

06~09H:保留字段。

0A~0DH:0x46=70,即从文件头部开始到位图数据需要偏移70个字节。

  • 位图信息头

Windows下为位图信息头定义了如下结构体:

typedef struct tagBITMAPINFOHEADER { 
	DWORD biSize;
	LONG biWidth; 
	LONG biHeight; 
    WORD biPlanes; 
    WORD biBitCount;
	DWORD biCompression; 
    DWORD biSizeImage; 
    LONG biXPelsPerMeter; 
    LONG biYPelsPerMeter; 
    DWORD biClrUsed; 
    DWORD biClrImportant;
} BITMAPINFOHEADER;

结构体成员说明如下表:

变量名

地址偏移

大小

作用

biSize

0EH

4 bytes

位图信息头大小。

biWidth

12H

4 bytes

图像的宽度,以像素为单位。

biHeight

16H

4 bytes

图像的高度,以像素为单位。
注意,这个值除了用于描述图像的高度之外,它还有另外一个用途,用于指明该图像是倒向的位图、还是正向的位图。 如果该值是一个正数,说明是倒向的位图;如果该值是一个负数,则说明是正向的位图。
一般情况下,BMP 图像都是倒向的位图,也就是该值是一个正数。

biPlanes

1AH

2 bytes

色彩平面数,该值总被设置为 1。

biBitCount

1CH

2 bytes

像素深度,指明一个像素点需要多少个bit 数据来描述,其值可为 1、4、8、16、24、32

biCompression

1EH

4 bytes

说明图像数据的压缩类型,取值范围如下:
①0 – RGB 方式
②1 – 8bpp 的RLE 方式,只用于 8bit 位图
③2 – 4bpp 的RLE 方式,只用于 4bit 位图
④3 – Bit-fields 方式
⑤4 – 仅用于打印机
⑥5 – 仅用于打印机

biSizeImage

22H

4 bytes

说明图像的大小,以字节为单位,当压缩类型为 BI_RGB 时,可设置为 0。

biXPelsPerMeter

26H

4 bytes

水平分辨率,用像素/米来表示,有符号整数。

biYPelsPerMeter

2AH

4 bytes

垂直分辨率,用像素/米来表示,有符号整数。

biClrUsed

2EH

4 bytes

说明位图实际使用的彩色表中的颜色索引数。

biClrImportant

32H

4 bytes

说明对图像显示有重要影响的颜色索引的数目,如果是0,则表示都重要。

从上表描述信息,再对照实际文件数据:

guowenxue@ubuntu20:~/linux/tftp$ hexdump -C image.bmp | head  
00000000  42 4d 48 b8 0b 00 00 00  00 00 46 00 00 00 38 00  |BMH.......F...8.|
00000010  00 00 20 03 00 00 e0 01  00 00 01 00 10 00 03 00  |.. .............|
00000020  00 00 02 b8 0b 00 c2 0e  00 00 c2 0e 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 f8  00 00 e0 07 00 00 1f 00  |................|

从一行0x38(第15个字节)开始,往后40个字节。

0E~11H:0x00000038=56,这说明这个位图信息头的大小为56个字节。

12~15H:0x00000320=800,图像宽度为800个像素,与文件属性一致。

6~19H:0x000001E0=480,图像高度为480个像素,与文件属性一致;

1A~1BH:0x0001=1,这个值总为1。

1C~1DH:0x0010=16,表示每个像素占16个bit,即色深为16Bpp。

1E~21H:0x00000003=3,bit-fileds方式。

22~25H:0x000BB802=768002,图像的大小,注意图像的大小并不是BMP文件的大小,而是图像数据的大小。

26~29H:0x00000EC2=3778,水平分辨率为3778像素/米。

2A~2DH:0x00000EC2=3778,垂直分辨率为3778像素/米。

2E~31H:0x00000000=0,本位图未使用调色板。‘

32~35H:0x00000000=0。

注意:只有压缩方式选项被设置为bit-fileds(0x3)时,位图信息头的大小才会等于56字节,否则为40字节。

  • 位图数据

位图数据其实就是图像的数据,对于24位位图,使用3个字节数据来表示一个像素点的颜色,对于16位位图,使用2个字节数据来表示一个像素点的颜色。BMP位图分为正向的位图和倒向的位图,主要区别在于图像数据存储的排列方式,如下如所示(左边对应的是正向位图,右边对应的则是倒向位图):

bit_map_D_R

正向位图先存储图像的第一行数据,从左到右依次存放,接着存放第二行,依次这样;而倒向位图,则先存储图像的最后一行(倒数第一行)数据,也是从左到右依次存放,接着倒数二行,依次这样。

2.12.6.2 LCD上显示BMP图片

下面将在上文代码的预留位置添加BMP图片显示的代码,源码如下

// 在主函数的switch中的case CMD_SHOW_BMP:添加如下代码
show_bmp(&fb_ctx, bmp_file);

...
    
typedef struct bmp_file_head_s 
{
    uint8_t  bfType[2];        /* BMP file type: "BM" */
    int32_t bfSize;           /* BMP file size */
    int32_t bfReserved;       /* reserved */
    int32_t bfoffBits;        /* image data offset */
}__attribute__((packed)) bmp_file_head_t; 
//__attribute__((packed))的作用是告诉编译器取消结构在编译过程中的优化对齐

typedef struct bmp_bitmap_info_s 
{
    int32_t biSize;           /* this struture size */
    int32_t biWidth;          /* image width in pix */
    int32_t biHeight;         /* image height in pix */
    uint16_t biPlanes;         /* display planes, always be 1 */
    uint16_t biBitCount;       /* bpp: 1,4,8,16,24,32 */
    int32_t biCompress;       /* compress type */
    int32_t biSizeImage;      /* image size in byte */
    int32_t biXPelsPerMeter;  /* x-res in pix/m */
    int32_t biYPelsPerMeter;  /* y-res in pix/m */
    int32_t biClrUsed;
    int32_t biClrImportant;
}__attribute__((packed)) bmp_bitmap_info_t; 

/*
 * BMP image file format:
 * +----------------+------------------+--------------------+---------------+
 * | File Head(14B) | Bitmap Info(40B) | color palette(OPT) |  bitmap data  |
 * +----------------+------------------+--------------------+---------------+
 */
int show_bmp(fb_ctx_t *fb_ctx, char *bmp_file)
{
    struct fb_var_screeninfo    *vinfo;
    char                        *fb_addr;
    bmp_file_head_t            *file_head;
    bmp_bitmap_info_t          *bitmap_info;
    struct stat                 statbuf;
    char                       *f_addr;
    char                       *d_addr;
    char                       *p_addr;
    int                         fd;
    int                         bpp_bytes;
    int                         i, j;
	int                         width, height;

    if( !fb_ctx || !bmp_file )
    {
        printf("ERROR: Invalid input arguments\n");
        return -1;
    }

    /* 打开bmp文件 */
    if( (fd=open(bmp_file, O_RDONLY)) < 0 )
    {
        printf("ERROR: Open file '%s' failure: %s\n", bmp_file, strerror(errno));
        return -2;
    }

    /* fstat函数获取文件状态,使用状态参数中的size成员 */
    fstat(fd, &statbuf);
    /* 对bmp图片进行内存映射 */
    f_addr =(char *)mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fd ,0);  
    if ( !f_addr )   
    {    
        printf ("ERROR: BMP file mmap() failure: %s\n", strerror(errno));  
        return -2; 
    }   

    /* BMP file header */
    file_head = (bmp_file_head_t *)f_addr;/* 获取 bmp文件头*/
    /* 确定前2字节是否符合类型 */
    if( memcmp(file_head->bfType, "BM", 2) != 0)  
    {
        printf("ERROR: It's not a BMP file\n");
        return -3;
    }

    /* BMP bitmap information header */
    bitmap_info = (bmp_bitmap_info_t *)(f_addr+sizeof(bmp_file_head_t));/* 获取 位图信息头*/

    /* BMP bitmap data */
    d_addr = f_addr + file_head->bfoffBits;/* 利用bmp文件头的数据偏移数据确定实际位图数据地址 */

    vinfo = &fb_ctx->vinfo;
    fb_addr = fb_ctx->fbp;
    bpp_bytes = vinfo->bits_per_pixel / 8;

    /* 控制图片大小小于LCD尺寸 */
    width = bitmap_info->biWidth>vinfo->xres ? vinfo->xres : bitmap_info->biWidth;
    height = bitmap_info->biHeight>vinfo->yres ? vinfo->yres : bitmap_info->biHeight;

    printf("BMP file '%s': %dx%d and display %dx%d\n", bmp_file, bitmap_info->biWidth, bitmap_info->biHeight, width, height);

	/* if biHeight is positive, the bitmap is a bottom-up DIB */
	for(i=0; i<height; i++)
	{
        //由于biHeight为正数,则位图数据排布方式自底至上
        /* 按照一行一行图片进行内存拷贝
         * 第一行显示的数据,为位图数据的最后一行数据
         * 最后一行位图数据的偏移为 (height-1)*bitmap_info->biWidth*bpp_bytes
         */
		p_addr=d_addr+(height-1-i)*bitmap_info->biWidth*bpp_bytes;
		memcpy(fb_addr, p_addr, bpp_bytes*width);
		fb_addr += bpp_bytes * vinfo->xres;
	}

    munmap(f_addr, statbuf.st_size);
    close (fd);

    return 0;
}

2.12.6.3 测试运行

编译和传输和上文一致,并且将图片也传输至开发板中。运行该程序进行测试:

root@igkboard:~# ./lcd_test -b image.bmp 
LCD information : 800x480, bpp:16 rgba:5/11,6/5,5/0,0/0
BMP file 'image.bmp': 800x480 and display 800x480

开发板的LCD上正常显示图片,实物图如下,测试通过。

LCD_show_bmp_real