"); //-->
By Toradex 胡珊逢
简介在嵌入式领域中,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 屏幕驱动。
专栏文章内容及配图由作者撰写发布,仅供工程师学习之用,如有侵权或者其他违规问题,请联系本站处理。 联系我们
相关推荐
uClinux系统分析
走进智能工厂 为何智能诊断是持续运行的关键
Arm宣布推出Performix,为开发者带来 AI 时代必备的可扩展性能
ARM开发详解
Linux系统的DS18B20驱动程序源代码
Arm 宣布推出 Performix,为开发者带来 AI 时代必备的可扩展性能
最新ARM技术和嵌入式技术发展动态 中
ARM嵌入式软件编程经验谈
ARM Axion 处理器加持谷歌第八代 TPU,云端全面转向智能体 AI 架构
uClinux系统分析
ARM 展示小型低功耗上网本样品
最新ARM技术和嵌入式技术发展动态 下
汽车暖通空调(HVAC)控制参考设计
Linux内核源代码的阅读和工具介绍(aqian转)
Arm遭遇监管危机:FTC针对其技术授权启动反垄断调查
嵌入式LINUX开发套件常见问题解答
面向ARM系统集成的FPGA片上系统解决方案
ARM嵌入式系统开发
ARM嵌入式系统开发:软件设计与优化
Arm CEO:AI智能体将推动CPU核心数升至 512
WinCE+ARM开发及关键技术 上
MF RC522读写器电路图
[转帖]NeuLinux嵌入式Linux开发平台
arm学习资料
基于ARM-Linux的MiniGUI的仿真与移植
基于Linux平台的温度传感器DS18B20驱动程序设计
边缘 AI 加速的 Arm Cortex‑M0+ MCU 如何为电子产品注入更强智能
Linux系统下USB摄像头驱动开发
WinCE+ARM开发及关键技术 下
Arm财报过山车:营收创纪录,股价跌7%