ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32MP1 Linux显示驱动实战:从LTDC到DRM/KMS设备树配置全解析

STM32MP1 Linux显示驱动实战:从LTDC到DRM/KMS设备树配置全解析 这几个月一直在调一块基于STM32MP157的板子显示链路从内核配置、设备树到DRM/KMS驱动整个走了一遍中间踩了不少坑也把ST这套显示框架的底摸了个大概。这篇文章想把LCD显示系统在STM32MP1上的完整实践写清楚从整体架构、硬件时序、内核配置到设备树写法再到实际点屏遇到的典型问题尽量让正在做MP1显示方案的工程师少走弯路。内容适合两类人看一是刚把手上的MP1板子点亮、想上LCD但不知道从哪下手的人二是从MCU转过来、对Linux显示驱动不熟想理解DRM/KMS这套东西到底怎么运转的人。1. 整体架构与关键设计思路1.1 STM32MP1的显示链路到底是怎么走的STM32MP1系列以MP157为例芯片内部有双核Cortex-A7和一个Cortex-M4。LCD显示系统通常跑在A7侧的Linux上M4核一般负责实时控制类任务基本不参与显示链路除非你做的是双核协同的裸机方案。这意味着显示驱动的核心战场在内核空间而不是单片机式的寄存器操作。从硬件数据流来看显示链路大致是这样的Linux应用层通过DRM提交一帧画面DRM分配显存并把数据放到DDR里然后LTDCLCD-TFT Display Controller作为DMA引擎把DDR里的显存数据取出来按照设定的时序和分辨率把像素时钟、行同步、场同步、DE信号以及RGB数据一起送到LCD接口上。如果是MIPI DSI屏则数据先到DSI主机控制器再经过DSI协议层最终通过差分信号线传给面板。理解这个链路的关键在于LTDC本质上不是GPU它不负责画图只负责当搬运工和时序发生器。画面是谁画的在STM32MP157上有一个Vivante GPU但它跑在更高层的图形栈上底层还是要靠LTDC输出。所以驱动开发者的核心工作就是两件事把LTDC和DSI的时序、时钟、引脚配对然后把标准DRM/KMS设备树和驱动对接好让用户态能用标准接口提交画面。1.2 为什么是DRM/KMS而不是传统fbdev很多从单片机转过来的朋友最开始接触Linux显示时会下意识去找fbdev也就是/dev/fb0那种传统帧缓冲接口。它的逻辑很直白应用层直接往/dev/fb0里写像素数据驱动把它映射到显存刷新交给内核。做简单UI、固定分辨率、单图层时确实够用但这套东西在现代Linux里已经被边缘化了。原因是多方面的。fbdev没有统一的模式设置管理没有多图层合成能力也不支持vblank同步更不用谈HDMI热插拔、DPMS电源管理等现代显示场景。而DRM/KMS把显示系统抽象成一套清晰的对象模型CRTC对应显示控制器Encoder对应信号编码器Connector对应物理接口Plane对应显示图层。用户态通过libdrm或者更上层的Wayland合成器操作这些对象既可以在应用层做多图层合成也可以在驱动层做modeset和buffer管理。STM32MP1在mainline内核里已经有很成熟的DRM/KMS驱动路径在drivers/gpu/drm/stm/。开发时直接用这套标准驱动比维护一份私有fbdev驱动要稳妥得多。这也是我强烈建议不要去走老路的原因内核版本一升级fbdev接口越来越没人管而DRM/KMS则是所有主流SoC和GPU厂商的共同方向。后面所有配置和调试我都会围绕DRM/KMS展开。2. 硬件接口与时序参数的关键点2.1 RGB并口、MIPI DSI、LVDS怎么选STM32MP1的LTDC可以直接输出RGB并口信号支持8位、16位、18位、24位等格式最常见的24位RGB888并口屏直接可以连。除此之外MP1内部还有一个DSI主机控制器所以它可以直接接MIPI DSI接口的屏幕。至于LVDS屏MP1没有原生的LVDS控制器一般是通过DSI转LVDS桥接芯片来实现比如SN65DSI83/84这类桥接器在设备树里把桥接芯片挂在DSI总线上就行。从驱动角度这三者的差异主要在设备树里体现。RGB屏直接挂在LTDC上不需要额外的桥接节点DSI屏则要把面板节点挂在DSI控制器下面并且需要配置DSI的数据lane数、时钟速率等参数LVDS桥接屏则需要多配置一个桥接芯片的节点并且保证上电时序和复位时序正确。选型时建议按这个思路来能用RGB并口解决的优先用RGB并口因为时序简单、调试直观产品对走线宽度和EMI敏感就上DSI屏幕尺寸较大、传输距离较长再考虑LVDS桥接方案。接口类型对比我直接整理成表格方便做方案评估时对照。接口类型典型引脚数带宽适合屏尺寸MP1接入方式调试难度RGB并口24根数据时钟同步线取决于像素时钟800x48060Hz约33MHz小于等于7寸LTDC直接输出低示波器可直测MIPI DSI1~4对差分数据1对时钟每lane约1Gbps5寸到10寸STM32MP1内置DSI控制器中需要检查lane训练LVDS桥接经桥接后差分对走线可达数Gbps7寸以上DSI转LVDS桥接芯片较高桥接芯片配置多2.2 时序参数的计算逻辑不是照抄那么简单LTDC要正常工作必须把屏幕的行场同步、有效显示区间、像素时钟这些参数配置到寄存器里。这些参数从哪来最权威的来源是屏幕的数据手册或者屏厂给的初始化代码。很多开发板BSP里已经写好了适配好的设备树但这不代表你可以不看时序——你换一块同样分辨率、不同厂家的屏时时序参数很可能是不同的。以经典的800x480 RGB屏为例它的一行参数可能是有效像素800个水平前沿HFP大概40个像素时钟水平同步脉冲HSYNC大概48个像素时钟水平后沿HBP大概88个像素时钟那么一行总长度就是 800404888976个像素时钟。垂直方向同理一帧有效行480行加上垂直前沿VFP、垂直同步VSYNC、垂直后沿VBP假设分别是13、3、29一帧总行数就是 48013329525行。像素时钟的计算公式是像素时钟 行总像素 × 帧总行数 × 刷新率。按上面的数值就是 976 × 525 × 60 ≈ 30.7MHz。这个值不是让你精确到小数点后三位去追而是要你明白如果实际提供的像素时钟偏了屏幕就会出各种怪问题比如闪屏、图像左移或右移、滚动条纹等。调试时如果发现图像只差那么几个像素的位置优先检查HFP/HBP和极性而不是怀疑内核配置错了。还有一个必须强调的点对于RGB并口屏LTDC配置数据同步方式时大部分屏都支持DE数据使能同步模式。DE模式下有效像素区间由DE信号控制时序调试会比HSYNC/VSYNC同步模式简单很多。我在实际项目中基本都优先让屏工作在DE模式这样即使HSYNC或VSYNC极性配错了影响也不会那么致命。2.3 背光与触摸调试中反而最容易拖进度背光这块很多人觉得简单一个GPIO拉高就能点亮。但如果你想让亮度可以调节最好用PWM背光。STM32MP1有多个PWM实例一般用PWM2或PWM3专接背光。设备树里用pwm-backlight这个驱动的标准绑定方式配置PWM频率和亮度等级数组。PWM频率一般设在1kHz到20kHz之间太低了能听到蜂鸣声太高了可能引入电源噪声。默认亮度等级可以设置成百分之一的步进也可以按屏的实际gamma曲线做非线性的等级表。触摸屏多数是I2C接口也有SPI接口的。I2C触摸屏在设备树里挂到对应的I2C总线上中断脚选一个带上下拉的GPIO同时确认最大时钟频率。我遇到过一个项目触摸IC要求I2C频率不超过400kHz结果默认的i2c频率配成1MHz导致触摸时断时续排查了很久才发现是时钟超规格了。这类细节数据手册里写了你就当真不要觉得“差一点没关系”。3. 内核配置与设备树实操3.1 内核裁剪时显示相关的配置项在正式写设备树之前内核配置得先过一遍。STM32MP1的显示驱动在menuconfig里的路径是Device Drivers → Graphics support → DRM Support for STMicroelectronics SoC系列。需要重点保证以下配置打开内核宏作用说明CONFIG_DRMDRM核心框架必须CONFIG_DRM_STMSTM32MP1的DRM驱动必须CONFIG_DRM_STM_DSIMIPI DSI控制器驱动DSI屏时必须CONFIG_DRM_PANEL_SIMPLE通用简单面板很多RGB屏和部分DSI屏靠它CONFIG_DRM_PANEL_XXXX具体面板型号驱动按屏的compatible来选择CONFIG_BACKLIGHT_PWMPWM背光驱动可调亮度背光时必须CONFIG_BACKLIGHT_GPIOGPIO背光驱动纯开关背光时可用CONFIG_DRM_FBDEV_EMULATIONfbdev模拟层需要/dev/fb0兼容层时打开我的建议是即使你打算在用户态直接用Wayland也把CONFIG_DRM_FBDEV_EMULATION打开。这样早期调试时还能通过/dev/fb0快速验证画面输出省掉一层Wayland的干扰。调试完、确定不需要fbdev了再关掉也不迟。配置完内核重新编译然后把新内核和dtb烧到板子上。这里提醒一下STM32MP1的启动要求TF-A、U-Boot、内核、dtb一起配套。如果你只更新了内核和dtbU-Boot里的fdt文件路径以及引导参数里指定的设备树文件名一定要对上否则板子起不来。3.2 设备树节点逐段拆解STM32MP1的SOC级设备树文件在arch/arm/boot/dts/stm32mp157.dtsi里已经定义好了LTDC和DSI控制器的节点一般在板级dts里做引用和配置。我们平时改的是板级dts比如stm32mp157a-xxx.dts。先看LTDC引脚配置。LTDC的信号比较多包括24根数据线、像素时钟、HSYNC、VSYNC、DE等每组信号都要在pinctrl里配置成复用功能。以RGB888并口屏为例需要把所有用到的引脚定义为ltdc_xxx_pins引脚的电气特性速度、上下拉也要设定好。别小看这块高速信号如果PIN脚的drive strength配得太弱屏幕上会出现细小的噪点或者边缘发虚。给一个最常见的设备树片段这段是我在RGB屏上实际用过的结构ltdc { pinctrl-names default, sleep; pinctrl-0 ltdc_pins_a; pinctrl-1 ltdc_pins_sleep_a; status okay; port { ltdc_ep0_out: endpoint0 { reg 0; remote-endpoint panel_ep; }; }; };代码里remote-endpoint是DRM框架用来连接显示控制器和面板的图端点这段一定不能漏。面板节点一般放在根节点下或者单独定义常见的简单面板节点如下panel_backlight { compatible pwm-backlight; pwms pwm2 0 1000000 0; brightness-levels 0 100 200 300 400 500 600 700 800 900 1000; default-brightness-level 900; status okay; }; panel_rgb { compatible panel-simple; backlight panel_backlight; enable-gpios gpiog 13 GPIO_ACTIVE_HIGH; width-mm 108; height-mm 65; /* 时序参数单位是像素时钟个数 */ panel-timing { clock-frequency 30700000; hactive 800; vactive 480; hfront-porch 40; hsync-len 48; hback-porch 88; vfront-porch 13; vsync-len 3; vback-porch 29; hsync-active 0; vsync-active 0; de-active 1; pixelclk-active 1; }; };从这段设备树里能看出几个关键点pwms里的三个参数分别是PWM控制器引用、通道号和PWM周期单位纳秒。周期1ms对应频率1kHz。brightness-levels定义了亮度等级表这里写了11级实际可以写256级。panel-timing里的字段非常直白hactive和vactive就是有效分辨率hsync-len是同步脉冲宽度hfront-porch是前沿hback-porch是后沿极性参数用-active指定。clock-frequency必须和前面计算出的像素时钟一致单位是Hz。我曾经因为少写一个0导致屏上出现条纹花了半天排查设备树和驱动日志最后才发现是时钟频率少了一位。DSI屏的设备树结构和RGB屏不同面板节点要挂在DSI控制器下面给一个DSI屏的结构参考dsi { status okay; #address-cells 1; #size-cells 0; panel0 { compatible 厂商,型号; reg 0; reset-gpios gpioa 8 GPIO_ACTIVE_LOW; backlight panel_backlight; status okay; port { panel_in: endpoint { remote-endpoint dsi_out; }; }; }; ports { #address-cells 1; #size-cells 0; port1 { reg 1; dsi_out: endpoint { remote-endpoint panel_in; }; }; }; };DSI屏的具体参数lanes数量、时钟频率、初始化序列通常写在内核里的面板驱动中而不是设备树里。如果你的屏是国产屏没有mainline驱动常见做法是找厂商要MIPI初始化序列然后在驱动里用mipi_dsi_dcs_write系列接口把初始化指令发出去。这个过程比较绕但搞清楚dsi的寄存器读写时序后也不复杂。3.3 配置完设备树后怎么确认系统认了这块屏设备树编译和烧录都正确之后系统启动时会在串口日志里打印DRM相关的初始化信息。打开串口终端执行dmesg | grep -i drm如果面板正常识别多半能看到stm32-drm display-subsystem这类日志输出以及panel驱动probe成功的字样。然后检查DRM的设备节点状态ls /sys/class/drm/正常情况下会输出类似card0-DSI-1或者card0-LVDS-1、card0-HDMI-A-1这样的目录。继续看连接器状态cat /sys/class/drm/card0-DSI-1/status输出connected说明内核识别到面板连接了。此时如果屏幕还没亮大概率是背光或像素时钟有问题按后面章节的思路去查。如果你想直接验证画面输出在启动时让内核或者initramfs引导一个DRM应用或者手动执行modetest命令。ST的BSP一般带有modetest工具没有的话可以用apt install libdrm-tests装一个。执行modetest -M stm -p这条命令会列出所有连接器、CRTC和模式。找到你的连接器后指定模式输出测试画面。比如modetest -M stm -s 32:800x480-60 -d-s后面第一个数字是connector id800x480-60是分辨率和刷新率-d是输出测试彩条。能看到彩条基本说明显示链路通了剩下的就是调背光和上电时序。4. 常见问题与排查技巧实录4.1 点不亮有背光没画面先查时序还是先查电源白屏或者有背光但完全没有内容这是最常见的故障状态。排查顺序我建议严格按这四条往下走。第一确认电源。屏的电源轨是否正常主流3.3V和显示相关的IO电压是否都到位。用万用表量屏接口上的每一个电源引脚不要只看排针上有电压就觉得没问题线上可能有虚焊或者接触不良。第二确认复位。屏的复位脚是否被拉高deassert时间是否满足。特别是一些DSI屏复位脚时序要求很苛刻在上电序列里必须先拉低再延时拉高延时时间要按数据手册来。很多屏点不亮其实就是复位时序没做好Linux 内核里的panel驱动一般会处理这个逻辑但如果你用的是自写的GPIO控制就得自己检查时序。第三确认像素时钟。用示波器量LTDC输出的像素时钟脚看是否有正常的方法波。如果输出频率和你设备树里设的clock-frequency完全对应却依然无画面那问题大概率不在时序而在数据/使能信号配置。如果示波器上根本没有时钟波形则说明LTDC没有被驱动起来检查设备树里status okay是否生效以及pinctrl是否冲突。第四确认背光控制逻辑。背光使能信号是否有输出信号是有源高还是有源低。背光亮不亮和有没有图像是两个独立维度有背光无画面说明LTDC数据链路有问题无背光无画面先查背光控制再查数据链路。4.2 花屏、闪屏、图像偏移的问题排查花屏这个现象细分下来有三种截然不同的原因。第一种是数据位序不对图像颜色完全错乱像PS过的负片效果。RGB888的顺序如果接错了比如红色和蓝色数据线对调就会出现红蓝互换的现象这是硬件上最容易犯的错。排查时用纯红、纯绿、纯蓝的测试图案或者直接用modetest的彩条输出一眼就能看出来。第二种是图像有规律地左移或右移若干像素或者上下滚动。这是HSYNC/VSYNC同步信号的电平极性和前沿/后沿参数不对导致的。极性反了同步信号在错误的时间点触发图像就会偏移。解决办法是在panel-timing里把hsync-active、vsync-active翻转着试配合微调hback-porch和hfront-porch。第三种是整个画面闪烁、条纹滚动多数是像素时钟频率不匹配。时钟偏快或偏慢会导致像素被压缩或拉宽肉眼看到的就是滚动条纹。这类问题最可靠的解法是用示波器测量屏接口上的像素时钟然后和设备树里的clock-frequency对比把偏差控制在百分之几之内。如果实在对不上可以尝试把刷新率从60Hz降到55Hz或50Hz降低对时钟精度的敏感度。4.3 休眠唤醒黑屏的典型坑系统进入suspend之后屏幕黑掉这本是正常现象但有时会出问题唤醒后屏幕依然黑着或者图像出现明显异常。这个现象的根本原因是背光、LTDC和面板在上电时序上的恢复顺序不一致。内核在resume时会按照设备树里的依赖关系依次恢复设备但LTDC、DSI、背光、面板之间的先后关系往往不是自动建立的。我在实际项目中遇到过这种场景第一次唤醒后屏幕正常第二次唤醒后黑屏排查发现是面板驱动和背光驱动的resume顺序没对齐后端的事件丢失了。处理思路是在设备树里把背光节点挂到面板的backlight属性下让面板驱动持有背光的引用这样内核的驱动模型会尽量按依赖关系排序。另外一个更稳妥的做法是在面板驱动的resume回调里做一次完整的上电序列重放包括复位、初始化命令集、背光开启彻底避免系统唤醒后的时序倒挂。这也是不少国产屏驱动在树外patch里最常见的改动。4.4 问题速查表我把实践中遇到的问题、可能原因和解决办法整理成一个速查表真正调试时对着表查能少走很多弯路。现象可能原因排查方法解决办法白屏无画面背光未开/屏供电异常量背光使能脚、电源电压检查GPIO配置确认上电时序有背光无图像LTDC未工作/时序参数错示波器量像素时钟读内核日志确认LTDC节点、面板节点状态复核时序图像红蓝互换数据线位序接错输出纯色测试图像调整RGB数据线接线顺序图像左移/右移同步极性或前后沿参数错翻转hsync/vsync的active值微调HBP/HFP参数画面滚动条纹像素时钟偏差大测像素时钟对比clock-frequency将刷新率降低或修正像素时钟唤醒后黑屏设备resume顺序不对打开drm_debug看resume日志在面板驱动resume回调重放上电序列触摸漂移/断触I2C时钟超规格检查I2C实际频率将触摸I2C总线降到400kHz或以下屏幕闪烁PWM频率过低人耳或示波器观察PWM频率提高PWM频率到10kHz以上这套内容实操下来大方向没问题的话一块RGB屏通常一到两天能点亮DSI屏需要多一些时间主要花在初始化序列的调试上。我个人这几年反复踩过同一类坑太相信屏厂给的初始化代码直接复制结果在Linux环境下上电时序和MCU裸机完全不同必须结合DRM驱动的生命周期来调整。所以再强调一次拿到一块新屏先去数据手册里把上电时序图和时序参数表找出来对着设备树逐行核对再动手改代码这个习惯能省下一整天的排查时间。后来我每次新做一版板子都会把屏的时序参数和上电时序做成一个独立的表格放在工程目录下方便后续接手的人按图索骥——这比任何调试技巧都管用。
返回列表