5.2. LCD Driver
5.2.1. Overview
本文主要介绍AC792N SDK 的LCD驱动框架。目前LCD驱动已支持MIPI屏、RGB屏、MCU屏和SPI屏。
LCD
Driver在SDK中的作用,是将各种类型屏幕
和显示接口
抽象出一套统一的接口,供给上层应用调用。
在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进行操作。开发者可以查看app/common/lcd/lcd_driver/lcd_driver.c
,并根据实际屏幕驱动需求进行修改。
一般来说,开发者只需要添加相应屏幕的配置文件,如lcd_480x480_st7701s.c
,即可驱动屏幕,不需要修改lcd_driver.c
。
目前驱动不支持多屏显示。
5.2.2. LCD驱动接口
5.2.2.1. Device接口
1.打开设备
操作:使用dev_open()
打开设备。接口会遍历注册的屏幕配置,并根据屏幕的配置信息初始化显示接口等。
参数:arg0-设备名;arg1-NULL。
返回值:打开的设备的句柄。后续其他操作可通过该句柄对该设备进行操作。
注意:无。
lcd_hdl = dev_open("lcd", NULL);
2.关闭设备
操作:使用dev_close()
关闭设备。。
参数:lcd设备open时获得的句柄。
返回值:固定为0。
注意:无。
dev_close(lcd_hdl);
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()
来向屏幕发送图像数据。
5.2.3. 添加LCD屏驱方法
添加屏幕驱动,需要修改三个位置:
1.
添加屏幕配置。将屏幕类型、IO分配、init_code、屏厂提供的配置(如HFP)等信息,添加到屏驱配置文件中。可参考已支持的屏驱配置,比如开发板标配的屏幕配置文件lcd_480x800_st7701s.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); ///< 背光控制接口
union lcd_dev_info *dev; ///< LCD配置信息结构体的地址。需根据type类型选择配置项
};
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发送口,如lcd_480x800.c
#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();
//timing_setup(ts) timing_hold(th) timing_width(tw):数据传输配置
/*
写时钟信号: __________ _________
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系统时钟宽度
*/
添加分辨率配置。在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
部分参数进行说明。
init函数:初始化屏幕的接口。AC792N SDK在这个接口里将屏厂提供的init_code通过显示接口或者专用的通讯接口发送给屏幕初始化(MIPI屏使用DSI接口的command模式发送DCS命令集)。一般情况下,AC792N 的init_code发送,RGB屏使用单独的CLK+DAT两线串行接口发送,MCU屏使用pap接口发送,SPI屏则直接用spi接口发送。
draw函数:显示一帧图像。AC792N
SDK中只有MCU屏(LCD_MCU
类型)和SPI屏需要实现此接口。
uninit函数:顾名思义。
bl_ctrl函数:背光控制接口。
LCD配置信息结构体:
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命令集打印出
};
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; ///< 时序参数
};
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推MCU屏使用LCD_MCU_SINGLE_FRAME模式。由于LCD_MCU_SINGLE_FRAME模式使用到了IMD的配置参数,相对复杂,因此调屏过程可以先用LCD_MCU模式,将init_code和PAP接口等调通后,再改为LCD_MCU_SINGLE_FRAME模式来调整IMD参数。
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已经打开标志
};
注意: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时钟;
PAP接口——修改TS、TH、TW时钟以及LSB时钟;(PAP的WR速率 = LSB / (TS + TH + TW))
DPI接口——修改.dclk_cfg的时钟源和分频值;
当然,也可以调整MCU/SPI屏刷新帧率,需要咨询屏厂怎么修改。
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驱动开始推数据给屏幕到结束的时间。
TE normal!
”的打印时,即调整完成,调整完成后即可关闭调试功能(如果2倍关系处于临界状态,受关闭TE调试代码的影响推屏速率有稍微变化,可能又出现撕裂条纹,这时需要再微调一下)。5.2.7. 注意点
由于
lcd driver
和spi/pap
都使用了device设备这一套接口,如果屏幕使用了相应的外设,那么就对设备注册有顺序要求。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 }, ///< MUC屏使用了pap,pap设备需要放在lcd前面注册 { "spi2", &spi_dev_ops, (void *)&spi2_data }, ///< SPI屏使用了spi2,spi2设备需要放在lcd前面注册 { "lcd", &lcd_dev_ops, (void *)&lcd_data }, };