跳到主要内容

SPI LCD 显示驱动

image-20250403112830462

SPI LCD 一般用于小分辨率的 SPI 屏显示,该驱动支持使用 SPI 协议软件控制 DC 方式驱动 SPI 屏幕,另外也对支持 MIPI DBI 接口芯片支持硬件控制 DC,3 线 9Bit 模式。SPI LCD 一般遵循 MIPI DBI 协议。部分芯片平台的 SPI 接口支持 MIPI DBI 模式,其规格如下:

  • 支持 DBI Type C 3线/4线接口模式
  • 支持 2 DATA LANE 接口模式
  • 支持来自 CPU 或 DMA 的数据源
  • 支持 RGB111/444/565/666/888 视频格式
  • 最大分辨率:RGB666 240 x 320@30Hz,1 DATA LANE
  • 最大分辨率:RGB888 240 x 320@60Hz 或 RGB666 320 x 480@30Hz, 2 DATA LANE
  • 支持 TE

MIPI DBI(显示总线接口)也称为MCU接口。MIPI-DBI用于与具有集成图形内存(GRAM)的LCD进行通讯的接口。像素数据首先在显示驱动芯片的本地图形内存中更新,然后该内存会不断刷新显示器。主机和显示模块可以通过简单的GPIO连接。

MIPI-DBI的类型如下:

  • DBI TYPE A:基于摩托罗拉 6800 总线
    • 支持 8/9/16/18/24 位并行数据传输
  • DBI TYPE B:基于Intel® 8080总线
    • 支持8/9/16/18/24位并行数据传输
  • DBI TYPE C:基于SPI协议
    • 支持3线或4线SPI接口
注意

SPI LCD 仅支持 UI 图层,不支持将 MIPI 摄像头/解码器输出的 YUV 直接送显示,需要进行 YUV2RGB 转换后再送显。

支持屏幕接口如下表所示:

DBI 协议协议名称信号线市场惯用叫法驱动方式是否支持
L3I13 线 1 DataCS, SCK, SDA3 线 SPI
3 Line 9Bit
DBI
L3I23 线 2 Data(单独的读取IO)CS, SCK, SDA, SDO3 线 SPI
3 Line 9Bit
DBI
L4I14 线 1 DataCS, SCK, SDA, DC4 线 SPI
SPI 4W
SPI/DBI
L4I24 线 2 Data(单独的读取IO)CS, SCK, SDA, DC, SDO4 线 SPIDBI
D2LI2 Data LaneCS, SCK, SDA, WR2 Data SPI
3Wire 2data 9bit DSPI
DBI
\QSPI 单线CS, CLK, D0QSPIQSPI
\QSPI 双线CS, CLK, D0, D1QSPIQSPI
\QSPI 四线CS, CLK, D0, D1, D2, D3QSPIQSPI

接口硬件复用关系如下表所示,DBI 复用仅有 SPI1 控制器支持,SPI/QSPI 任意 SPI 均可:

SPIL3I1L3I2L4I1L4I2D2LIQSPI
SPI-CSDBI-CSXDBI-CSXDBI-CSXDBI-CSXDBI-CSXQSPI-CS
SPI-HOLD//DBI-DCXDBI-DCX/QSPI-D3
SPI-CLKDBI-SCLKDBI-SCLKDBI-SCLKDBI-SCLKDBI-SCLKQSPI-CLK
SPI-MOSIDBI-SDADBI-SDODBI-SDADBI-SDODBI-SDAQSPI-D0
SPI-MISO/DBI-SDI/DBI-SDIWRXQSPI-D1
SPI-WPDBI-TEDBI-TEDBI-TEDBI-TEDBI-TEQSPI-D2
提示

QSPI 不限制 SPI 控制器,任意 SPI 控制器均可,对于 V821 来说,具体下表所示。

TFT 模块IO 电平QSPI-CSQSPI-CLKQSPI-D0QSPI-D1QSPI-D2QSPI-D3支持模式
SPI 引脚\SPI-CSSPI-CLKSPI-MOSISPI-MISOSPI-WPSPI-HOLD\
SPI0(PC6—PC11)3.3VPC10PC9PC8PC11PC6PC7QSPI 1线/2线/4线
SPI1(PD1—PD6)3.3VPD1PD2PD3PD4PD6PD5QSPI 1线/2线/4线
SPI1(PD5—PD8)3.3VPD8PD5PD6PD7\\QSPI 1线/2线
SPI1(PD12—PD15)3.3VPD15PD12PD13PD14\\QSPI 1线/2线
SPI1(PD14—PD19)3.3VPD14PD15PD16PD17PD19PD18QSPI 1线/2线/4线
SPI2(PA5—PA10)1.8VPA5PA6PA7PA8PA10PA9QSPI 1线/2线/4线
SPI2(PD1—PD6)3.3VPD1PD2PD3PD4PD6PD5QSPI 1线/2线/4线
SPI2(PD14—PD1)3.3VPD14PD15PD16PD17PD19PD18QSPI 1线/2线/4线
注意

同一个控制器仅能接一块屏幕,意思是,当使用了PD1—PD6的SPI1接了屏幕,就不可以用PD14—PD19的SPI1接另外一块屏幕,虽然引脚没有冲突,但是控制器重复了。同一个控制器仅能接一块屏幕,如果需要接可以切换使用 SPI2。

源码结构介绍

SPI LCD 驱动在内核中叫做 LCD FB 驱动。其源码如下所示:

.
├── dev_fb.c
├── dev_fb.h
├── dev_lcd_fb.c
├── dev_lcd_fb.h
├── disp_display.c
├── disp_display.h
├── disp_lcd.c
├── disp_lcd.h
├── include.h
├── Kconfig
├── lcd_fb_feature.h
├── lcd_fb_intf.c
├── lcd_fb_intf.h
├── logo.c
├── logo.h
├── Makefile
└── panels
├── Kconfig
├── kld2844b.c
├── kld2844b.h
├── ...
├── lcd_source.h
├── panels.c
├── panels.h
└── spi_panel.c

SPI LCD 源码包括驱动源码与 SPI 屏幕驱动。其中屏幕驱动位于 panels 文件夹内。新增一款屏幕需要在 panels 文件夹中添加新屏幕驱动。

模块配置介绍

内核模块配置

模块配置路径如下所示:

Allwinner BSP  ---> 
Device Drivers --->
Video Drivers --->
SPI LCD Panel Drivers --->
<*> LCD FB Driver Support (SPI LCD)

LCDFB 驱动路径

LCD 显示面板驱动位于

Allwinner BSP  ---> 
Device Drivers --->
Video Drivers --->
SPI LCD Panel Drivers --->
LCD FB Panels select --->
[*] LCD support kld2844B panel
...

LCD 显示面板驱动

设备树配置

这里以配置双屏,不同型号屏幕,屏幕 1 使用 SPI1 的 MIPI DBI 方式驱动,屏幕 2 使用 SPI2 的 SPI 软件 DC 方式驱动作为示例。配置如下所示:

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
spi_panel1: endpoint@1 {
reg = <1>;
remote-endpoint = <&panel_st7789v_spi2>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default &spi1_pins_hold>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_DBI>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "st7789v";
lcd_if = <1>;
lcd_dbi_if = <2>;
lcd_data_speed = <48>;
lcd_x = <240>;
lcd_y = <240>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 4 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

&spi2 {
clock-frequency = <100000000>;
pinctrl-0 = <&spi2_pins_default>;
pinctrl-1 = <&spi2_pins_sleep>;
pinctrl-names = "default", "sleep";
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";

panel_st7789v_spi2: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "st7789v";
lcd_if = <0>;
lcd_dbi_if = <0>;
lcd_data_speed = <48>;
lcd_x = <172>;
lcd_y = <320>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_spi_dc_pin = <&pio PD 17 GPIO_ACTIVE_LOW>;
lcd_gpio_0 = <&pio PD 13 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

lcd_fb 节点用于配置和管理 LCD 屏幕的显示功能。该节点通过 port 属性配置了两款不同的 SPI 接口屏幕,分别为 spi_panel0spi_panel1。每个屏幕的配置都通过 endpointremote-endpoint 关联,指定了屏幕的接口和驱动信息。

spi 节点定义了 SPI 控制器使用的引脚,SPI 驱动方式,SPI 最大频率,其中定义了 Panel 的节点标识该 LCD 显示面板位于该 SPI 节点下。

panel_st7789v 节点定义了屏幕的物理尺寸,时序,接口,背光 PWM 等,配置项如下表所示

配置项说明
lcd_usedLCD显示屏是否启用。值为 <1> 表示启用。
lcd_driver_nameLCD显示屏驱动的名称,此处为 st7789v,表示使用ST7789V驱动。必须与屏驱动中strcut __lcd_panel变量的name成员一致。
lcd_ifLCD接口类型,
<0> 代表使用SPI接口。
<1> 代表使用DBI接口。
lcd_dbi_ifLCD DBI接口类型
0:L3I1
1:L3I2
2:L4I1
3:L4I2
4:D2LI
lcd_data_speed数据传输速率,<48> 表示设置为48 MHz。用于设置 SPI/DBI 接口时钟的速率,单位MHz。
lcd_x屏幕的横向分辨率,<240> 表示240像素。
lcd_y屏幕的纵向分辨率,<240> 表示240像素。
lcd_pixel_fmt像素格式,<10> 表示使用RGB565格式。
lcd_dbi_fmtDBI接口的格式,<2> 表示并行接口的16位数据格式。
lcd_rgb_orderRGB像素顺序,<0> 表示RGB顺序。
lcd_widthLCD宽度,<60> 表示60mm宽。
lcd_heightLCD高度,<60> 表示60mm高。
lcd_pwm_used是否启用PWM调光,<0> 表示未启用。
lcd_pwm_chPWM通道,<6> 表示使用通道6。
lcd_pwm_freqPWM频率,<5000> 表示5000 Hz。
lcd_pwm_polPWM极性,<1> 表示正极性。
lcd_frm是否启用 frm 模式
lcd_gamma_en是否启用Gamma校正,<1> 表示启用。
fb_buffer_num帧缓冲区的数量,<2> 表示有两个缓冲区。
lcd_backlight背光亮度,<100> 表示设置为100%。
lcd_fps显示帧率,<60> 表示每秒60帧。
lcd_dbi_teDBI接口的TE信号,<0> 表示禁用。
lcd_dbi_clk_modeDBI时钟模式
0: 自动停止。有数据就有时钟,没发数据就没有
1: 一直保持。无论发不发数据都有时钟
lcd_spi_dc_pinLCD 的 DC 引脚,使用 SPI 模式时需要配置
lcd_gpio_0LCD 的 GPIO 配置,< &pio PD 4 GPIO_ACTIVE_LOW > 表示连接到PD4,低电平有效。一般配置为屏幕 RST 引脚
status配置状态,"okay" 表示配置正常启用。

模块参数配置

lcd_driver_name

LCD 屏驱动的名字(字符串),必须与屏驱动中 strcut __lcd_panel 变量的 name 成员一致。

lcd_if

设置相应值的对应含义为:

enum sunxi_lcd_fb_disp_lcd_if {
LCD_FB_IF_SPI = 0,
LCD_FB_IF_DBI = 1,
LCD_FB_IF_QSPI = 2,
};

SPI 接口就是俗称的 4 线 SPI 模式,这是因为发送数据时需要额外借助DC线来区分命令和数据,与sclkcssda共四线。

SPI 接口时序如下所示:

SPI 模式时序

如果设置了dbi接口,那么还需要进一步区分dbi接口,需要设置 lcd_dbi_if,如果设置了 qspi 接口,那么还需要进一步区分qspi接口,需要设置 lcd_qspi_if

lcd_dbi_if

LCD DBI 接口设置。

这个参数只有在 lcd_if=1 时才有效。

设置相应值的对应含义为:

enum sunxi_lcd_fb_dbi_if {
LCD_FB_L3I1 = 0x0,
LCD_FB_L3I2 = 0x1,
LCD_FB_L4I1 = 0x2,
LCD_FB_L4I2 = 0x3,
LCD_FB_D2LI = 0x4,
};

所有模式在发送数据时每个周期的比特数量根据不同像素格式不同而不同。

L3I1L3I2 是三线模式(不需要DC脚,SPI 使用 9BIT,第一个 BIT 区分是 Command 还是 Data)

L3I1L3I2 的区别是读时序,也就是是否需要额外脚来读寄存器。读写时序图如下:

  • L3I 写时序:

L3I 写时序

  • L3I 读时序

L3I 读时序

L4I1L4I2 是四线模式,与 SPI 接口协议一样,区别是 DBI 下 DC 脚的控制是硬件自动化控制,而 SPI 模式下需要 CPU 软件控制 DC 脚。另外 I2 和 I1 的区别是读时序,也就是否需要额外脚来读取寄存器。

  • L4I 写时序

L4I 写时序

  • L4I读时序

L4I读时序

D2LI 是 2 DATA LANE 模式。硬件连接上,第二根数据脚连接到原来 1 DATA LANE 的DC脚,2 DATA LANE 在传输数据时就自带D/C (Data/Commend)信息了,所以原来的 DC 脚就可以空出来作为第二根数据线了。

引脚功能
CSX芯片使能
DCX串行时钟
SDA串行数据输入/输出
WRX串行数据输入 2
  • D2LI 命令写时序
提示

2 DATA LANE 模式接口的命令写入协议与3线串行接口相同,因此用户可以忽略WRX的输入数据。任何指令都可以按任意顺序发送给驱动程序。最高有效位(MSB)先传输。当CSX为高电平时,串行接口初始化。在此状态下,SCL时钟脉冲或SDA数据没有影响。CSX的下降沿使串行接口启用,并指示数据传输的开始。

D2LI 命令写时序

  • D2LI SRAM 写时序
提示

2数据线串行接口的SRAM写入模式需要使用 SDA 引脚和 WRX 引脚作为数据输入引脚。

D2LI SRAM 写时序

  • D2L1 读时序
提示

2 DATA LANE 模式接口的命令读协议与3线串行接口相同。

D2L1 读时序

lcd_dbi_fmt

DBI接口像素格式。

enum lcdfb_dbi_fmt {
LCDFB_DBI_RGB111 = 0x0,
LCDFB_DBI_RGB444 = 0x1,
LCDFB_DBI_RGB565 = 0x2,
LCDFB_DBI_RGB666 = 0x3,
LCDFB_DBI_RGB888 = 0x4,
};

选择的依据是接收端屏Driver IC的支持情况,请查看Driver IC手册或询问屏厂。

然后必须配合 lcd_pixel_fmt 的选择,比如说选 RGB565 时,lcd_pixel_fmt也要选565格式。

lcd_dbi_te

使能 TE 触发。

TE 即(Tearing Effect),也就是撕裂的意思,由于读写不同导致撕裂现象,TE 脚的功能就是用于同步读写,TE 脚的频率也就是屏的刷新率,所以 TE 脚也可以看做 VSYNC 脚(垂直同步脚)

0: 禁止te
1: 下降沿触发
2: 上升沿触发

lcd_dbi_clk_mode

选择 dbi 时钟的行为模式。

0:自动停止。有数据就有时钟,没发数据就没有
1:一直保持。无论发不发数据都有时钟

注意上面的选项关系屏兼容性。部分屏幕需要一直提供时钟进行刷屏,否则不会更新显示。

lcd_rgb_order

输入图像数据 rgb 顺序识别设置,仅当 lcd_if=1 配置为 DBI 模式时有效。

0:RGB
1:RBG
2:GRB
3:GBR
4:BRG
5:BGR
6:G_1RBG_0
7:G_0RBG_1
8:G_1BRG_0
9:G_0BRG_1

非 RGB565 格式用 0 到 5 即可。

针对 RGB565 格式说明如下:

RGB565 格式会遇到大小端问题,ARM/RISC-V 平台和 PC 平台存储都是小端(little endian,低字节放在低地址,高字节放在高地址),但是许多 SPI 屏都是默认大端(Big Endian)。

也就是存储的字节顺序和发送的字节顺序不对应。这个时候选择 6 以下,DBI接口就会自动将小端转成大端。如果遇到默认是小端的 SPI 屏,则需要选择 6 以上,DBI接口会自动用回小端方式。

提示

6 以上格式这样解释:

R是5比特,G是6比特,B是5比特,再把G拆成高3位(G_1)和低3位(G_0) 所以以下两种顺序:

rgb565像素排列

  1. R-G_1-G_0-B,大端。
  2. G_0-B-R-G_1,对应上面的9,小端。

lcd_qspi_if

LCD QSPI 接口设置。

这个参数只有在 lcd_if=2 时才有效。

设置相应值的对应含义为:

enum sunxi_lcd_fb_qspi_if {
LCD_FB_QSPI_LANE1 = 0x0,
LCD_FB_QSPI_LANE2 = 0x1,
LCD_FB_QSPI_LANE4 = 0x2,
};

其具体时序如下:

  • QSPI 读时序

image-20250409113454912

  • QSPI 单线写时序(LCD_FB_QSPI_LANE1

image-20250409113314256

  • QSPI 双线写时序(LCD_FB_QSPI_LANE2

image-20250409113351810

  • QSPI 四线写时序(LCD_FB_QSPI_LANE4

image-20250409113430521

lcd_x

显示屏的水平像素数量,注意如果屏支持横竖旋转,那么 lcd_x 和 lcd_y 也要对调。

lcd_y

显示屏的行数,注意如果屏支持横竖旋转,那么 lcd_x 和 lcd_y 也要对调。

lcd_data_speed

用于设置 SPI/DBI 接口时钟的速率,单位MHz。

  1. 发送端(SoC) 的最大限制需要参考芯片数据手册。
  2. 接收端(屏Driver IC)的限制,请查看对应Driver IC手册或者询问屏厂支持。
  3. 超出以上限制都有可能导致显示异常。

lcd_data_speed_hz

用于设置 SPI/DBI 接口时钟的速率,单位Hz。

  1. 发送端(SoC) 的最大限制需要参考芯片数据手册。
  2. 接收端(屏Driver IC)的限制,请查看对应Driver IC手册或者询问屏厂支持。
  3. 超出以上限制都有可能导致显示异常。
  4. 使用该配置后,lcd_data_speed 所配置的 MHz 参数将弃用,优先使用 lcd_data_speed_hz

lcd_fps

设置屏的刷新率,单位Hz。当 lcd_dbi_te 使能时,这个值设置无效。

lcd_pwm_used

是否使用 PWM。此参数标识是否使用 PWM 用以背光亮度的控制。

lcd_pwm_ch

此参数标识使用的 PWM 通道。

lcd_pwm_freq

这个参数配置PWM信号的频率,单位为Hz。

lcd_pwm_pol

这个参数配置PWM信号的占空比的极性。设置相应值对应含义为:

0:active high
1:active low

lcd_pwm_max_limit

最高限制,以亮度值表示。

比如150,则表示背光最高只能调到150,0~255范围内的亮度值将会被线性映射到0~150范围内。用于控制最高背光亮度,节省功耗。

lcd_backlight

默认背光值,取值范围0到255,值越大越亮。

lcd_bl_en

背光使能脚定义

lcd_spi_dc_pin

指定作为 DC 的管脚,用于 SPI 接口时软件控制 DC 模式。

lcd_gpio_x

x 表示数字。如果有多个 gpio 脚需要控制,则定义 lcd_gpio_0lcd_gpio_1 等。

lcd_pixel_fmt

选择传输数据的像素格式。

可选值如下,当你更换RGB分量顺序的时候,也得相应修改 lcd_rgb_order,或者修改屏驱动的 RGB 分量顺序(一般是3Ah寄存器)。

备注

DBI接口支持 RGB32 和 RGB16 的情况。

SPI 接口只支持 RGB16 的情况。

enum lcdfb_pixel_format {
LCDFB_FORMAT_ARGB_8888 = 0x00, // MSB A-R-G-B LSB
LCDFB_FORMAT_ABGR_8888 = 0x01,
LCDFB_FORMAT_RGBA_8888 = 0x02,
LCDFB_FORMAT_BGRA_8888 = 0x03,
LCDFB_FORMAT_XRGB_8888 = 0x04,
LCDFB_FORMAT_XBGR_8888 = 0x05,
LCDFB_FORMAT_RGBX_8888 = 0x06,
LCDFB_FORMAT_BGRX_8888 = 0x07,
LCDFB_FORMAT_RGB_888 = 0x08,
LCDFB_FORMAT_BGR_888 = 0x09,
LCDFB_FORMAT_RGB_565 = 0x0a,
LCDFB_FORMAT_BGR_565 = 0x0b,
LCDFB_FORMAT_ARGB_4444 = 0x0c,
LCDFB_FORMAT_ABGR_4444 = 0x0d,
LCDFB_FORMAT_RGBA_4444 = 0x0e,
LCDFB_FORMAT_BGRA_4444 = 0x0f,
LCDFB_FORMAT_ARGB_1555 = 0x10,
LCDFB_FORMAT_ABGR_1555 = 0x11,
LCDFB_FORMAT_RGBA_5551 = 0x12,
LCDFB_FORMAT_BGRA_5551 = 0x13,
};

fb_buffer_num

显示 framebuffer 数量,为了平滑显示,这里一般是2个,为了省内存也可以改成1。

模块配置案例

SPI 模式接口屏配置

时序图:

SPI 时序

如果 IC 支持 DBI 接口,那么就没有必要用 SPI 接口,DBI 接口其协议能覆盖所有情况。

提示

SPI 接口协议与 DBI 的 L4I1L4I2 四线模式时序一样,区别是DBI 下 DC 脚的控制是硬件自动化控制,而 SPI 模式下需要 CPU 软件控制 DC 脚,由于是非硬件操作,所以刷图性能平均较低。一般 SPI 屏幕配置 DBI L4I1 模式即可。由硬件控制 DC 脚即可。

注意

部分屏幕需要特殊时序的 DC 脚,需要交由 CPU 软件控制 DC 时序,此时仅可使用 SPI 模式。如果使用 SPI 接口,它有一些限制。

  1. 不支持 2 data lane。
  2. 必须指定DC脚。这是由于spi协议不会自动控制DC脚来区分数据命令,通过设置 lcd_spi_dc_pin 可以完成这个目的,这跟管脚不必用 SPI 控制器中的脚,可以任意选择GPIO。
  3. 只支持 RGB565 的像素格式。由于只有单 data lane,速度过慢,RGB565 以上格式都不现实。

配置如下:

&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3"; /* CS, SCK, SDA */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_sleep: spi1@2 {
pins = "PD1", "PD2", "PD3";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};


&spi1 {
clock-frequency = <100000000>;
pinctrl-0 = <&spi2_pins_default>;
pinctrl-1 = <&spi2_pins_sleep>;
pinctrl-names = "default", "sleep";
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "st7789v";
lcd_if = <0>;
lcd_dbi_if = <0>;
lcd_data_speed = <48>;
lcd_x = <172>;
lcd_y = <320>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_spi_dc_pin = <&pio PD 17 GPIO_ACTIVE_LOW>;
lcd_gpio_0 = <&pio PD 13 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

DBI 模式 L4I1 接口屏配置

提示

SPI 接口协议与 DBI 的 L4I1L4I2 四线模式时序一样,区别是DBI 下 DC 脚的控制是硬件自动化控制,而 SPI 模式下需要 CPU 软件控制 DC 脚,由于是非硬件操作,所以刷图性能较低。一般 SPI 屏幕配置 DBI L4I1 模式即可。由硬件控制 DC 脚即可。

L4I1时序

&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3"; /* CS, SCK, SDA */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_hold: spi1@1 {
pins = "PD5"; /* DC */
function = "spi1_hold";
allwinner,drive = <3>;
bias-pull-up;
};

spi1_pins_sleep: spi1@2 {
pins = "PD1", "PD2", "PD3", "PD5";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default &spi1_pins_hold>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_DBI>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "st7789v";
lcd_if = <1>;
lcd_dbi_if = <2>;
lcd_data_speed = <48>;
lcd_x = <240>;
lcd_y = <240>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 4 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

DBI 模式 L3I1 接口屏配置

时序图:

L3I1时序

&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3"; /* CS, SCK, SDA */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_sleep: spi1@2 {
pins = "PD1", "PD2", "PD3";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_DBI>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "st7789v";
lcd_if = <1>;
lcd_dbi_if = <0>;
lcd_data_speed = <48>;
lcd_x = <240>;
lcd_y = <240>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 4 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

DBI 模式 D2I1 接口屏配置

时序图:

D2I1时序

&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3", "PD4"; /* CS, SCK, SDA, WRX */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_sleep: spi1@2 {
pins = "PD1", "PD2", "PD3", "PD4";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_nv3031a_spi1>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_DBI>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";

panel_nv3031a_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "nv3031a";
lcd_if = <1>;
lcd_dbi_if = <4>;
lcd_data_speed = <48>;
lcd_x = <240>;
lcd_y = <240>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 4 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

QSPI 模式单线 QSPI 接口屏配置

时序图:

image-20250409113936063

&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3"; /* CS, SCK, D0 */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_sleep: spi1@3 {
pins = "PD1", "PD2", "PD3";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_SOFT>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
spi-rx-bus-width = <1>;
spi-tx-bus-width = <1>;
lcd_used = <1>;
lcd_driver_name = "st77916_qspi";
lcd_if = <2>;
lcd_dbi_if = <0>;
lcd_qspi_if = <0>;
lcd_data_speed = <48>;
lcd_x = <360>;
lcd_y = <360>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 14 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

QSPI 模式双线 QSPI 接口屏配置

时序图:

image-20250409114011763

&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3", "PD4"; /* CS, SCK, D0, D1 */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_sleep: spi1@3 {
pins = "PD1", "PD2", "PD3", "PD4";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_SOFT>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
spi-rx-bus-width = <2>;
spi-tx-bus-width = <2>;
lcd_used = <1>;
lcd_driver_name = "st77916_qspi";
lcd_if = <2>;
lcd_dbi_if = <0>;
lcd_qspi_if = <1>;
lcd_data_speed = <48>;
lcd_x = <360>;
lcd_y = <360>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 14 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

QSPI 模式四线 QSPI 接口屏配置

时序图:

image-20250409113550253

&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3", "PD4"; /* CS, SCK, D0, D1 */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_hold: spi1@1 {
pins = "PD5"; /* D2 */
function = "spi1_hold";
allwinner,drive = <3>;
bias-pull-up;
};

spi1_pins_wp: spi1@2 {
pins = "PD6"; /* D3 */
function = "spi1_wp";
allwinner,drive = <3>;
bias-pull-up;
};

spi1_pins_sleep: spi1@3 {
pins = "PD1", "PD2", "PD3", "PD4", "PD5", "PD6";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default &spi1_pins_hold &spi1_pins_wp>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_SOFT>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
spi-rx-bus-width = <4>;
spi-tx-bus-width = <4>;
lcd_used = <1>;
lcd_driver_name = "st77916_qspi";
lcd_if = <2>;
lcd_dbi_if = <0>;
lcd_qspi_if = <2>;
lcd_data_speed = <48>;
lcd_x = <360>;
lcd_y = <360>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 14 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

带 TE 脚的屏

TE 即(Tearing Effect),也就是撕裂的意思,是由于读写不同步导致撕裂现象,TE 脚的功能就是用于同步读写,TE 脚的频率也就是屏的刷新率,所以 TE 脚也可以看做 Vsync 脚(垂直同步脚)。

  1. 硬件设计阶段,需要将屏的 TE 脚连接到 IC 的 DBI 接口的 TE 脚。
  2. 配置上接口使用 DBI 接口。
  3. 然后使能 lcd_dbi_te
  4. 屏驱动使能 TE 功能,寄存器一般是 35h,详情看屏对应的 Driver IC 手册。
  5. 屏驱动设置帧率,根据屏能接受的传输速度选择合理的帧率(比如ST7789里面是通过c6h来设置 TE 频率)。
&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3", "PD6"; /* CS, SCK, SDA, TE */
function = "spi1";
allwinner,drive = <3>;
};

spi1_pins_hold: spi1@1 {
pins = "PD5"; /* DC */
function = "spi1_hold";
allwinner,drive = <3>;
bias-pull-up;
};

spi1_pins_sleep: spi1@2 {
pins = "PD1", "PD2", "PD3", "PD5", "PD6";
function = "io_disabled";
};
};

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};

&spi1 {
pinctrl-0 = <&spi1_pins_default>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_DBI>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";

panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "st7789v";
lcd_if = <1>;
lcd_dbi_if = <2>;
lcd_data_speed = <48>;
lcd_x = <240>;
lcd_y = <240>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <1>;
lcd_dbi_clk_mode = <0>;
lcd_gpio_0 = <&pio PD 4 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

其他配置

横竖屏旋转

  1. 平台没有硬件旋转功能,软件旋转太慢而且耗费CPU。
  2. 不少 SPI 屏支持内部旋转,需要在屏驱动初始化的时候进行设置,一般是36h寄存器。
/* 转成横屏 */
sunxi_lcd_cmd_write(sel, 0x36);
sunxi_lcd_para_write(sel, 0xa0);
  1. lcd_fb 的配置中需要将 lcd_xlcd_y 的值对调,此时软件将屏视为横屏。
  2. 屏内部旋转出图效果可能会变差,建议选屏的时候直接选好方向。

帧率控制

屏的刷新率受限于多方面:

  1. SPI/DBI 硬件传输速度,也就是时钟脚的频率。设置 lcd_data_speed 可以设置硬件传输速度,一般最大不超过100MHz。如果屏能正常接收,这个值自然是越大越好。
  2. 屏 Driver IC 接收能力。Driver IC 手册中会提到屏的能接受的最大 SCLK 周期。
  3. 使用 2 DATA LANE 还是 1 DATA LANE,理论上 2 DATA LANE 的速度会翻倍。见[DBI 模式 D2I1 接口屏配置](#DBI 模式 D2I1 接口屏配置)。
  4. 像素格式。像素格式决定需要传输的数据量,颜色数量越小的像素格式,帧率越高,但是效果越差。
  5. 带TE脚的屏一节中我们知道,TE 相关设置直接影响到屏刷新率。
  6. 如果不支持 TE,可以通过设置lcd_fps来控制帧率,你需要根据第一点和第二点选择一个合适的值。

背光控制

  1. 硬件需要支持 PWM 背光电路。
  2. 驱动支持 PWM 背光调节,只需要配置好lcd_pwm开头,lcd_backlightlcd_bl_en等背光相关配置即可。

像素格式相关

  1. lcd_pixel_fmt,这个设置项用于设置fbdev的像素格式。
  2. lcd_dbi_fmt,这个用于设置DBI接口发送的像素格式。

SPI/DBI 发送数据的时候没有必要发送 alpha 通道,但是应用层却有对应的 alpha 通道,比如 ARGB8888 格式。

这个时候硬件会自动帮我们处理好 alpha 通道,所以 lcd_pixel_fmt 选择有 alpha 通道的格式时,lcd_dbi_fmt 可以选 rgb666 或者 rgb888,不用和它一样。

电源配置

有多个电源的情况,就用 lcd_power1lcd_power2 然后屏驱动里面调用 sunxi_lcd_power_enable 接口即可。

GPIO配置说明

lcd_bl_enlcd_spi_dc_pin以及 lcd_gpio_x 都是属于GPIO属性类型。

lcd_spi_dc_pin  = <&pio PD 4 GPIO_ACTIVE_LOW>;

多个显示 LCD

  1. 确定硬件有没有多余的 SPI/DBI 接口。
  2. 需要在设备树里面新增 lcd_fb 中的 endpoint
注意

每个 SPI 控制器仅能接一块屏幕,不支持一个 SPI 接口挂多个屏幕使用 CS 控制。

例如配置 6 个 SPI 控制器驱动 6 块屏:

&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
spi_panel1: endpoint@1 {
reg = <1>;
remote-endpoint = <&panel_st7789v_spi2>;
};
spi_panel2: endpoint@2 {
reg = <2>;
remote-endpoint = <&panel_st7789v_spi3>;
};
spi_panel3: endpoint@3 {
reg = <3>;
remote-endpoint = <&panel_st7789v_spi4>;
};
spi_panel4: endpoint@4 {
reg = <4>;
remote-endpoint = <&panel_st7789v_spi5>;
};
spi_panel5: endpoint@5 {
reg = <5>;
remote-endpoint = <&panel_st7789v_spi6>;
};
};
};

编写屏驱动

屏驱动源码位置:

bsp/drivers/video/sunxi/lcd_fb/panels
  1. 在屏驱动源码位置下拷贝现有一个屏驱动,包括头文件和源文件,然后将文件名改成有意义的名字,比如屏型号。
  2. 修改源文件中的strcut __lcd_panel变量的名字,以及这个变量成员name的名字,这个名字必须和 board.dts[lcd_fb0]lcd_driver_name一致。
  3. 在屏驱动目录下修改panel.cpanel.h。在全局结构体变量panel_array中新增刚才添加strcut __lcd_panel的变量指针。panel.h中新增strcut __lcd_panel的声明。并用宏括起来。
  4. 修改bsp/drivers/video/sunxi/lcd_fb/panels/Kconfig,新增一个config,与第三点提到的宏对应。
  5. 修改bsp/drivers/video/sunxi/lcd_fb路径下的Makefile文件。给lcd_fb-obj变量新增刚才加入的源文件对应.o
  6. 根据本手册以及屏手册,Driver IC手册修改设备树中的[lcd_fb0]节点下面的属性
  7. 实现屏源文件中的LCD_open_flowLCD_close_flowLCD_panel_initLCD_power_on等函数

开关屏流程函数解析

开关屏的操作流程如下图所示。

其中,LCD_open_flowLCD_close_flow 称为开关屏流程函数,该函数利用 LCD_OPEN_FUNC 进行注册回调函数,先注册先执行,可以注册多个,不限制数量。

开关屏函数流程

LCD_open_flow

功能:初始化开屏的步骤流程。

原型:

static __s32 LCD_open_flow(__u32 sel)

函数常用内容为:

static __s32 LCD_open_flow(__u32 sel)
{
LCD_OPEN_FUNC(sel, LCD_power_on,10);
LCD_OPEN_FUNC(sel, LCD_panel_init, 50);
LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 100);
LCD_OPEN_FUNC(sel, LCD_bl_open, 0);
return 0;
}

如上,初始化整个开屏的流程步骤为四个:

  1. 打开LCD电源,再延迟10ms。
  2. 初始化屏,再延迟50ms;(不需要初始化的屏,可省掉此步骤)。
  3. 向屏发送全黑的数据。这一步骤是必须的,而且需要在开背光之前。
  4. 打开背光,再延迟0ms。

LCD_open_flow 函数只会系统初始化的时候调用一次,执行每个 LCD_OPEN_FUNC 即是把对应的开屏步骤函数进行注册,并没有立即执行该开屏步骤函数LCD_open_flow 函数的内容必须统一用 LCD_OPEN_FUNC(sel, function, delay_time) 进行函数注册的形式,确保正常注册到开屏步骤中。

LCD_OPEN_FUNC 的第二个参数是前后两个步骤的延时长度,单位ms,注意这里的数值请按照屏手册规定去填,乱填可能导致屏初始化异常或者开关屏时间过长,影响用户体验。

LCD_close_flow

功能:初始化关屏的步骤流程。

原型:

static __s32 LCD_close_flow(__u32 sel)

函数常用内容为:

static __s32 LCD_close_flow(__u32 sel)
{
LCD_CLOSE_FUNC(sel, LCD_bl_close, 50);
LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10);
LCD_CLOSE_FUNC(sel, LCD_power_off, 10);
return 0;
}
  1. LCD_bl_close,是关背光,关完背光在处理其它事情,不会影响用户视觉。
  2. LCD_panel_exit,发送命令让屏退出工作状态。
  3. 关电复位,让屏彻底关闭。

LCD_OPEN_FUNC

功能:注册开屏步骤函数到开屏流程中,记住这里是注册不是执行!

原型:

void LCD_OPEN_FUNC(__u32 sel, LCD_FUNC func, __u32 delay)

参数说明:

func 是一个函数指针,其类型是:void (*LCD_FUNC) (__u32 sel),用户自己定义的函数必须也要用统一的形式。比如:

void user_defined_func(__u32 sel)
{
//do something
}

delay 是执行该步骤后,再延迟的时间,时间单位是毫秒。

LCD_power_on

这是开屏流程中第一步,一般在这个函数使用sunxi_lcd_gpio_set_value进行GPIO控制,用sunxi_lcd_power_enable函数进行电源开关。

参考屏手册里面的上电时序(Power on sequence)。

LCD_panel_init

这是开屏流程第二步,一般使用sunxi_lcd_cmd_write和sunxi_lcd_para_write对屏寄存器进行初始化。

请向屏厂索要初始化寄存器代码或者自行研究屏Driver IC手册。

lcd_fb_black_screen

向屏传输全黑数据的接口,是必须的,否则打开背光后,呈现的将是雪花屏。

LCD_bl_open

这是背光使能,固定调用。

  1. sunxi_lcd_backlight_enable, 打开lcd_bl_en脚。
  2. sunxi_lcd_pwm_enable, 使能pwm。

LCD_bl_close

这是关闭背光。固定调用下面两个函数,分别是:

  1. sunxi_lcd_backlight_disable,lcd_bl_en关闭
  2. sunxi_lcd_pwm_disable, 关闭pwm。

LCD_power_off

这是关屏流程中最后一步,一般在这个函数使用sunxi_lcd_gpio_set_value进行GPIO控制,用sunxi_lcd_power_enable函数进行电源开关。

参考屏手册里面的下电时序(Power off sequence)。

sunxi_lcd_delay_ms

函数:sunxi_lcd_delay_ms/sunxi_lcd_delay_us

功能:延时函数,分别是毫秒级别/微秒级别的延时。

原型:s32 sunxi_lcd_delay_ms(u32 ms); / s32 sunxi_lcd_delay_us(u32 us);

sunxi_lcd_backlight_enable

函数:sunxi_lcd_backlight_enable/ sunxi_lcd_backlight_disable

功能:打开/关闭背光,操作的是lcd_bl_en

原型:

  • void sunxi_lcd_backlight_enable(u32 screen_id);

  • void sunxi_lcd_backlight_disable(u32 screen_id);

sunxi_lcd_pwm_enable

函数:sunxi_lcd_pwm_enable / sunxi_lcd_pwm_disable

功能:打开/关闭 pwm 控制器,打开时 pwm 将往外输出 pwm 波形。对应的是 lcd_pwm_ch 所对应的那一路 pwm。

原型:

  • s32 sunxi_lcd_pwm_enable(u32 screen_id);

  • s32 sunxi_lcd_pwm_disable(u32 screen_id);

sunxi_lcd_power_enable

函数:sunxi_lcd_power_enable / sunxi_lcd_power_disable

功能:打开/关闭Lcd电源,操作的是板级配置文件中的lcd_power/lcd_power1/lcd_power2。(pwr_id 标识电源索引)。

原型:

  • void sunxi_lcd_power_enable(u32 screen_id, u32 pwr_id);

  • void sunxi_lcd_power_disable(u32 screen_id, u32 pwr_id);

  1. pwr_id = 0:对应于配置文件中的lcd_power。
  2. pwr_id = 1:对应于配置文件中的lcd_power1。
  3. pwr_id = 2:对应于配置文件中的lcd_power2。
  4. pwr_id = 3:对应于配置文件中的lcd_power3。

sunxi_lcd_cmd_write

函数:sunxi_lcd_cmd_write

功能:使用 SPI/DBI 发送命令。

原型:s32 sunxi_lcd_cmd_write(u32 screen_id, u8 cmd);

sunxi_lcd_para_write

函数:sunxi_lcd_para_write

功能:使用 SPI/DBI 发送参数。

原型:s32 sunxi_lcd_para_write(u32 screen_id, u8 para);

sunxi_lcd_qspi_cmd_write

函数:sunxi_lcd_qspi_cmd_write

功能:使用 QSPI 发送命令。

原型:s32 sunxi_lcd_qspi_cmd_write(u32 screen_id, u8 cmd);

sunxi_lcd_qspi_para_write

函数:sunxi_lcd_qspi_para_write

功能:使用 QSPI 发送参数。

原型:s32 sunxi_lcd_qspi_para_write(u32 screen_id, u8 cmd, u8 para);

sunxi_lcd_qspi_multi_para_write

函数:sunxi_lcd_qspi_multi_para_write

功能:使用 QSPI 发送多组参数。

原型:s32 sunxi_lcd_qspi_para_write(u32 screen_id, u8 cmd, u8 *tx_buf, u32 len);

sunxi_lcd_gpio_set_value

函数:sunxi_lcd_gpio_set_value

功能:LCD_GPIO PIN 脚上输出高电平或低电平。

原型:

s32 sunxi_lcd_gpio_set_value(u32 screen_id, u32 io_index, u32 value);

参数说明:

  • io_index = 0:对应于配置文件中的lcd_gpio_0。
  • io_index = 1:对应于配置文件中的lcd_gpio_1。
  • io_index = 2:对应于配置文件中的lcd_gpio_2。
  • io_index = 3:对应于配置文件中的lcd_gpio_3。
  • value = 0:对应IO输出低电平。
  • Value = 1:对应IO输出高电平。

只用于该GPIO定义为输出的情形。

sunxi_lcd_gpio_set_direction

函数:sunxi_lcd_gpio_set_direction

功能:设置 LCD_GPIO PIN 脚为输入或输出模式。

原型:

s32 sunxi_lcd_gpio_set_direction(u32 screen_id, u32 io_index, u32 direction);

参数说明:

  • io_index = 0:对应于配置文件中的lcd_gpio_0。
  • io_index = 1:对应于配置文件中的lcd_gpio_1。
  • io_index = 2:对应于配置文件中的lcd_gpio_2。
  • io_index = 3:对应于配置文件中的lcd_gpio_3。
  • direction = 0:对应IO设置为输入。
  • direction = 1:对应IO设置为输出。

FAQ

怎么判断屏初始化成功

屏初始化成功,一般呈现的现象是雪花屏。因为屏驱动里面,在 LCD_open_flow 中添加了lcd_fb_black_screen的注册,故正常情况下开机是有背光的黑屏画面。

黑屏-无背光

一般是电源或者pwm相关配置没有配置好。参考lcd_pwm开头的相关配置。

送图无显示

排除步骤:

  1. 屏驱动里面,在LCD_open_flow中删除lcd_fb_black_screen的注册,启动后,如果屏初始化成功应该是花屏状态(大部分屏如此)。
  2. 如果屏没有初始化成功,请检查屏电源,复位脚状态。
  3. 如果屏初始化成功,但是发数据时又没法显示,那么需要检查是不是帧率过快,查看帧率控制
  4. 如果电源复位脚正常,请检查配置,lcd_dbi_if, lcd_dbi_fmt是否正确,屏是否支持, 如果支持,在屏驱动里面是否有对应上。
  5. 尝试修改lcd_dbi_clk_mode

闪屏

非常有可能是速度跑太快,参考帧率控制一小节。

画面偏移

画面随着数据的发送偏移越来越大。

尝试修改lcd_dbi_clk_mode

屏幕白屏

屏幕白屏,但是背光亮起

image-20231017113731873

白屏是因为屏幕没有初始化,需要检查屏幕初始化序列或者初始化数据是否正确。

屏幕花屏

屏幕花屏,无法控制

image-20231017113841569

花屏一般是因为屏幕初始化后没有正确设置 addrwin,或者初始化序列错误。

LVGL 屏幕颜色不正确

出现反色,颜色异常

image-20231017113620580

请配置 LVGL LV_COLOR_DEPTH 参数为 16,LV_COLOR_16_SWAP 为 1,这是由 SPI LCD 的特性决定的。

image-20231017113520621

显示反色

这是由于屏幕启动了 RB SWAP,一般是 0x36 寄存器修改

正常显示

sunxi_lcd_cmd_write(sel, 0X36);
sunxi_lcd_para_write(sel, 0x00);

反色显示

sunxi_lcd_cmd_write(sel, 0X36);
sunxi_lcd_para_write(sel, 0x08);

出现部分花屏

image-20231023103039467

  • 检查 address 函数是否正确
  • 检查 board.dts 屏幕配置分辨率是否正确

SPI LCD 颜色相关问题

首先,得先确定显示屏使用的是SPI接口,还是DBI接口,不同的接口,输入数据的解析方式是不一样的。

DBI接口的全称是 Display Bus Serial Interface ,在显示屏数据手册中,一般会说这是SPI接口,所以有人会误认为 SPI 屏可以使用 normal spi 去直接驱动。

阅读lcd_dbi_if部分的介绍可以知道,在3线模式时,发送命令前有1位A0用于指示当前发送的是数据,还是命令。而命令后面接着的数据就没有这个A0位了,代表SPI需要在9位和8位之间来回切换,而在读数据时,更是需要延时 dummy clock 才能读数据,normal spi 都很难,甚至无法实现。所以 normal spi 只能使用软件控制 DC GPIO 去模拟 4 线的DBI的写操作。

对于这类支持DBI接口的CPU,可以选择不去了解SPI。如果需要用到SPI去驱动显示屏,必须把显示屏设置成小端。

RGB565和RGB666

SPI显示屏一般支持RGB444,RGB565和RGB666,RGB444使用的比较少,所以只讨论RGB565和RGB666.

RGB565代表一个点的颜色由2字节组成,也就是R(红色)用5位表示,G(绿色)用6位表示,B(蓝色)用5位表示,如下图所示:

image-20231016100553340

RGB666一个点的颜色由3字节组成,每个字节代表一个颜色,其中每个字节的低2位会无视,如下图所示:

image-20231016100620890

SPI 接口

因为SPI接口的通讯效率不高,所以建议使用RGB565的显示,以 jlt35031c 显示屏为例,他的显示驱动芯片是 ST7789,设置显示格式的方式是往 3a 寄存器写入0x55(RGB565)或者 0x66(RGB666)

sunxi_lcd_cmd_write(sel, 0x3a);
sunxi_lcd_para_write(sel, 0x55);

在例程中,输入的数据是 0xff,0x00,0xff,0x00,对于SPI接口,是按字节发送。实际上,例程只需要每次发送2字节即可,因为前后发送的都是相同的ff 00,所以没有看出问题。

根据对 565 的数据解析,我们拆分 ff 00 就可以得到红色分量是 0b11111,也就是 31,绿色是0b111000,也就是 56,,蓝色是 0.我们等效转换成 RGB888,有:

R = 31/31*255 = 255
G = 56/63*255 = 226

在调色板输入对应颜色,就可以得到黄色

image-20231016100913213

DBI 接口

因为 DBI 通讯效率较高,所以可以使用 RGB565 或者 RGB666,使用 DBI 接口,也就是 lcd_if 设置为1时,驱动会根据 lcd_pixel_fmt 配置寄存器,以 SDK 中的 kld2844b.c 为例,这显示屏的显示驱动也是 ST7789,但是不同的屏幕,厂家封装时已经限制了通讯方式,所以即使是能使用 DBI 接口的驱动芯片的屏幕,或许也用不了DBI。

sunxi_lcd_cmd_write(sel, 0x3A); /* Interface Pixel Format */
/* 55----RGB565;66---RGB666 */
if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565 ||
info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGR_565) {
sunxi_lcd_para_write(sel, 0x55);
if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565)
rotate &= 0xf7;
else
rotate |= 0x08;
} else if (info[sel].lcd_pixel_fmt < LCDFB_FORMAT_RGB_888) {
sunxi_lcd_para_write(sel, 0x66);
if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRA_8888 ||
info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRX_8888 ||
info[sel].lcd_pixel_fmt == LCDFB_FORMAT_ABGR_8888 ||
info[sel].lcd_pixel_fmt == LCDFB_FORMAT_XBGR_8888) {
rotate |= 0x08;
}
} else {
sunxi_lcd_para_write(sel, 0x66);
}

对于 DBI 格式,不再是以字节的形式去解析,而是以字的方式去解析,为了统一,软件已经规定了,RGB565 格式时,字大小是2字节,也就是16位,而 RGB666 格式时,字大小是4字节,也就是32位。

对于 RGB565 格式,同样是设置为 0xff,0x00。因为屏幕是大端,而芯片存储方式是小端,所以芯片的 DBI 模块,会自动把数据从新排列,也就是实际上 DBI 发送数据时,会先发送0x00,再发送0xff,也就是红色分量为0,绿色分量为 0b000111,也就是7,蓝色分量是 0x11111,也就是31,我们同样转换成RGB888

G = 7/63*255 = 28
B= 31/31*255 = 255

在调色板上输入,可以得到蓝色。

image-20231016101233907

如果是 RGB666,虽然占用的是3个字节,但是没有CPU是3字节对齐的,所以需要一次性输入4字节,然后 DBI 硬件模块,会自动舍弃1个字节,软件同意舍弃了最后一个字节。

依旧以例程为例,例程输入了 0xff,0x00,0xff,0x00,为了方便说明,标准为 0xff(1),0x00(1),0xff(2),0x00(2),其中 0x00(2)会被舍弃掉,然后发送顺序是0xff(2),0x00(1),0xff(1),也就是 0xff(2) 是红色分量,0xff(1) 是蓝色分量,混合可以得到紫色。

image-20231016101308346

总结

调试LCD显示屏实际上就是调试发送端芯片(全志SOC)和接收端芯片(LCD屏上的driver IC)的一个过程:

  1. 添加屏驱动请看编写屏驱动
  2. 仔细阅读屏手册以及driver IC手册(有的话)。
  3. 仔细阅读板级显示配置参数详解。
  4. 确保LCD所需要的各路电源管脚正常。1gei