Zephyr 中 Display 驱动的硬件与软件层级关系
在嵌入式系统中,显示设备作为人机交互的核心组件,其硬件接口的多样性给驱动开发带来了的挑战。Zephyr RTOS 通过一套统一的驱动模型,支持从低功耗 OLED 到高清 MIPI 面板的各种显示设备。本文从驱动之间的硬件和软件层次关系进行说明,并通过设备树展示显示驱动中这种层级关系。
从结构上看最顶层是 Zephyr 的 Display 驱动,不同的 Display 设备驱动搭配不同的显示接口。
Display 驱动接口 链接到标题
Display 驱动接口提供统一的 Display API,在 zephyr/include/zephyr/drivers/display.h 中定义
static inline int display_write(const struct device *dev, const uint16_t x,
const uint16_t y,
const struct display_buffer_descriptor *desc,
const void *buf);
static inline int display_read(const struct device *dev, const uint16_t x,
const uint16_t y,
const struct display_buffer_descriptor *desc,
void *buf);
static inline int display_clear(const struct device *dev);
static inline void *display_get_framebuffer(const struct device *dev);
static inline int display_blanking_on(const struct device *dev);
static inline int display_blanking_off(const struct device *dev);
static inline int display_set_brightness(const struct device *dev,
uint8_t brightness);
static inline void display_get_capabilities(const struct device *dev,
struct display_capabilities *
capabilities);
display_set_pixel_format(const struct device *dev,
const enum display_pixel_format pixel_format);
static inline int display_set_orientation(const struct device *dev,
const enum display_orientation
orientation);
以上 API 都可以做到见名思意,就不做每个的说明了。在 Display 驱动的实现过程中只要实现了struct display_driver_api
中的函数即可。
__subsystem struct display_driver_api {
display_blanking_on_api blanking_on;
display_blanking_off_api blanking_off;
display_write_api write;
display_read_api read;
display_clear_api clear;
display_get_framebuffer_api get_framebuffer;
display_set_brightness_api set_brightness;
display_set_contrast_api set_contrast;
display_get_capabilities_api get_capabilities;
display_set_pixel_format_api set_pixel_format;
display_set_orientation_api set_orientation;
};
在实践操作中,Display 驱动只要实现了blanking_on
,blanking_off
,write
,get_capabilities
,set_pixel_format
即可工作。
在实现 Display 驱动时又会根据设备接口和显示硬件写入不同的流程。
Zephyr 支持的主流显示接口 链接到标题
Zephyr 支持的主流显示接口,从驱动实现上来看包括 4 种
- MIPI DBI
- MIPI DSI
- 并行 RGB LCD
- SPI/I2C
MIPI DBI 接口 链接到标题
Zephyr 的 MIPI DBI 驱动类支持 MIPI DBI-compliant 显示控制器,主要有三种接口类型:
- Type A:Motorola 6800 并行总线(常用于并行接口)
- Type B:Intel 8080 并行总线
- Type C:SPI 串行接口
- OP1: 三线 (CLK,CS,SDA)9bit:前 8bit 数据,最后 1bit 命令/数据选择
- OP2: 三线 (CLK,CS,SDA)18bit:前 7bit 无效,第 8bit 命令/数据选择,后 8bit 数据, 当前 Zephyr 的 API 不支持该模式
- OP3: 四线 (CLK,CS,SDA,DC)8bit:8bit 数据,通过 DC 线进行命令/数据选择
Zephyr 的 MIPI DBI 驱动代码路径位于’zephyr/drivers/mipi_dbi’,主要包括以下几个文件:
- mipi_dbi_bitbang.c :使用普通 GPIO 实现的 MIPI DBI Type A 和 B 接口驱动程序。支持 8 位、9 位和 16 位数据总线宽度。
- mipi_dbi_nxp_flexio_lcdif.c : 使用 NXP FlexIO 实现的 MIPI DBIType A 和 B 接口驱动程序。支持 8 位、16 位数据总线宽度。
- mipi_dbi_nxp_dcnano_lcdif.c :使用 NXP DCNANO LCDIF 控制器实现的 MIPI DBI Type A 和 B 接口驱动程序。支持 8 位、9 位和 16 位数据总线宽度。
- mipi_dbi_nxp_lcdic.c : 使用 NXP LCDIC 控制器实现的 MIPI DBI Type A/B 的支持 8 位、9 位和 16 位数据总线宽度。支持 TYPE C 的 OP1 和 OP3 模式。
- mipi_dbi_stm32_fmc.c : 使用 STM32 FMC 实现的 MIPI DBI Type B 接口驱动程序。支持 16 位数据总线宽度。
- mipi_dbi_spi.c: 使用 Zephyr SPI 标准驱动实现的 MIPI DBI Type C 接口驱动程序。支持 OP1 和 OP3 模式。
Zephyr 为 MIPI DBI 提供统一的 API,定义在zephyr/include/zephyr/drivers/mipi_dbi.h
内
static inline int mipi_dbi_command_write(const struct device *dev,
const struct mipi_dbi_config *config,
uint8_t cmd, const uint8_t *data,
size_t len);
static inline int mipi_dbi_command_read(const struct device *dev,
const struct mipi_dbi_config *config,
uint8_t *cmds, size_t num_cmd,
uint8_t *response, size_t len);
static inline int mipi_dbi_write_display(const struct device *dev,
const struct mipi_dbi_config *config,
const uint8_t *framebuf,
struct display_buffer_descriptor *desc,
enum display_pixel_format pixfmt);
static inline int mipi_dbi_reset(const struct device *dev, uint32_t delay_ms);
static inline int mipi_dbi_release(const struct device *dev,
const struct mipi_dbi_config *config);
static inline int mipi_dbi_configure_te(const struct device *dev,
uint8_t edge,
uint32_t delay_us);
在 Display 驱动中需要用到 MIPI DBI 的地方就使用这些统一的 MIPI API,具体选择使用哪个 DBI 驱动交给设备树,Display 驱动实现代码中不用关心。设备树上体现为显示设备挂在 MIPI DBI 总线上。
例如:这是 st7789v 挂在 stm32 fmc 总线下通过 mipi dbi 的驱动的 type B 16bit 的设备树
&fmc {
pinctrl-0 = <&fmc_a0_pf0 &fmc_ne1_pc7 &fmc_nwe_pd5 &fmc_noe_pd4
&fmc_d0_pd14 &fmc_d1_pd15 &fmc_d2_pd0 &fmc_d3_pd1
&fmc_d4_pe7 &fmc_d5_pe8 &fmc_d6_pe9 &fmc_d7_pe10
&fmc_d8_pe11 &fmc_d9_pe12 &fmc_d10_pe13 &fmc_d11_pe14
&fmc_d12_pe15 &fmc_d13_pd8 &fmc_d14_pd9 &fmc_d15_pd10>;
pinctrl-names = "default";
status = "okay";
sram {
compatible = "st,stm32-fmc-nor-psram";
#address-cells = <1>;
#size-cells = <0>;
bank@0 {
reg = <0x0>;
st,control = <STM32_FMC_DATA_ADDRESS_MUX_DISABLE
STM32_FMC_MEMORY_TYPE_SRAM
STM32_FMC_NORSRAM_MEM_BUS_WIDTH_16
STM32_FMC_BURST_ACCESS_MODE_DISABLE
STM32_FMC_WAIT_SIGNAL_POLARITY_LOW
STM32_FMC_WAIT_TIMING_BEFORE_WS
STM32_FMC_WRITE_OPERATION_ENABLE
STM32_FMC_WAIT_SIGNAL_DISABLE
STM32_FMC_EXTENDED_MODE_DISABLE
STM32_FMC_ASYNCHRONOUS_WAIT_DISABLE
STM32_FMC_WRITE_BURST_DISABLE
STM32_FMC_CONTINUOUS_CLOCK_SYNC_ONLY
STM32_FMC_WRITE_FIFO_DISABLE
STM32_FMC_PAGE_SIZE_NONE>;
st,timing = <1 1 32 0 2 2 STM32_FMC_ACCESS_MODE_A>;
fmc-mipi-dbi {
compatible = "st,stm32-fmc-mipi-dbi";
reset-gpios = <&gpioh 13 GPIO_ACTIVE_LOW>;
power-gpios = <&gpioc 6 GPIO_ACTIVE_LOW>;
register-select-pin = <0>;
#address-cells = <1>;
#size-cells = <0>;
st7789v: lcd-panel@0 {
compatible = "sitronix,st7789v";
reg = <0>;
mipi-mode = "MIPI_DBI_MODE_8080_BUS_16_BIT";
/* A write cycle should be 68ns */
mipi-max-frequency = <14705882>;
width = <240>;
height = <240>;
x-offset = <0>;
y-offset = <0>;
vcom = <0x1F>;
gctrl = <0x35>;
vdvs = <0x20>;
mdac = <0x00>;
gamma = <0x01>;
colmod = <0x05>;
lcm = <0x2c>;
porch-param = [0c 0c 00 33 33];
cmd2en-param = [5a 69 02 00];
pwctrl1-param = [a4 a1];
pvgam-param = [D0 08 11 08 0C 15 39 33 50 36 13 14 29 2D];
nvgam-param = [D0 08 10 08 06 06 39 44 51 0B 16 14 2F 31];
ram-param = [00 F0];
rgb-param = [40 02 14];
};
};
};
};
};
例如:这是 st7789v 挂在 esp32 SPI 总线下通过 mipi dbi 的驱动的 type C 4wire 的设备树
&mipi_dbi {
compatible = "zephyr,mipi-dbi-spi";
spi-dev = <&spi2>;
dc-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>; /* G33 */
reset-gpios = <&gpio1 2 GPIO_ACTIVE_LOW>; /* G34 */
write-only;
#address-cells = <1>;
#size-cells = <0>;
st7789v: st7789v@0 {
compatible = "sitronix,st7789v";
reg = <0>;
mipi-max-frequency = <27000000>;
width = <128>;
height = <128>;
x-offset = <2>;
y-offset = <1>;
vcom = <0x28>;
gctrl = <0x35>;
vrhs = <0x10>;
vdvs = <0x20>;
mdac = <0x00>;
gamma = <0x01>;
colmod = <0x55>;
lcm = <0x0c>;
porch-param = [0c 0c 00 33 33];
cmd2en-param = [5a 69 02 00];
pwctrl1-param = [a4 a1];
pvgam-param = [d0 00 02 07 0a 28 32 44 42 06 0e 12 14 17];
nvgam-param = [d0 00 02 07 0a 28 31 54 47 0e 1c 17 1b 1e];
ram-param = [00 E0];
rgb-param = [40 02 14];
mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";
};
};
MIPI DSI 接口 链接到标题
MIPI DSI(Display Serial Interface)是由 MIPI 联盟制定的高速串行显示接口标准,专为移动设备(如手机、平板)设计,现已扩展至车载显示、AR/VR 等领域。Zephyr 的 MIPI DBI 驱动代码路径位于’zephyr/drivers/mipi_dbi’,主要包括以下几个文件:
- dsi_stm32.c:STM32 DSI 控制器驱动
- dsi_renesas_ra.c:Renesas RA DSI 控制器驱动
- dsi_mcux.c:NXP MCUX DSI 控制器驱动
- dsi_mcux_2l.c:NXP MCUX DSI 控制器驱动,针对新一代 NXP 芯片的增强版驱动,重点提升了硬件加速能力(DMA/LCDIF)、功耗控制和时钟灵活性
Zephyr 为 MIPI DSI 提供统一的 API,定义在zephyr/include/zephyr/drivers/mipi_dsi.h
内
static inline int mipi_dsi_attach(const struct device *dev,
uint8_t channel,
const struct mipi_dsi_device *mdev);
static inline ssize_t mipi_dsi_transfer(const struct device *dev,
uint8_t channel,
struct mipi_dsi_msg *msg);
ssize_t mipi_dsi_generic_read(const struct device *dev, uint8_t channel,
const void *params, size_t nparams,
void *buf, size_t len);
ssize_t mipi_dsi_generic_write(const struct device *dev, uint8_t channel,
const void *buf, size_t len);
ssize_t mipi_dsi_dcs_read(const struct device *dev, uint8_t channel,
uint8_t cmd, void *buf, size_t len);
ssize_t mipi_dsi_dcs_write(const struct device *dev, uint8_t channel,
uint8_t cmd, const void *buf, size_t len);
static inline int mipi_dsi_detach(const struct device *dev,
uint8_t channel,
const struct mipi_dsi_device *mdev);
在 Display 驱动中需要用到 MIPI DSI 的地方就使用这些统一的 MIPI API,具体选择使用哪个 DBI 驱动交给设备树,Display 驱动实现代码中不用关心。设备树上体现为显示设备挂在 MIPI DSI 总线上。
例如:这是 st7701 挂在 rt1160 mipi dsi 的设备树
zephyr_mipi_dsi: &mipi_dsi {
dphy-ref-frequency = <24000000>;
st7701: st7701@0 {
status = "okay";
compatible = "sitronix,st7701";
reg = <0x0>;
height = <800>;
width = <480>;
data-lanes = <2>;
pixel-format = <MIPI_DSI_PIXFMT_RGB565>;
rotation = <0>;
gip-e0 = [E0 00 00 02];
gip-e1 = [E1 08 00 0A 00 07 00 09 00 00 33 33];
gip-e2 = [E2 00 00 00 00 00 00 00 00 00 00 00 00 00];
gip-e3 = [E3 00 00 33 33];
gip-e4 = [E4 44 44];
gip-e5 = [E5 0E 60 A0 A0 10 60 A0 A0 0A 60 A0 A0 0C 60 A0 A0];
gip-e6 = [E6 00 00 33 33];
gip-e7 = [E7 44 44];
gip-e8 = [E8 0D 60 A0 A0 0F 60 A0 A0 09 60 A0 A0 0B 60 A0 A0];
gip-eb = [EB 02 01 E4 E4 44 00 40];
gip-ec = [EC 02 01];
gip-ed = [ED AB 89 76 54 01 FF FF FF FF FF FF 10 45 67 98 BA];
pvgamctrl = [B0 40 C9 91 0D 12 07 02 09 09 1F 04 50 0F E4 29 DF];
nvgamctrl = [B1 40 CB D0 11 92 07 00 08 07 1C 06 53 12 63 EB DF];
display-timings {
compatible = "zephyr,panel-timing";
hsync-active = <1>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
hback-porch = <40>;
hsync-len = <32>;
hfront-porch = <8>;
vback-porch = <6>;
vsync-len = <8>;
vfront-porch = <9>;
};
};
};
LCD RGB 链接到标题
Zephyr 中没有对 LCD RGB 接口进行抽象, Display 驱动中直接调用 soc vender 提供的 hal 接口。例如display_mcux_elcdif.c
,display_renesas_ra.c
,等。这一种通常搭配的就是通用的 LCD RGB 并口屏,在设备树中设置好屏参即可。
例如下面是 rt1062 外接 LCD RGB 接口屏的设备树,其中display-timings
是描述外接屏的屏参
&lcdif {
status = "okay";
width = <480>;
height = <480>;
display-timings {
compatible = "zephyr,panel-timing";
hsync-len = <41>;
hfront-porch = <4>;
hback-porch = <8>;
vsync-len = <10>;
vfront-porch = <4>;
vback-porch = <2>;
de-active= <1>;
pixelclk-active = <1>;
hsync-active = <0>;
vsync-active = <0>;
clock-frequency = <9210240>;
};
pixel-format = <PANEL_PIXEL_FORMAT_ARGB_8888>;
data-bus-width = "24-bit";
pinctrl-0 = <&pinmux_lcdif>;
pinctrl-names = "default";
backlight-gpios = <&gpio2 31 GPIO_ACTIVE_HIGH>;
};
SPI/I2C 链接到标题
还有一些小型的显示设备并不是前面三种标准的显示接口,而是有自定的协议,通过 SPI 或 I2C 进行传输,例如ssd1306.c
,display_max7219.c
等。这一种需要 display 驱动自己调用 Zephyr 提供的标准 SPI 或 I2C API 按照屏的硬件特性进行驱动。而设备树上是体现为显示设备挂在 SPI/I2C 的总线上。
例如下面是 esp32 的 i2c 总线上挂 ssd1306 的设备树
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_STANDARD>;
sda-gpios = <&gpio0 21 GPIO_OPEN_DRAIN>;
scl-gpios = <&gpio0 22 GPIO_OPEN_DRAIN>;
pinctrl-0 = <&i2c0_default>;
pinctrl-names = "default";
ssd1306_128x64: ssd1306@3c {
compatible = "solomon,ssd1306fb";
reg = <0x3c>;
width = <128>;
height = <64>;
segment-offset = <0>;
page-offset = <0>;
display-offset = <0>;
multiplex-ratio = <63>;
segment-remap;
com-invdir;
prechargep = <0x22>;
};
};
例如下面是 esp32 的 spi 总线上挂 max7219 的设备树
&spi3 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
pinctrl-0 = <&spim3_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 18 GPIO_ACTIVE_LOW>;
max7219_max7219_8x8: max7219@0 {
compatible = "maxim,max7219";
reg = <0>;
spi-max-frequency = <1000000>;
num-cascading = <1>;
intensity = <0>;
scan-limit = <7>;
};
};
参考 链接到标题
https://docs.zephyrproject.org/latest/hardware/peripherals/display/index.html https://docs.zephyrproject.org/latest/hardware/peripherals/mipi_dbi.html https://docs.zephyrproject.org/latest/hardware/peripherals/mipi_dsi.html