# MS-RTOS LCD 驱动开发

本章将介绍 MS-RTOS LCD 驱动开发及测试。

LCD 是液晶显示器(Liquid Crystal Display)的英文缩写,相对于上一代CRT显示器(阴极射线管显示器),LCD显示器具有功耗低、体积小、承载的信息量大及不伤眼的优点。LED点阵彩色显示器的单个像素点内包含红绿蓝三色LED灯,显示原理类似LED彩灯,通过控制红绿蓝颜色的强度进行混色,实现全彩颜色输出,多个像素点构成一个屏幕,像素密度低。新一代的OLED显示器与LED点阵彩色显示器的原理类似,但由于它采用的像素单元是"有机发光二极管"(Organic Light Emitting Diode),所以像素密度比普通LED点阵显示器高得多。

# 1. LCD 基础知识

# 1.1 显示器基本参数

常见的显示器参数如下:

  • 像素:显示器的像素指它成像最小的点
  • 分辨率:嵌入式设备的显示器常常以"行像素值x列像素值"表示屏幕的分辨率
  • 色彩深度:显示器的每个像素点能表示多少种颜色,一般用"位"(bit)来表示
  • 显示器尺寸:指屏幕对角线的长度,如5英寸、21英寸、24英寸等
  • 点距:指两个相邻像素点之间的距离,它会影响画质的细腻度及观看距离
  • 亮度:表示LCD在白色画面下明亮的程度,其电压(亮度)可由PWM调节,亮灭可有背光引脚控制
  • 对比度:表示LCD上同一点最亮时(白色)与最暗时(黑色)的亮度的比值

标准 5 寸电容屏:

ltdc_interface

# 1.2 LTDC 控制器

LTDC 控制器的结构框图,它主要包含信号线、图像处理单元、寄存器及时钟信号。

ltdc_arch

(1)液晶面板控制信号

  • RGB 信号线

    RGB565:表示红绿蓝的数据线数分别为5、6、5根,一共为16个数据位;RGB888:表示红绿蓝的数据线数分别为8、8、8根,一共24位数据线。

  • 同步时钟信号 CLK

    液晶屏与外部使用同步通讯方式,以CLK信号作为同步时钟,在同步时钟的驱动下,每个时钟传输一个像素点数据。

  • 水平同步信号 HSYNC

    水平同步信号HSYNC(Horizontal Sync)用于表示液晶屏一行像素数据的传输结束,每传输完成液晶屏的一行像素数据时,HSYNC会发生电平跳变。

  • 垂直同步信号 VSYNC

    垂直同步信号VSYNC(Vertical Sync)用于表示液晶屏一帧像素数据的传输结束,每传输完成一帧像素数据时,VSYNC会发生电平跳变。

  • 数据使能信号 DE

    数据使能信号DE(Data Enable)用于表示数据的有效性,当DE信号线为高电平时,RGB信号线表示的数据有效。

(2)数据传输时序

下图表示向液晶屏传输一帧图像数据的时序,中间省略了多行及多个像素点。

ltdc_clock_timing

显示指针的扫描方向方向从左到右、从上到下,一个像素点一个像素点地描绘图形。这些像素点的数据通过RGB数据线传输至液晶屏,它们在同步时钟CLK的驱动下一个一个地传输到液晶屏中,交给显示指针,传输完成一行时,水平同步信号HSYNC电平跳变一次,而传输完一帧时VSYNC电平跳变一次。但是,液晶显示指针在行与行之间,帧与帧之间切换时需要延时,而且HSYNC及VSYNC信号本身也有宽度。

时间参数 参数说明
VBP (vertical back porch) 表示在一帧图像开始时,垂直同步信号以后的无效的行数
VFP (vertical front porch) 表示在一帧图像结束后,垂直同步信号以前的无效的行数
HBP (horizontal back porch) 表示从水平同步信号开始到一行的有效数据开始之间的CLK的个数
HFP (horizontal front porth) 表示一行的有效数据结束到下一个水平同步信号开始之间的CLK的个数
VSW (vertical sync width) 表示垂直同步信号的宽度,单位为行
HSW (horizontal sync width) 表示水平同步信号的宽度,单位为同步时钟CLK的个数

STM32F429系列芯片内部自带一个LTDC液晶控制器,使用SDRAM的部分空间作为显存,可直接控制液晶面板,无需额外增加液晶控制器芯片。STM32的LTDC液晶控制器最高支持800x600分辨率的屏幕;可支持多种颜色格式,包括RGB888、RGB565、ARGB8888和ARGB1555等(其中的"A"是指透明像素);支持2层显示数据混合,利用这个特性,可高效地做出背景和前景分离的显示效果,如以视频为背景,在前景显示弹幕。

LTDC 通过"AHB接口"获取显存中的数据,然后按分层把数据分别发送到两个"层FIFO"缓存,每个FIFO可缓存64x32位的数据,接着从缓存中获取数据交给"PFC"(像素格式转换器),它把数据从像素格式转换成字(ARGB8888)的格式,再经过"混合单元"把两层数据合并起来,最终混合得到的是单层要显示的数据,通过信号线输出到液晶面板。这部分结构与DMA2D的很类似,我们在下一小节详细讲解。

# 1.3 DMA2D 控制器

STM32F429芯片使用LTDC、DMA2D及RAM存储器,构成了一个完整的液晶控制器。LTDC负责不断刷新液晶屏,DMA2D用于图像数据搬运、混合及格式转换,RAM存储器作为显存。其中显存可以使用STM32芯片内部的SRAM或外扩SDRAM/SRAM,只要容量足够大即可(至少要能存储一帧图像数据)。

ltdc_dma2d

FG FIFO(Foreground FIFO)与BG FIFO(Backgroun FIFO)是两个64x32位大小的缓冲区,它们用于缓存从AHB总线获取的像素数据,分别专用于缓冲前景层和背景层的数据源。AHB总线的数据源一般是SDRAM,也就是说在LTDC外设中配置的前景层及背景层数据源地址一般指向SDRAM的存储空间,使用SDRAM的部分空间作为显存。

FG PFC(FG Pixel Format Convertor)与BG PFC(BG Pixel Format Convertor)是两个像素格式转换器,分别用于前景层和背景层的像素格式转换,不管从FIFO的数据源格式如何,都把它转化成字的格式(即32位),ARGB8888。

"CLUT"表示颜色查找表(Color Lookup Table),颜色查找表是一种间接的颜色表示方式,它使用一个256x32位的空间缓存256种颜色,颜色的格式是ARGB8888或RGB888。见图 2714,利用颜色查找表,实际的图像只使用这256种颜色,而图像的每个像素使用8位的数据来表示,该数据并不是直接的RGB颜色数据,而是指向颜色查找表的地址偏移,即表示这个像素点应该显示颜色查找表中的哪一种颜色。在图像大小不变的情况下,利用颜色查找表可以扩展颜色显示的能力,其特点是用8位的数据表示了一个24或32位的颜色,但整个图像颜色的种类局限于颜色表中的256种。DMA2D的颜色查找表可以由CPU自动加载或编程手动加载。

FIFO中的数据源经过PFC像素格式转换器后,前景层和背景层的图像都输入到混合器中运算。

OUT PFC是输出像素格式转换器,它把混合器转换得到的图像转换成目标格式,如ARGB8888、RGB888、RGB565、ARGB1555或ARGB4444,具体的格式可根据需要在输出PFC控制寄存器DMA2D_OPFCCR中选择。

ltdc_background

这里说的 背景层 与前面提到的"前景层/背景层"概念有点区别,它们对应下图中的"第2层/第1层",而在这两层之外,还有一个最终的背景层,当第1第2层都透明时,这个背景层就会被显示,而这个背景层是一个纯色的矩形,它的颜色值就是由这三个结构体成员配置的,各成员的参数范围为0x00- 0xFF。

# 2. LCD 驱动框架

# 2.1 驱动相关数据结构

(1)ms_io_device_t

ms_fb_fix_screeninfo_t 用于保存 Framebuffer 设备的固定信息,ms_fb_var_screeninfo_t 用于保存 Framebuffer 设备的可变信息,后面的章节会详细介绍这两个结构体。

/*
 * Private Info
 */
typedef struct {
    ms_fb_var_screeninfo_t var;
    ms_fb_fix_screeninfo_t fix;
} privinfo_t;

/*
 * FrameBuffer Device
 */
typedef struct {
    privinfo_t      priv;
    ms_io_device_t  dev;
} fb_dev_t;

(2)ms_io_driver_t

MS-RTOS 中 FrameBuffer 设备也是已字符设备的形式进行注册的,当前并没有提供重型的驱动框架。

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t fb_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __fb_open,
        .close  = __fb_close,
        .ioctl  = __fb_ioctl,
};

/*
 * Device driver
 */
static ms_io_driver_t stm32_fb_drv = {
        .nnode = {
            .name = "stm32_fb",
        },
        .ops  = &fb_drv_ops,
};

# 2.2 驱动的注册和卸载

(1)LCD 驱动开发流程:

  • 获取必要的软硬件开发资源,了解设备的基本特性;

  • 参照手册的相关流程和代码规范,编写寄存器相关宏定义,封装通用硬件操作接口;

  • 申请必要的系统资源,根据默认参数初始化硬件的工作模式,实现中断处理函数;

  • 实现 ms_io_driver_ops_t 中的必要操作接口,并向 MS-RTOS IO 系统注册驱动和设备节点;

  • 检查代码质量和代码风格,编写测试程序;

(2)驱动的注册和卸载接口:

ms_err_t ms_io_driver_register(ms_io_driver_t *drv);
ms_err_t ms_io_driver_unregister(ms_io_driver_t *drv); //此接口暂不开放

(3)设备节点的注册和卸载接口:

ms_err_t ms_io_device_register(ms_io_device_t *dev, const char *dev_path, 
                               const char *drv_name, ms_ptr_t ctx);
ms_err_t ms_io_device_unregister(ms_io_device_t *dev);

# 2.3 LCD ioctl 命令

以下仅列出几个最基本的命令,可以在 sdk/src/driver/ms_drv_fb.h 文件中找到所有命令的定义。

命令 描述 参数
MS_FB_CMD_GET_VSCREENINFO 获取 Framebuffer 设备无关的数据信息,比如图形硬件上实际的帧缓存空间的大小、能否硬件加速等信息。 ms_fb_var_screeninfo_t 指针
MS_FB_CMD_GET_FSCREENINFO 获取 Framebuffer 设备有关的可变信息,之所以可变,是因为对同样的图形硬件,可以工作在不同的模式下。 ms_fb_fix_screeninfo_t 指针
MS_FB_CMD_PAN_DISPLAY 打开或关闭显示 ms_bool_t
MS_FB_CMD_SET_VSCREENINFO 设定设备无关的数据信息 ms_fb_var_screeninfo_t 指针
MS_FB_CMD_2D_FLUSH 2D刷新图像 ms_fb_2d_flush_t 指针
MS_FB_CMD_2D_BLEND 2D混合图像 ms_fb_2d_blend_t 指针
MS_FB_CMD_2D_FILL 2D填充图像 ms_fb_2d_fill_t 指针

**(1)ms_fb_fix_screeninfo_t **

该结构体用于保存 Framebuffer 设备的固定信息。可以通过 ioctl 获取显存的起始地址和大小。

typedef struct {
#define MS_FB_CAP_2D_FLUSH  MS_BIT(0U)
#define MS_FB_CAP_2D_BLEND  MS_BIT(1U)
#define MS_FB_CAP_2D_FILL   MS_BIT(2U)
    ms_uint32_t         	capability;		// Framebuffer 设备具备的能力/功能
    ms_addr_t           	smem_start;		// Framebuffer 设备的显存起始地址
    ms_size_t           	smem_len;		// Framebuffer 设备的显存大小
    ms_size_t           	line_length;	// Framebuffer 设备的显存单行像素的大小
} ms_fb_fix_screeninfo_t;

(2)ms_fb_var_screeninfo_t

该结构体用于保存 Framebuffer 设备的可变信息。可以通过 ioctl 设置/获取某一区域的颜色。

typedef struct {
    ms_uint16_t         xres;				// 定义屏幕一行有多少个像素点
    ms_uint16_t         yres;				// 定义屏幕一列由多少个像素点
    ms_uint16_t         xres_virtual;		// 虚拟屏幕一行有多少个像素点
    ms_uint16_t         yres_virtual;		// 虚拟屏幕一列由多少个像素点
    ms_uint16_t         xoffset;			// 虚拟到可见(实际)之间的行方向偏移
    ms_uint16_t         yoffset;			// 虚拟到可见(实际)之间的列方向偏移
    ms_uint8_t          bits_per_pixel;		// 每像素位数(多少BPP),单位为字节
    ms_fb_bitfield_t    red;				// fb 缓存的红色位域
    ms_fb_bitfield_t    green;				// fb 缓存的绿色位域
    ms_fb_bitfield_t    blue;				// fb 缓存的蓝色位域
    ms_fb_bitfield_t    transp;				// fb 缓存的透明度位域
} ms_fb_var_screeninfo_t;

# 2.4 LCD 驱动示例

LCD 驱动示例,仅作为参考。

#define __MS_IO
#include "config.h"
#include "ms_kern.h"
#include "ms_io_core.h"

/*
 * Private info
 */
typedef struct {
    ms_fb_var_screeninfo_t var;
    ms_fb_fix_screeninfo_t fix;
} privinfo_t;

/*
 * Open device
 */
static int stm32_fb_open(ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode)
{
    privinfo_t *priv = ctx;
    int ret;

    if (ms_atomic_inc(MS_IO_DEV_REF(file)) == 1) {
        BSP_LCD_Init();
#if BSP_CFG_LCD_BPP == 16U
        BSP_LCD_LayerRgb565Init(0, (uint32_t)priv->fix.smem_start);
#else
        BSP_LCD_LayerDefaultInit(0, (uint32_t)priv->fix.smem_start);
#endif
        BSP_LCD_SelectLayer(0);
        BSP_LCD_Clear(LCD_COLOR_WHITE);
        BSP_LCD_SetLayerVisible(0, ENABLE);
        BSP_LCD_DisplayOn();
        ret = 0;

    } else {
        ms_atomic_dec(MS_IO_DEV_REF(file));
        ms_thread_set_errno(EBUSY);
        ret = -1;
    }

    return ret;
}

/*
 * Close device
 */
static int stm32_fb_close(ms_ptr_t ctx, ms_io_file_t *file)
{
    if (ms_atomic_dec(MS_IO_DEV_REF(file)) == 0) {
        BSP_LCD_DisplayOff();
        BSP_LCD_DeInit();
    }

    return 0;
}

/*
 * Control device
 */
static int stm32_fb_ioctl(ms_ptr_t ctx, ms_io_file_t *file, int cmd, void *arg)
{
    privinfo_t *priv = ctx;
    int ret;

    switch (cmd) {
    case MS_FB_CMD_GET_VSCREENINFO:
        memcpy(arg, &priv->var, sizeof(priv->var));
        ret = 0;
        break;

    case MS_FB_CMD_GET_FSCREENINFO:
        memcpy(arg, &priv->fix, sizeof(priv->fix));
        ret = 0;
        break;

    default:
        ms_thread_set_errno(EINVAL);
        ret = -1;
        break;
    }

    return ret;
}

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t stm32_fb_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = stm32_fb_open,
        .ioctl  = stm32_fb_ioctl,
        .close  = stm32_fb_close,
};

/*
 * Device driver
 */
static ms_io_driver_t stm32_fb_drv = {
        .nnode = {
            .name = "stm32_fb",
        },
        .ops  = &stm32_fb_drv_ops,
};

/*
 * Register frame buffer device driver
 */
ms_err_t stm32_fb_drv_register(void)
{
    return ms_io_driver_register(&stm32_fb_drv);
}

/*
 * Register frame buffer device file
 */
ms_err_t stm32_fb_dev_register(const char *path)
{
    static privinfo_t priv;
    static ms_io_device_t dev;

    priv.var.xres            = BSP_CFG_LCD_WIDTH;
    priv.var.yres            = BSP_CFG_LCD_HEIGHT;
    priv.var.xres_virtual    = BSP_CFG_LCD_WIDTH;
    priv.var.yres_virtual    = BSP_CFG_LCD_HEIGHT;
    priv.var.xoffset         = 0U;
    priv.var.yoffset         = 0U;
    priv.var.bits_per_pixel  = BSP_CFG_LCD_BPP;

#if BSP_CFG_LCD_BPP == 16U
    priv.var.red.offset      = 11U;
    priv.var.red.length      = 5U;
    priv.var.red.msb_right   = 0U;

    priv.var.green.offset    = 5U;
    priv.var.green.length    = 6U;
    priv.var.green.msb_right = 0U;

    priv.var.blue.offset     = 0U;
    priv.var.blue.length     = 5U;
    priv.var.green.msb_right = 0U;
#else
    priv.var.red.offset      = 16U;
    priv.var.red.length      = 8U;
    priv.var.red.msb_right   = 0U;

    priv.var.green.offset    = 8U;
    priv.var.green.length    = 8U;
    priv.var.green.msb_right = 0U;

    priv.var.blue.offset     = 0U;
    priv.var.blue.length     = 8U;
    priv.var.green.msb_right = 0U;
#endif

    priv.fix.smem_start      = BSP_CFG_FB_RAM_BASE;
    priv.fix.smem_len        = LCD_BYTE_PER_PIXEL * BSP_CFG_LCD_WIDTH * BSP_CFG_LCD_HEIGHT;
    priv.fix.line_length     = LCD_BYTE_PER_PIXEL * BSP_CFG_LCD_WIDTH;

    return ms_io_device_register(&dev, path, "stm32_fb", &priv);
}

# 3. LCD 应用程序

# 3.1 MS-RTOS 已经支持的 GUI 库

名称 描述
LittlevGL 开源免费(MIT许可)的GUI
TouchGFX TouchGFX以界面华丽流畅著称,在ST MCU、MPU可免费使用
AWTK 是 ZLG 开发的开源 GUI 引擎
emWin 德国Segger公司开发,ST_emWin、NXP_emWin

# 3.1 向屏幕填充不同颜色

下面的以 RGB-565 类型的 LCD 为例,打开 /dev/fb0 设备文件,获取设备信息,向显存写入数据:

#include <ms_rtos.h>
#include <stdlib.h>
#include <string.h>
#include <driver/ms_drv_fb.h>

#define FB_DEVICE_PATH    "/dev/fb0"

static char 	 *fb_addr;
static unsigned   fb_size;

int print_screen(char *buf, int width, int height);

int main(int argc,char *argv[])
{
    int             		ret;
    int 					screen_fb = 0;
    short 				   *picture;
    ms_fb_fix_screeninfo_t  fb_fix;
    ms_fb_var_screeninfo_t  fb_var;

    screen_fb = ms_io_open(FB_DEVICE_PATH, O_RDWR, 0666);
    if (screen_fb < 0) {
        ms_printf("failed to open file %s\n", FB_DEVICE_PATH);
        abort();
    }

    ret = ms_io_ioctl(screen_fb, MS_FB_CMD_GET_FSCREENINFO, &fb_fix);
    if (ret != 0) {
        ms_printf("ioctl MS_FB_CMD_GET_FSCREENINFO failed!\n");
        ms_io_close(screen_fb);
        return  (-1);
    }
    printf("fb_fix.smem_start = %d, fb_fix.smem_len = %d\n", 
           fb_fix.smem_start, fb_fix.smem_len);

    ret = ms_io_ioctl(screen_fb, MS_FB_CMD_GET_VSCREENINFO, &fb_var);
    if (ret != 0) {
        ms_printf("ioctl MS_FB_CMD_GET_VSCREENINFO failed!\n");
        ms_io_close(screen_fb);
        return  (-1);
    }
    printf("fb_var.xres = %d\n", fb_var.xres);
    printf("fb_var.yres = %d\n", fb_var.yres);
    
    fb_addr = fb_fix.smem_start;
    fb_size = fb_var.yres * fb_fix.line_length;

    // 0xFFFF 就是白色, RGB-565
    picture = (char *)malloc(fb_var.yres * fb_fix.line_length);
    if (picture == MS_NULL) {
        ms_printf("malloc picture failed!\n");
        ms_io_close(screen_fb);
        return  (-1);
    }    
    memset(picture, 0xFF, fb_var.yres * fb_fix.line_length);

    print_screen(picture, fb_var.xres, fb_var.yres);

    return 0;
}

int print_screen(char *buf, int width, int height)
{
    int 	bytew;
    short  *t_data 	  = (short *)buf;
    short  *t_fb_addr = (short *)fb_addr;

    // 像素数乘以2即是字节数,因为颜色深度是2个字节(16bit)
    bytew = width << 1;

    while (--height >= 0) {
       memcpy(t_fb_addr, t_data, bytew);
       t_fb_addr += width;
       t_data += width;
    }
    
    return  (0);
}

# 附录(Appendix)

1. Reference

LTDC/DMA2D 液晶显示:https://blog.csdn.net/flyleaf91/article/details/52325518

2. FAQ

(1)是否有相关 GUI 应用程序的示例?

MS-RTOS 官方推出的开发板 IoT Pi Pro 是带有触摸屏的,该板卡的 BSP 和 应用程序示例可以在 GitHub 上找到,网址为:https://github.com/ms-rtos 。