简介这份资源面向嵌入式Linux驱动开发者与海思平台调试人员提供Sony IMX335图像传感器在Hi3559芯片上的适配驱动代码解决传感器与主控之间接口协议、时序控制、电源管理等兼容性问题适用于安防监控、智能分析、工业相机等场景的底层开发与移植参考。压缩包共6个文件约362KB包含2个C源文件、2个编译产物、1个头文件与1个Makefile分别对应传感器寄存器配置、CMOS驱动逻辑、公共头文件定义及编译构建脚本结构紧凑便于直接集成到V4L2或UVC框架中。目前已有1069人学习下载。读者可从中获取经过调试与功能验证的驱动实现参考其初始化流程、数据传输机制与错误处理思路快速完成IMX335在Hi3559上的点亮与出图减少重复适配与排错成本。1. 把 IMX335 挂到 Hi3559 上一条 MIPI 链路背后的四件事手里有一块 Hi3559 的开发板又拿到几片索尼 IMX335 的 sensor 模组想把它们接起来跑出第一帧图像——这件事听起来只是「写个驱动」实际动手才会发现它同时牵扯四件互相咬合的事I2C 上能不能把 sensor 的寄存器写对、MIPI 差分链路的时钟和数据 lane 有没有对齐、Hi3559 这一侧的 ISP 和 VI 管线怎么把 RAW 数据接住、以及最终出图时曝光和增益由谁控制。sony_imx335 这颗 500 万像素、1/2.8 英寸的 CMOS在安防和机器视觉里出镜率很高而 hi3559 又是海思这边做多路视频输入的常客所以「imx335 driver 适配 hi3559」这个组合被反复问到不是没有原因。这篇东西面向的是手上真有板子、真要把它点亮的工程师。我会按「先搞清楚链路长什么样 → 再一步步把驱动和配置落地 → 最后把踩过的坑摊开」的顺序讲中间给到能直接抄的寄存器配置片段、设备树节点写法和调试命令。新手可以顺着步骤走熟手可以直接跳到参数表和避坑那章看边界。需要说明的是不同厂家的 Hi3559 板子Hi3559A、Hi3559C、Hi3559V100 等在 VI 管线和 SDK 版本上差异不小下面给的是通用路径具体寄存器地址和结构体字段要以你手上的 SDK 头文件为准。2. IMX335 与 Hi3559 的链路拆解从 I2C 到 MIPI 再到 ISP2.1 先看清这条链路到底由谁管谁把 sensor 接到 SoC 上物理上只有两组线一组 I2CSCL/SDA负责「说话」一组 MIPICLK 差分对 若干 DATA 差分对负责「传图」。但逻辑上控制权是分层的。IMX335 自己只负责按你给的寄存器配置把光信号转成 RAW10 或 RAW12 的数字流从 MIPI 吐出来它不知道 Hi3559 的 ISP 长什么样也不关心后面怎么处理。Hi3559 这一侧则要完成三件事通过 I2C 把 sensor 初始化到能出图的状态、通过 MIPI 接收器把差分信号解成并行数据、再通过 VIVideo Input和 ISP 把 RAW 数据转成可用的 YUV 或直接给到编码器。所以「写驱动」这件事在 Linux 下通常拆成两层一层是标准的 V4L2 subdev 驱动负责 I2C 通信、上电时序、寄存器表下发、曝光增益这些控制项另一层是 Hi3559 SDK 里的 MIPI 和 VI 配置负责把硬件接收通路打开、把 sensor 输出的时序参数分辨率、帧率、lane 数、data type告诉接收端。两层对不上最常见的表现就是 I2C 能读到 ID、但 VI 一直拿不到中断或者拿到中断但图像是花的。2.2 上电时序和时钟别小看这几根 GPIOIMX335 对上电顺序是有要求的。典型做法是先给 AVDD2.8V 模拟、再给 DOVDD1.8V IO、最后给 DVDD1.2V 数字核心中间还要保证 XCLK外部晶振输入常见 24MHz 或 27MHz在电源稳定后再起振复位XRESET和待机STANDBY引脚按手册时序拉高拉低。很多板子把这些电源做成了固定输出那上电顺序就靠硬件保证如果做成可控制的 regulator驱动里就得按顺序 enable。下面是一段典型的电源和时钟初始化逻辑用伪代码表示实际要替换成你板子上的 GPIO 号和 regulator 名/* imx335_power_on: 按手册要求的顺序上电并释放复位 */ static int imx335_power_on(struct device *dev) { int ret; /* 1. 先给模拟电源 AVDD 2.8V */ ret regulator_enable(avdd); if (ret) return ret; usleep_range(1000, 1200); /* 2. 再给 IO 电源 DOVDD 1.8V */ ret regulator_enable(dovdd); if (ret) return ret; usleep_range(1000, 1200); /* 3. 最后给数字核心 DVDD 1.2V */ ret regulator_enable(dvdd); if (ret) return ret; usleep_range(1000, 1200); /* 4. 使能外部时钟 XCLK常见 24MHz */ ret clk_prepare_enable(xclk); if (ret) return ret; usleep_range(5000, 6000); /* 5. 拉高 XRESET 释放复位低电平复位有效 */ gpiod_set_value_cansleep(reset_gpio, 1); usleep_range(10000, 12000); return 0; }这段逻辑里每个usleep_range的延时不是随便写的要对着 IMX335 手册里的上电时序表来。延时不够sensor 内部 PLL 可能没锁住表现就是 I2C 读 ID 正常但出图异常延时过长启动慢多路场景下会拖累整体初始化时间。xclk的频率必须和驱动里配置的 PLL 输入频率一致否则算出来的 MIPI 时钟会对不上图像直接花屏。2.3 I2C 地址与 ID 校验第一步就要能读通IMX335 的 I2C 从地址通常是 0x1A7 位部分模组会因为 SID 引脚接法不同变成 0x36。上电后第一件事就是读它的 model ID 寄存器确认通信正常。IMX335 的 ID 在 0x3000 到 0x3003 这几个寄存器里常见值是 0x33 0x50 开头。# 在目标板 Linux 下用 i2c-tools 确认 sensor 是否在总线上 # 假设 sensor 挂在 i2c-3 上地址 0x1a i2cdetect -y 3 # 期望看到 0x1a 位置显示 1a而不是 -- # 读 model ID 寄存器16 位地址8 位数据 i2cget -y 3 0x1a 0x30 0x00 w # 返回类似 0x3350 说明 ID 正确如果i2cdetect扫不到地址先别怀疑驱动用万用表量 SCL/SDA 的静态电平正常应该是被上拉到高电平。拉不上去要么是上拉电阻没焊要么是 sensor 没上电。这一步是血泪经验很多「驱动不工作」的问题最后查出来是硬件上电或上拉的问题跟代码没关系。2.4 MIPI 参数lane 数、时钟频率和 data type 必须三方一致IMX335 支持 2 lane 和 4 lane 两种 MIPI 输出模式常见配置是 4 lane、每 lane 速率 891Mbps 或 1.2Gbps 左右具体取决于你选的帧率和分辨率。这里有个关键点sensor 输出的 MIPI 时钟频率、Hi3559 接收端配置的 lane 数、以及 VI 里声明的 data typeRAW10 还是 RAW12这三者必须完全一致。任何一处对不上结果就是接收端报「MIPI 错误」或者干脆没有中断。计算 MIPI 时钟的常用公式是总数据率 分辨率宽 × 高 × 帧率 × 每像素 bit 数 × 冗余系数。以 2592×1944、30fps、RAW10 为例理论数据率约 2592×1944×30×10 ≈ 1.51Gbps分到 4 lane 上每 lane 约 378Mbps再考虑消隐区实际配置会留余量。这个数算出来是为了和 sensor 寄存器里配的 PLL 参数对上不是给接收端用的——接收端只关心 lane 数和每 lane 速率范围。3. 驱动落地从设备树到第一帧图像3.1 设备树节点怎么写才不会被内核忽略在 Linux 下sensor 通常作为一个 I2C 从设备挂在对应的 I2C 控制器节点下。设备树里要写清楚 compatible、reg、时钟、GPIO、电源以及和 MIPI 接收端的关联。下面是一个简化后的节点示例i2c3 { status okay; clock-frequency 400000; imx335: imx3351a { compatible sony,imx335; reg 0x1a; clocks clk_24m; clock-names xclk; reset-gpios gpio1 12 GPIO_ACTIVE_LOW; standby-gpios gpio1 13 GPIO_ACTIVE_LOW; avdd-supply avdd_2v8; dovdd-supply dovdd_1v8; dvdd-supply dvdd_1v2; port { imx335_out: endpoint { remote-endpoint mipi_in_imx335; >/* 关键寄存器片段以 2592x1944 30fps 4lane RAW10 为例 */ static const struct regval imx335_2592x1944_30fps[] { {0x3000, 0x01}, /* 进入 standby准备写配置 */ {0x3002, 0x00}, /* PLL 设置具体值取决于 XCLK 频率 */ {0x3030, 0x02}, {0x3031, 0x00}, /* MIPI 4 lane 输出 */ {0x3050, 0x00}, /* 输出 RAW10 */ {0x3051, 0x00}, /* 帧长、行长决定帧率 */ {0x30a0, 0x0a}, {0x30a1, 0x8c}, /* 退出 standby开始出图 */ {0x3000, 0x00}, };写寄存器表时0x3000先置 1 进 standby 是必须的很多配置寄存器在出图状态下写不进去。写完再置 0 退出。PLL 相关的值一定要用索尼官方或模组厂给的配置工具算手算容易错。如果出图后帧率不对优先查帧长行长这两个寄存器它们直接决定每帧的时间。3.3 Hi3559 侧 VI 和 MIPI 的配置要点Hi3559 SDK 里MIPI 接收和 VI 的配置通常通过 MPIMPP 接口完成。核心步骤是先配置 MIPI 设备属性lane 数、data type、时钟模式再创建 VI 通道并绑定到 MIPI最后启动。下面是一段示意代码结构体字段名以你手上的 SDK 为准/* 配置 MIPI 接收属性 */ sample_comm_vi_set_mipi_attr(lane_num, data_type, clock_mode); /* 创建 VI 通道绑定到 sensor 的时序 */ vi_chn_attr.stSize.u32Width 2592; vi_chn_attr.stSize.u32Height 1944; vi_chn_attr.enPixelFormat PIXEL_FORMAT_RGB_BAYER_10BPP; vi_chn_attr.enCompressMode COMPRESS_MODE_NONE; /* 启动 MIPI 和 VI */ sample_comm_vi_start_mipi(); sample_comm_vi_start_vi();这里最容易出问题的是enPixelFormatRAW10 对应的是 10BPP 的 Bayer 格式写错成 8BPP 或 12BPPISP 出来的颜色就是错的。另外Hi3559 的 VI 对时序参数有范围要求如果 sensor 输出的消隐区太小VI 可能报「时序不满足」而拿不到数据这时候要回去调 sensor 的帧长行长把消隐区留够。3.4 点亮第一帧用命令确认数据真的进来了配置完成后不要急着看画面先用命令确认 VI 有没有拿到数据。Hi3559 SDK 一般提供sample_vi或类似的测试程序也可以自己写个小程序读 VI 通道的统计信息。# 加载驱动模块 insmod imx335.ko # 确认 sensor 被识别 dmesg | grep imx335 # 期望看到 imx335 3-001a: Detected IMX335 sensor # 运行 SDK 自带的 VI 采集示例抓一帧存成文件 ./sample_vi -a 1 -f 1 -o /tmp/frame.raw # 检查文件大小2592x1944 RAW10 一帧约 6.3MB ls -l /tmp/frame.raw如果dmesg里能看到 sensor 被识别但sample_vi抓不到数据问题基本在 MIPI 或 VI 配置上而不是 I2C 驱动。这时候用示波器或逻辑分析仪量 MIPI 的时钟 lane 有没有在跑是最直接的判断手段。4. 避坑与排查那些让第一帧迟迟不来的问题4.1 现象I2C 能读到 ID但 VI 一直没中断原因通常有三种MIPI lane 数配错、data type 不匹配、或者 sensor 根本没进入出图模式。先确认 sensor 的 standby 寄存器0x3000是不是已经置 0再核对接收端 lane 数和 sensor 寄存器里配的是否一致。还有一种情况是 MIPI 时钟模式continuous/noncontinuous两边不一致接收端等不到时钟就永远不产生中断。解决方法是逐项对照用i2cget读回 0x3000 确认是 0x00用 SDK 的调试命令打印 MIPI 接收状态看有没有报 lane 错误。如果报 lane 错误优先怀疑硬件走线或 lane 顺序。4.2 现象出图了但画面是花的颜色也不对这几乎可以锁定在 data type 或 Bayer 顺序上。IMX335 输出的是 Bayer RAWBayer 的起始像素排列RGGB、BGGR 等要和 ISP 里配的一致。如果 ISP 按 RGGB 解而 sensor 实际是 BGGR颜色就会整体偏。另外RAW10 的数据在 MIPI 上是打包传输的如果接收端按 RAW8 解画面会明显错位。解决方法是查 sensor 手册里 Bayer 排列的说明在 ISP 配置里改对应的 pattern。同时确认 VI 的 pixel format 是 10BPP 而不是 8BPP。4.3 现象帧率只有预期的一半或更低先查帧长行长寄存器。帧率 MIPI 时钟 / (帧长 × 行长)如果行长设大了帧率就掉。另一个常见原因是 VI 的缓存或带宽不够多路场景下尤其明显。Hi3559 的 VI 对每路的分辨率和帧率有总带宽限制超了就会自动降帧。解决方法是先单独跑一路确认单路帧率正常再逐步加路数。如果单路就不对回去调 sensor 的帧长行长如果单路正常多路掉就要算总带宽必要时降分辨率或帧率。4.4 现象驱动加载时报 probe 失败但看不出具体原因probe 失败最常见的原因是电源或时钟没准备好。检查 regulator 名字和设备树里写的是否一致clk_prepare_enable有没有返回错误。还有一种情况是 GPIO 申请失败比如 reset-gpios 被别的驱动占用了。解决方法是在 probe 函数里每一步都加日志确认卡在哪一步。用cat /sys/kernel/debug/gpio看 GPIO 占用情况用regulator_summary看电源状态。4.5 现象换了一块板子同样的代码就不工作了这通常不是代码问题而是硬件差异。不同板子的 XCLK 频率可能不同24MHz vs 27MHzPLL 配置就要跟着改MIPI lane 顺序可能不同设备树里的>/* 设置曝光单位是行 */ static int imx335_set_exposure(struct v4l2_subdev *sd, u32 exposure) { struct i2c_client *client v4l2_get_subdevdata(sd); u16 shr exposure; /* SHR0 低 8 位SHR0 高 8 位在另一个寄存器 */ imx335_write_reg(client, 0x3058, shr 0xff); imx335_write_reg(client, 0x3059, (shr 8) 0xff); return 0; }这里要注意曝光寄存器的值不能超过帧长减去最小消隐否则会出错。增益寄存器的值和实际 dB 之间是非线性关系要查手册里的对照表不能直接线性换算。5.2 验证 3A 是否真的在调最直接的方法是在不同光照下抓图看亮度有没有变化。如果亮度不变说明 3A 没生效。可以在 ISP 的调试接口里打印当前曝光和增益值确认它们有没有随光照变化。另一个方法是手动通过 V4L2 接口设一个固定曝光看画面亮度是否跟着变以此判断控制通路是否通。5.3 一个我常用的调试习惯每次调新 sensor我都会先写一个最小的测试程序只做三件事读 ID、写一组固定曝光增益、抓一帧存文件。这个程序不依赖 3A也不依赖上层应用能最快定位问题在驱动、MIPI 还是 ISP。等这一帧能稳定出来再去接 3A 和上层。这个习惯帮我省了很多来回折腾的时间也希望帮到你。本文还有配套的精品资源点击获取