工控網首頁
>

應用設計

>

從0開始點亮DSI屏幕

從0開始點亮DSI屏幕

簡介

在嵌入式領域中,DSI 接口顯示屏變得越來越主流。以樹莓派為代表的諸多開發板均提供了 MIPI DSI 顯示接口。Toradex 的在其新的 Verdin 和 Aquila 家族產品中,也基本都兼容 DSI 顯示接口。但是 DSI 屏幕的配置往往比 LVDS 屏幕更加復雜。文章將介紹如何在 Verdin iMX8M Plus 上從零開始驅動一塊 DSI 屏幕,其中也將提供一些相關的調試技巧。

為何選擇 DSI 屏幕

隨著嵌入式系統向更高分辨率、更低功耗和更輕薄的設計演進,傳統的并行 RGB 和 LVDS 等接口已逐漸接近其性能極限。

MIPI DSI(顯示串行接口)采用高速差分通道協議替代了寬并行總線,每通道傳輸速率最高可達 4.5 Gbit/s。這使得全高清面板僅需單通道即可驅動,顯著減少了引腳數量、電磁干擾和PCB復雜度——這些優勢對于醫療、工業和智慧城市應用中的緊湊型設備至關重要。

與舊式接口不同,MIPI DSI 屏幕具有專門的控制器,可以支持雙向通信:開發者可向面板控制器發送初始化指令或亮度調節、色彩調節等命令,實現對顯示內容的靈活實時控制。

i.MX8M Plus MIPI DSI 架構

如下圖顯示,MIPI DSI 架構包括多個組件。LCDIF 可以從內存中讀取需要顯示的圖像信息,它們可以直接顯示在 TFT LCD 面板上。MIPI DSI 接收來自 LCDIF 的圖形信息,并將它們串化為符合 MIPI DSI 規范的格式。除了傳輸圖形信息外,MIPI DSI 還發送或接收 DSI 命令。在后面的配置中會涉及到 LCDIF 的內容。

1.png

MIPI DSI 屏幕

本次測試使用型號為 WKS50095 的 DSI 屏幕。它的分辨率為 720 x 1280,是一塊豎直顯示的面板。面板驅動芯片為 ILI9881D。i.MX8M Plus MIPI DSI 和 ILI9881D 通信,完成對屏幕的初始化以及顯示內容。和傳統的 LVDS 屏幕不同,為了點亮 DSI 屏幕,除了需要屏幕的 datasheet 外,我們還需要:

  1. 驅動芯片的 datasheet

  2. 驅動芯片初始化序列

為了點亮 DSI 屏幕,i.MX8M Plus 需要以 DSI 命令的方式先向驅動芯片例如這里的 ILI9881D 寫入初始化序列。通常這是一些二進制序列,因此需要借助 datasheet 才可能了解它們的含義。例如下面是 WKS50095 供應商提供的初始化序列的偽代碼。首先寫入一個 4 字節的命令,0xFF-0x98-0x81-0x03。它的含義是 ILI9881D 切換到 Page 3。然后第二命令是兩個字節,0x01-0x00,它是指往 Page 3 上地址位 0x01 的寄存器寫入 0x00。

SSD2828_WritePackageSize(4); SPI_WriteData(0xFF); SPI_WriteData(0x98); SPI_WriteData(0x81); SPI_WriteData(0x03);

SSD2828_WritePackageSize(2); SPI_WriteData(0x01); SPI_WriteData(0x00);

但需要注意的是,即便是獲得了 ILI9881D 的 datasheet,用戶可能也無法了解初始化序列中所有寄存器的配置含義。這些寄存器的定義有些是屬于 MIPI DSI 通用規范,另外一些則是每個廠商自己的定義。后者可能是非公開內容。有了供應商提供的初始化序列,點亮屏幕一般都不存在問題。

Device Tree

驅動 DSI 屏幕的軟件工作主要是來自 device tree 和內核驅動。在 device tree 中,不僅要直接配置 MIPI DSI panel 節點,還要完成相關的設置如背光和連接的其他節點如 lcdif。這里我們以使用 kernel 6.6 的 BSP 7 為例進行說明。在 imx8mp.dtsi 文件中,mipi_dsi 的 port@0 已經連接到 lcdif1 輸出端口 lcdif1_disp。因此,在配置 DSI 屏幕的 device tree 中,我們只需要把 MIPI DSI 的 port@1 連接到屏幕的 panel 定義的端口。

mipi_dsi: mipi_dsi@32e60000 {   ....   port@0 {       dsim_from_lcdif: endpoint {           remote-endpoint = <&lcdif_to_dsim>;       };   }; };

lcdif1: lcd-controller@32e80000 {   ....   lcdif1_disp: port {       lcdif_to_dsim: endpoint {           remote-endpoint = <&dsim_from_lcdif>;       };   }; };

下面是在 device tree 中 三者的連接順序。

lcdif -> mipi_dsi -> panel

用于屏幕的 device tree 我們這里采用device tree overlays的方式進行配置,直接集成到單個 device tree 文件中的配置也基本是一致的。在 dts 文件中,最主要 mipi_dsi 配置如下:

&mipi_dsi { #address-cells = <1>; #size-cells = <0>; status = "okay";

port@1 {       reg = <1>;       mipi_dsi_out: endpoint {           remote-endpoint = <&panel_in>;       };   };

panel@0 { reg = <0>; compatible = "test,wks50095"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpio_10_dsi>; backlight = <&backlight>; reset-gpios = <&gpio4 28 GPIO_ACTIVE_LOW>; wait-until-enabled; dsi,lanes = <4>; video-mode = <2>; port {           panel_in: endpoint {               remote-endpoint = <&mipi_dsi_out>;           };       };

}; };

在 mipi_dsi 中增加子節點panel@0compatible = "test,wks50095";可以找到對應的驅動進行加載,這也是后面需要自己編寫的驅動源碼。&pinctrl_gpio_10_dsi用于配置復位屏幕的 GPIO。&backlight對應的屏幕的背光 PWM。由于 NXP 目前 kernel 6.6 的 DSI 驅動中,無法的 prepare 階段傳輸初始化序列,所以添加了wait-until-enabled;。這可以將初始化序列傳輸移動到 enable 階段進行。接下來的內核驅動部分我們將繼續說明。panel 也會定義 port,通過 remote-endpoint 和 mipi_dsi 的 port@1 連接。

完整的 dts 可以從這里下載。device tree overlays 的編譯方法請參考Build Device Tree Overlays from Source Code。

內核驅動

panel 的驅動位于drivers/gpu/drm/panel/目錄中,可以創建一個panel-mipi-dsi.c文件。在 mipi_dsi_panel_of_match 添加和前面 device tree 對應的 compatible 字符串。

static const struct of_device_id mipi_dsi_panel_of_match[] = {   ...... { .compatible = "wisecoco,top055fhd01a", .data = &top055fhd01a_desc},   { .compatible = "test,wks50095", .data = &wks50095_desc}, { } };

wks50095_desc結構體中配置 DSI 屏幕的參數,如時序、色彩格式、初始化序列入口。

static const struct mipi_dsi_panel_panel_desc wks50095_desc= { .mode = &wks50095_mode, .lanes = 4, .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE, .format = MIPI_DSI_FMT_RGB888, .supply_names = ts8550b_supply_names, .num_supplies = ARRAY_SIZE(ts8550b_supply_names), .panel_sleep_delay = 200, .init_sequence = wks50095_init_sequence, .panel_has_backlight = false };

wks50095_mode中是屏幕時序信息。部分時序可以從屏幕的 datasheet 直接讀取,部分 front/back porch 內容可能不會明確地在屏幕 datasheet 中有描述。此時可以從初始化序列中結合屏幕控制器 ILI9881D 寄存器說明反推出這些信息。如果初始化序列中也沒有,那么就使用 ILI9881D 寄存器的默認值。下面是 WKS50095 按上面方法得到的時序。

2.png

wks50095_init_sequence是 WKS50095 的初始化序列。把前面的偽代碼改為 i.MX8M Plus 發送 MIPI DSI 命令的格式,配置的寄存器和順序同前面的偽代碼中一致。

static void wks50095_init_sequence(struct mipi_dsi_device *dsi) { dsi->mode_flags |= MIPI_DSI_MODE_LPM;

/* Page 3 Configuration */ MIPI_DSI_SEQ(dsi, 0xFF, 0x98, 0x81, 0x03); MIPI_DSI_SEQ(dsi, 0x01, 0x00); MIPI_DSI_SEQ(dsi, 0x02, 0x00); MIPI_DSI_SEQ(dsi, 0x03, 0x73); MIPI_DSI_SEQ(dsi, 0x04, 0x00);

通過查詢 wait_until_enabled 的狀態,將初始化序列號發送mipi_dsi_panel_enable函數中進行。

static int mipi_dsi_panel_enable(struct drm_panel *panel) {   ... if (dsi_panel->wait_until_enabled) { pr_debug("%s - %s:%d\n", __func__, __FILE__, __LINE__); dsi_panel->desc->init_sequence(dsi_panel->dsi); dsi_panel->prepared = true; }

完成上面內容的配置,那么在內核驅動中就實現了 WKS50095 屏幕的驅動。最后修改drivers/gpu/drm/panel/目錄下的 Makefie 和 Kconfig 文件編譯添加的驅動。

  • Makefile

obj-$(CONFIG_DRM_PANEL_MIPI_DSI) += panel-mipi-dsi.o

  • Kconfig

config DRM_PANEL_MIPI_DSI tristate "MIPI DSI panel support" depends on DRM_MIPI_DSI default n help  Support for MIPI DSI panels

最后重新編譯內核和所有驅動模塊,并部署到 Verdin iMX8MP 模塊上。因為上面使用了 device tree overlays 的方式,所以也需要修改 /boot/overlays.txt 文件使其生效。

Weston 配置

WKS50095 是一塊豎直分辨率的屏幕,weston 默認使用的 g2d 渲染器無法正確處理,所以改用 GPU 渲染。修改 /etc/xdg/weston/weston.ini。

[core] #use-g2d=false renderer=gl

[output] name=DSI-1 mode=720x1280 transform=normal

重啟后就可以看到 DSI 屏幕顯示的內容。

3.png

調試

調試期間往往會由于各種因素導致屏幕無法點亮,下面是本次調試過程用的一些方法幫助確定問題。

  • 驅動加載

lsmod命令可以檢查驅動是否加載。如果沒有加載可以嘗試使用 modprobe 手動加載。當單獨編譯 kernel moduesl 時,可能由于符號文件不一致也不能自己加載。depmod 命令則可以進行更新。

~# lsmod |grep panel_mipi_dsi panel_mipi_dsi         65536  0

~# modprobe panel_mipi_dsi

~# depmod -a

  • 開啟動態調試

對于 BSP 中于編譯好的驅動,可以通過設置 dyndbg 的方式開啟它的調試輸出,而無需修改代碼。

# setenv tdxargs dyndbg="\"file drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c +p\""

  • 靜態調試

而像 panel-mipi-dsi.c 這樣本身就需要自己寫的代碼,在一開始就可以在其中加入調試輸出函數。打開 DEBUG 定義。在必要的地方插入 pr_debug,這樣就可以追蹤到程序執行到的位置。

#define DEBUG ... pr_debug("%s - %s:%d\n", __func__, __FILE__, __LINE__);

__LINE__可以顯示代碼的行數。

[    9.009656] mipi_dsi_panel_probe - drivers/gpu/drm/panel/panel-mipi-dsi.c:917 [    9.009717] mipi_dsi_panel_probe - drivers/gpu/drm/panel/panel-mipi-dsi.c:924

  • probe 函數

驅動中的 probe 函數是在加載時第一個被調用的函數。在其中插入一些調試函數,可以確定加載情況。如果 probe 函數沒有被調用,通常是它所依賴的其他驅動沒有完成初始化。

static int mipi_dsi_panel_probe(struct mipi_dsi_device *dsi) { const struct mipi_dsi_panel_panel_desc *desc; struct mipi_dsi_panel *dsi_panel; int ret, i;

dump_stack(); panic("DIAGNOSTIC: Probe reached for panel %s\n", dsi->name);

例如前面配置 device tree overlays 時,mipi_dsi 的 port@1 曾經寫為mipi_dsi/ports/port@1這種層級,從而導致 mipi_dsi 初始化失敗,也就不會去調用 panel(panel-mipi-dsi.c)驅動。它的 probbe 函數一直沒有被執行。

總結

本文以一塊實際的 DSI 為例介紹如何為 Verdin iMX8M Plus 編寫驅動和 device tree overlays 文件,使其可以顯示內容。同時也介紹一些調試技巧,方便用戶移植其他 DSI 屏幕驅動。

審核編輯(
王靜
)
投訴建議

提交

查看更多評論
其他資訊

查看更多

基于 NXP iMX8MP ARM平臺安裝測試 Openclaw

在NXP iMX8QM上使用 Jailhouse

基于 Toradex 硬件和 ROS 2 加速機器人原型開發:SiBrain 的技術視角

Weston 桌面雙屏顯示獨立觸摸配置

NXP iMX8MP 使用 OP-TEE