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_FRAME和LCD_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-A与RGB接口的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_90、ROTATE_180、ROTATE_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_90和ROTATE_270,LCD_W和LCD_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倍关系:
不能让读屏指针赶上写屏指针。写屏ram的数据时间(W)应该小于等于2倍MCU/SPI屏的自刷新时间(R)。
不能让写屏指针赶上读屏指针。写屏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); // 自动探测在线屏幕
驱动自动探测流程:
遍历所有注册的
lcd_dev_drive,用logo匹配板级配置对匹配成功的屏驱调用
__lcd_open(),其中会执行lcd->check()检测在线RGB屏和MIPI屏通过
check接口检测在线第一个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)。

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

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


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

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

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 不需要修改,当然也可根据需要反过来配置

7、应用层修改
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);
}
摄像头图像指定lcd显示
修改
video_disp_start函数,增加req.display.disp_id请求参数即可
req.display.disp_id = id; //id为lcd id号
5.2.9. 注意点
1、由于lcd driver和spi/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_90或ROTATE_270,LCD_W和LCD_H必须对调。
5、TE调试完成后需关闭调试功能,若2倍关系处于临界状态,关闭调试代码可能导致推屏速率微变而再次出现撕裂,需再微调。