ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

泰山派驱动0.23寸MIPI OLED屏:DRM与设备树实战

泰山派驱动0.23寸MIPI OLED屏:DRM与设备树实战 泰山派开发板在嵌入式 Linux 学习中经常被用来做显示实验但大多数入门案例停留在 HDMI 输出或 SPI/I2C 小屏上。真正把一块国产 0.23 寸 OLED 屏接到泰山派的 MIPI DSI 接口并点亮难度会明显上升屏幕只有一个很小尺寸但启动时却要依赖完整的 DRM 链路、设备树节点、MIPI D-PHY 时序、面板初始化序列和电源控制。很多人在这个项目里卡住不是硬件连线错误而是不了解 MIPI 屏的软件驱动顺序。这篇文章会从泰山派驱动国产 0.23 寸 MIPI OLED 屏的过程讲起拆解显示链路、准备内核环境、修改设备树、编写 DRM panel 驱动再给出编译烧录、运行验证和排错思路。整个过程同样适用于 RK3566/RK3568 系列开发板也适用于其他通过 MIPI DSI 接口连接的国产 LCD/OLED 屏。1. 先把“MIPI 驱动 OLED”这条链路拆开看1.1 MIPI DSI 和 0.23 寸 OLED 的定位先解决一个基础认知问题MIPI DSI 驱动的屏幕并不是像 SSD1306 那样通过 I2C/SPI 把显存一字节一字节刷上去。SSD1306 那种屏幕本身带 SRAM 显存主机只需要往显存里写像素。MIPI DSI 屏幕不一样SoC 里的显示控制器需要持续不断把整帧像素数据发往屏幕屏幕收到后由驱动 IC 完成像素刷新。0.23 寸 OLED 虽然尺寸小但如果它设计成 MIPI DSI 接口它内部就不是简单的一颗 OLED Controller。这类屏通常会自带源驱动、栅极驱动、时序控制甚至还有局部亮度控制逻辑。主机侧要做的是开机时通过 MIPI 命令通道发送屏幕初始化序列正常工作时通过 MIPI 数据通道持续发送图像信号。因此MIPI OLED 的驱动工作比 SPI OLED 更接近“驱动一块 LCD”这也是为什么项目命名会强调“0.23 寸 OLED”而不是常见的“0.96 寸 OLED”。1.2 Linux 侧显示的完整软件链在泰山派这类 RK3566 平台上屏幕点亮并不是“写一个驱动”就能解决的它至少经过以下链路应用/显示服务 - /dev/dri/card0 DRM/KMS - Rockchip VOP - MIPI DSI Host Controller - D-PHY - MIPI 差分信号线 - 屏幕驱动 IC这里每一个环节都不可缺少VOPVideo Output Processor负责把 Framebuffer 里的图像数据按指定的显示模式发送出来。MIPI DSI Host Controller 负责把像素数据和命令打包成 DSI 包。D-PHY 负责物理层传输使用差分信号线。Panel 驱动负责告诉 DRM“这块屏支持什么分辨率、什么时序”并且负责复位、上电、发送初始化序列。很多人只关注到“驱动面板”忽略了 VOP 和 DSI Host 对屏幕参数的校验。如果设备树中 VOP 输出通道没有连到 DSI或者 DSI 没有正确解析屏幕 mode内核启动日志里不会报很醒目的错但屏幕大概率不会亮。1.3 屏幕资料需要确认的参数清单动手前先不要急着写代码。如果你是手工接线需要和屏幕模组供应商确认以下参数。如果是现成接口板这些参数通常会写在厂家的规格书或参考驱动里。参数含义不确定时会出现的现象驱动 IC 型号屏幕内部时序控制芯片型号初始化序列无法匹配白屏或花屏分辨率水平像素 x 垂直像素mode 不匹配时可能只显示一部分区域DSI Lane 数量1/2/3/4 lanelane 配置错误时无法进入 high speed像素格式RGB888、RGB666 等颜色偏色最高支持色深不足视频模式/命令模式Video Mode 或 Command Mode白屏、撕裂、无法刷新初始化序列寄存器地址和参数表能上电但不显示或显示错误复位时序Reset 高低电平保持时间上电后驱动 IC 初始化不完整电源供应需要几路电压、顺序如何电压不稳导致开机蓝屏或电流异常物理尺寸/整屏宽高影响 EDID/DPMS 和 touch 坐标触摸/显示坐标不一致一般不是 MIPI 问题例如常见的 Rockchip MIPI LCD 会提供 reset 脚、电源使能脚、背光 PWM 脚。OLED 不一定有背光但可能额外有 VCI、VDDIO、ELVDD 等电压输入具体看模组原理图。不要直接按 LCD 驱动模板照抄。2. 环境准备先确认内核、设备树和 config再谈写驱动2.1 泰山派 SDK 目录很长先确认内核版本泰山派有多种资料包常见的是 Rockchip Linux SDK。拿到 SDK 后不要盲目使用网上任意版本的 RK3566 内核最好以内核源码根目录里的 version 文件和配置文件为准。在开发板上先查看当前内核uname -a cat /proc/version在编译主机上查看 SDK 自带内核版本cd kernel head -5 MakefileRockchip 平台的内核通常基于 4.19、5.10 或更高版本。不同版本中 DRM panel API 有差异尤其是drm_panel_init参数在较新内核中增加了 connector type 支持。后面代码示例注释会指出这一点。2.2 打开 MIPI DSI 和 Panel 相关配置Rockchip SDK 一般已经有默认.config但出厂配置不一定把你要用的屏幕 panel 驱动编译进去。如果编写自定义 panel 驱动建议先把代码放到drivers/gpu/drm/panel/下再通过 Kconfig 加上对应的菜单项。在 menuconfig 中可以搜索关键字make ARCHarm64 rockchip_linux_defconfig make ARCHarm64 menuconfigmenuconfig 里搜索MIPI、DSI、PANEL三个关键字把显示子系统和面板驱动都选为y或m。不同 SDK 中的 config 名不统一常见的有CONFIG_DRMy CONFIG_DRM_ROCKCHIPy CONFIG_DRM_PANELy CONFIG_DRM_PANEL_SIMPLEy如果你把面板驱动写成独立模块检查对应目录下的 Makefile 是否加入了新文件。例如obj-$(CONFIG_DRM_PANEL_TAISHANPAI_023OLED) panel-taishanpai-023oled.o2.3 交叉编译工具与根文件系统在编译 ARM64 内核前需要设置环境变量。这里的工具链路径以本地实际解压路径为准export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- export PATH$PATH:/opt/toolchains/aarch64-linux-gnu/bin如果你的环境里没有/opt/toolchains可以先确认 SDK 中是否自带 prebuiltsfind / -maxdepth 5 -name *aarch64*gcc* -type d 2/dev/null编译内核时泰山派官方文档通常会给出一套 SDK 脚本例如./build.sh kernel。如果只想验证设备树和驱动也可以进入 kernel 目录直接编译 Image 和 dtbs。注意执行前先确认内核源码目录。2.4 开始写代码前的最小检查清单下面的清单能避免多数“代码看是写了但系统根本没跑这条路径”的问题。内核源码目录对应实际烧录版本不能仅凭uname判断源码分支。开发板设备树文件里存在dsi0或dsi1节点且没有别的项目占用。屏幕电源和复位 GPIO 在系统启动时没有被其他驱动复用。驱动代码所在目录已写进 Makefile编译日志里能看到该.o文件。启动日志中能看到mipi_dsi注册信息。准备一条 HDMI 或串口日志避免屏幕不亮时没有观察输出。注意不要只验证程序能启动还要验证输入、输出、异常分支和日志是否符合预期。屏幕驱动的关键不只是“编译通过”而是启动后屏是否真的收到初始化序列。3. 从设备树开始接入一块 MIPI OLED3.1 在 board dts 中找到显示相关节点并打开 DSI泰山派的外设节点一般继承自 SoC 的 dtsi。不同型号板子的板级 dts 路径不同常见位置是kernel/arch/arm64/boot/dts/rockchip/rk3566-xxx.dts kernel/arch/arm64/boot/dts/rockchip/rk3566-xxx.dtsi打开文件后可以先搜索dsigrep -rn dsi0 arch/arm64/boot/dts/rockchip/板级 dts 中通常只需要设置 status 以及面板节点。MIPI DSI controller 本身在 SoC dtsi 中已经定义DSI 的输入端口会连接到对应 VOP 输出端口。以下是一个简化示例video_phy0 { status okay; }; dsi0 { status okay; #address-cells 1; #size-cells 0; };video_phy0是 RK3566 中对应 DSI 的 PHY 节点。不同 SDK 可能叫mipi_dphy0或dsi_phy0具体节点名应以源码中的rk3568.dtsi或rk3566.dtsi为准。3.2 panel 节点设计compatible、reg、GPIO 与 port在dsi0节点下面加入 panel 子节点。panel 节点的compatible必须和驱动中的of_device_id完全一致否则驱动不会匹配。下面示例使用taishanpai,023oled作为示例 compatibledsi0 { status okay; #address-cells 1; #size-cells 0; panel0 { compatible taishanpai,023oled; reg 0; reset-gpios gpio3 RK_PC0 GPIO_ACTIVE_LOW; enable-gpios gpio3 RK_PC1 GPIO_ACTIVE_HIGH; pinctrl-names default; pinctrl-0 oled_reset_pin; port { panel_in_dsi: endpoint { remote-endpoint dsi0_out_panel; }; }; }; };这里需要解释几件事。reg 0表示 DSI 控制器下的虚拟设备地址不少 DSI panel 会写成 0。reset-gpios使用 GPIO 控制屏幕复位GPIO_ACTIVE_LOW 表示拉低有效屏幕规格书里一般会写 reset 低有效。enable-gpios不是所有屏都有如果模组没有 power enable 引脚可以删掉。port节点用于 DRM 层级连接。实际树莓派风格 DTS 中dsi0_out_panel这个节点可能由 SDK 自带的 DSI controller dtsi 提供。如果你在后续编译时报缺少 endpoint需要检查 SoC dtsi 中 DSI output port 和 VOP 的连接方式不要随意改名字。3.3 电源和背光不能照抄 LCD 方案传统 LCD 屏会有 PWM 背光屏不亮时大家会先查背光。OLED 屏一般不使用 LED 背光0.23 寸微型 OLED 更是如此。如果屏幕上没有“背光”就不需要为亮度引入一个独立 backlight 设备否则启动时背光引脚可能处于高阻态反而干扰电源。OLED 微型屏常见的电源包括VDDII/O 电压通常为 1.8V 或 3.3VVCI逻辑电源ELVDD/ELVSSOLED 像素驱动电压RESET复位低有效如果驱动芯片要求分步上电设备树中可以使用 regulator 或通过驱动中 GPIO 控制。尽量在原理图上确认哪些电源引脚已经固定连接哪些需要 SoC 控制。驱动代码中prepare函数会负责这个顺序。3.4 引脚复用冲突最容易在点屏时报“No such device”RK3566 每个引脚都可以有多个功能。如果把 MIPI DSI 引脚复用成普通 GPIO或者把 reset/backlight 用到了一个已经被其他外设占用的引脚点屏时会出现现象与软件无关的失败。可以这样检查cat /sys/kernel/debug/gpio cat /sys/kernel/debug/pinctrl/pinctrl/pinmux-pins如果看到引脚被占用设备树中要先把对应 pinctrl 解除。调试初期最好先使用 SDK 默认的开发板底板原理图不要把屏幕接到摄像头、触摸、SDIO 等复用严重冲突的接口上。4. Panel 驱动到底怎么写4.1 选择 panel_simple 还是自己写 MIPI DSI 驱动很多教程让你把屏幕直接改成panel-simple只要设置 mode 和 power GPIO。但panel-simple不会发自定义初始化序列。对于国产 0.23 寸 OLED 屏尤其是驱动 IC 需要厂家脚本的屏幕强烈建议写一个独立的drm_panel驱动。也有例外如果屏幕不初始化也能输出那可以直接复用简单面板但市面上 0.23 寸 MIPI OLED 不初始化就能正常显示的很少。白屏问题基本都出现在初始化序列没发对或者发送时序不对。如果你拿到一个 ST7701S 等常见屏幕驱动 IC可以先去找 SDK 中已有的panel-st7701s.c之类的驱动看它的命令发送方式再替换成自家屏幕的初始化数组。这比自己从drm_panel基础结构写起要稳。4.2 mipi_dsi_driver 注册骨架在drivers/gpu/drm/panel/下新建文件例如panel-taishanpai-023oled.c。头文件至少需要#include linux/gpio/consumer.h #include linux/module.h #include linux/of_device.h #include drm/drm_mipi_dsi.h #include drm/drm_panel.h定义私有结构体struct taishanpai_oled { struct drm_panel panel; struct mipi_dsi_device *dsi; struct gpio_desc *reset_gpio; struct gpio_desc *enable_gpio; }; static inline struct taishanpai_oled *to_taishanpai_oled(struct drm_panel *p) { return container_of(p, struct taishanpai_oled, panel); }probe 函数里需要绑定设备树属性并设置 DSI 参数static int taishanpai_oled_probe(struct mipi_dsi_device *dsi) { struct device *dev dsi-dev; struct taishanpai_oled *ctx; int ret; ctx devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; ctx-dsi dsi; mipi_dsi_set_drvdata(dsi, ctx); ctx-reset_gpio devm_gpiod_get_optional(dev, reset, GPIOD_OUT_HIGH); if (IS_ERR(ctx-reset_gpio)) return PTR_ERR(ctx-reset_gpio); ctx-enable_gpio devm_gpiod_get_optional(dev, enable, GPIOD_OUT_LOW); if (IS_ERR(ctx-enable_gpio)) return PTR_ERR(ctx-enable_gpio); drm_panel_init(ctx-panel, dev, taishanpai_oled_panel_funcs, DRM_MODE_CONNECTOR_DSI); drm_panel_add(ctx-panel); dsi-lanes 2; dsi-format MIPI_DSI_FMT_RGB888; dsi-mode_flags MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST; ret mipi_dsi_attach(dsi); if (ret 0) return ret; return 0; }这里dsi-lanes必须和设备树实际连接一致。MIPI_DSI_MODE_VIDEO表示工作在 video modeMIPI_DSI_MODE_VIDEO_BURST表示允许 burst。如果你是 command mode 屏幕这两个标志位要改。不要照抄 LCD 示例。驱动注册部分static const struct of_device_id taishanpai_oled_of_match[] { { .compatible taishanpai,023oled }, {} }; MODULE_DEVICE_TABLE(of, taishanpai_oled_of_match); static struct mipi_dsi_driver taishanpai_oled_driver { .driver { .name taishanpai-023oled, .of_match_table of_match_ptr(taishanpai_oled_of_match), }, .probe taishanpai_oled_probe, .remove taishanpai_oled_remove, .shutdown taishan
返回列表