开发板LCD显示器操作

Linux下一切皆文件,因此LCD显示器也是一个文件。因此如果想要让显示器显示颜色或图像我们只需要打开对应的设备文件并写入需要显示的内容即可。


一 液晶屏的基本概念

  • 像素:
    屏幕上显示颜色的最小单位,英文叫 pixel。注意,位图(如jpg、bmp等格式的常见图片)也是由一个个的像素点构成的,跟屏幕的像素点的概念一样。原理上讲,将一张位图显示到屏幕上,就是将图片上的像素点一个个复制到屏幕像素点上
像素示意图
  • 分辨率:
    • 宽、高两个维度上的像素点数目
    • 分辨率越高,所需要的显存越大
    • 分辨率越高,图像的细腻程度越高
像素示意图
  • 色深:
    • 每个像素所对应的内存位数,一般有8位、16位、24位或32位
    • GEC6818开发板的屏幕的色深是32位的(ARGB)
    • 32位色深的屏幕一般被称为真彩屏,或1600万色屏
显示原理

二、如何让显示器显示颜色

  • 打开显示器设备文件
// - 打开显示器设备文件
    int fd_lcd = open( LCD_PATH , O_WRONLY );
    if (fd_lcd < 0)
    {
        perror("open lcd error");
        return -1 ;
    }
  • 写入RGB颜色值
// - 写入RGB颜色值
int Color = 0x00E066FF ;

for (int i = 0; i < 800 * 480; i++)
{
    write( fd_lcd , &Color , sizeof(Color) );
}
  • 关闭释放资源
// - 关闭释放资源
close(fd_lcd);

三、提升显示效率(使用内存映射)

mmap()
该函数全称是 memory map,意为内存映射,即将某个文件与某块内存关联起来,达到通过操作这块内存来间接操作其所对应的文件的效果。

mmap函数原型
mmap函数原型

四、图像显示

常见的图像格式有jpg,gif,png, bmp,实际上如果需要让开发板来显示图像,则需要从图像文件中读取RGB颜色值,并把RGB颜色值写入到屏幕文件中即可。

BMP格式:
Windows操作系统中。的标准图像文件格式,能够被多种Windows
应用程序所支持。这种格式的特点是包含的图像信息较丰富,几乎不进行压缩,但由此导致了占用磁盘空间过大

BMP(Bitmap-File)图形文件是Windows采用的图形文件格式,在Windows环境下运行的所有图像处理软件都支持BMP图像文件格式。Windows系统内部各图像绘制操作都是以BMP为基础的。BMP位图文件默认的文件扩展名是BMP或者bmp(有时它也会以.DIB或.RLE作扩展名)。

位图文件由4个部分组成:位图文件头(bitmap-file header)位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节(位图数据,即图像数据,Data Bits 或Data Body)阵列。

结构名称(中文) 结构名称(英文符号) 类型/变量名 说 明
位图文件头 BITMAPFILEHEADER bmfh 最开头的 14 字节,存放“这是不是一个 BMP 文件、整个文件多大、数据从第几个字节开始”等全局信息。
位图信息头 BITMAPINFOHEADER bmih 紧跟在后面的 40 字节(经典版本),存放“图像宽、高、色深、是否压缩、分辨率”等详细信息。
彩色表(调色板) RGBQUAD aColors[] 只有 1、2、4、8 位色需要调色板;16/24/32 位色时这一项长度为 0。每个 RGBQUAD 占 4 字节(B,G,R,Reserved)。
图像数据阵列 BYTE aBitmapBits[] 真正的像素区,按“从左到右、从下往上、行 4 字节对齐”存放。
图像信息头
图像信息头

调色板数据(24位的位图无调色板)

调色板数据根据BMP版本的不同而不同, Palette N * 4 byte 调色板规范。对于调色板中的每个表项,这4个字节用下述方法来描述RGB的值:

1字节用于蓝色分量
1字节用于绿色分量
1字节用于红色分量
1字节用于填充符(设置为0)

图像数据

根据BMP版本及调色板尺寸的不同而不同, Bitmap Data xxx bytes 该域的大小取决于压缩方法及图像的尺寸和图像的位深度,它包含所有的位图数据字节,这些数据可能是彩色调色板的索引号,也可能是实际的RGB值,这将根据图像信息头中的位深度值来决定

位图文件头

位图文件头包含有关于文件类型、文件大小、存放位置等信息,在Windows 3.0以上版本的位图文件中用BITMAPFILEHEADER结构来定义:

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

颜色阵列:

有关RGB三色空间我想大家都很熟悉,在Windows下,RGB颜色阵列存储的格式其实BGR。也就是说,对于24位的RGB位图像素数据格式是:

颜色矩阵

行对齐

由于Windows在进行行扫描的时候最小的单位为4个字节,所以当
图片宽 X 每个像素的字节数 != 4的整数倍
时要在每行的后面补上缺少的字节,以0填充(一般来说当图像宽度为2的幂时不需要对齐)。位图文件里的数据在写入的时候已经进行了行对齐,也就是说加载的时候不需要再做行对齐。但是这样一来图片数据的长度就不是:宽 X 高 X 每个像素的字节数了,我们需要通过下面的方法计算正确的数据长度:

// 计算并检测是否存在行尾空白符(为了能被4整除)
int Whitespace = 0 ;
if( PicInfo.biWidth * 3 % 4 )
{
    // 计算的到为了能4整除而补充的空白符数量
    Whitespace = 4 - PicInfo.biWidth * 3 % 4 ;
}

示例(任意位置显示任意大小图像)

int ShowBmp( lcdInfo * lcd , const char * FilePath , int x , int y )
{
    // 打开图像文件
    FILE * fp = fopen( FilePath  , "r");
    if (fp == NULL)
    {
        perror("open file error");
        return -1 ;
    }

    // 读取图像的头部信息(文件头+信息头)
    // 读取文件信息头数据
    BITMAPFILEHEADER fileInfo = {0};
    int retVla = fread(  &fileInfo , 1 ,sizeof(fileInfo) , fp );
    printf("retVal:%d\n", retVla );

    printf("文件大小:%d\n" , fileInfo.bfSize);
    printf("RGB偏移位置: %d\n" , fileInfo.bfOffBits );

    // 读取图像信息头数据
    BITMAPINFOHEADER PicInfo = { 0 } ; 
    retVla = fread(  &PicInfo , 1 ,sizeof(PicInfo) , fp );
    printf("retVal:%d\n", retVla );

    printf("图像宽高[%d,%d]\n" , PicInfo.biWidth , PicInfo.biHeight );
    printf("图像色深[%d]\n" , PicInfo.biBitCount );
    printf("压缩模式[%d]\n" , PicInfo.biCompression );

    // 计算并检测是否存在行尾空白符(为了能被4整除)
    int Whitespace = 0 ;

    if( PicInfo.biWidth * 3 % 4 )
    {
        // 计算的到为了能4整除而补充的空白符数量
        Whitespace = 4 - PicInfo.biWidth * 3 % 4 ;
    }

    // 读取图像的BGR颜色值
    char Bgr [ PicInfo.biHeight * PicInfo.biWidth * 3 +  PicInfo.biHeight * Whitespace ];
    retVla = fread( Bgr , 1 , sizeof(Bgr) , fp );
    printf("读取到图像RGB:%d\n" , retVla );

    // 关闭图像文件
    fclose(fp) ;

    // 把BGR转换为ARGB
    int ARGB [PicInfo.biHeight] [PicInfo.biWidth] ;
    for (int y = 0; y < PicInfo.biHeight ; y++)
    {
        for (int x = 0; x < PicInfo.biWidth ; x++)
        {
            ARGB[PicInfo.biHeight-y-1][x] =     Bgr[(x+y*PicInfo.biWidth)*3+0 + y*Whitespace] << 0 | 
                                                Bgr[(x+y*PicInfo.biWidth)*3+1 + y*Whitespace] << 8 | 
                                                Bgr[(x+y*PicInfo.biWidth)*3+2 + y*Whitespace]<< 16 ;
        }
        
    }

    // 把转换后的ARGB显示到屏幕中
    for (int Tmp_y = 0; Tmp_y < PicInfo.biHeight ; Tmp_y++)
    {
        for (int Tmp_x = 0; Tmp_x < PicInfo.biWidth ; Tmp_x++)
        {
            // map = ARGB ;
            if ((Tmp_x + x) < LCD_WIDTH && (Tmp_y+y) < LCD_HEIGHT )
            {
                *( lcd->map +Tmp_x+x+((Tmp_y+y)*LCD_WIDTH)) 
                                        = ARGB[Tmp_y][Tmp_x] ;
            }
        }
    }

    // 返回
    return 0 ;

}
效果图

五、lcd屏幕信息

屏幕参数设定

首先明确,屏幕的硬件参数,都是由硬件驱动工程师,根据硬件数据手册和内核的相关规定,填入某个固定的地方的,然后再由应用开发工程师,使用特定的函数接口,将这些特定的信息读出来。
对于GEC6818开发板而言,上述所谓“某个固定的地方”,指的是如下这些重要的结构体(节选):

struct fb_fix_screeninfo
{
    char id[16];              /* identification string eg "TT Builtin" */
    unsigned long smem_start; /* Start of frame buffer mem */
                              /* (physical address) */
    __u32 smem_len;           /* Length of frame buffer mem */
    __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;        /* length of a line in bytes    */
    ...
    ...
};

struct fb_var_screeninfo
{
    __u32 xres;           /* 可见区宽度(单位:像素) */
    __u32 yres;           /* 可见区高度(单位:像素) */
    __u32 xres_virtual;   /* 虚拟区宽度(单位:像素) */
    __u32 yres_virtual;   /* 虚拟区高度(单位:像素) */
    __u32 xoffset;        /* 虚拟区到可见区x轴偏移量 */
    __u32 yoffset;        /* 虚拟区到可见区y轴偏移量 */

    __u32 bits_per_pixel; /* 色深 */

    // 像素内颜色结构
    struct fb_bitfield red;   // 红色  
    struct fb_bitfield green; // 绿色
    struct fb_bitfield blue;  // 蓝色
    struct fb_bitfield transp;// 透明度
    ...
    ...
};

struct fb_bitfield
{
    __u32 offset;   /* 颜色在像素内偏移量 */
    __u32 length;   /* 颜色占用数位长度 */
    ...
    ...
};

上述结构体的具体定义在系统的如下路径中:

/usr/include/linux/fb.h
硬件工程师

如何获取屏幕的信息

如上图所示,如果板卡已经具备LCD的驱动程序,那么应用程序就可以通过 ioctl() 来检索LCD的硬件参数信息。以GEC6818开发板配套的群创AT070TN92-7英寸液晶显示屏为例,具体代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <fcntl.h>

int lcd;
struct fb_fix_screeninfo fixinfo; // 固定属性
struct fb_var_screeninfo varinfo; // 可变属性

void get_fixinfo()
{
    if(ioctl(lcd, FBIOGET_FSCREENINFO, &fixinfo) != 0)
    {
        perror("获取LCD设备固定属性信息失败");
        return;
    }
}

void get_varinfo()
{
    if(ioctl(lcd, FBIOGET_VSCREENINFO, &varinfo) != 0)
    {
        perror("获取LCD设备可变属性信息失败");
        return;
    }
}

void show_info()
{
    // 获取LCD设备硬件fix属性
    get_fixinfo();
    printf("\n获取LCD设备固定属性信息成功:\n");
    printf("[ID]: %s\n", fixinfo.id);
    printf("[FB类型]: ");
    switch(fixinfo.type)
    {
        case FB_TYPE_PACKED_PIXELS:      printf("组合像素\n");break;
        case FB_TYPE_PLANES:             printf("非交错图层\n");break;
        case FB_TYPE_INTERLEAVED_PLANES: printf("交错图层\n");break;
        case FB_TYPE_TEXT:               printf("文本或属性\n");break;
        case FB_TYPE_VGA_PLANES:         printf("EGA/VGA图层\n");break;
    }
    printf("[FB视觉]: ");
    switch(fixinfo.visual)
    {
        case FB_VISUAL_MONO01:             printf("灰度. 1=黑;0=白\n");break;
        case FB_VISUAL_MONO10:             printf("灰度. 0=黑;1=白\n");break;
        case FB_VISUAL_TRUECOLOR:          printf("真彩色\n");break;
        case FB_VISUAL_PSEUDOCOLOR:        printf("伪彩色\n");break;
        case FB_VISUAL_DIRECTCOLOR:        printf("直接彩色\n");break;
        case FB_VISUAL_STATIC_PSEUDOCOLOR: printf("只读伪彩色\n");break;
    }
    printf("[行宽]: %d 字节\n", fixinfo.line_length);

    // 获取LCD设备硬件var属性
    get_varinfo();
    printf("\n获取LCD设备可变属性信息成功:\n");
    printf("[可见区分辨率]: %d×%d\n", varinfo.xres, varinfo.yres);
    printf("[虚拟区分辨率]: %d×%d\n", varinfo.xres_virtual, varinfo.yres_virtual);
    printf("[从虚拟区到可见区偏移量]: (%d,%d)\n", varinfo.xoffset, varinfo.yoffset);
    printf("[色深]: %d bits\n", varinfo.bits_per_pixel);
    printf("[像素内颜色结构]:\n");
    printf("  [透明度] 偏移量:%d, 长度:%d bits\n", varinfo.transp.offset, varinfo.transp.length);
    printf("  [红] 偏移量:%d, 长度:%d bits\n", varinfo.red.offset, varinfo.red.length);
    printf("  [绿] 偏移量:%d, 长度:%d bits\n", varinfo.green.offset, varinfo.green.length);
    printf("  [蓝] 偏移量:%d, 长度:%d bits\n", varinfo.blue.offset, varinfo.blue.length);
    printf("\n");
}

int * initLcd ()
{
    lcd = open("/dev/fb0", O_RDWR);
    if(lcd == -1)
    {
        perror("打开 /dev/fb0 失败");
        exit(0);
    }

    // 显示LCD设备属性信息
    show_info();

    // 内存映射 (申请的尺寸与虚拟区相同)
    int * map = mmap(NULL , varinfo.xres_virtual * varinfo.yres_virtual * 4 ,
                        PROT_READ | PROT_WRITE , MAP_SHARED , lcd , 0 );
    if (map == MAP_FAILED)
    {
        perror("mmap error");
        return NULL ;
    }
    
    return map ;
}

如何设置可见区域

// 将可见区设定为A区
vinfo.xoffset = 0;
vinfo.yoffset = 0;
ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);

注意:

  • 偏移量只要不是位于当前屏幕的起始位置,系统就默认直接跳转到下一屏幕中
posted @ 2025-11-12 08:43  林明杰  阅读(1)  评论(0)    收藏  举报