最近在STM32MP257F-EV1评估板上调DCMIPP,用并行接口接收BT.656格式的视频流,从硬件连接到内核配置,从设备树到V4L2采集,整个流程完整走了一遍。这个需求在工业视觉、安防和视频采集类项目里非常典型,但网上资料大多讲MIPI CSI-2,很少讲到BT.656这种内嵌同步的并行信号。写这篇东西,就是把我在评估板上实际调试的过程、设备树参数怎么定、media pipeline怎么配、常见坑有哪些,一次性整理出来,给后来的人省点时间。适合正在做STM32MP25系列视频采集、或者想把老式模拟解码芯片接到新平台上的工程师参考。
1. 项目概述与整体设计思路
1.1 为什么是DCMIPP而不是老一代DCMI
STM32MP1系列老平台上有个视频采集外设叫DCMI,大家比较熟悉。到了STM32MP25这一代,ST把DCMI升级成了DCMIPP,全称是Digital Camera Memory Interface Pixel Processor。别看名字只多了个Pixel Processor,这意味着它不只是把数据收进来写进内存这么简单,而是在接收前端里多了一块可以做像素级处理的硬件模块,支持裁剪、缩放、格式转换等操作。对实际项目来说,最直接的好处是可以在采集的同时把图像缩小一块再送入内存或送显示,减少了CPU和GPU的后续开销,对带宽紧张的场景很有用。
驱动层面差别也很大。老的DCMI在Linux里走的是普通的V4L2 video device,而DCMIPP走的是media controller框架,subdev和video node之间需要显式配置链路。内核源码里对应的是drivers/media/platform/st/stm32/stm32-dcmipp.c这个文件,如果你在STM32MP257F-EV1上打开SDK源码,可以在里面看到完整的驱动实现。我一开始按老DCMI的思路去配,结果发现media链路根本起不来,浪费了不少时间。
DCMIPP支持两种输入路径:一种是MIPI CSI-2,另一种就是并行接口。BT.656走的是并行接口路径,数据线8位,加一根像素时钟,信号格式上是完全兼容的。所以“DCMIPP + 并行接口 + BT.656”这个组合,在硬件上其实并不复杂,真正的复杂度在于协议理解、设备树参数和V4L2链路配置。
1.2 BT.656协议到底在传什么
BT.656是ITU-R BT.656标准定义的数字视频接口格式,广泛用于模拟视频解码芯片输出数字YCbCr信号。它和BT.601最大的区别就是省掉了HSYNC和VSYNC两根同步线,行场同步信息通过一组内嵌的定时基准码嵌在数据流里。
具体来说,BT.656的数据格式是8bit YCbCr 4:2:2,按“Cb Y Cr Y”的顺序排列,也就是V4L2里的UYVY。每行数据从EAV码开始,接着是水平消隐样本,然后是SAV码,最后才是有效视频数据。EAV和SAV都是四个字节的定时基准码:FF 00 00 XY。其中XY字节里包含了F(场标识)、V(消隐标识)、H(行标识)以及保护位信息,解码端靠这些位来判断当前数据处于哪个场、是否处于消隐区,以及当前是有效行还是消隐行。
理解这个协议对调试至关重要。我举个例子,BT.656在PAL制式下,每帧625行,其中有效行是576行,每行总像素含消隐大约是864个像素时钟,像素时钟典型值是13.5MHz。NTSC制式则是525行,有效行480行,每行总像素约858个像素时钟。同样的13.5MHz,PAL帧率25fps,NTSC帧率30fps。在设备树和V4L2设置里,这些参数都会直接出现。
| 参数 | PAL(BT.656) | NTSC(BT.656) |
|---|---|---|
| 总行数 | 625 | 525 |
| 有效行数 | 576 | 480 |
| 每行总像素(含消隐) | 约864 | 约858 |
| 有效像素每行 | 720 | 720 |
| 帧率 | 25fps(隔行) | 30fps(隔行) |
| 像素时钟 | 13.5MHz | 13.5MHz |
把这张表记牢,后面遇到“为什么subdev格式是720x625”这类问题就不会懵了。
1.3 评估板上的硬件连接怎么接
STM32MP257F-EV1评估板通过扩展连接器把DCMIPP的并行接口引出来了,包括D0到D7八根数据线、PIXCLK像素时钟,以及可选的HSYNC和VSYNC。接BT.656信号时,理论上只需要D0到D7和PIXCLK四组信号就够了,HSYNC和VSYNC可以不接。
但这里有个前提:输出端必须确实工作在BT.656内嵌同步模式。很多视频解码芯片,比如常见的TVP5150、ADV7180、ADV7280,都支持通过I2C寄存器在BT.601离散同步和BT.656内嵌同步之间切换。默认出厂状态未必是BT.656,如果你用I2C工具把寄存器配成BT.601模式,DCMIPP这边又没有独立同步线可用,那就彻底收不到行场信息,表现出来就是V4L2一直超时,一个buffer都没有。这一点在排查无信号问题时是第一个要确认的。
电平匹配也要留意。早期的模拟解码芯片输出往往是3.3V逻辑电平,STM32MP257F的DCMIPP引脚电平可能配置为1.8V或者3.3V,接错轻则采不到数据,重则损伤引脚。我在评估板上直接用3.3V电平没问题,但如果你的解码芯片输出5V电平,中间必须加电平转换,不能直接怼到MPU引脚上。
2. 内核配置与设备树:让DCMIPP先跑起来
2.1 先把DCMIPP驱动编译进内核
在STM32MP257F-EV1上,ST的OpenSTLinux SDK默认内核通常已经包含了DCMIPP驱动,但如果你用的是自己裁剪的内核,或者从mainline kernel拉下来配置的,就需要确认CONFIG_VIDEO_STM32_DCMIPP这个选项有没有打开。
配置路径在menuconfig里大概是Device Drivers -> Multimedia support -> Media drivers -> STM32 Digital Camera Memory Interface Pixel Processor。它依赖于VIDEO_DEV、MEDIA_CONTROLLER、MEDIA_SUPPORT这些基础选项,这些一般默认都有。另外你用的解码芯片驱动也要编进内核或者作为模块加载,比如CONFIG_VIDEO_TVP5150、CONFIG_VIDEO_ADV7180,这取决于你的硬件方案。
我推荐先把DCMIPP和解码芯片都编进内核(=y),这样启动后设备节点一定是存在的,后续调试少一层变量。等整个链路通了再改成模块也不迟。编译完启动后,检查/dev/下有没有video0设备,同时看看/media0是否存在。如果没有media0,多半是内核的media controller支持或者dcmipp驱动没有正确probe,优先回头检查设备树。
2.2 设备树endpoint参数逐个拆解
设备树是BT.656并行接口调试里最容易出问题的地方。DCMIPP节点在设备树里通过port/endpoint来描述与外部解码芯片的连接,remote-endpoint指向解码芯片输出端的endpoint。下面是一个通用结构,具体以你使用的内核版本和SDK里的dtsi为准:
&dcmipp { pinctrl-names = "default", "sleep"; pinctrl-0 = <&dcmipp_pins>; pinctrl-1 = <&dcmipp_sleep_pins>; status = "okay"; port { dcmipp_ep: endpoint { remote-endpoint = <&bt656_sensor_ep>; bus-width = <8>; bus-type = <2>; /* V4L2_MBUS_BT656 */ pclk-sample = <1>; }; }; }; &i2c2 { bt656_sensor: video-decoder@20 { compatible = "your,decoder-compatible"; reg = <0x20>; reset-gpios = <&gpioi 5 GPIO_ACTIVE_LOW>; status = "okay"; port { bt656_sensor_ep: endpoint { remote-endpoint = <&dcmipp_ep>; bus-width = <8>; bus-type = <2>; pclk-sample = <1>; }; }; }; };逐个解释关键参数。bus-width表示并行数据线位宽,BT.656一般是8。bus-type是关键中的关键,它告诉DCMIPP当前使用的是哪种总线类型。对于BT.656,内核头文件include/dt-bindings/media/video-interfaces.h里定义V4L2_MBUS_BT656的值为2,设备树里编译时可以直接引用宏,也可以写数字。如果你的代码里bus-type没配成BT.656,驱动就会按普通并行接口来处理,同步信号解析完全对不上,基本不出图。
pclk-sample表示在像素时钟的哪个沿采样数据,0是下降沿,1是上升沿。这个参数看似简单,实际调试时经常要来回试。BT.656的PCLK由解码芯片产生,MPU和芯片之间可能因为走线长度、电平转换引入延迟,导致数据在时钟沿上不稳定。sample沿选反了,图像会显著错位,严重的直接黑屏。
另外还有hsync-active、vsync-active这类离散同步极性参数,对于BT.656内嵌同步来说不需要,也不建议在设备树里写,免得驱动以为你走的是并行+离散同步模式。数据端的active-high、active-low也是一样,普通数据线没有极性概念,除非你用的是10位甚至更高位宽的奇偶分开接口。
2.3 时钟和引脚复用:DCMIPP收不到PCLK十有八九是这里
很多人在DCMIPP上卡住,不是协议问题,是引脚复用根本没对。PCLK不是由MPU生成的,是解码芯片输出的,所以设备树里不存在“配置时钟频率”这个动作,你需要做的是确保PCLK引脚和D0-D7引脚的复用功能选成了DCMIPP,而不是GPIO或者其他外设。
在STM32MP257F-EV1上,通过STM32CubeMX可以很方便地根据硬件原理图生成pinctrl配置,然后拷到设备树里。例如一组典型的并行接口引脚定义如下:
&pinctrl { dcmipp_pins: dcmipp-0 { pins1 { pinmux = <STM32PINMUX('PI', 0, AF11)>, /* D0 */ <STM32PINMUX('PI', 1, AF11)>, /* D1 */ <STM32PINMUX('PI', 2, AF11)>, /* D2 */ <STM32PINMUX('PI', 3, AF11)>, /* D3 */ <STM32PINMUX('PI', 4, AF11)>, /* D4 */ <STM32PINMUX('PI', 5, AF11)>, /* D5 */ <STM32PINMUX('PI', 6, AF11)>, /* D6 */ <STM32PINMUX('PI', 7, AF11)>; /* D7 */ slew-rate = <2>; drive-push-pull; bias-disable; }; pins2 { pinmux = <STM32PINMUX('PI', 8, AF11)>; /* PIXCLK */ slew-rate = <2>; drive-push-pull; bias-disable; }; }; };不同板卡引脚编号不一样,这里只是示例。有一点要注意,PCLK引脚的slew rate尽量选快一些,尤其是信号频率在13.5MHz附近,slower slew rate会让边沿变缓,导致采样抖动。驱动端一般不需要上拉,数据线是推挽输出,上拉反而可能引入额外负载。如果条件允许,建议在原理图阶段就给PCLK和D0-D7加串阻,靠近MPU端放,能有效改善信号质量。
3. 用V4L2把BT.656视频流转起来
3.1 理解media controller的管线
DCMIPP在Linux里是一个media controller设备,启动后会在/dev下注册media0、video0等节点。整个pipeline从解码芯片的subdev开始,经过DCMIPP的subdev,最后到达video设备节点。与普通V4L2设备最大的不同是,即使硬件上已经连接好了,软件上不显式建立链路,数据流也不会通。
第一步是用media-ctl把当前拓扑打出来看:
media-ctl -d /dev/media0 -p你会看到几个entity,比如“your-decoder 2-0020”是解码芯片的subdev,“stm32-dcmipp.0”是DCMIPP的subdev,后面还带着main、jpeg、pp等video node。这里的main path就是普通并行接口采集应该走的路径,jpeg path和pp path是DCMIPP内部的不同输出管线,分别对应JPEG编码和像素后处理通道。BT.656采集直接用main path就行,不要选错。
3.2 用media-ctl配置链路与格式
启动后第一件事是清空链路,避免上次配置残留影响判断:
media-ctl -d /dev/media0 -r然后把解码芯片输出和DCMIPP输入链路起来:
media-ctl -d /dev/media0 -l "'your-decoder 2-0020':0 -> 'stm32-dcmipp.0':0 [1]"注意实体名要跟media-ctl -p打印出来的一致,不同驱动注册名称有差异,直接复制命令很可能报找不到entity。链路建立后,设置subdev输出格式:
media-ctl -d /dev/media0 -V "'your-decoder 2-0020':0[fmt:UYVY8_2X8/720x625@1/25]"这里你会看到720x625这个尺寸,PAL制式下subdev格式包含消隐行,所以是625行而不是576行。很多人在这一步会犹豫,怀疑自己是不是写错了,其实这是正常的。V4L2的video节点层才设置有效分辨率,subdev层看到的是完整行数。
最后设置video节点的格式:
v4l2-ctl -d /dev/video0 --set-fmt-video=width=720,height=576,pixelformat=UYVY这里就是576了,因为video节点输出的是有效画面。BT.656是隔行信号,两个场合成一帧输出,所以你在video节点拿到的720x576其实是交替场的合成帧,直接看会有横条纹,加个去隔行处理即可。
3.3 实际采集并验证数据
链路和格式都配好之后,用v4l2-ctl抓几帧验证:
v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=30 --stream-to=bt656_30f.yuv抓完检查文件大小。720x576x2字节x30帧,理论上应该是24883200字节,差太大说明有丢帧或者分辨率配置不对,用ls -l确认一下。如果有ffplay,可以本地预览:
ffplay -f rawvideo -pixel_format uyvy422 -video_size 720x576 -i bt656_30f.yuv看到画面正常,基本链路就是通的。如果不想用命令行,也可以用GStreamer:
gst-launch-1.0 v4l2src device=/dev/video0 ! "video/x-raw,format=UYVY,width=720,height=576" ! videoconvert ! autovideosink这里要注意,GStreamer里的format要写UYVY,跟BT.656的Cb Y Cr Y顺序一致,写YUYV的话颜色会是错的。实际项目中很多人喜欢用yavta,也同样可以做采集:
yavta -f UYVY -s 720x576 -n 4 --capture=30 --file=out.yuv /dev/video0yavta的好处是打印信息详细,能直接看到每个buffer的timestamp和帧间隔,判断帧率是否稳定很方便。
4. 踩坑记录与排查思路
4.1 黑屏无数据:先确认时序,再怀疑驱动
BT.656调试最常见的现象就是v4l2-ctl一直打印select timeout,一个buffer都拿不到。遇到这个问题,我建议按下面这个顺序排查。
先用i2cdetect确认解码芯片在总线上,接着用i2cget读解码芯片的状态寄存器。不同芯片的状态寄存器地址不一样,但通常会有lock状态位和输出格式状态位,确认芯片已经锁定输入信号并且输出BT.656。很多解码芯片内部有同步检测寄存器,能看到当前有没有识别到CVBS或者YPbPr输入,这个信息能直接排除前端问题。
然后是示波器,这一步别省。拿示波器探PCLK,确认频率是不是13.5MHz附近。然后看D0数据线上的电平,BT.656的同步头是FF 00 00,在示波器上能看到很明显的周期性脉冲串。如果PCLK没有,查解码芯片供电和I2C配置;如果PCLK有但数据线上没有同步头,查解码芯片是否配成BT.601离散同步模式了。
软件侧再回头检查设备树里的bus-type是否配置成了BT.656,以及pclk-sample方向是否正确。这个顺序非常重要,我在实际调试中遇到过好几次一开始软件参数没问题,但硬件电平没拉起来导致全黑的情况。先把时序确认了,再回来纠结驱动参数,效率高很多。
4.2 画面花屏、锯齿和颜色错乱
如果图像能出来但位置不对,比如整幅画面明显左右偏移几个像素,或者看起来有斜向撕裂,大概率是pclk-sample采样沿反了。BT.656的PCLK沿和数据跳变沿如果没有对齐,接收端会在数据跳变过程中采样,采到不稳定的值,表现出来就是整行错位。把设备树里的pclk-sample从1改成0,或者从0改成1,重新编译设备树,通常马上能解决。
颜色异常是另一个高频问题。BT.656默认数据顺序是Cb Y Cr Y,也就是UYVY。如果你在media-ctl或v4l2-ctl里配成了YUYV,采集来的数据虽然能出图,但色度和亮度会错乱,画面看起来像滤镜坏了,红蓝通道明显不对。遇到颜色怪,先确认链路格式和video节点格式都是UYVY,不要只看一边。
隔行带来的横向条纹也容易被误判成故障。BT.656输入本来就是隔行信号,一帧由奇偶两场组成,V4L2输出到应用层时通常会保留这个交替结构。直接用播放器看会看到明显的横线,这是正常的,后续加个去隔行滤波器就行。判断是否为“故障”横向条纹,可以抓单帧看是不是只有偶数行或奇数行内容,如果是,那是场模式配置问题;如果两场都有但错开,那就是隔行显示的正常现象。
4.3 帧率与分辨率对不上
subdev格式里设的是720x625,video节点设的是720x576,很多人会嘀咕分辨率到底对不对。分开理解就行:625是PAL的完整总行数,576是有效视频行数。如果你的是NTSC输入,总行数是525,有效行是480,那么subdev要设成720x525,video节点设成720x480。
帧率这里有个容易混淆的点。BT.656是隔行传输,PAL是25fps,但场频是50Hz。也就是说每秒有50个场,两个场合成一帧。V4L2驱动通常按帧上报,所以你在v4l2-ctl里看到的是25fps。如果用的解码芯片输出是NTSC,帧率就是30fps。如果发现实际采集帧率和预期对不上,先确认解码芯片输出的是PAL还是NTSC,再看看media-ctl里subdev格式是否跟硬件制式匹配。
还有一个容易被忽略的场景是某些解码芯片内部带scaler或者对输入做了resize,它输出的行数未必等于625或者525,而是某个中间分辨率。这时候不要死磕BT.656标准值,直接看示波器上每行有多少个PCLK,每帧有多少个行同步头,用实际测量值去配V4L2格式。
4.4 调试三板斧:示波器、debugfs、Trace
最后把我常用的三个调试手段完整列一遍。第一是示波器,这真的是并行接口调试的最终裁判。我不止一次在软件里翻来覆去找问题,最后发现就是PCLK被干扰导致边沿抖动。示波器挂在PCLK和D0上,直接看13.5MHz时钟是否稳定,看FF 00 00同步头是否周期性出现,比看一百遍dmesg都管用。
第二是内核动态调试。DCMIPP驱动在probe和stream on阶段会打印不少有用信息,默认情况下这些打印被关了。可以动态打开:
echo 'file stm32-dcmipp* +p' > /sys/kernel/debug/dynamic_debug/control然后重新跑采集,观察dmesg里有没有同步检测失败、超时、DMA错误之类的关键字。ST的驱动在frame start、frame end中断处理里也有调试打印,打开后能看到帧中断是否正常触发。如果frame中断压根没触发,基本可以肯定是前端同步信号没进来。
第三是debugfs。DCMIPP驱动通常会暴露一些内部状态,比如中断计数、帧计数、当前配置的时序参数。在/sys/kernel/debug下找dcmipp相关目录,把里面的状态文件cat出来,看看中断计数在stream on之后有没有递增。这个信息在定位“驱动起来了但不出帧”的问题时特别管用,比反复读寄存器省事得多。
这几招组合下来,BT.656并行接口的绝大多数问题都能在一个小时内定位到具体环节。我在这个平台上印象最深的坑,是第一次上电时明明所有配置都对,就是不出图,折腾了一下午。后来用示波器抓数据线才发现,解码芯片输出的同步码完全正常