Zephyr上添加RGB接口屏和调试方法

Creative Commons
本作品采用知识共享署名

本文说明如何向Zephyr添加RGB接口屏和调试方法。

本文所有的调试都是基于mm_swiftio, 一款使用rt1052 mcu的zephyr开发板。

RGB接口屏

嵌入式使用的小屏接口一般有MCU接口,RGB接口,8080接口,LVDS。其中MCU接口需要占用硬件连线最少,但速度也最慢,通常采用SPI总线。其它的都是并行接口,占用硬件连线多,速度快。RGB接口的屏一般来说不自带framebuffer,需要MCU有专门的lcd控制器来驱动。

屏的基本参数

像素:屏的最小显示单位是像素,屏上显示图像的内容是由像素组成。
分辨率:“行像素值 x 列像素值”表示屏幕的分辨率。
色彩深度:像素能显示多少种颜色,用bit数表示。一般有1,8,16,24,32bit几种, 其是2的幂次对应的颜色数量
尺寸:屏的对角线长度,有4.3寸,5寸,7寸等。

RGB接口屏控制信号

屏是由像素组成,设置屏上每个像素的色彩就控制了屏的显示,RGB接口屏的控制信号如下:
R[0:7] :红
G[0:7]:绿
B[0:7]:蓝
DE :数据使能
VSYNC:场同步(垂直)
HSYNC:行同步(水平)
CLK:时钟信号
其中RGB是颜色信号。DE数据使能为高时RGB上的数据才有效。clk是时钟信号每一个时钟上的RGB信号就是一个像素的颜色。HSYNC是行同步,当一行数据传送结束时配合一个HSYNC。 VSYNC是场同步,当一屏的数据传送结束时配合一个VSYNC。
为什么需要VSYNC和HSYNC信号,因为光靠clk信号传送数据,可能由于clk的频偏导致积累误差出现画面扭曲,及画面场数不正常,因此需要使用行场同步来进行纠正。对于不同的屏这几组信号之间会有一定的时序要求,驱动屏比较重要的有下面几个,LCD屏的规格书会有列出
HSW:行同步信号的宽度,以CLK为单位表示
HFP:行同步前肩,是前一行数据结束到行同步之间的clk数量
HBP:行同步后肩,是行同步到下一行数据开始之间的clk数量
VSW:场同步信号的宽度,以CLK为单位表示
VFP:场同步前肩,是前一帧数据结束到场同步之间的clk数量
VBP:场同步后肩,是场同步到下一帧数据开始之间的clk数量
示例,为了简化说明我们以一个3X2分辨率的屏进行说明时序:
timing

LCD控制器

rt1052带有一个柔性的LCD控制器,不占用CPU,直接通过DMA读出framebuffer的数据送到IO进行LCD刷新

  1. 每一个clk传递一个像素, 可选上升沿和下降沿
  2. DE有效时RGB的数据才有效,可选高有效或低有效
  3. 一行数据传递完后有一个HSYNC通知行结束,可选高有效或低有效
  4. 一帧数据传递玩后有一个VSYNC通知帧结束,可选高有效或低有效
  5. 支持8/16/18/24 bit的lcd

添加屏方法

Zephyr已经支持了rt1052的LCD驱动,但目前只支持RK043FN02H,对于不同的RGB屏的支持需要进行修改。这里说明如何添加其它屏的支持

Zephyr增加LCD屏

1. Kconfig增加新屏

在 drivers/display/Kconfig.mcux_elcdif 中添加屏的配置选项

1
2
config MCUX_ELCDIF_PANEL_RGBLCD
bool "lcd,rgb-interface"

2. 增加新屏的配置参数

在drivers/display/display_mcux_elcdif.c中的mcux_elcdif_config_1结构体中增加屏的参数,这些参数的意义前面已经介绍,需要根据屏的规格来配置,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifdef CONFIG_MCUX_ELCDIF_PANEL_RGBLCD
.rgb_mode = {
.panelWidth = DT_LCD_RGB_INTERFACE_PANEL_WIDTH, //LCD分辨率,宽度
.panelHeight = DT_LCD_RGB_INTERFACE_PANEL_HEIGHT, //LCD分辨率,高度
.hsw = DT_LCD_RGB_INTERFACE_PANEL_HSW, //HSW
.hfp = DT_LCD_RGB_INTERFACE_PANEL_HFP, //HFP
.hbp = DT_LCD_RGB_INTERFACE_PANEL_HBP, //HBP
.vsw = DT_LCD_RGB_INTERFACE_PANEL_VSW, //VSW
.vfp = DT_LCD_RGB_INTERFACE_PANEL_VFP, //VFP
.vbp = DT_LCD_RGB_INTERFACE_PANEL_VBP, //VBP
.polarityFlags = kELCDIF_DataEnableActiveHigh | //DE高电平有效
kELCDIF_VsyncActiveLow | //VSYNC低有效
kELCDIF_HsyncActiveLow | //VSYNC高有效
kELCDIF_DriveDataOnRisingClkEdge, //clk上升沿时采集数据
.pixelFormat = kELCDIF_PixelFormatRGB565, // 使用RGB565 color format
.dataBus = kELCDIF_DataBus16Bit, /16bit depth
},
.pixel_format = PIXEL_FORMAT_BGR_565,
.bits_per_pixel = 16,
#endif

以上DT_开头的宏需要我们自己定义或者直接按照规格写实际的值就可以了,其它的宏都是hal_nxp内定义的。
由于我驱动的几片屏基本都只配置这些参数,因此是使用zephyr的dts来产生的这些宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define DT_LCD_RGB_INTERFACE_PANEL_WIDTH            800
#define DT_INST_0_LCD_RGB_INTERFACE_WIDTH DT_LCD_RGB_INTERFACE_PANEL_WIDTH
#define DT_LCD_RGB_INTERFACE_PANEL_HEIGHT 480
#define DT_INST_0_LCD_RGB_INTERFACE_HEIGHT DT_LCD_RGB_INTERFACE_PANEL_HEIGHT
#define DT_LCD_RGB_INTERFACE_PANEL_HSW 1
#define DT_INST_0_LCD_RGB_INTERFACE_HSW DT_LCD_RGB_INTERFACE_PANEL_HSW
#define DT_LCD_RGB_INTERFACE_PANEL_HFP 22
#define DT_INST_0_LCD_RGB_INTERFACE_HFP DT_LCD_RGB_INTERFACE_PANEL_HFP
#define DT_LCD_RGB_INTERFACE_PANEL_HBP 46
#define DT_INST_0_LCD_RGB_INTERFACE_HBP DT_LCD_RGB_INTERFACE_PANEL_HBP
#define DT_LCD_RGB_INTERFACE_PANEL_VSW 1
#define DT_INST_0_LCD_RGB_INTERFACE_VSW DT_LCD_RGB_INTERFACE_PANEL_VSW
#define DT_LCD_RGB_INTERFACE_PANEL_VFP 22
#define DT_INST_0_LCD_RGB_INTERFACE_VFP DT_LCD_RGB_INTERFACE_PANEL_VFP
#define DT_LCD_RGB_INTERFACE_PANEL_VBP 23
#define DT_INST_0_LCD_RGB_INTERFACE_VBP DT_LCD_RGB_INTERFACE_PANEL_VBP

由于不确定这种做法是不是符合标准,另外并没有对全部信号(背光,显示控制等)和参数(color format, 信号电平等)进行dts配置,这里就不做详细介绍了。

RT1052 配置

除了对屏参进行专门的设置外,还要对Zephyr 中RT1052其它部分进行配置才能驱动起来屏

1.Pin mux

rt1052的的pin是复用的,因此当pin做为lcd驱动信号输出时需要对pin进行配置,在boards/arm/mm_swiftio/pinmux.c中的mm_swiftio_init函数添加下面代码进行pin功能配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#ifdef CONFIG_DISPLAY_MCUX_ELCDIF
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_00_LCD_CLK, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_01_LCD_ENABLE, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_02_LCD_HSYNC, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_03_LCD_VSYNC, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_04_LCD_DATA00, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_05_LCD_DATA01, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_06_LCD_DATA02, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_07_LCD_DATA03, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_08_LCD_DATA04, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_09_LCD_DATA05, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_10_LCD_DATA06, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_11_LCD_DATA07, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_12_LCD_DATA08, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_13_LCD_DATA09, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_14_LCD_DATA10, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B0_15_LCD_DATA11, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B1_00_LCD_DATA12, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B1_01_LCD_DATA13, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B1_02_LCD_DATA14, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_B1_03_LCD_DATA15, 0);

IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_00_LCD_CLK, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_01_LCD_ENABLE, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_02_LCD_HSYNC, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_03_LCD_VSYNC, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_04_LCD_DATA00, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_05_LCD_DATA01, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_06_LCD_DATA02, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_07_LCD_DATA03, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_08_LCD_DATA04, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_09_LCD_DATA05, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_10_LCD_DATA06, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_11_LCD_DATA07, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_12_LCD_DATA08, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_13_LCD_DATA09, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_14_LCD_DATA10, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B0_15_LCD_DATA11, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B1_00_LCD_DATA12, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B1_01_LCD_DATA13, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B1_02_LCD_DATA14, 0x01B0B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B1_03_LCD_DATA15, 0x01B0B0u);

#endif

2.LCD时钟配置

LCD的时钟决定了屏的刷新频率,因此配置非常重要, 修改soc/arm/nxp_imx/rt/soc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifdef CONFIG_INIT_VIDEO_PLL
const clock_video_pll_config_t videoPllConfig = {
.loopDivider = 38,
.postDivider = 8,
.numerator = 0,
.denominator = 0,
};

#ifdef CONFIG_DISPLAY_MCUX_ELCDIF
CLOCK_SetMux(kCLOCK_LcdifPreMux, 2);
CLOCK_SetDiv(kCLOCK_LcdifPreDiv, 1);
CLOCK_SetDiv(kCLOCK_LcdifDiv, 1);
#endif

时钟计算方法:
外部晶振频率为24M,lcd控制器输出的clk = 24M*(loopDivider+denominator/numerator)/postDivider/(kCLOCK_LcdifPreDiv+1)/(kCLOCK_LcdifPreDiv+1)=28.5M
帧率计算:
一帧需要的clk=(width+hsw+hfp+hbp)*(height+vsw+vfp+vbp) = 457094
因此一秒的帧率 = 28.5M/457094 = 62帧
从上面的计算可以看到一帧所需要的clk数量是由屏参决定,如果我们要修改帧率就要调整LCD的输出clock,值得注意的是不同的屏的刷新率是有上限的,我们在设置LCD输出时钟时应该参考LCD屏的最高帧率

Zephyr Display驱动使用

配置

需要先配置启动display, 我们使用的屏是800*480,16bit,因此一帧数据的framebuffer大小为800*480*2 = 750K,因此需要配置elcdif pool大于750k,elcdif的驱动使用的是double buffer,因此pool number需要配置为2

1
2
3
4
5
6
7
8
CONFIG_MCUX_ELCDIF_POOL_BLOCK_MIN=0x100000
CONFIG_MCUX_ELCDIF_POOL_BLOCK_MAX=0x100000
CONFIG_MCUX_ELCDIF_POOL_BLOCK_NUM=2
CONFIG_MCUX_ELCDIF_POOL_BLOCK_ALIGN=64
CONFIG_DISPLAY=y
CONFIG_DISPLAY_LOG_LEVEL_ERR=y
CONFIG_DISPLAY_MCUX_ELCDIF=y
CONFIG_MCUX_ELCDIF_PANEL_RGBLCD=y源代码

实际的rt1052在lcd上的驱动可以看drivers/display/display_mcux_elcdif.c

使用

Zephyr有标准的display驱动接口,include/display.h,接口定义的说明可以参考ssd1306驱动要点中Zephyr display driver 模型章节,这里不做详细介绍了.
实际帧率的测试方法:rt1052每刷新完一帧会产生一次中断,在display_mcux_elcdif.c的mcux_elcdif_isr中加入时间打印就可以探测到实际的帧率了。

调试方法

本小节只是罗列在调屏时遇到的问题和检查应对方法

问题罗列

屏幕不显示图像,发白或者成细线条无规律散开装

clk/vsync/hsync 不正常,检查参数配置和对应信号连线是否正常

部分屏幕显示形状正常,但颜色异常

帧率过高,降低帧率

画一个区域影响其它区域显示

帧率过高或者是vsync线连接不好,这一般只出现在调试阶段用杜邦线连接屏和开发板

案例及分析

在初步调好屏后,画了R,G,B三种颜色,看起来正常。于是配置lvgl,画了一些控件发现显示异常,如下图下半部分,所有的红色空间看起来都有一些红色的彩条
error
为了对比问题使用了lvgl定义的蓝色直接在framebuffer上画了一个矩形,看起来也正常,因此开始怀疑是lvgl的渲染的锅,相同的代码在SDL上面模拟跑了一下看起来又正常,因此还是将问题定位在display驱动以下,使用下面方法分别显示各种R,G,B所有颜色空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
u16_t *colorb = buf;
for (int i = 0; i <= 0x01f; i++)
{

for (int j = 0; j < buf_size; j++)
{
colorb[j] = i;
}

buf_desc.buf_size = 20 * 2U * 20;
buf_desc.width = 20;
buf_desc.pitch = 20;
buf_desc.height = 80;

display_write(display_dev, 80 + i * 20, 70, &buf_desc, buf);
}

u16_t *colorg = buf;
for (int i = 0; i <= 0x3f; i++)
{

for (int j = 0; j < buf_size; j++)
{
colorg[j] = i << 5;
}

buf_desc.buf_size = 10 * 2U * 20;
buf_desc.width = 10;
buf_desc.pitch = 10;
buf_desc.height = 80;

display_write(display_dev, 80 + i * 10, 200, &buf_desc, buf);
}

u16_t *colorr = buf;
for (int i = 0; i <= 0x01f; i++)
{

for (int j = 0; j < buf_size; j++)
{
colorr[j] = i << 11;
}

buf_desc.buf_size = 20 * 2U * 20;
buf_desc.width = 20;
buf_desc.pitch = 20;
buf_desc.height = 80;

display_write(display_dev, 80 + i * 20, 330, &buf_desc, buf);
}

看到下图:
color
可以明显的看到红色的颜色空间有问题(发现问题后,我在每个红色之间加了gap以方便观察),这样问题已经有很明显的指向性了,因为蓝色和绿色没有问题,因此不太可能是驱动的问题,查硬件是红色的线序插反了,调整过来后,显示正常了
pass ok

参考

i.MX RT1050 Processor Reference Manual