ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

海思Hi3516+IMX214实战:从I2C调通到VI出图全记录

海思Hi3516+IMX214实战:从I2C调通到VI出图全记录 最近帮客户调试一个视频采集板主控是海思Hi3516sensor 用了索尼 IMX214。刚开始觉得这就是个常规动作I2C 拉通寄存器灌进去VI 采集出来应该一两天搞定。结果从 I2C 无 ACK 到出图花屏前后折腾了一周。这篇文章把整个实战过程完整写下来重点讲清楚三件事I2C 如何从零调通、IMX214 初始化序列怎么匹配海思 sensor 驱动、VI 通路怎么构建并验证出图。里面所有命令和排查思路都来自我的实际现场给正在做 IPC、运动相机、边缘盒子的朋友一个可抄作业的参考。1. 项目概述为什么把 IMX214 接到 Hi3516 上1.1 选型逻辑与平台背景先说为什么是这个组合。IMX214 是索尼一颗很经典的 13M CMOS sensor1/3 英寸左右靶面支持 MIPI CSI-2 输出1080p 和 4K 都能跑暗光表现放到现在也还算能打。关键是这芯片出货量巨大模组厂家多资料好拿二手和库存价格也便宜很多做低成本 IPC、运动相机的方案喜欢拿它来撑性价比。海思 Hi3516 这边优势是 SDK 成熟ISP 算法不需要自己写H.264/H.265 硬编也是直接调 API。相比纯 FPGA 方案或者瑞芯微平台海思在“sensor 接入 编码输出”这条链路上的体系非常完整只要把 sensor 驱动和 VI 通路配好后面接 VPSS、VENC、智能分析都顺理成章。我手上这颗 Hi3516 支持 4-lane MIPI 输入匹配 IMX214 的 4 lane CSI-2 输出刚好。项目里并没有把 IMX214 跑到 13M 满分辨率而是工作在 1080p30fps 裁剪模式。这样对 DDR 带宽和 VI 的压力都小发热也控制得住。1.2 整体接入链路与开发流程接入链路从物理层面看是IMX214 的 MIPI 四对差分数据/时钟线接到 Hi3516 MIPI RXI2C 的 SCL/SDA 接到 SoC 的 I2C 控制器另外还有 MCLK、RESET、PWDN 三根控制线。软件层面的链路是Sensor 上电 - 时钟稳定 - 复位释放 - I2C 扫描找设备 - 读 Sensor ID 确认 - 写入初始化寄存器序列 - Sensor 输出 MIPI 信号 - VI 接收 - VPSS 处理 - VENC 编码。我习惯把开发顺序拆成四段每一段都有明确的验证标准阶段任务验证方式硬件检查电源、时钟、复位、I2C 上拉万用表、示波器I2C 调通扫描地址、读寄存器i2cdetect、i2ctransferSensor 驱动初始化序列、MIPI 参数能读到 ID能触发 streamVI 通路VI/VBUF/VPSS 配置抓帧出图、帧计数增长这个顺序千万别跳。很多人在 I2C 都还没通的情况下急着去调 VI最后所有问题混在一起定位非常累。2. I2C 调试Sensor 能不能被“看见”2.1 硬件连接与原理图检查I2C 调不通八成不是软件问题是硬件没起来。第一件事不是写代码而是拿万用表量 IMX214 各路供电。IMX214 一般需要多路电源模拟供电、内核供电、IO 供电具体电压值以模组规格书为准。只要有一路电压没到位sensor 内部的 I2C 模块就不会工作表现在总线上就是 SCL 有波形但 SDA 永远不 ACK。电源没问题后再看 I2C 上拉电阻。I2C 总线必须接上拉常见阻值是 1.5K 到 4.7K上拉到 SoC I2C 可接受的 IO 电平。如果上拉电阻太大信号边沿变缓高速通信时容易出错如果阻值太小灌电流太大会拉不低 SDA。我第一次拿到这块板子I2C 怎么扫都扫不到设备最后发现上拉电阻贴的是 10K而总线电容又偏大400KHz 模式下 SDA 的低电平根本达不到识别阈值。后来把 I2C 频率降到 100KHz勉强能读再换 2.2K 上拉后 400KHz 完全稳定。另外要确认的还有管脚复用。Hi3516 很多 I2C 引脚和 GPIO、PWM 功能复用SDK 里默认的管脚配置不一定是 I2C 模式。如果上电后i2cdetect完全无反应先检查 SoC 的管脚复用寄存器或者在设备树里确认 I2C 控制器已经使能。2.2 用 i2cdetect 扫描总线确认地址板子起来后进入 Linux 终端先看有哪些 I2C 总线。海思 SDK 默认会把i2c_dev模块编进去如果没有需要在内核里打开CONFIG_I2C_CHARDEVi2cdetect -l输出类似i2c-0 i2c hi3516-i2c.0 I2C adapter i2c-1 i2c hi3516-i2c.1 I2C adapterIMX214 挂在哪条总线上需要对照原理图或看 SDK 默认的 bus 配置。假设是i2c-0直接扫描i2cdetect -y -r 0如果一切正常会看到某个地址上出现数字0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --我这边扫到的是0x20这是 Linux I2C 子系统的 7bit 地址。如果某个位置显示UU说明该地址被内核驱动占用了需要先确认驱动是否已经绑定到正确设备。地址这块有个容易踩坑的点海思 SDK 的sensor_addr在不同版本里解释不一样。有的 SDK 填 7bit 地址有的要求把 7bit 地址左移一位变成 8bit 写地址。比如 i2cdetect 看到 0x20代码里可能填 0x20也可能填 0x40。怎么判断看底层 I2C 读写函数的实现。如果它在调用HI_MPI_I2C_Read前自己做了addr 1那填 7bit 地址就行如果直接把地址塞给 i2c_transfer一般填 8bit 地址。最稳妥的办法是先用命令行调通再对着源码抄。2.3 用 i2ctransfer 读写寄存器验证扫描到地址只是第一步还得确认 I2C 通信内容正确。IMX214 寄存器地址是 16bit寄存器数据是 8bit所以写一个寄存器需要三字节先写高 8 位地址再写低 8 位地址最后写数据。用 i2ctransfer 读芯片 ID 看看# 读 0x0000 寄存器连续读 2 字节 i2ctransfer -f -y 0 w20x20 0x00 0x00 r2如果读到0x02 0x14基本就能确定是 IMX214。不同批次可能有差异但这类型号 ID 一般是固定的。读不到的话再检查上面说的硬件、地址、总线频率。读寄存器时还要注意时序。很多 sensor 支持地址自增连续读多个字节时可以简化命令但也有些寄存器是 bank 切换模式读取前必须先写对 bank 寄存器否则读回来永远是同一份数据。IMX214 这类索尼 sensor 用0x0136做 bank 切换调试时如果发现某个寄存器怎么读都是默认值去查一下当前 bank 对不对。3. IMX214 初始化序列与驱动移植3.1 关键寄存器与初始化序列整理I2C 通了之后真正的活才开始。IMX214 必须先写入一长串初始化寄存器让它完成 PLL 配置、分辨率切换、输出格式设置才能从 MIPI 口吐数据。厂家通常会给一份imx214_1080p_30fps_settings.h之类的寄存器表常见格式是“地址数据”的数组static const HI_U8 g_astImx214Init[][3] { {0x01, 0x36, 0x00}, // bank {0x01, 0x37, 0x00}, {0x01, 0x38, 0x00}, // ... 一堆寄存器 {0x01, 0x00, 0x00}, // stream off };这份表可以直接复制到海思 sensor 驱动的寄存器数组里但有几个点必须注意。第一初始化序列里如果有0x0100stream control寄存器初始值一定要写成0x00让 sensor 处于 standby 状态。真正开始出流时由海思的sensor_start回调写0x01。如果初始化表末尾直接写了0x01看起来图像正常但 SDK 做HI_MPI_VI_EnableChn时无法控制流开关后续切分辨率、做低功耗恢复都会出问题。第二寄存器数组顺序不能乱。索尼 sensor 的 PLL 配置是一套联动的先写主时钟分频再写 PLL 倍频再写输出分频。如果收到厂家提供的序列按原顺序写不要自己优化排序。我见过有人为了“看起来更整齐”把寄存器按地址排序结果 MIPI 时钟完全乱了出图全是条带。第三注意 bank 寄存器。IMX214 很多寄存器实际上分 bank写任何寄存器前都要确保当前 bank 正确。厂家表格里已经处理好了但如果你要自己修改曝光值或增益寄存器一定要知道当前 bank 是什么否则会写到别的影子寄存器里。3.2 MIPI 参数匹配IMX214 输出 MIPI CSI-2默认是 4 lane常见位深是 RAW10。海思 VI 端 MIPI RX 必须和 sensor 输出侧对齐否则就算 I2C 配置全部正确VI 也收不到数据。比较关键的一个参数是 MIPI 数据速率。估算方法很简单链路总速率 ≈ H_Total × V_Total × 帧率 × 每像素bit数 单 lane 速率 ≈ 链路总速率 / lane数这里H_Total和V_Total要含消隐不是图像的宽和高。假设 IMX214 工作在 1080p30fps10bit 输出H_Total 2200V_Total 11254 lane那单 lane 速率大概在 180Mbps 量级。这个值不一定要算得特别精确因为海思 MIPI RX 对 rx data rate 有一定的容错空间但不能差一个数量级。如果你从厂家寄存器表里看到 PLL 配出来的 MIPI clock直接按寄存器算更准。海思 SDK 里 MIPI 参数不是单独一个文件而是和 VI 属性绑在一起。MIPI_SENSOR_INFO_S结构体里的lane_num、data_rate、img_width、img_height都要和实际 sensor 输出一致。我建议直接复制 SDK 里最接近的 sensor 模板比如 IMX327、IMX334 的配置然后把分辨率、lane 数、像素格式改成 IMX214。少去自己从零填结构体能省很多排查时间。3.3 海思 Sensor 驱动框架注册方式海思的 sensor 驱动可以理解成一张映射表sensor_list.c里记录 sensor 名称、对应ISP_SNS_OBJ_S对象sample_comm_isp.c根据snsType找到这个对象再调用里面的回调函数。移植时最容易的做法是复制一个现有 sensor 文件。比如把sensor_imx327.c复制成sensor_imx214.c然后分四步改替换掉寄存器初始化表换成 IMX214 的表。把对象结构体里的sensor_addr改成实际 I2C 地址bus改成对应 I2C 总线号snsType改成自定义枚举。把sensor_init、sensor_exit、sensor_start、sensor_stop这些回调函数名跟着改掉。在sensor_list.c里注册新的 sensor ID比如imx214、IMX214或者直接按枚举名注册。代码结构大致是这样ISP_SNS_OBJ_S g_stSnsImx214 { .pstSnsRegs g_stImx214Regs, .stSnsInfo { .sensor_addr 0x20, .bus 0, .snsType IMX214_MIPI_13M_30FPS_10BIT, }, .sensor_init imx214_sensor_init, .sensor_exit imx214_sensor_exit, };编译时把新增的sensor_imx214.c加进 Makefile重新生成libsns静态库。海思 SDK 的编译体系比较老建议先保持和模板文件一致的目录结构避免Makefile里的 wildcard 没抓到新文件。4. VI 通路构建与出图调试4.1 VI 初始化参数配置Sensor 驱动注册完成后VI 通路就是最后一道坎。VI 的本质是把 MIPI RX 收到的数据按像素格式和分辨率解析成内存里的图像 buffer。Hi3516 的 VI 配置主要分三块MIPI RX 属性、VI 设备属性、VBUF 缓冲池。MIPI RX 属性必须和 sensor 输出严格匹配MIPI_SENSOR_INFO_S stMipiInfo; memset(stMipiInfo, 0, sizeof(stMipiInfo)); stMipiInfo.devno 0; stMipiInfo.input_mode INPUT_MODE_MIPI; stMipiInfo.data_rate 200; // 单位 Mbps/lane按实际调整 stMipiInfo.img_width 1920; stMipiInfo.img_height 1080; stMipiInfo.lane_num 4; // 对应 IMX214 的 4-lane 输出 stMipiInfo.lane_polarity 0; stMipiInfo.lane_map 0;lane_polarity和lane_map默认填 0 就行。只有遇到 MIPI 信号线交叉、极性接反的情况才需要改。比如硬件工程师把 MIPI lane0 和 lane1 接反了但不想动板子就只能在这里用 lane_map 软件交换。VI 设备属性里面enInputMode要设成 MIPIenPixFmt要和 sensor 输出的 Bayer 顺序一致。IMX214 手册里会写清楚第一行第一列的颜色排布常见是 RGGB 或 BGGR。海思这边对应的枚举一般是PT_RAW_RGGB_10BIT或者PT_RAW_BGGR_10BIT。这一项如果填错图像不会没有而是会出现很难看的伪彩。当时我因为随手复制了上一个项目的 Bayer 配置出来的图像整体发紫排查了很久才想到是这里。VBUF 是另一个容易被忽略的点。IMX214 一张 13M 满分辨率 RAW 图非常大如果只跑 1080p 也要给足 buffer。VBUF 太小最典型的现象是VI 设备能 start但通道 start 失败报HI_ERR_VB_NOMEM。我习惯给 VI 链路分配块大小的时候按宽×高×2 来算第一版虽然浪费一点内存但至少不会因为 buffer 不够卡住验证。4.2 配置 pipeline 并抓图验证VI 通路最稳的验证方式不是直接上编码器而是先做一个最小闭环VI 进VPSS 出用HI_MPI_VPSS_GetChnFrame抓一帧数据保存成文件。这样能快速断定问题是在 VI 接收端还是后面编码/显示环节。核心流程大概是// 1. 设置 MIPI 属性并启动 VI 设备 HI_MPI_VI_SetMipiAttr(0, stMipiInfo); HI_MPI_VI_EnableMipiId(0, 0); HI_MPI_VI_SetChnAttr(0, 0, stViChnAttr); HI_MPI_VI_EnableChn(0, 0); // 2. 创建 VPSS 组绑定 VI 通道 HI_MPI_VPSS_CreateGrp(0, stGrpAttr); HI_MPI_VPSS_EnableChn(0, 0); MPP_CHN_S stSrc {0}; stSrc.enModId HI_ID_VI; stSrc.s32DevId 0; stSrc.s32ChnId 0; MPP_CHN_S stDst {0}; stDst.enModId HI_ID_VPSS; stDst.s32DevId 0; stDst.s32ChnId 0; HI_MPI_SYS_Bind(stSrc, stDst); // 3. 抓帧并保存 HI_MPI_VPSS_GetChnFrame(0, 0, stFrame, 2000); // 把 stFrame 里的数据写到本地文件或通过网口传出来 HI_MPI_VPSS_ReleaseChnFrame(0, 0, stFrame);第一次出图时我不建议直接看显示效果而是把抓到的原始 RAW 或者 YUV 数据保存下来在 PC 上用 YUV viewer 打开。RAW 图如果能看到明显的物体轮廓说明 MIPI 和 VI 通路已经通了接下来只是 ISP 和 Bayer 顺序的事如果文件纯黑或者纯花说明 MIPI 和 VI 的参数还有问题。4.3 图像异常时的快速定位图像异常时我会按下面这个顺序排查能少走很多弯路。先看 VI 有没有在收帧。不同 SDK 路径不同有的在/proc/umap/vi有的在/proc/mpp/vi里面一般会有 channel 的帧计数。如果帧计数不增长说明 sensor 可能根本没输出 MIPI data或者 MIPI RX 没起来。这时候回头查0x0100寄存器有没有写 1MIPI clock 有没有输出。再看画面纹理。如果图像是斜纹或者撕裂感大概率是 MIPI lane 顺序或极性不对。如果有正常轮廓但颜色怪异优先怀疑 Bayer 顺序。如果整幅图亮度和对比度都不对再看 sensor 曝光/增益寄存器是否被驱动覆盖了。最后看有没有丢帧。VPSS GetChnFrame 超时是最常见的 API 调用失败类问题原因不外乎三种VBUF 不够、VI 通道没使能、VPSS group 参数和 VI 输出不匹配。把这三项逐一确认掉90% 的 issue 都能解决。5. 实战问题清单与解决记录5.1 I2C 无 ACK现象i2cdetect扫不到任何设备或者命令直接卡死。优先查这几项sensor 供电是否到位各路电压是否正确I2C 上拉电阻是否虚焊、阻值是否合理SCL/SDA 是否接反SoC 管脚复用是否正确尝试把 I2C 频率降到 100KHz。如果以上都正常用示波器抓 SCL 和 SDA。正常情况下第一个 ACK bit 应该在 SDA 最低电平出现如果 SDA 一直高基本说明 sensor 没应答要么地址不对要么 sensor 没上电。5.2 能读到 ID 但无图像能读到芯片 ID说明 I2C 链路和 sensor 基本功能正常问题出在“从配置到输出”的环节。查初始化序列有没有把0x0100写成 stream on以及驱动 start 回调有没有被调用查 MCLK 有没有给频率对不对。IMX214 一般要求 24MHz如果板子用了 27MHz 无源晶振sensor 很可能不工作查 MIPI RX 的 lane 数、data rate、分辨率是否匹配查 VI 的enPixFmt是否匹配 sensor 输出格式。这个坑我踩得最深的是 MCLK。那次 I2C 全部正常sensor ID 能读到初始化序列也写了但 VI 端就是没有帧。折腾一下午才发现是板载晶振焊成了 27MHzsensor 手册写的是 24MHzPLL 配置全乱了。5.3 图像偏色/花屏/条纹现象大概率原因处理方式整体偏绿、偏紫Bayer order 不对修改 VIenPixFmt在 RGGB/BGGR 之间切换斜纹、撕裂MIPI lane 顺序/极性反调整lane_map、lane_polarity规则竖条纹MIPI data rate 偏差大按 sensor 寄存器表重新计算修改data_rate亮度忽明忽暗增益/曝光寄存器被覆盖检查驱动 start 回调里的曝光初始化图像类问题最好一帧一帧抓出来看趋势不要盯着屏幕闪烁猜。用固定场景、固定曝光抓帧保存多张对比异常规律比肉眼盯着显示设备高效很多。5.4 帧率不对或丢帧帧率问题在 VI 通了、开始跑编码之后才会暴露。常见原因VBUF 缓冲池不够导致 VPSS 取不到帧编码器码率太大DDR 带宽被占满sensor 输出帧率和 VIstCapRect不匹配导致裁剪后像素时钟不一致。排查时先把编码器关掉只跑 VI VPSS 抓帧看帧率是否正常。如果正常说明问题在编码侧如果仍然不正常回头检查 VBUF 和 sensor 寄存器里的帧率配置。6. 一点调试心得这套流程跑通之后我自己最大的体会是sensor 接入的坑八成在“参数一致性”上。I2C 地址是否一致、寄存器 bank 是否一致、MIPI lane 是否一致、Bayer 顺序是否一致任何一个地方不一致表现出的现象都可能是“黑屏”或者“花屏”原因却完全不同。所以我会在项目一开始就建一个小表把硬件原理图上的连接关系、厂家寄存器表里的输出时序、海思代码里的 MIPI/VI 参数列在同一张纸上逐项对完再上电。这样能省出至少一天的调试时间。另外一个小技巧I2C 调通后先不要急着写完整驱动直接用命令行把初始化序列一条一条写进 sensor再用示波器量 MIPI 数据线有没有波形。这样能确认初始化序列本身是否正确排除后面驱动代码的干扰。等确认 sensor 已经能出 MIPI 数据再搬到驱动里去跑效率会高很多。
RELATED READING

延伸阅读

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