1. 背景:为什么要在泰山派上点亮一块0.23寸OLED屏
先交代一下使用背景。
在嵌入式开发中,很多时候我们并不是去驱动一块7寸、10寸的RGB/LVDS大屏,而是需要驱动一种“看起来很小但协议并不简单”的屏——比如0.23寸OLED微型显示屏。这类屏大量出现在AR眼镜、电子取景器、瞄准镜、头戴显示器、双目夜视仪等设备中,传统开发板很少默认适配,资料也比较零散。
泰山派是一块基于瑞芯微RK3566的国产ARM Linux开发板,核心优势是接口全、资料开放、价格友好,在国产化项目中经常作为主控方案。如果我们想让泰山派通过MIPI DSI接口驱动一块国产0.23寸OLED屏,需要解决的问题就非常多:
- MIPI DSI接口怎么配置?
- 屏的初始化序列如何加载?
- RK3566的DSI控制器如何驱动?
- 设备树怎么写?
- 显示出来后颜色、帧率、分辨率如何确认正常?
本文将围绕“泰山派 + MIPI DSI + 0.23寸OLED屏”展开,从协议基础、环境搭建、设备树配置、驱动框架到调试方法逐一说明。适合正在做ARM显示驱动、RK平台方案评估、国产屏适配的开发者阅读。学完后你可以掌握一套相对完整的MIPI点屏思路,而不是只复制几行命令。
2. 先搞清楚:什么是MIPI DSI,为什么小OLED屏用它
2.1 MIPI DSI是什么
MIPI是移动行业处理器接口联盟制定的一套标准。DSI(Display Serial Interface)是其中的显示串行接口,专门用于处理器与显示屏之间传输图像数据。
通俗理解:MIPI DSI就像一条“窄但速度很快的高速公路”,用少量信号线把主机端(SoC)的显示数据送到屏幕模组上。
传统的RGB接口屏需要很多根线,例如RGB888需要24根数据线加上时钟和同步信号,PCB布线压力很大。而MIPI DSI把数据全部串行化,将RGB分量拆到高速差分线对上传输,常用配置是4 lane + 1 clock lane,物理信号加起来也就10根左右,非常适合空间受限的小型显示模组。
2.2 DSI的协议层次
DSI规范可以分为三层:
| 层次 | 作用 |
|---|---|
| D-PHY物理层 | 负责高速差分信号传输,定义lane的数量、电压、时序 |
| DSI协议层 | 负责打包像素数据、命令数据,定义包类型 |
| 应用层 | 屏幕模组或SoC的显示控制器通过DSI接口发送命令和帧数据 |
在驱动开发中,我们更关心的是协议层的数据内容,比如发送多少命令去初始化屏幕、当前工作在Video Mode还是Command Mode。
2.3 两种工作模式
MIPI DSI屏幕按数据刷新方式分为两类:
- Video Mode(视频模式):SoC持续不断地把画面数据流发送给屏幕,屏幕自身没有帧缓存,依赖主机端持续刷新。这类屏的驱动配置重点在时序。
- Command Mode(命令模式):屏幕内部带显存,SoC先把数据写入屏幕的GRAM,之后屏幕自己刷新显示。这类屏通常需要额外的TE信号做同步,驱动时要关注命令通道和刷新控制。
0.23寸OLED这类微型屏既有视频模式方案,也有命令模式方案,具体取决于模组驱动IC的设计。驱动前必须先从屏厂规格书中确认。
3. 认识0.23寸OLED屏的特点与驱动难点
3.1 0.23寸OLED是什么样的屏
“0.23寸”通常指屏幕对角线长度为0.23英寸。这个尺寸下的OLED屏有一个显著特征:像素密度非常高。
和常用的0.96寸I2C OLED不同,那种屏尺寸单位接近1英寸、分辨率只有128x64,接口是I2C/SPI,驱动芯片是SSD1306,属于字符/图形点阵屏。
而0.23寸OLED通常是硅基OLED或者高PPI微显示方案,分辨率可以达到640x400、1920x1080等,单位面积像素密度远超普通屏。接口一般不会使用I2C——因为I2C频率有限,根本传不了高清画面,所以会采用MIPI DSI或并行RGB接口。
3.2 是否需要驱动IC初始化序列
这是很多新手最容易忽略的一点。
普通的RGB屏通过简单的DE、HSYNC、VSYNC信号就能出图,但MIPI OLED模组通常在屏内部有一颗驱动IC,它需要通过MIPI命令来配置工作模式、Gamma曲线、亮度、扫描方向等参数。
屏厂一般会提供一份“初始化序列”,多是以十六进制命令序列的形式给出。驱动开发的核心工作之一,就是把这份初始化序列在恰当的时机通过DSI接口发送给屏。如果初始化序列缺失或者时序不对,屏幕就会出现白屏、花屏甚至不亮。
3.3 0.23寸OLED驱动常见难点
- 像素格式适配:常见有RGB565、RGB666、RGB888,驱动若配置错会造成颜色错乱。
- lane数量不一致:有的屏只支持2 lane,有的支持4 lane,SoC端的DSI控制器需与屏端匹配。
- 初始化序列长度:部分屏的初始化序列很长,甚至有几百条命令,任何一个字节出错都可能导致点不亮。
- 电压与电源时序:微显示模组对供电时序比较敏感,必须先供电、再拉复位、最后发初始化命令。
4. 泰山派软件环境与准备工作
泰山派的核心SoC是RK3566,它内部集成了MIPI DSI控制器,支持MIPI DSI输出,可配置为单通道或多通道。在软件层面,我们主要通过Linux内核的DRM/KMS框架去驱动MIPI屏。
4.1 开发环境
建议准备一台Ubuntu主机(18.04或20.04 64位均可,新版SDK通常要求在64位系统下编译),用于编译内核和SDK。
常见开发环境说明:
| 项目 | 建议 |
|---|---|
| 操作系统 | Ubuntu 18.04 / 20.04 x64 |
| 交叉编译工具链 | aarch64-linux-gnu- 或Rockchip SDK自带工具链 |
| 泰山派SDK | 根据官方发布版本下载 |
| 内核版本 | 以SDK自带版本为准,常见为Linux 5.10/5.15系列 |
| 烧录工具 | RKDevTool / upgrade_tool / 官方烧录工具 |
需要特别说明的是:不同批次泰山派板卡、不同SDK版本,设备树路径和内核配置可能会有差异。本文的重点是梳理配置思路,具体路径请按你手里的SDK做对应调整。
4.2 内核需要开启的配置
在Linux内核下驱动MIPI DSI屏,通常依赖DRM子系统。如果SDK预编译内核已经支持MIPI DSI,可以省略这一步;如果是从零配置内核,需要打开如下相关选项:
CONFIG_DRM=y CONFIG_DRM_ROCKCHIP=y CONFIG_DRM_ROCKCHIP_DW_MIPI_DSI=y CONFIG_DRM_PANEL=y CONFIG_DRM_PANEL_SIMPLE=y CONFIG_DRM_PANEL_ROCKCHIP=y CONFIG_BACKLIGHT_CLASS_DEVICE=y不同内核版本的配置名称会有差异,例如有些版本使用CONFIG_DRM_ROCKCHIP_MIPI_DSI。在配置菜单中检索DRM、MIPI、ROCKCHIP就可以快速定位。
4.3 先确认系统能正常启动
在动手写屏驱动前,最好先确认泰山派能正常启动到Linux,并且串口/网络登录都正常。驱动屏幕时,我们通常需要一边改设备树,一边看内核日志。通常通过以下方式确认:
# 查看串口信息,确认内核启动 dmesg | grep mipi # 查看DRM是否注册成功 ls /sys/class/drm/如果能看到card0-DSI-1之类的节点,说明MIPI DSI链路已经被内核识别,只是屏可能还没有正式点亮。
5. RK3566的MIPI DSI接口与设备树配置思路
5.1 在设备树中找到MIPI DSI节点
RK3566的DSI控制器在设备树中的节点名称一般形如dsi@fe060000,通常挂在与display-subsystem相关的链路上。
当我们内核对某个屏幕适配良好时,常见的做法是使用Rockchip提供的panel-simple或panel-rockchip驱动,在设备树里新增一个屏幕节点并在DSI节点中引用。
5.2 设备树添加MIPI OLED屏节点
下面是一个简化后的设备树配置示例,展示如何定义一块MIPI OLED panel节点:
/ { compatible = "rockchip,rk3566"; backlight: backlight { compatible = "pwm-backlight"; pwms = <&pwm0 0 50000 0>; brightness-levels = <0 1 2 3 4 5 6 7 8 9 10>; default-brightness-level = <8>; status = "okay"; }; panel_oled: panel-oled { compatible = "example,oled-023"; status = "okay"; power-supply = <&vcc3v3_lcd>; backlight = <&backlight>; reset-gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_LOW>; ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; panel_in_dsi: endpoint { remote-endpoint = <&dsi_out_panel>; }; }; }; }; };然后在DSI控制器节点内增加端口引用:
&dsi { status = "okay"; rockchip,lane-rate = <480>; ports { #address-cells = <1>; #size-cells = <0>; port@1 { reg = <1>; dsi_out_panel: endpoint { remote-endpoint = <&panel_in_dsi>; }; }; }; };注意:
reset-gpios的GPIO编号必须根据你的板卡实际原理图来填写。power-supply需要指向板卡上OLED屏对应的电源节点。compatible需要与panel驱动中匹配的字符串一致。
5.3 lane相关配置
RK3566的DSI控制器和屏幕的lane数必须匹配。如果屏是4 lane,在驱动里会读取设备树或固定匹配参数;如果屏是2 lane,则需要在驱动或平台配置中把lane数调整为2。
这里的常见错误是:硬件设计时DSI lane数是固定的,但当SoC与屏的lane数不一致时,往往表现为屏幕无信号或者显示异常。遇到这种情况,优先检查屏规格书确定lane数量,再到DSI驱动确认实际配置。
6. 驱动适配:从panel-simple到专用驱动
6.1 复用内核现有panel驱动
如果0.23寸OLED屏的驱动IC比较通用,并且厂家没有额外初始化序列,可以直接使用Linux内核的panel-simple驱动,只需要在设备树中定义一个带时序信息的节点。
例如drivers/gpu/drm/panel/panel-simple.c中新增一个timing结构需要重新编译内核。对于终端用户,我们可以选择在设备树中直接使用已经支持的panel型号,或者给kernel提交patch新增panel描述。
但实际情况中,很多国产OLED微显示模组都需要厂家初始化序列,单纯靠panel-simple可能无法点亮,此时需要为它编写一个简单的平台驱动。
6.2 构造一个初始化序列发送框架
Linux内核DRM子系统提供了mipi_dsi_dcs_write_buffer接口用于向屏幕发送命令。我们设计的panel驱动,核心就是做三件事:
- 解析设备树中定义的电压、复位GPIO、背光。
- 上电并复位。
- 在
prepare阶段发送初始化命令列表,在unprepare阶段关闭屏幕。
下面是一个框架示例,注意这段代码是“示意思路”,实际寄存器命令必须由你手里的屏厂规格书提供:
// 文件路径:drivers/gpu/drm/panel/panel-oled-023.c #include <linux/module.h> #include <linux/of.h> #include <linux/gpio/consumer.h> #include <linux/regulator/consumer.h> #include <video/mipi_display.h> #include <drm/drm_panel.h> #include <drm/drm_mipi_dsi.h> struct oled023 { struct drm_panel base; struct mipi_dsi_device *dsi; struct regulator *supply; struct gpio_desc *reset_gpio; struct gpio_desc *enable_gpio; }; static inline struct oled023 *to_oled023(struct drm_panel *panel) { return container_of(panel, struct oled023, base); } static int oled023_prepare(struct drm_panel *panel) { struct oled023 *oled = to_oled023(panel); int ret = 0; /* 1. 给屏幕供电 */ if (oled->supply) { ret = regulator_enable(oled->supply); if (ret) return ret; } /* 2. 复位延时 */ gpiod_set_value_cansleep(oled->reset_gpio, 1); msleep(20); gpiod_set_value_cansleep(oled->reset_gpio, 0); msleep(20); /* 3. 发送初始化序列,按屏厂提供的命令表填写 */ /* mipi_dsi_dcs_write_buffer(oled->dsi, init_cmd, sizeof(init_cmd)); */ return ret; } static int oled023_unprepare(struct drm_panel *panel) { struct oled023 *oled = to_oled023(panel); gpiod_set_value_cansleep(oled->reset_gpio, 1); if (oled->supply) regulator_disable(oled->supply); return 0; } static const struct drm_panel_funcs oled023_funcs = { .prepare = oled023_prepare, .unprepare = oled023_unprepare, .get_modes = oled023_get_modes, }; static int oled023_probe(struct mipi_dsi_device *dsi) { ... } static const struct of_device_id oled023_of_match[] = { { .compatible = "example,oled-023" }, { } }; MODULE_DEVICE_TABLE(of, oled023_of_match); static struct mipi_dsi_driver oled023_driver = { .probe = oled023_probe, .remove = oled023_remove, .driver = { .name = "panel-oled-023", .of_match_table = oled023_of_match, }, }; module_mipi_dsi_driver(oled023_driver); MODULE_LICENSE("GPL");上面的代码只写了prepare和unprepare的核心流程,实际还需要补全get_modes、enable、disable、probe/remove中的检查项。真正编写时还需要为dsi设备设置:
dsi->mode_flagsdsi->formatdsi->lanes
6.3 常见的作用函数解释
get_modes是panel驱动中最重要的一部分,它决定内核向DRM子系统上报哪些显示分辨率与刷新率。通常做法是定义drm_display_mode结构体,并在get_modes中转换为drm_mode。
static const struct drm_display_mode oled023_mode = { .clock = 15000, .hdisplay = 640, .hsync_start = 640 + 16, .hsync_end = 640 + 16 + 48, .htotal = 640 + 16 + 48 + 96, .vdisplay = 400, .vsync_start = 400 + 1, .vsync_end = 400 + 1 + 1, .vtotal = 400 + 1 + 1 + 1, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, };时序参数(porch、sync等)完全取决于屏规格书,如果拿不到具体数值,宁可不写也不能随便填,否则屏幕显示可能偏移或闪烁。
7. 编译、烧录与验证流程
7.1 编译内核与设备树
泰山派SDK通常提供一键编译脚本。以手动编译为例,假设我们已经进入内核目录:
export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- make rockchip_linux_defconfig # 如果需要确认配置项 make menuconfig # 编译内核 make -j$(nproc) Image dtbs编译完成后,会生成内核镜像和设备树文件。设备树一般位于arch/arm64/boot/dts/rockchip/目录下,文件类似rk3566-taishanpai.dts。我们需要把我们新增的panel节点和dsi配置合入对应的dts文件,再重新编译。
7.2 烧录验证
烧录步骤因SDK发布方式不同而不同,常见路径是把boot.img或单独的内核与dtb烧到板卡。烧录完成后启动系统,接着检查:
# 查看mipi dsi是否触发 dmesg | grep -i mipi # 查看drm驱动注册状态 dmesg | grep -i drm # 检查panel是否匹配 dmesg | grep -i panel如果驱动探测成功,一般会在dmesg中看到类似信息:
panel-oled-023 0-0000: Linked as a consumer to regulator.0 rockchip-drm display-subsystem: bound 0:0.0 (ops rockchip_dsi_drm_ops)之后我们用modetest来查看系统识别到的显示模式:
modetest -M rockchip -p如果屏幕已经点亮,可以尝试往framebuffer上写颜色测试:
cat /dev/urandom > /dev/fb0如果你使用的是Linux DRM应用,也可以用modetest刷单色画面。正常情况屏幕会显示对应颜色。
8. 常见问题与排查思路
8.1 插上屏幕后完全没有显示
| 现象 | 常见原因 | 解决思路 |
|---|---|---|
| 屏幕全黑,背光不亮 | 电源未正确开启 | 检查power-supply节点与GPIO控制,确保供电正常 |
| 屏幕全黑,背光亮 | 初始化序列未执行或复位未释放 | 查看probe是否成功,复位IO极性是否正确 |
| DRM设备里没有DSI节点 | 设备树dsi节点status不是okay | 检查设备树覆盖配置 |
| 屏幕白屏 | 初始化序列错误 | 逐帧核对屏厂初始化序列数据,注意时序延时 |
8.2 开机显示花屏或颜色不对
花屏和颜色错乱是MIPI屏调试中非常常见的现象。最常见原因是Pixel Format不匹配。
- 如果屏是RGB666,而驱动设置成RGB888,颜色的低位会错乱,尤其在渐变图形上表现明显。
- 如果屏是RGB565而驱动设置为RGB888,则会出现明显的色斑、彩色干扰。
检查时可以读屏规格书,和代码中的dsi->format做对照:
MIPI_DSI_FMT_RGB888 MIPI_DSI_FMT_RGB666 MIPI_DSI_FMT_RGB5658.3 屏幕能亮画面但会闪烁
闪烁一般原因如下:
- 背光PWM频率过低。
- 屏处于Command Mode,但TE信号同步不正确。
- 刷新率和帧缓冲不匹配。
优先检查PWM背光频率,再检查是否在Video Mode下设置了稳定的刷新率。
8.4 泰山派识别到RK3566但系统进入ADB设备模式
这是一个比较典型的调试环境问题。很多泰山派板卡默认烧录的是Android或包含ADB的镜像,接入电脑后,电脑识别设备为ADB设备,导致一部分开发者误以为没有正常运行Linux。
此时我们首先要区分两点:
- 如果板卡作为ADB设备被识别,说明系统已经在Android或带ADB的Linux中运行,这并不影响调试,你可以通过ADB登录。
- 如果我们要做MIPI显示调试,建议优先使用串口登录系统,因为ADB在某些定制的Linux镜像中并没有完全启用,且串口能看完整的内核日志。
一句话:电脑识别成ADB设备不代表驱动失败,不要被设备名称误导,重点是检查内核日志中DSI链路是否起来。
9. 工程实践建议
9.1 拿到屏先做“最小验证”
很多开发者在拿到新屏幕后,第一件事就是对照驱动框架写一大堆代码,结果出问题后完全不知道是硬件接错还是软件配置错。更稳妥的做法是:
- 先用简单的点屏工具(例如RK平台的
dsi_test或读写寄存器工具)验证能否通过MIPI命令和屏幕通信。 - 确认通信OK后,再尝试发初始化序列。
- 如果能点亮,再进入DRM框架做完整的显示链路适配。
9.2 保存原始命令表,不要随意“优化”
屏厂提供的初始化序列往往是经过大量实验得到的,里面可能包含时序修复命令、电压配置命令、gamma命令。不要因为某条命令看不出作用就删掉。建议做法是:
- 把原始命令表完整保存为.h或.c文件。
- 在每条命令上方添加注释。
- 如果有修改,单独维护一个差异记录。
9.3 电源时序要谨慎处理
MIPI屏对电源时序要求往往比RGB屏严格。常见的需求包括:
- VDDI 先上电。
- 主电源后上电。
- 复位信号在电源稳定后解除。
- 初始化序列必须在复位释放后等待一段延时再发送。
驱动代码中尽量使用regulator框架和gpio_desc来控制电源和复位,不要用杂散的GPIO操作绕过框架,否则后续做休眠唤醒时非常容易出现屏幕无法恢复问题。
9.4 背光控制与显示状态分开
在业务开发中,很容易把背光和显示当成一个东西。实际上,屏幕的亮灭和背光的亮灭应该分层控制:
- DRM的
prepare/unprepare控制屏幕电源与初始化。 - DRM的
enable/disable控制视频流启停。 - 背光由独立的backlight驱动控制。
如果业务需要“息屏但保持Android系统运行”,关闭背光即可,不要直接拔屏幕电源。
9.5 调试时要留足日志打印
在实际调屏开发中,最忌讳闷头改代码。比较推荐的做法是,在probe、prepare、unprepare、enable、disable函数中都加一条dev_info或dev_dbg日志,这样可以快速定位是驱动没有加载,还是加载了但状态切换异常。
调试完成后,再把冗余日志改成dev_dbg,保留在代码中即可。
10. 总结与后续学习方向
泰山派驱动0.23寸OLED屏的核心链路是:
RK3566 DSI控制器 → MIPI DSI信号线 → OLED模组驱动IC → OLED像素阵列
在Linux软件栈上,我们需要打通链路是:
设备树(DSI节点+panel节点) → panel驱动(报告模式、配置电源时序) → DRM/KMS框架 → 用户空间显示程序。
如果只是点亮屏幕,上面任何一环都不能出错;如果要做业务产出,还需要进一步掌握DRM framebuffer、图层合成、PWM背光调节、休眠唤醒等知识点。
但在做实际项目时,还有几个问题值得重点关注:
- 屏幕规格书上的时序参数必须亲自核对,网络上别人的屏幕参数只能用来理解,不能直接借用。
- 厂家给的初始化序列在驱动中如何组织、如何延时,需要结合RK3566的MIPI DSI控制器状态机来调试。
- 国产屏的兼容性验证需要做足,尤其是不同批次模组的初始化差异。
如果你正准备在泰山派上调试MIPI OLED,建议先把自己手头的模组规格书完整过一遍,确认好lanes、format、mode_flags这三项,再开始动设备树。祝点屏顺利。