
简介这是一份面向嵌入式Linux驱动开发者的Sony IMX335传感器驱动适配资料针对海思Hi3559平台定制解决图像传感器在该芯片上无法正常初始化与取流的问题。资源包共6个文件以C源码与头文件为核心包含imx335_cmos.c、imx335_sensor_ctl.c及对应头文件另附Makefile与编译生成的.o目标文件整体约362KB结构精简便于直接编译验证。驱动已针对Hi3559的接口协议、时序控制与电源管理完成适配调试功能验证无误可帮助读者理解V4L2框架下传感器注册、寄存器配置、MIPI CSI-2数据传输及中断与帧率控制等关键环节。目前已有1072人学习下载适合从事安防监控、无人机或工业相机开发、需要快速移植IMX335驱动的工程师参考也可作为Hi3559平台ISP调试与图像采集链路的实践素材。1. IMX335 上 Hi3559一颗 500 万像素 Sensor 的驱动适配到底难在哪手里有一块 Hi3559A 的开发板又拿到几片索尼 IMX335 模组想把它们接起来跑出图——这件事听起来只是「写个驱动」实际动手才会发现它横跨了 MIPI 物理层、I2C 寄存器时序、海思 MPP 的 VI 管线配置和 ISP 标定四条链路任何一环对不上现象都是黑屏或者花屏而日志里往往什么都不报。IMX335 是一颗 1/2.8 英寸、最高 2592×194430fps 的 CMOS Sensor走 MIPI CSI-2 接口I2C 从地址 0x368bit 写地址 0x6CHi3559 系列则是海思面向智能视觉的 SoCSensor 接入靠的是 MPP 框架下的 sensor 驱动 ISP 库。所谓「适配」本质是把索尼的寄存器手册翻译成海思 sensor 驱动框架能识别的结构体再让 VI 能正确采样到 MIPI 数据。这篇笔记面向的是已经能编译海思 SDK、手上有示波器或逻辑分析仪的嵌入式工程师我会把寄存器配置、驱动注册、VI 绑定和踩坑逐条拆开新手能照着走熟手能直接对参数边界。2. 先搞懂 Hi3559 的 Sensor 驱动框架长什么样2.1 海思 MPP 里 Sensor 驱动的三层结构海思的 sensor 适配不是写一个 Linux 字符设备那么简单它嵌在 MPPMedia Process Platform里分三层。最底层是 I2C 读写和上电时序负责把寄存器值真正写进 Sensor中间层是sample_comm_isp里注册的 sensor 对象包含sensor_obj、sensor_exp_func这些回调最上层是 VIVideo Input设备通过HI_MPI_VI_SetDevAttr把 MIPI 参数、时序和 sensor 时钟绑上去。理解这三层才知道改哪里。很多人一上来就改寄存器表结果发现连 I2C 都没通白忙一天。驱动代码通常放在 SDK 的mpp/component/isp/sensor/sony_imx335/目录下核心文件是imx335_sensor_ctl.c寄存器操作和imx335_cmos.c回调注册。imx335_cmos.c里那个sensor_register_callback是入口它把cmos_init、cmos_exit、cmos_set_image_mode等函数指针交给 ISP 框架。你要做的第一件事是确认 SDK 里有没有现成的 imx335 目录——有就改没有就从同系列的 imx290 或 imx415 抄一份骨架因为索尼这几颗的寄存器风格接近。2.2 IMX335 的关键寄存器分组IMX335 的寄存器按功能分成几组适配时按组推进比逐条查手册高效得多。第一组是系统控制地址 0x3000 附近负责 standby 释放、主时钟分频第二组是 MIPI 输出配置0x3000~0x30xx 区间决定 lane 数、输出位宽、Lane 速率第三组是曝光和增益0x3500 附近这是 AE 调用的核心第四组是帧率和裁剪窗口0x3700 附近。下面这张表是我实际适配时整理的常用寄存器值对应 2592×194430fps、4 lane、10bit 输出寄存器典型值作用备注0x30000x01standby 控制写 0x01 进 standby0x00 释放0x30020x00主时钟分频配合 INCK 频率0x300C0x3BMIPI 全局配置与 lane 数相关0x30180x04Lane 模式4 lane 时按手册设0x30300x00输出位宽10bit 输出0x30500x00黑电平相关影响暗部0x35000x00曝光高字节AE 写入0x37000x00裁剪窗口起始全尺寸时保持默认提示寄存器值不要照抄必须对着你手上那颗 Sensor 的 datasheet 版本核对索尼不同批次的手册在 0x300C 这类全局配置上会有差异。2.3 从零建一个 sensor 目录的最小步骤假设 SDK 里没有 imx335我一般这样起步。先复制一份 imx290 目录改名然后改 Makefile 里的编译目标再逐步替换寄存器表。这一步的关键是让编译先通过功能后面再调。# 进入海思 SDK 的 sensor 目录 cd mpp/component/isp/sensor/ # 复制同系列 sensor 作为骨架 cp -r sony_imx290 sony_imx335 cd sony_imx335 # 重命名源文件保持命名一致 mv imx290_sensor_ctl.c imx335_sensor_ctl.c mv imx290_cmos.c imx335_cmos.c mv imx290_cmos_ex.h imx335_cmos_ex.h改完文件名还要改Makefile里的SRCS和INC路径以及mpp/component/isp/sensor/Makefile里把新目录加进编译列表。逻辑说明海思的 sensor 编译是遍历目录的目录名和文件名必须和 Makefile 里的变量对上否则链接阶段会报undefined reference to sensor_register_callback。参数说明sony_imx290只是骨架来源如果你 SDK 里连 imx290 都没有就找任意一个索尼 sensor 目录寄存器风格差异靠后续替换解决。3. 寄存器配置与 I2C 时序让 Sensor 先出数据3.1 上电时序和 I2C 地址确认IMX335 上电有严格顺序先给 AVDD2.9V再给 DOVDD1.8V最后给 DVDD1.2V复位信号 XCLR 要在电源稳定后拉高INCK 时钟必须在复位释放前就稳定。顺序错了Sensor 会锁死I2C 读出来全是 0xFF 或 0x00。I2C 从地址是 0x367bit换算成海思驱动里常用的 8bit 写地址就是 0x6C。先用i2c_read读一下 0x3000 寄存器能读到非 0xFF 的值说明 I2C 通了。/* imx335_sensor_ctl.c 里的 I2C 地址定义 */ #define IMX335_I2C_ADDR 0x6C /* 8bit 写地址对应 7bit 0x36 */ #define IMX335_I2C_ADDR_READ 0x6D /* 读地址 */ /* 读一个寄存器返回 0 表示成功 */ static int imx335_read_reg(unsigned int addr, unsigned char *val) { /* 海思封装好的 I2C 读接口addr 是 16bit 寄存器地址 */ return sensor_i2c_read(IMX335_I2C_ADDR, addr, val, 1); }逻辑说明海思的sensor_i2c_read内部会处理寄存器地址的 16bit 拆分你只需要给对从地址。参数说明IMX335_I2C_ADDR必须是 8bit 形式如果你写成 0x36读出来会一直失败这是最常见的翻车点之一。3.2 初始化寄存器表的写法初始化表是一个{addr, val}数组按顺序写下去。海思的sensor_write_reg通常带延时参数因为有些寄存器写完要等几毫秒才能写下一条。下面是我用的初始化片段重点是 standby 释放和 MIPI 配置的顺序。/* 初始化寄存器序列最后一项用 0xFF 结尾 */ static const struct imx335_reg imx335_init_seq[] { {0x3000, 0x01}, /* 先进入 standby */ {0x3002, 0x00}, /* 主时钟分频 */ {0x300C, 0x3B}, /* MIPI 全局配置 */ {0x3018, 0x04}, /* 4 lane 模式 */ {0x3030, 0x00}, /* 10bit 输出 */ {0x3050, 0x00}, /* 黑电平 */ {0x3000, 0x00}, /* 释放 standby开始出图 */ {0xFFFF, 0xFF}, /* 结束标志 */ }; static int imx335_init(void) { int i 0; while (imx335_init_seq[i].addr ! 0xFFFF) { /* 写寄存器部分寄存器需要延时 */ sensor_write_reg(IMX335_I2C_ADDR, imx335_init_seq[i].addr, imx335_init_seq[i].val); i; } return 0; }逻辑说明先写 0x30000x01 进 standby是为了在配置其他寄存器时 Sensor 不输出乱数据所有配置写完再写 0x30000x00 释放。参数说明0x3018的 lane 模式值必须和硬件走线一致4 lane 接成 2 lane 会导致 MIPI 采样错位现象是花屏。0x3030的位宽要和 VI 里配置的DATA_TYPE对上10bit 对应 RAW10。3.3 用逻辑分析仪验证 I2C 和 MIPI寄存器写完不代表成功要用逻辑分析仪抓 I2C 波形确认每条写都有 ACK。如果某条没有 ACK先查从地址再查上拉电阻一般 4.7kΩ。MIPI 侧用示波器看 CLK lane 有没有差分信号速率对不对。IMX335 在 2592×194430fps、10bit、4 lane 下Lane 速率大约 594Mbps这个值要和 VI 里MIPI_RATE配置一致差太多会丢帧或黑屏。4. 把 Sensor 挂进 VI 管线MPP 配置与绑定4.1 VI 设备属性里的 MIPI 参数Sensor 出数据只是第一步Hi3559 的 VI 要正确采样得配VI_DEV_ATTR_S。这里最容易错的是MIPI_WDR_MODE、LANE_ID和DATA_TYPE。下面是一个 4 lane、RAW10、线性模式的配置示例。VI_DEV_ATTR_S dev_attr; memset(dev_attr, 0, sizeof(dev_attr)); dev_attr.enIntfMode VI_MODE_MIPI; /* MIPI 接口 */ dev_attr.enWorkMode VI_WORK_MODE_1Multiplex; dev_attr.enScanMode VI_SCAN_PROGRESSIVE; dev_attr.enDataSeq VI_DATA_SEQ_YUYV; /* RAW 域下按实际设 */ dev_attr.enInputDataType DATA_TYPE_RAW_10BIT; /* 和 sensor 输出位宽一致 */ dev_attr.szInputSize.width 2592; dev_attr.szInputSize.height 1944; dev_attr.enCompressMode COMPRESS_MODE_NONE; dev_attr.stDevRect.x 0; dev_attr.stDevRect.y 0; dev_attr.stDevRect.width 2592; dev_attr.stDevRect.height 1944; /* MIPI 相关 */ dev_attr.enMipiRate MIPI_RATE_594M; /* 与 sensor lane 速率匹配 */ dev_attr.enLaneNum 4; /* 4 lane */逻辑说明enInputDataType必须和 Sensor 的0x3030输出位宽一致RAW10 对 RAW10。参数说明enMipiRate是枚举值海思 SDK 里通常有 594M、891M 等档位选最接近实际速率的enLaneNum要和硬件走线一致接 4 根就写 4。4.2 绑定 ISP 和启动 VI 的顺序VI 配好后要按VI_CreateDev→VI_SetDevAttr→VI_EnableDev→ 绑定 ISP →VI_StartPipe的顺序调用。顺序错了会返回HI_ERR_VI_NOT_CONFIG之类的错误码。下面这段是典型的启动流程。/* 创建并配置 VI 设备 */ HI_MPI_VI_SetDevAttr(0, dev_attr); HI_MPI_VI_EnableDev(0); /* 绑定 ISPsensor 类型要和注册的一致 */ HI_MPI_ISP_SetSensorType(0, SONY_IMX335); /* 启动 pipe开始出图 */ HI_MPI_VI_StartPipe(0);逻辑说明HI_MPI_VI_EnableDev之后才能绑 ISP否则 ISP 拿不到设备句柄。参数说明SONY_IMX335这个枚举要在hi_comm_isp.h里定义如果你新增了 sensor记得在枚举里加一项否则SetSensorType会失败。4.3 用 sample 程序快速验证出图海思 SDK 自带sample_vi之类的示例改一下 sensor 类型就能跑。我一般先用它验证不要一上来就写自己的应用。跑通后能看到 YUV 文件用yuvplayer打开能出正常图像说明驱动链路通了。如果黑屏先看cat /proc/umap/vi里的帧计数计数不涨说明 MIPI 没数据计数涨但图像花查 DATA_TYPE 和 lane 配置。5. 适配过程中最容易翻车的几个点5.1 现象I2C 读寄存器一直返回 0xFF原因从地址写错或者上电时序不对导致 Sensor 没起来。海思驱动里从地址要 8bit很多人直接填 7bit 的 0x36。解决改成 0x6C再用逻辑分析仪确认电源和 XCLR 时序XCLR 必须在 INCK 稳定后至少延时 1ms 再拉高。5.2 现象VI 帧计数正常但图像全绿或全花原因DATA_TYPE 和 Sensor 输出位宽不匹配或者 MIPI lane 数配错。RAW10 配成 RAW12解出来的像素就是错的。解决核对0x3030和enInputDataType再确认enLaneNum和硬件一致。全绿还可能是 ISP 没加载标定参数检查isp_calib文件路径。5.3 现象图像能出但曝光不生效画面过亮或过暗原因AE 回调没注册或者曝光寄存器地址写错。IMX335 的曝光是 0x3500 三字节海思 AE 库会调cmos_set_exposure如果这个函数没实现或地址错AE 就失效。解决在imx335_cmos.c里实现cmos_set_exposure按手册把 24bit 曝光值拆到 0x3500/0x3501/0x3502。5.4 现象编译报 undefined reference to sensor_register_callback原因新加的 sensor 目录没进 Makefile或者文件名和 Makefile 变量不一致。解决检查mpp/component/isp/sensor/Makefile里的SRCS是否包含新目录文件名是否和imx335_cmos.c一致。海思的编译脚本对大小写敏感IMX335和imx335不能混。5.5 现象跑一段时间后丢帧或 VI 报超时原因MIPI 速率配置偏高或偏低或者 DDR 带宽不够。2592×194430fps 的 RAW10 数据量不小如果同时跑编码带宽会紧张。解决把enMipiRate调到和实际匹配的档位降低帧率到 25fps 试试或者检查 VI 的 buffer 数量是否够。6. 进阶用寄存器回读和 ISP 标定把画质调到位驱动跑通只是及格线真正让 IMX335 在 Hi3559 上出好图还得做两件事寄存器回读校验和 ISP 标定。寄存器回读是在初始化后把关键寄存器读回来比对确认写入生效。我一般写个小函数把 0x3000、0x300C、0x3018、0x3030 读出来打印和预期值对不上就说明 I2C 写失败或 Sensor 没接受。/* 回读关键寄存器用于调试 */ static void imx335_dump_regs(void) { unsigned char val; unsigned int regs[] {0x3000, 0x300C, 0x3018, 0x3030}; int i; for (i 0; i 4; i) { if (imx335_read_reg(regs[i], val) 0) { printf(reg 0x%04X 0x%02X\n, regs[i], val); } } }逻辑说明回读能区分「写没写进去」和「写进去了但 Sensor 没执行」两种情况。参数说明回读时要注意有些寄存器是只读的读出来和写入值不同是正常的以手册标注为准。ISP 标定这块海思提供isp_calib工具需要暗场、亮场和色卡图。IMX335 的暗电流和镜头 shading 因模组而异不标定的话暗部噪点和四角偏色会很明显。标定流程是先拍暗场算 BLC再拍亮场算 LSC最后拍色卡算 AWB 和 CCM。标定文件生成后放到/etc/isp/下VI 启动时加载。这一步没有捷径环境光要稳定色卡要正对镜头。我自己的习惯是每换一批模组就重新跑一次暗场标定因为索尼不同批次的 Sensor 暗电流有差异用旧标定文件会出现固定位置的噪点。另外调试时把printf打在cmos_init和cmos_set_exposure里能快速定位是初始化问题还是 AE 问题。这套流程我在几块 Hi3559 板子上都跑过最花时间的从来不是写代码而是对着示波器确认 MIPI 那几根差分线有没有虚焊。希望帮到你。本文还有配套的精品资源点击获取