5.2. LCD Driver

5.2.1. Overview

本文主要介绍AC792N SDK 的LCD驱动框架。目前LCD驱动已支持MIPI屏、RGB屏、MCU屏和SPI屏。

LCD Driver在SDK中的作用,是将各种类型屏幕显示接口抽象出一套统一的接口,供给上层应用调用。

                  +------------------+
                  |        FB        |
                  +------------------+
                           |
                           v
                  +--------+----------+
                  |  lcd_dev_ops      |  <-- LCD设备驱动(lcd_driver.c)
                  | (init/open/ioctl/ |
                  |  close)           |
                  +--------+----------+
                           |
        +------------------+------------------+
        |                  |                  |
+-------v------+   +-------v------+   +------v-------+
|  LCD_MIPI    |   |   LCD_RGB    |   | LCD_MCU/SPI  |
|  (DSI接口)   |   |   (DPI接口)   |   | (PAP/SPI接口) |
+-------+-----+    +-------+------+   +------+-------+
        |                  |                 |
+-------v-----+    +-------v------+   +------v-------+
| MIPI屏驱动   |   |  RGB屏驱动    |   | MCU/SPI屏驱动 |
| (st7701s/    |   | (st7701s/    |   | (ili9488/    |
|  ili9881c/   |   |  hx8264/     |   |  gc9307/     |
|  fl7707/...) |   |  ssd2828/... |   |  nv3006a...) |
+--------------+   +--------------+   +--------------+

在AC792N芯片里,上述显示接口,DSI推MIPI屏、DPI推RGB/MCU屏、PAP(Parallel Active Port)推MCU屏、SPI可推SPI屏。DMM(Display Memory Management)模块负责从memory读取显示数据,再把显示数据提供给显示接口(仅DSI、DPI显示接口支持联动DMM)。

LCD Driver使用了device设备接口层,也就是说,上层应用需要通过dev_open()dev_ioctl()等接口来对LCD进行操作。开发者可以查看apps/common/lcd/lcd_driver/lcd_driver.c,并根据实际屏幕驱动需求进行修改。

一般来说,开发者只需要添加相应屏幕的配置文件,如lcd_480x480_st7701s.c,即可驱动屏幕,不需要修改lcd_driver.c

lcd_driver支持多屏驱配置,适用于双屏显示 或者 兼容多款屏时使用。


5.2.2. LCD驱动接口

5.2.2.1. Device接口

5.2.2.1.1. 打开设备

操作:使用dev_open()打开设备。接口会遍历注册的屏幕配置,找到匹配的屏驱配置,并根据屏幕的配置信息初始化显示接口等。

参数:arg0-设备名;arg1-(单屏驱)NULL或者(多屏驱)lcd_id/lcd_name

返回值:打开的设备的句柄。后续其他操作可通过该句柄对该设备进行操作。

注意:无。

/* 单屏驱 TCFG_LCD_SUPPORT_MULTI_DRIVER_EN = 0 */
lcd_hdl = dev_open("lcd", NULL);


/* 多屏驱 TCFG_LCD_SUPPORT_MULTI_DRIVER_EN = 1 */
// 1、传id时,根据屏驱配置中的.id值匹配
u8 lcd_id = 0;
lcd_hdl = dev_open("lcd", (void *)&lcd_id);

// 2、传屏驱名,根据屏驱配置中的.logo字符串匹配
lcd_hdl = dev_open("lcd", "MIPI_480x800_ST7701S");

5.2.2.1.2. 关闭设备

操作:使用dev_close()关闭设备。

参数:lcd设备open时获得的句柄。

返回值:固定为0。

注意:无。

dev_close(lcd_hdl);

5.2.2.1.3. IOCTL控制

LCD支持的IOCTL命令:

#define IOCTL_LCD_RGB_SET_ISR_CB              _IOW('F', 1, sizeof(void *(*)(void)))
#define IOCTL_LCD_RGB_WAIT_FB_SWAP_FINISH     _IOW('F', 2, sizeof(int))
#define IOCTL_LCD_RGB_START_DISPLAY           _IOW('F', 3, sizeof(void *))
#define IOCTL_LCD_RGB_GET_LCD_HANDLE          _IOR('F', 4, sizeof(struct lcd_dev_drive **))
#define IOCTL_LCD_RGB_SET_LINE_ISR_CB         _IOW('F', 5, sizeof(int (*)(int)))
#define IOCTL_LCD_RGB_WAIT_LINE_FINISH        _IOW('F', 6, sizeof(int))
  • IOCTL_LCD_RGB_SET_ISR_CB

功能:设置中断回调函数。仅MIPI和RGB屏支持。

参数:回调函数地址。

返回值:固定为0。

dev_ioctl(lcd_hdl, IOCTL_LCD_RGB_SET_ISR_CB, (u32)lcd_isr_cb);
  • IOCTL_LCD_RGB_WAIT_FB_SWAP_FINISH

功能:等待FB交换BUF地址完成。

参数:图像buf地址。

返回值:固定为0。

dev_ioctl(lcd_hdl, IOCTL_LCD_RGB_WAIT_FB_SWAP_FINISH, 0);
  • IOCTL_LCD_RGB_START_DISPLAY

功能:启动显示传入的图像buf。

参数:图像buf的地址。

返回值:固定为0。

dev_ioctl(lcd_hdl, IOCTL_LCD_RGB_START_DISPLAY, (u32)display_buf);
  • IOCTL_LCD_RGB_GET_LCD_HANDLE

功能:获取屏幕配置数据句柄。

参数:存放句柄的指针变量的地址。

返回值:固定为0。

struct lcd_dev_drive *lcd;
dev_ioctl(lcd_hdl, IOCTL_LCD_RGB_GET_LCD_HANDLE, (u32)&lcd);
  • IOCTL_LCD_RGB_SET_LINE_ISR_CB

功能:等待行数据发送中断回调函数。

参数:回调函数地址。

返回值:固定为0。

dev_ioctl(lcd_hdl, IOCTL_LCD_RGB_SET_LINE_ISR_CB, (u32)line_isr_cb);
  • IOCTL_LCD_RGB_WAIT_LINE_FINISH

功能:等待行数据发送完成。

参数:无。

返回值:固定为0。

dev_ioctl(lcd_hdl, IOCTL_LCD_RGB_WAIT_LINE_FINISH, (u32)0);

5.2.2.2. 其他接口

void WriteCOM(u8 cmd);                     ///< 发送1 byte命令
void WriteDAT_8(u8 dat);                   ///< 发送1 byte数据
void WriteDAT_one_page(u8 *dat, int len);  ///< 写一帧图像数据

MCU屏和SPI屏,通过WriteCOM()WriteDAT_8()接口来发送init_code。

MCU屏和SPI屏,通过调用WriteDAT_one_page()来向屏幕发送图像数据。

完整的命令/数据操作接口:

WriteCOM(lcd_id, cmd);              // 写命令到LCD
WriteDAT_8(lcd_id, dat);            // 写1字节数据到LCD
WriteDAT_one_page(lcd_id, dat, len); // 写一页数据(推屏)
ReadDAT(lcd_id, cmd, buf, len);     // 从LCD读数据(SPI/MCU)
lcd_cs_pinstate(lcd_id, state);     // 控制LCD片选脚电平
lcd_rs_pinstate(lcd_id, state);     // 控制LCD命令/数据脚电平
lcd_rst_pinstate(lcd_id, state);    // 控制LCD复位脚电平

5.2.3. 添加LCD屏驱方法

添加屏幕驱动,需要修改三个位置:

5.2.3.1. 添加屏幕配置

将屏幕类型、IO分配、init_code、屏厂提供的配置(如HFP)等信息,添加到屏驱配置文件中。可参考已支持的屏驱配置,比如开发板标配的屏幕配置文件lcd_mipi_st7701s_480x800.c。开发者需要实现如下结构体配置信息。

struct lcd_dev_drive {
    const char *logo;                        ///< LCD logo。例如"st7701s"
    enum LCD_IF type;                        ///< LCD 类型。例如 LCD_MIPI、LCD_RGB等
    int (*init)(void *_data);                ///< 初始化接口。
    int (*draw)(void *_data);                ///< 显示一帧图像的接口
    int (*uninit)(void *_data);              ///< 反初始化接口
    void (*bl_ctrl)(void *_data, u8 onoff);  ///< 背光控制接口
};

5.2.3.2. 修改板级配置

参考《屏接口说明》章节进行IO分配,并修改板级配置文件(比如board_develop_AC7926A.h)里LCD部分的IO。

// board.h
#define TCFG_LCD_BL_VALUE           1              ///< 背光点亮时的IO电平,0-低电平,1-高电平。
#define TCFG_LCD_RESET_IO           IO_PORTA_15    ///< 复位脚。
#define TCFG_LCD_BL_IO              -1             ///< 背光控制脚。
#define TCFG_LCD_RS_IO              IO_PORTB_01    ///< 指令/数据选择脚,MCU屏和SPI屏特有。
#define TCFG_LCD_CS_IO              IO_PORTB_00    ///< 片选信号脚。
#define TCFG_LCD_SPI_INTERFACE      "spi2"         ///< 分配给屏的SPI口,SPI屏特有。
#define TCFG_LCD_TE_ENABLE          1              ///< TE(Tearing effect)防撕裂功能使能。MCU屏和SPI屏特有。
#define TCFG_LCD_TE_IO              IO_PORTA_14    ///< TE信号脚。
#define TCFG_LCD_SDA_IO             IO_PORTC_00    ///< SDA脚。部分屏幕需要单独的init code发送口
#define TCFG_LCD_SCL_IO             IO_PORTC_01    ///< SCL脚。同上。
// 在board.c,注册lcd配置
LCD_PLATFORM_DATA_BEGIN(lcd_data)
    .lcd_name               = TCFG_LCD_DEVICE_NAME,
    .lcd_io                 = {
        .backlight          = TCFG_LCD_BL_IO,
        .backlight_value    = TCFG_LCD_BL_VALUE,
        .lcd_reset          = TCFG_LCD_RESET_IO,
        .lcd_cs             = TCFG_LCD_CS_IO,
        .lcd_rs             = TCFG_LCD_RS_IO,
    },
LCD_PLATFORM_DATA_END()

另外,MCU屏使用到了PAP接口(配置参考如下),SPI屏使用到了SPI接口(配置参考专门的SPI文档),因此需要对相应接口参数进行配置。

// board.h
#define TCFG_PAP_ENABLE                     1                    ///< PAP接口使能。
#define TCFG_PAP_DATA_WIDTH                 PAP_DATAWIDTH_8BIT   ///< PAP接口位宽,可选8bit或16bit。
#define TCFG_PAP_DATA_ENDIAN                PAP_ENDIAN_BE        ///< 大小端选择
#define TCFG_PAP_WRITE_ACTIVE_LEVEL         PAP_WR_LEVEL_LOW     ///< WR写有效电平
#define TCFG_PAP_READ_ACTIVE_LEVEL          PAP_RD_LEVEL_LOW     ///< RD读有效电平
#define TCFG_PAP_READ_ENABLE                0                    ///< 读使能
#define TCFG_PAP_WE_IO                      IO_PORTA_12          ///< WR脚
#define TCFG_PAP_RE_IO                      -1                   ///< RD脚
// board.c
PAP_PLATFORM_DATA_BEGIN(pap_data)
    .datawidth              = TCFG_PAP_DATA_WIDTH,
    .endian                 = TCFG_PAP_DATA_ENDIAN,
    .pwe                    = TCFG_PAP_WRITE_ACTIVE_LEVEL,
    .pre                    = TCFG_PAP_READ_ACTIVE_LEVEL,
    .rd_en                  = TCFG_PAP_READ_ENABLE,
    .we_port                = TCFG_PAP_WE_IO,
    .re_port                = TCFG_PAP_RE_IO,
    .port_sel               = PAP_PORT_A,                       ///< PAP出口选择。可选A或B端口
    .timing_setup           = PAP_TS_0_CLK,                     ///< TS时序配置
    .timing_hold            = PAP_TH_0_CLK,                     ///< TH时序配置
    .timing_width           = PAP_TW_1_CLK,                     ///< TW时序配置
PAP_PLATFORM_DATA_END();

PAP时序参数说明(timing_setup/timing_hold/timing_width):

写时钟信号:
                     __________               _________
pwe=H:  ____________|          |_____________|         |__________
pwe=L:  ____________            _____________           __________
                    |__________|             |_________|

数据信号:
             ________________________                       ______
data:   _____|                        |_____________________|

             |      |          |      |
ts,tw,th:    |      |          |      |
             |<-ts->|<---tw--->|<-th->|
             |      |          |      |

ts:0-3,ts个lsb系统时钟宽度
tw:0-15,0-->16个lsb系统时钟宽度,1-15:tw个lsb系统时钟宽度
th:0-3,th个lsb系统时钟宽度

5.2.3.3. 添加分辨率配置

lcd_config.h中添加相应屏幕分辨率。

// 在lcd_config.h
// MIPI LCD
#if TCFG_LCD_MIPI_ST7701S_480x800
#define LCD_W 480
#define LCD_H 800
#endif

5.2.4. 屏幕配置文件说明

int (*init)(void *_data);                ///< 初始化接口
int (*draw)(void *_data);                ///< 显示一帧图像的接口
int (*uninit)(void *_data);              ///< 反初始化接口
void (*bl_ctrl)(void *_data, u8 onoff);  ///< 背光控制接口
union lcd_dev_info *dev;                 ///< LCD配置信息结构体的地址。需根据type类型选择配置项

开发者在添加新屏驱时,一般只需要在SDK中找到同一类型屏幕的配置文件,稍加修改即可。下面对struct lcd_dev_drive部分参数进行说明。

1、init函数

初始化屏幕的接口。AC792N SDK在这个接口里将屏厂提供的init_code通过显示接口或者专用的通讯接口发送给屏幕初始化。一般情况下,AC792N 的init_code发送:

  • RGB屏:部分屏幕需要使用单独的CLK+DAT两线串行接口发送

  • MIPI屏:使用DSI接口发送DCS命令集

  • MCU屏:使用PAP接口发送

  • SPI屏:直接用SPI接口发送

2、draw函数

显示一帧图像。AC792N SDK中只有MCU屏(LCD_MCU类型)和SPI屏需要实现此接口。典型实现方式(以SPI屏为例):

static int lcd_spi_240x296_gc9307_draw_page(void *_data)
{
    struct lcd_draw_data *data = (struct lcd_draw_data *)_data;
    WriteCOM(data->lcd_id, 0x2c);  // 写命令:开始写显存
    WriteDAT_one_page(data->lcd_id, data->data, data->len);  // 写一帧图像数据
    return 0;
}

3、uninit函数

反初始化接口,用于关闭屏幕、释放资源。

4、bl_ctrl函数

背光控制接口,通过GPIO控制背光的开关。

5.2.4.1. LCD配置信息结构体

5.2.4.1.1. MIPI屏

struct mipi_dev {
    struct imd_dmm_info info;                           ///< DMM配置
    struct pll4_info pll4;                              ///< MIPI PLL时钟配置
    struct te_mode_ctrl te_mode;                        ///< TE模式配置
    struct dsi_lane_mapping lane_mapping;               ///< 使用的硬件lane接口配置。使用多少lane,是否对调P/N等配置
    struct dsi_video_timing video_timing;               ///< DIS video时序配置。前肩、后肩等参数
    struct dsi_timing timing;                           ///< DSI时序设置

    u8 dsi_open_flag;                                   ///< DSI已经打开标志
    unsigned int reset_gpio;                            ///< 复位IO
    void (*lcd_reset)(unsigned int reset_gpio);         ///< 复位函数
    void (*lcd_reset_release)(unsigned int reset_gpio); ///< 复位释放函数

    const u8 *cmd_list;                                 ///< DCS初始化命令集
    u16 cmd_list_item;                                  ///< DCS初始化命令集占用空间大小

    u8 debug_mode;                                      ///< 调试模式。使能时,DSI驱动会将DCS命令集打印出
};
// MIPI时钟设置大致参考
#define freq 414
// mipi_clock = (VS + VFP + VBP + VDA) * (HS + HFP + HBP + HDA) * pixel_byte_num / lane_num * frame_rate
// pixel_byte_num  ——  单像素需要多少字节,比如RGB565需要2byte
// lane_num        ——  推屏lane数量
// frame_rate      ——  目标推屏帧率

5.2.4.1.2. RGB屏

struct imd_dev {
    struct imd_dmm_info info;       ///< DMM配置
    struct te_mode_ctrl te_mode;    ///< TE模式配置

    enum LCD_LSB data_out_mode;     ///< 输出数据大小端。仅输出RGB565支持
    enum NCYCLE ncycle;             ///< 每像素时钟数。如数据格式RGB888,接口位宽8bit,cycle = 24bit / 8bit =3 cycle
    enum PHASE raw_odd_phase;       ///< 奇行相位
    enum PHASE raw_even_phase;      ///< 偶行相位
    enum RAW_MODE raw_mode;         ///< RAW模式选择
    enum LCD_PORT data_width;       ///< 数据位宽
    enum LCD_GROUP lcd_group;       ///< 输出端口选择
    u8 avout_mode;                  ///< 保留
    u8 show_buf_mode;               ///< 显示buf模式

    u8 set_io_hd;                   ///< IO强驱配置。所有dat、hsy、vsy、clk、den脚都会根据该配置设置强驱档位。
    u8 dclk_set;                    ///< dclk使能以及极性配置
    u8 sync0_set;                   ///< 配置为DE/HSYNC/VSYNC
    u8 sync1_set;                   ///< 配置为DE/HSYNC/VSYNC
    u8 sync2_set;                   ///< 配置为DE/HSYNC/VSYNC
    u8 sync3_set;                   ///< Reversed
    u8 dpi_open_flag;               ///< dpi打开标志
    u32 clk_cfg;                    ///< dpi clk时钟配置。dpi模块的时钟配置
    u32 dclk_cfg;                   ///< dclk时钟配置。dpi输出的dclk的时钟源和时钟分频配置
    struct imd_dpi_timing timing;   ///< 时序参数
};

5.2.4.1.3. MCU屏

MCU屏可以用两种.type去推,分别是LCD_MCU_SINGLE_FRAMELCD_MCU。它们有如下区别:

  • LCD_MCU_SINGLE_FRAME:发送init_code和cmd时使用PAP接口发送,发送图像数据时,使用DPI接口(的EMI模式)发送。该模式下因为使用了DPI,需要用到struct imd_dev参数配置。需要注意,LCD的IO分配必须使用MCU接口中的PORT-A才能用该模式。

    (PAP和DPI是两个独立的接口,可以看下《屏接口说明》章节的IO分配,PAP接口的PORT-ARGB接口的PORT-A的信号线出线是一样的。)

  • LCD_MCU:init_code、cmd和图像数据全部都通过PAP接口来发送。在需要大小端数据转换情况下,lcd_driver会需要malloc多一块buf用于存放转换后的数据,内存空间有点浪费。

LCD_MCU_SINGLE_FRAME模式推屏的优点是可以使用DMM模块,这样,DMM模块的一些特性就可以用上,例如大小端转换颜色增强等。

AC792N平台上,建议使用LCD_MCU_SINGLE_FRAME模式。由于LCD_MCU_SINGLE_FRAME模式使用到了IMD的配置参数,相对复杂,因此调屏过程可以先用LCD_MCU模式,将init_code和PAP接口等调通后,再改为LCD_MCU_SINGLE_FRAME模式来调整IMD参数。

5.2.4.1.4. SPI屏

struct spi_dev {
    struct imd_dmm_info info;       ///< DMM配置
    struct te_mode_ctrl te_mode;    ///< TE模式配置
    enum LCD_LSB data_out_mode;     ///< 输出数据大小端。选择MODE_BE时,将进行大小端数据转换。
    char *interface_name;           ///< 使用的SPI口名。比如"SPI2"
    u8 spi_open_flag;               ///< SPI LCD已经打开标志
};

注意:MCU屏(特指type为LCD_MCU)和SPI屏参数如果配置了大小端转换(即.data_out_mode = MODE_BE),lcd_driver将会malloc一块buf空间用于存放转换后的图像数据,空间占用多一些。


5.2.5. 屏幕旋转配置

AC792N支持旋转ROTATE_90ROTATE_180ROTATE_270度。配置方式:

1、修改配置文件旋转角度

驱动会根据.rotate的值,来旋转图像地址空间。

// lcd_xxx.c
REGISTER_IMD_DEVICE_BEGIN(lcd_mcu_dev) = {
    .info = {
// ...
        .len             = LEN_256,
        .interlaced_mode = INTERLACED_NONE,
        .rotate          = ROTATE_0,                 ///< 旋转
// ...
REGISTER_IMD_DEVICE_END()

2、调整分辨率宽/高

如果旋转角度选择了ROTATE_90ROTATE_270LCD_WLCD_H需要对调。该配置影响LVGL生成的图像分辨率。

// lcd_config.h
/* #define LCD_W 320 */
/* #define LCD_H 480 */
#define LCD_W 480
#define LCD_H 320

5.2.6. TE(Tearing Effect)使用说明

MCU/SPI屏显示为避免出现撕裂问题(Tearing effect),lcd driver需要检测到帧TE信号后才开始推数据给屏幕,并且推屏速率需要遵循2倍关系:

  1. 不能让读屏指针赶上写屏指针。写屏ram的数据时间(W)应该小于等于2倍MCU/SPI屏的自刷新时间(R)。

  2. 不能让写屏指针赶上读屏指针。写屏ram的数据时间(W)应该大于MCU/SPI屏的自刷新时间(R)。即lcd_te_int_time < lcd_push_data_time <= 2 * lcd_te_int_time

为达到上述要求,可通过调整接口推屏速率实现。推屏接口的时钟频率决定了推数据速率,各个接口的时钟频率调整位置:

  • SPI接口——修改波特率和LSB时钟;

  • MCU屏(type为LCD_MCU的屏,PAP接口推图像数据)——修改TS、TH、TW时钟以及LSB时钟;(PAP的WR速率 = LSB / (TS + TH + TW))

  • MCU屏(type为LCD_MCU_SINGLE_FRAME的屏,DPI接口推图像数据)——修改.dclk_cfg的时钟源和分频值;

5.2.6.1. 开启TE功能

在board.h中配置宏来使能TE功能:

#define TCFG_LCD_TE_ENABLE                  1               ///< 非0开启TE
#define TCFG_LCD_TE_IO                      IO_PORTA_14     ///< 配置TE脚

开启TE功能后,驱动会在lcd初始化时配置TCFG_LCD_TE_IO的引脚为外部中断脚,以判断TE信号的到来。UI刷新图像给驱动,lcd驱动会等待TE信号来之后立马开始推一帧图像。

5.2.6.2. TE调试

为了方便调试TE,使得推屏速率需要遵循2倍关系,驱动提供一个调试方式,可配置LCD_TE_DEBUG_EN为1来使能。

#define LCD_TE_DEBUG_EN         0          ///< 开启TE调试。通过打印判断推屏速率是否在合适范围。

TE速率判断逻辑:

if (lcd_te_int_time > lcd_push_data_time) {
    // lcd推数据太快
    printf("TE issue! lcd push data is too fast. diff_time = %d\n", lcd_te_int_time - lcd_push_data_time);
} else if (lcd_te_int_time * 2 < lcd_push_data_time) {
    // lcd推数据太慢
    printf("TE issue! lcd push data is too slow. diff_time = %d\n", lcd_push_data_time - lcd_te_int_time * 2);
} else {
    // lcd推数据速率刚刚好
    printf("TE normal! diff_time = %d\n", lcd_te_int_time * 2 - lcd_push_data_time);
}

其中,lcd_te_int_time为通过TE中断获得的TE信号时间间隔。lcd_push_data_time为lcd驱动开始推数据给屏幕到结束的时间。

开发者需要通过调整推屏速率或者屏自刷新帧率来实现2倍关系,当log里出现”TE normal!”的打印时,即调整完成,调整完成后即可关闭调试功能(如果2倍关系处于临界状态,受关闭TE调试代码的影响推屏速率有稍微变化,可能又出现撕裂条纹,这时需要再微调一下)。


5.2.7. 多屏驱

lcd_driver支持多屏驱配置,通过TCFG_LCD_SUPPORT_MULTI_DRIVER_EN宏配置:

  • 单屏驱——宏配置为0。SDK有且只能注册一个屏驱配。如果注册了多个屏驱配置,驱动断言报错。公版SDK默认为此配置。

  • 多屏驱——宏配置为1。SDK至少需要注册一个屏驱配置,驱动通过dev_open时传的参数来匹配屏驱配置。适用于项目单屏需要兼容多款屏或者同时推2个屏幕的场景

5.2.7.1. 多屏驱打开方式

TCFG_LCD_SUPPORT_MULTI_DRIVER_EN = 1多屏驱模式下,dev_open("lcd", arg)arg参数决定打开行为:

方式一:自动探测模式(arg = NULL)

遍历所有已注册的屏驱,对每个屏驱执行check检测是否在线,第一个check成功的屏幕将被打开,因此该方式下必须实现.check接口。适用于开机时不确定连接了哪块屏的场景。

void *lcd_hdl = dev_open("lcd", NULL);  // 自动探测在线屏幕

驱动自动探测流程:

  1. 遍历所有注册的lcd_dev_drive,用logo匹配板级配置

  2. 对匹配成功的屏驱调用__lcd_open(),其中会执行lcd->check()检测在线

  3. RGB屏和MIPI屏通过check接口检测在线

  4. 第一个open成功的屏驱被选中,且check成功,后续屏驱不再尝试

方式二:指定名称模式(arg = “screen_name”)

按屏幕logo名称打开指定屏幕。

void *lcd_hdl = dev_open("lcd", "MIPI_480x800_ST7701S");

方式三:指定ID模式(arg = &lcd_id)

按ID号打开指定屏幕,适用同时推2块屏幕的场景(参考下一个章节)。

u8 lcd_id = 0;  // 或 lcd_id = 1
void *lcd_hdl = dev_open("lcd", &lcd_id);

注意,上述方式一方式二的中使能的所有屏驱配置的分辨率必须一样,通过LCD_W和LCD_H宏来设置。

5.2.7.2. 多屏驱的程序修改

1、在板级配置中使能TCFG_LCD_SUPPORT_MULTI_DRIVER_EN  1(比如board_develop_AC7926A.h)。

image1

2、lcd_config.h中修改目标分辨率。(仅支持具有相同分辨率的屏)

image2

3、使能多个屏驱。由于lcd_driver是通过名字来匹配板级配置屏驱配置,因此板级配置的.lcd_name需要与屏驱配置的.logo名必须一致。

image3

image4

4、在board.c中通过LCD_PLATFORM_DATA_ADD( )添加多个板级配置

image5

5、屏驱配置中需要添加check接口。

image6


5.2.8. 双屏显示

顾名思义是同一环境同时支持2个屏幕显示。通过该功能,开发者可以轻松实现双屏幕显示不同内容、内容同步、窗口管理等功能。

5.2.8.1. 适用场景

  • 双屏显示设备(如大型 TFT 显示屏和小型单色显示屏等)

  • 需要同时在两个屏幕上显示不同内容的应用

  • 需要实现屏幕间交互的应用

5.2.8.2. 快速配置

以开发板驱动 一个mipi 屏 + 一个 spi 屏为例说明。

1、开启多屏驱支持

#define TCFG_LCD_SUPPORT_MULTI_DRIVER_EN    1 ///< 多屏驱支持

2、配置第一块mipi屏

#define TCFG_LCD_MIPI_ST7701S_480x800             1

#if TCFG_LCD_MIPI_ST7701S_480x800
#define TCFG_LCD_DEVICE_NAME                "MIPI_480x800_ST7701S"
#define TCFG_LCD_BL_VALUE                   1
#define TCFG_LCD_RESET_IO                   IO_PORTB_00
#define TCFG_LCD_BL_IO                      IO_PORTB_01
#define TCFG_LCD_RS_IO                      -1
#define TCFG_LCD_CS_IO                      -1
#define TCFG_LCD_TE_ENABLE                  0
#define TCFG_LCD_TE_IO                      -1
#define TCFG_LCD_SPI_INTERFACE              NULL
#endif

3、配置第二块 spi屏

#define TCFG_LCD_SPI_GC9307_240X296             1

#if TCFG_LCD_SPI_GC9307_240X296
#define TCFG_LCD1_DEVICE_NAME                "SPI_240x296_GC9307"
#define TCFG_LCD1_BL_VALUE                   1
#define TCFG_LCD1_RESET_IO                   IO_PORTA_15
#define TCFG_LCD1_BL_IO                      -1
#define TCFG_LCD1_RS_IO                      IO_PORTB_11
#define TCFG_LCD1_CS_IO                      IO_PORTB_10
#define TCFG_LCD1_SPI_INTERFACE              "spi2"
#define TCFG_LCD1_TE_ENABLE                  1
#define TCFG_LCD1_TE_IO                      IO_PORTA_14
#endif

4、配置板级lcd_data

#if TCFG_LCD_ENABLE
LCD_PLATFORM_DATA_BEGIN(lcd_bd_cfg)
    .lcd_name               = TCFG_LCD_DEVICE_NAME,
    .lcd_io                 = {
        .backlight          = TCFG_LCD_BL_IO,
        .backlight_value    = TCFG_LCD_BL_VALUE,
        .lcd_reset          = TCFG_LCD_RESET_IO,
        .lcd_cs             = TCFG_LCD_CS_IO,
        .lcd_rs             = TCFG_LCD_RS_IO,
    },
    .te_mode                = {
        .te_mode_en         = TCFG_LCD_TE_ENABLE,
        .gpio               = TCFG_LCD_TE_IO,
        .edge               = EDGE_NEGATIVE,
    },
    .spi_lcd_interface      = TCFG_LCD_SPI_INTERFACE,
#if TCFG_LCD_SUPPORT_MULTI_DRIVER_EN
LCD_PLATFORM_DATA_ADD()
    .lcd_name               = TCFG_LCD1_DEVICE_NAME,
    .lcd_io                 = {
        .backlight          = TCFG_LCD1_BL_IO,
        .backlight_value    = TCFG_LCD1_BL_VALUE,
        .lcd_reset          = TCFG_LCD1_RESET_IO,
        .lcd_cs             = TCFG_LCD1_CS_IO,
        .lcd_rs             = TCFG_LCD1_RS_IO,
    },
    .te_mode                = {
        .te_mode_en         = TCFG_LCD1_TE_ENABLE,
        .gpio               = TCFG_LCD1_TE_IO,
        .edge               = EDGE_NEGATIVE,
    },
    .spi_lcd_interface      = TCFG_LCD1_SPI_INTERFACE,
#endif
LCD_PLATFORM_DATA_END()

static const struct lcd_platform_data lcd_data = {
    .cfg_num = ARRAY_SIZE(lcd_bd_cfg),
    .config_ptr = lcd_bd_cfg,
};

5、配置lcd_config.h,设置2块屏的分辨率和 ID号

...

#else /* TCFG_LCD_SUPPORT_MULTI_DRIVER_EN */
//LCD0 宽高和ID号定义
#define LCD_W  480
#define LCD_H  800
#define LCD_ID  0
/***** 不同分辨率的屏定义 */
//LCD1 宽高和ID号定义
#define LCD1_W   240
#define LCD1_H   296
#define LCD1_ID  1
#endif // TCFG_LCD_SUPPORT_MULTI_DRIVER_EN

...

6、修改spi 屏驱指定到LCD1,而mipi屏属于默认LCD0 不需要修改,当然也可根据需要反过来配置

image7

7、应用层修改

  1. LVGL显示

  • 修改apps/common/lvgl_v8/examples/porting/lv_port_disp.c 初始化2个显示器实例

void lv_port_disp_init(void)
{
    _lv_port_disp_prepare(0, LCD_W, LCD_H);// 初始化lcd0 --> disp0
    _lv_port_disp_prepare(1, LCD1_W, LCD1_H);//初始化lcd1 --> disp1
}
  • LVGL 内容默认会显示lcd0上,如果让内容默认显示在lcd1,需要在创建控件前指定disp1的所在屏幕

/**
 * Create a playback animation
 */
void lv_example_anim_2(void)
{
    extern lv_disp_t *lv_port_get_disp(uint8_t id);
    lv_obj_t *scr = lv_disp_get_scr_act(lv_port_get_disp(1));//获取disp1的屏幕
    lv_obj_t *obj = lv_obj_create(scr);//基于当前屏幕上创建obj

    /* lv_obj_t *obj = lv_obj_create(lv_scr_act()); */
    lv_obj_set_style_bg_color(obj, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, 0);

    lv_obj_align(obj, LV_ALIGN_LEFT_MID, 10, 0);

    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, obj);
    lv_anim_set_values(&a, 10, 50);
    lv_anim_set_time(&a, 1000);
    lv_anim_set_playback_delay(&a, 100);
    lv_anim_set_playback_time(&a, 300);
    lv_anim_set_repeat_delay(&a, 500);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);

    lv_anim_set_exec_cb(&a, anim_size_cb);
    lv_anim_start(&a);
    lv_anim_set_exec_cb(&a, anim_x_cb);
    lv_anim_set_values(&a, 10, 240);
    lv_anim_start(&a);
}
  1. 摄像头图像指定lcd显示

  • 修改video_disp_start函数,增加 req.display.disp_id请求参数即可

req.display.disp_id  = id; //id为lcd id号

5.2.9. 注意点

1、由于lcd driverspi/pap都使用了device设备这一套接口,如果屏幕使用了spi/pap外设,那么就对设备注册有顺序要求。SDK开机启动流程中会通过devices_init()对板级board_xxx.c里注册的设备进行基本的初始化操作,设备的初始化顺序与板级里设备注册顺序有关,注册得越靠前,越早初始化。LCD驱动里,SPI屏需要依赖spi接口通信,MCU屏需要pap接口,因此,当驱动的屏幕为SPI屏/MCU屏,spi/pap设备注册应早于lcd设备。参考如下顺序:

REGISTER_DEVICES(device_table) = {
    { "pap",   &pap_dev_ops, (void *)&pap_data  }, ///< MCU屏使用了pap,pap设备需要放在lcd前面注册
    { "spi2",  &spi_dev_ops, (void *)&spi2_data }, ///< SPI屏使用了spi2,spi2设备需要放在lcd前面注册
    { "lcd",   &lcd_dev_ops, (void *)&lcd_data  },
};

2、MCU屏推荐使用LCD_MCU_SINGLE_FRAME模式,该模式可以使用DMM模块的大小端转换和颜色增强等特性。调试时可先用LCD_MCU模式调通init_code和PAP接口,再切换到LCD_MCU_SINGLE_FRAME模式。

3、SPI屏和(type为LCD_MCU的)MCU屏,配置大小端转换(MODE_BE)时,lcd_driver会额外malloc一块buf空间用于存放转换后的图像数据,需注意内存开销。

4、旋转配置时,若选择ROTATE_90ROTATE_270LCD_WLCD_H必须对调。

5、TE调试完成后需关闭调试功能,若2倍关系处于临界状态,关闭调试代码可能导致推屏速率微变而再次出现撕裂,需再微调。