Zephyr 中 Display 驱动的硬件与软件层级关系

在嵌入式系统中,显示设备作为人机交互的核心组件,其硬件接口的多样性给驱动开发带来了的挑战。Zephyr RTOS 通过一套统一的驱动模型,支持从低功耗 OLED 到高清 MIPI 面板的各种显示设备。本文从驱动之间的硬件和软件层次关系进行说明,并通过设备树展示显示驱动中这种层级关系。

display

从结构上看最顶层是 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