從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 的內容。

MIPI DSI 屏幕
本次測試使用型號為 WKS50095 的 DSI 屏幕。它的分辨率為 720 x 1280,是一塊豎直顯示的面板。面板驅動芯片為 ILI9881D。i.MX8M Plus MIPI DSI 和 ILI9881D 通信,完成對屏幕的初始化以及顯示內容。和傳統的 LVDS 屏幕不同,為了點亮 DSI 屏幕,除了需要屏幕的 datasheet 外,我們還需要:
驅動芯片的 datasheet
驅動芯片初始化序列
為了點亮 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 文件中的配置也基本是一致的。在 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@0。compatible = "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/目錄中,可以創建一個文件。在 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 按上面方法得到的時序。
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 屏幕顯示的內容。
調試
調試期間往往會由于各種因素導致屏幕無法點亮,下面是本次調試過程用的一些方法幫助確定問題。
驅動加載
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



投訴建議