ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RV1126平台GC2093 MIPI摄像头驱动移植与ISP调通实践

RV1126平台GC2093 MIPI摄像头驱动移植与ISP调通实践

1. 先把方案想清楚:RV1126 接 GC2093 到底要动哪几层

在 RV1126 这类带 ISP 的视觉 SoC 上"添加一颗摄像头",从来不是改一行配置就完事的工作。它横跨硬件供电、时钟、MIPI 物理层、V4L2 media 拓扑、sensor 驱动、ISP 参数六大块,任何一块掉链子,你看到的都是同一句冷冰冰的timeout waiting for capture或者一片绿屏。我这次要在 RV1126 平台上把 GC2093 这颗 200 万像素、1/2.9 英寸靶面、支持 1080p 输出的 CMOS 传感器跑通,从裸板到/dev/video出流再调到画面能用,前后踩了不少坑,这篇就把整个过程按我实际的落地顺序复盘一遍。

先说清楚这篇适合谁看:如果你手上有一块 RV1126 的核心板或者整机板,需要外挂一颗新的 MIPI 摄像头,并且你手上有 SDK 源码、会看设备树、能用串口敲命令,那么这篇基本可以照着抄。如果你是纯应用层开发,只是想了解"为什么我的摄像头打不开",那看第 5 章的排查表就够了。下面所有内容都围绕 GC2093 这一颗具体型号展开,但它背后的方法在换任何一颗 MIPI RAW sensor 时都通用。

1.1 从信号链走一遍:数据是怎么从 sensor 走到 /dev/video 的

很多人移植失败,根本原因不是驱动写错,而是脑子里没有这张图。RV1126 的通路是:sensor 通过 MIPI D-PHY 差分对把 RAW 数据吐出来 → D-PHY 做电平接收和 lane 合并 → MIPI CSI Host 控制器把数据打包成帧 → RKCIF 做初步的尺寸处理和裁剪 → sditf 虚拟节点转交给 ISP → ISP 做去马赛克、3A、降噪 → 最终在/dev/videoX输出 NV12/YUYV。

这条链上每一段都有自己的"握手参数":lane 数量、每 lane 的速率、数据位宽、分辨率、帧率、MIPI 虚拟通道号。这些参数必须全链路一致,任何一处对不上,链路在 media-ctl 的拓扑图里看着是通的,但一开流就超时。我一般的习惯是先把这条链画在纸上,标好每个节点的参数,然后再开始改代码,比改完再一步步 debug 效率高得多。

GC2093 在这条链上的定位很清晰:它是一颗纯 RAW 输出 sensor,没有内置 ISP,所有图像处理必须依赖 RV1126 的硬件 ISP。这就意味着它不是"接上就能出彩色图"的器件,你必须有对应的 IQ 参数文件,否则出来的画面就是一块糊的灰绿噪点图。这一点和那些自带 YUV 输出的 sensor 有本质区别,也是新手最容易误判的地方——很多人看到画面是绿的,第一反应是接线错了,实际是 ISP 没拿到 IQ 文件。

1.2 为什么是 2 lane + RAW10 + 1080p30 这组参数

GC2093 支持多种输出模式,我选的是 1920×1080@30fps、2 lane MIPI、RAW10。选这个组合有三层考虑。

第一是带宽够用且留有余量。RV1126 的 D-PHY 单 lane 能力是足够的,用 2 lane 走 1080p30 完全不吃紧,多出来的余量可以用来提高 VTS 换取更长的曝光时间,对低照环境很关键。如果为了省 lane 数硬上 1 lane,曝光时间会被压缩,夜间效果直接崩。

第二是 RAW10 而非 RAW8。RAW8 省带宽,但动态范围和暗部信噪比明显差一档,GC2093 的定位本身就是给中低端视觉设备用的,再牺牲位宽就没意义了。RAW10 在 ISP 侧的解包开销也不大,MEDIA_BUS_FMT_SBGGR10_1X10是 RV1126 SDK 里最常见的格式。

第三是 2 lane 与 RV1126 的 D-PHY 通道映射更省事。RV1126 的 D-PHY 支持多种 lane 拆分组合,2 lane 时通常用 lane0/lane1 这一组,走线简单、阻抗好控、调试时示波器也好看。4 lane 虽然速率高,但板上要拖四对差分线,对布线和连接器要求陡增,对一颗 1080p sensor 来说属于浪费。

提醒:不要照抄别人的参数。你的 sensor 寄存器表里 HTS/VTS 是多少、MCLK 是 27MHz 还是 24MHz,直接决定了 link frequency 该取多少,这个后面 3.2 节会给计算公式。

2. 硬件先过关:上电、时钟、MIPI 和 GPIO 的确认清单

软件改一百遍,不如先拿万用表量一遍。我在这个项目上就吃过一次亏:驱动全部编译通过、i2cdetect 也能扫到地址,但一开流就超时,最后发现是 DOVDD 那一路 LDO 实际输出只有 1.55V,因为分压电阻贴错了。所以软件动手前,硬件必须逐项确认,这一步省不得。

2.1 电源与复位时序,最容易翻车的地方

GC2093 是三路供电:模拟域 AVDD、IO 域 DOVDD、数字核 DVDD,三路的典型值分别是 2.8V、1.8V、1.2V,具体以你手上的数据手册为准。三路的上电顺序在手册里是有要求的,一般是 AVDD 和 DOVDD 先建立,DVDD 后上,或者允许同时上但要求建立时间差在毫秒级以内。如果你的板上电源使能脚是同一个 GPIO 拉起来的,那基本没问题;如果是分开控制的,驱动里的 regulator 顺序要跟硬件一致。

时钟这块更关键。GC2093 的参考时钟(MCLK/XCLK)常规配 27MHz,这个时钟通常由 RV1126 的 MIPI PHY 侧输出。设备树里的clocks = <&cru CLK_MIPI0_OUT2IO>就是取这一路。这里有两个注意点:一是需要在 DTS 里确认这个时钟的实际输出频率被设成了 27MHz,如果时钟树默认给的是 24MHz,sensor 内部 PLL 算出来的像素时钟就会整体偏 11%,表现出来就是帧率不对、曝光时间算不准;二是如果你用的是 24MHz 的晶振方案,sensor 寄存器表里的 PLL 分频参数必须整套换掉,不能只改时钟不改表。

复位(RESET)和掉电(PWDN)两个 GPIO 的电平极性也很容易搞反。绝大多数模组是低电平复位、低电平掉电,也就是驱动里先拉高 PWDN 上电、延时、再拉高 RESET 释放复位,然后等待 10ms 以上让 sensor 内部稳定,才能开始 I2C 通信。这个延时如果写短了,I2C 读 chip id 会随机失败——注意是"随机",因为有时候刚好赶上,会让你误以为驱动没问题。

实操心得:我习惯在驱动的power_on函数里把每一步延时都加长到手册要求的两倍,先保证能通,等整机稳定了再往回压。早期调驱动时省下的那几毫秒,后期会以几小时的 debug 时间还回来。

2.2 MIPI 走线与 lane 映射的几个细节

差分对的 P/N 反接是硬件调试里出现频率最高的问题之一。P/N 反了之后,链路有时能协商上,但图像会出现花屏、横条纹或者偶发丢帧。如果你在软件上确认一切都是对的,图像却始终有规律性噪点,可以优先怀疑这个。有些 PHY 支持软件层面交换 P/N,但 RV1126 这边我建议直接改板,别在软件上绕。

lane 数量的物理连接也要和 DTS 里写的>config VIDEO_GC2093 tristate "GalaxyCore GC2093 sensor support" depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API select V4L2_FWNODE help This is a Video4Linux2 sensor driver for the GC2093 camera sensor.

obj-$(CONFIG_VIDEO_GC2093) += gc2093.o

打开内核配置时别只开CONFIG_VIDEO_GC2093,还要确认这几个配套项是开着的:CONFIG_VIDEO_ROCKCHIP_CIF、CONFIG_VIDEO_ROCKCHIP_ISP、CONFIG_VIDEO_ROCKCHIP_ISP1(或对应版本)、CONFIG_MEDIA_CONTROLLER、CONFIG_V4L2_FWNODE。ISP 相关的配置漏掉,编译能过,但启动时 ISP 不会 probe,media 拓扑里根本不会出现 isp 节点。

我一般用make ARCH=arm menuconfig进去搜一下确认,或者直接看.config里这几个宏是不是=y。这一步看着琐碎,但每年都会有人栽在"驱动编进去了,ISP 没编"上。

3.2 sensor 驱动里必须改的几处参数

驱动主体结构是标准的:probe里解析设备树、申请 regulator、配置时钟、上电、读 chip id 校验、注册 subdev。真正需要按你的实际硬件调整的是下面这几个宏和表。

<参考代码块:关键宏定义>

#define GC2093_LANES 2 #define GC2093_BITS_PER_SAMPLE 10 #define GC2093_LINK_FREQ_270MHZ 270000000ULL #define GC2093_PIXEL_RATE (GC2093_LINK_FREQ_270MHZ * 2 * GC2093_LANES / GC2093_BITS_PER_SAMPLE) #define GC2093_XVCLK_FREQ 27000000 #define GC2093_CHIP_ID 0x2093 #define GC2093_REG_CHIP_ID_H 0x03f0 #define GC2093_REG_CHIP_ID_L 0x03f1

link frequency 的算法要讲清楚,因为这是最容易照抄出错的地方。假设你的 sensor 寄存器表配的 HTS(水平总周期,单位是像素时钟)是 2200,VTS(垂直总周期,单位是行)是 1125,帧率 30fps,则:

  • 像素速率 = 2200 × 1125 × 30 = 74.25 M pixel/s
  • 总数据率 = 74.25M × 10bit = 742.5 Mbps
  • 2 lane 均分,每 lane = 371.25 Mbps
  • D-PHY 是 DDR 双沿采样,link frequency = 371.25 / 2 ≈ 185.6 MHz

实际取值会往标准档位靠,常见的是 270MHz(每 lane 540Mbps)或 337.5MHz(每 lane 675Mbps)这类整数值。关键不是数字好看,而是 sensor 内部 PLL 实际输出、驱动里写的 link_freq、DTS 里 D-PHY 配置三者一致。三者不一致的典型症状是:不出流,或者出流但每隔几帧丢一帧。

chip id 校验也是必改项。GC2093 的 ID 寄存器在 0x03f0/0x03f1,读出来应该是 0x20 和 0x93。驱动里要把这两个值拼成 0x2093 做比较。如果读出来是 0x00 或者 0xFF,基本可以判定是上电时序或者 I2C 地址错了,而不是驱动逻辑问题。

sensro 支持的 mode 表至少要有一项,包含分辨率和对应的寄存器数组:

static const struct gc2093_mode supported_modes[] = { { .width = 1920, .height = 1080, .max_fps = { .numerator = 10000, .denominator = 300000 }, .exp_def = 0x04b0, .hts_def = 0x0898, .vts_def = 0x0465, .reg_list = gc2093_linear_1080p_regs, .link_freq_index = GC2093_LINK_FREQ_270MHZ_INDEX, }, };

注意:hts_def、vts_def、exp_def这三个值必须和reg_list里实际写入 sensor 的寄存器值完全一致,否则曝光和增益的计算会整体偏移,画面会呈现"要么全白要么全黑"。

3.3 设备树完整配置与通路串联

设备树这块是整件事的核心,也是最容易出问题的地方。RV1126 的 camera 通路通常在rv1126.dtsi里已经定义好了各个节点,你只需要在板级 dts 里把 status 打开、把 endpoint 串起来。串的顺序是:sensor → csi2_dphy → mipi_csi2 → rkcif_mipi_lvds → rkcif_mipi_lvds_sditf → rkisp_vir。

<参考代码块:sensor 节点>

&i2c1 { status = "okay"; clock-frequency = <400000>; gc2093: gc2093@37 { status = "okay"; compatible = "galaxycore,gc2093"; reg = <0x37>; clocks = <&cru CLK_MIPI0_OUT2IO>; clock-names = "xvclk"; power-domains = <&power RV1126_PD_VI>; pinctrl-names = "default"; pinctrl-0 = <&gc2093_pwr>; reset-gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_LOW>; pwdn-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>; avdd-supply = <&vcc_2v8>; dovdd-supply = <&vcc_1v8>; dvdd-supply = <&vcc_1v2>; rockchip,camera-module-index = <0>; rockchip,camera-module-facing = "back"; rockchip,camera-module-name = "GC2093"; rockchip,camera-module-lens-name = "default"; port { gc2093_out: endpoint { remote-endpoint = <&mipi_in_gc2093>; >&csi2_dphy_hw { status = "okay"; }; &csi2_dphy0 { status = "okay"; ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; #address-cells = <1>; #size-cells = <0>; mipi_in_gc2093: endpoint@1 { reg = <1>; remote-endpoint = <&gc2093_out>; >dmesg | grep -i "iq file"

如果打印出来是no available iq file,先别怀疑 ISP 配置,八成是名字差了一个字母。我遇到过的最离谱的一次是 DTS 里写的是GC2093,文件名叫gc2093_GC2093_default.xml,但其实还要求目录里有一个gc2093_default_default.xml做兜底,少了它某些版本会报错。

如果你手上没有现成的 IQ 文件,有两个办法:一是找模组厂要,他们给模组的时候通常会配套提供,这是最省事的;二是拿 SDK 里相近型号(同靶面、同 Bayer 排列)的 IQ 文件先改名字顶上,能出一张"大致正确"的图,然后再用 ISP 调参工具逐步修正。第二种办法仅适合验证链路,正式产品必须用匹配的参数。

4.2 曝光、增益、帧率的联动调试

ISP 跑起来之后,日常调试最常动的就是这三个量。RV1126 的 ISP 3A 是自动跑的,但你可以通过 V4L2 control 手动干预:

# 关闭自动曝光 v4l2-ctl -d /dev/video0 -c exposure_auto=1 # 手动设置曝光行数 v4l2-ctl -d /dev/video0 -c exposure=800 # 手动设置模拟增益 v4l2-ctl -d /dev/video0 -c analogue_gain=200

这里有个联动关系必须搞清楚:曝光时间不能超过帧周期。1080p30 的帧周期是 33.33ms,如果 VTS 配的是 1125 行,那么单行时间约 29.6μs,最大曝光行数就是 1125 减去若干行消隐,大约是 1120 左右。你设一个 2000 进去,驱动会自己截断到最大值,画面不会更亮,反而会让你误以为"曝光控制失效"。

反过来,如果低照环境下想要更长曝光,就要降低帧率、提高 VTS。比如把帧率降到 15fps,VTS 拉到 2250,最大曝光行数就能翻倍。代价是运动场景会有拖影,这个取舍要根据你的实际场景定——做静态监控可以放低帧率,做动态识别就必须保帧率。

增益这边要注意模拟增益和数字增益的区别。模拟增益在 sensor 内部放大,噪声相对可控;数字增益在 ISP 里乘系数,拉高会明显放大噪点。调试时优先拉曝光和模拟增益,实在不够再动数字增益。GC2093 的模拟增益一般有 1x 到十几 x 的范围,实际用到 4x 以上噪点就很明显了,这时候应该考虑补光而不是继续拉增益。

白平衡同理,white_balance_automatic关掉之后可以手动设红蓝增益。但我不建议在没有色卡的情况下手调白平衡,很容易调出"看着还行,换个光源就废了"的结果。真要调,先在标准光源下用灰卡取一次基准值。

4.3 画面异常的快速归因

画面问题可以按"颜色、亮度、几何"三类来归因,这是我的经验分类法,比盲目试参数快得多。

颜色异常(偏绿、偏紫、全灰):基本锁定在 IQ 文件缺失或者 Bayer 排列配错。GC2093 常见的输出格式是 BGGR,如果驱动里报的是MEDIA_BUS_FMT_SBGGR10_1X10而实际是 GRBG,画面就会偏色。验证方法是把 IQ 文件临时换成一个已知正确的,看颜色是否正常。

亮度异常(全黑、全白、闪烁):优先查曝光和增益的默认值,其次查镜头光圈和 IR-CUT 状态。如果是白天全白、晚上正常,八成是 IR-CUT 没切过来或者滤光片装反了——这种问题在软件上折腾一天也找不到,属于典型的硬件问题。

几何异常(横条纹、错位、局部撕裂):这类几乎都是 MIPI 链路问题,优先怀疑 lane 数、link frequency、差分对极性。横条纹特别规律的话,往往是某个 lane 的数据没对齐。

5. 上电跑起来之后:验证命令与排错实录

驱动编好、设备树改完、内核烧进去,接下来就是验证。我习惯按固定顺序走一遍,每一步都有一个明确的"通过标准",不通过就停下来查,不要跳步。

5.1 一套按顺序走的验证流程

第一步:看 I2C 是否通。

i2cdetect -y 1

在 0x37 位置看到UU或者数字,说明 I2C 通信正常。如果什么都没有,先查上电和时钟,别急着改驱动。如果显示UU,说明已经有驱动占用了这个地址,这是正常的(驱动 probe 成功后会占用)。

第二步:看驱动 probe 日志。

dmesg | grep -i gc2093

期望看到类似gc2093 1-0037: Detected GC2093 sensor的输出。如果看到failed to read chip id,回到硬件章节查上电时序。

第三步:看 media 拓扑。

media-ctl -p -d /dev/media0

这条命令会打印出整条链路的节点和它们的连接关系。你要确认的是:sensor 节点存在、entity 之间的 link 是 ENABLED、格式是SBGGR10_1X10/1920x1080。任何一个环节断开或者格式不对,就把对应的 DTS 段落重新核对一遍。

第四步:抓一张图存下来。

v4l2-ctl -d /dev/video0 \ --set-fmt-video=width=1920,height=1080,pixelformat=NV12 \ --stream-mmap=4 \ --stream-count=10 \ --stream-to=/tmp/cap.yuv

把 cap.yuv 拷到 PC 上,用 YUV 查看器打开(记得选 NV12、1920x1080)。能出正常的彩色图像,说明整条链路彻底通了。如果出来是绿的,回第 4 章查 IQ 文件。

第五步:测帧率。

v4l2-ctl -d /dev/video0 --stream-mmap=4 --stream-count=300 --stream-to=/dev/null

这条命令会打印实际帧率。1080p30 的配置下应该稳定在 30fps 附近,如果只有 15fps 或者帧率跳动,去查 link frequency 和 VTS 配置。

5.2 典型故障速查表

下面这张表是我实际遇到过的问题汇总,按"现象 → 最可能原因 → 验证方法"组织,遇到问题直接对号入座。

现象最可能原因快速验证方法
i2cdetect 扫不到 0x37上电时序错、MCLK 未输出、PWDN 未释放万用表量三路电压、示波器看 MCLK
能扫到地址但 probe 失败chip id 读回不对、寄存器页未切换用 i2cget 手动读 0x03f0/0x03f1
media 拓扑里没有 sensor 节点compatible 不匹配、驱动没编进内核ls /sys/bus/i2c/drivers/看驱动是否注册
拓扑完整但开流超时lane 数不一致、link freq 不匹配对照 DTS 与驱动宏定义逐项核对
出图但全绿IQ 文件缺失或名字不对`dmesg
出图但偏色Bayer 排列配错、白平衡未收敛对比 IQ 文件里的 Bayer 顺序
帧率只有一半VTS 过大、link freq 偏低打印实际帧率与理论值对比
偶发丢帧、横条纹差分对 P/N 反接、排线过长换短 FPC 排线对比测试
高增益下噪点爆炸数字增益拉太高、曝光不足降增益、加曝光、补光
长时间运行后花屏电源纹波、散热导致 sensor 降频示波器量 AVDD 纹波

5.3 我踩过的几个坑

坑一:DTS 改了但没生效。症状是所有配置看着都对,日志里却是旧参数。原因是设备树覆盖文件(dtbo)没有重新编译,或者板子上加载的是分区里旧的 dtb。解决办法是烧写前确认编译时间戳,板子上用cat /proc/device-tree/直接读节点内容,眼见为实。

坑二:ISP 内存不够。症状是开流时提示 buffer 申请失败,但链路和格式都正常。原因就是isp_reserved给太小了。我一开始按别人的配置给了 0x04000000,单路 1080p 勉强能跑,加到第二路就崩。改成 0x10000000 后彻底稳定。

坑三:低照环境下自动曝光抖。症状是画面亮度在明暗之间反复跳。原因是 3A 算法的收敛速度和场景变化速度不匹配,室内灯光有 50Hz 频闪时会加剧。解决思路是把抗闪烁设为 50Hz,同时适当降低 AE 的响应速度,让算法"慢一点、稳一点"。

坑四:热起来之后偶发丢帧。这个查了很久,最后发现是 sensor 的 DVDD 那一路 LDO 在高温下压降变大,边缘时刻供电不足。换了一颗余量更大的 LDO 就好了。这类问题在常温台面上永远复现不了,必须做高低温老化测试。

坑五:模组厂的寄存器表和公版驱动不匹配。直接拿 SDK 自带的 gc2093.c 跑,能出图但曝光范围明显不对。对比后发现模组厂改了 VTS 的默认值,而驱动的vts_def还是老值。这类问题只能靠逐项比对寄存器表,没有捷径。

最后分享一个排查习惯:我给自己定了个规矩,任何一次改动只动一个变量,改完立刻记录现象。camera 这条链路上的参数耦合太强,同时改三处再去看结果,你会发现无论成功还是失败都不知道是哪一处起了作用,白白浪费一轮编译烧写的时间。

这套流程走下来,从裸板到稳定出图,我大概用了三天,其中两天是在硬件和时序上磨。真正写驱动和改设备树的时间反而不到一天——这也是我做嵌入式这些年最深的体会,camera 这类高速外设的调试,功夫大半在软件之外。如果你的板子还没定型,强烈建议在原理图阶段就把 MCLK、复位脚、三路电源的测试点留出来,后面能省下的事不是一点半点。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进