简介:这份资源面向嵌入式Linux驱动开发者与海思平台音视频工程师,提供Sony IMX335图像传感器在Hi3559芯片上的适配驱动代码,解决传感器与主控之间接口协议、时序控制、电源管理等兼容性问题,适用于安防监控、智能分析、工业相机等场景的二次开发与移植参考。压缩包共6个文件,约362KB,包含2个C源文件、2个编译生成的.o目标文件、1个Makefile构建脚本和1个头文件,覆盖传感器寄存器配置、CMOS驱动逻辑与编译规则,结构紧凑便于直接集成到V4L2框架工程中。目前已有1069人学习下载,说明该适配方案在同类平台移植中具备一定参考价值。读者可据此了解IMX335在Hi3559上的初始化流程、数据传输机制与错误处理思路,并借助已验证的驱动代码缩短调试周期,快速完成传感器点亮与图像采集验证。
1. IMX335 驱动适配 Hi3559:从源码包到出图的完整路径
手里拿到一个sony_imx335 hi3559.zip,解压后是imx335_cmos.c、imx335_sensor_ctl.c、imx335_cmos_ex.h、Makefile和几个.o文件,很多人第一反应是「这不就是海思 SDK 里 sensor 目录下那套东西吗」。但真正把它塞进 Hi3559 的工程里跑起来,才会发现事情没那么简单——I2C 地址对不上、MIPI 时序差一拍、曝光接口挂错 ISP 通道,任何一个环节翻车,VI 通道就是黑屏或者花屏。这份资源的价值在于:它把 Sony IMX335 在 Hi3559 平台上的 sensor 驱动层代码整理成了一个可直接对照的参考实现,包含寄存器初始化序列、cmos 参数结构体和 sensor 控制接口。适合正在做安防 IPC、工业相机、多路视频采集的嵌入式工程师,尤其是手上已经有 Hi3559 开发板、需要把 IMX335 点亮的人。下面按「这份代码是什么 → 怎么编进去 → 怎么调出图 → 坑在哪」的顺序拆一遍。
2. 拆解驱动包:IMX335 在 Hi3559 上的 sensor 层到底管什么
2.1 海思 sensor 驱动的三层结构
海思平台的摄像头驱动不是一个大文件搞定,它分三层:sensor 层、ISP 层、VI 层。这份资源里的文件全部属于 sensor 层,具体来说:
imx335_cmos.c:实现 sensor 的注册、初始化、曝光/增益设置、帧率控制等回调函数,是驱动的主体。imx335_sensor_ctl.c:封装 I2C 读写和 sensor 寄存器操作,负责把具体的寄存器值写进 IMX335。imx335_cmos_ex.h:定义 sensor 的寄存器地址宏、结构体、以及不同分辨率模式下的参数。Makefile:编译规则,把上面几个.c编成.o,最终链接进海思的libsns_imx335.a或直接编入内核模块。
理解这个分层很关键。很多人改驱动时直接在imx335_cmos.c里硬编码 I2C 地址,结果换一块板子就挂,就是因为没搞清楚sensor_ctl和cmos的职责边界。常见做法是:I2C 地址、复位引脚、时钟配置放在sensor_ctl里,曝光增益的换算逻辑放在cmos里,寄存器表放在cmos_ex.h里。
2.2 IMX335 的关键参数与 Hi3559 的匹配点
IMX335 是一颗 1/2.8 英寸的 CMOS,最高 5120×3840@30fps,MIPI CSI-2 接口,4 lane 输出。Hi3559 的 MIPI 接收端支持 4 lane,但需要确认几个参数:
| 参数项 | IMX335 典型值 | Hi3559 侧配置位置 |
|---|---|---|
| MIPI lane 数 | 4 lane | sensor_ctl.c中 MIPI_ATTR |
| 输入时钟 | 24MHz | hi3559_vo或hi3559_vi的 clk 配置 |
| I2C 地址 | 0x34(7bit) | sensor_ctl.c中 I2C_ADDR |
| 数据格式 | RAW10 / RAW12 | cmos.c中stSnsImageMode |
| 最大帧率 | 30fps@5M | cmos_ex.h中stSnsExp结构 |
这里最容易出问题的是 I2C 地址。IMX335 的 7bit 地址是 0x34,但海思的 I2C 读写函数通常要求传 8bit 地址,也就是 0x68。如果你在sensor_ctl.c里看到I2C_ADDR 0x34,而实际读写用的是0x68,不要慌,这是海思的惯例——底层驱动会自动左移一位。但如果你自己写了一个裸机 I2C 测试程序,就得用 0x68。
2.3 寄存器初始化序列的读取方式
imx335_cmos_ex.h里通常有一个很大的数组,类似:
static const unsigned int imx335_init_regs[] = { 0x3000, 0x01, 0x3001, 0x00, 0x3002, 0x01, // ... 几百行 };这个数组就是 IMX335 的上电初始化序列。不同分辨率、不同帧率对应的序列不一样,有的驱动包里会按模式拆成多个数组,比如imx335_5m_30fps[]、imx335_4m_30fps[]。拿到这份资源后,第一件事是确认你需要的模式在不在里面。如果只有 5M 序列,而你要跑 1080P,就得自己从 Sony 的 datasheet 里补寄存器,或者用海思自带的sensor_cfg工具生成。
提示:不要直接改
imx335_cmos_ex.h里的寄存器值来「试」。IMX335 的 PLL 配置和 MIPI 时序是联动的,改一个寄存器可能导致整个链路不输出。正确做法是先用示波器确认 MIPI 时钟有没有出来,再查 I2C 通信是否正常。
3. 把驱动编进 Hi3559 工程:Makefile 与交叉编译链配置
3.1 确认交叉编译工具链
Hi3559 的 SDK 通常自带arm-himix200-linux-或aarch64-himix100-linux-工具链。先确认你的环境变量:
echo $PATH | grep himix which arm-himix200-linux-gcc如果输出为空,说明工具链没装或者没加进 PATH。海思的 SDK 一般在osdrv/tools/pc/或osdrv/tools/board/下有工具链安装脚本,跑一遍source就行。这一步不做,后面make会直接报arm-himix200-linux-gcc: command not found。
3.2 修改 Makefile 适配你的 SDK 路径
资源里的Makefile大概率是参考模板,不能直接make。打开后重点看三个变量:
CROSS_COMPILE ?= arm-himix200-linux- SDK_DIR ?= /home/xxx/Hi3559V200_SDK SENSOR_DIR ?= $(SDK_DIR)/mpp/component/isp/sensorCROSS_COMPILE要和你的工具链前缀一致。SDK_DIR指向你的海思 SDK 根目录。SENSOR_DIR是 sensor 驱动最终要放的位置。常见做法是先把整个sony_imx335文件夹拷到$(SDK_DIR)/mpp/component/isp/sensor/下,再改 Makefile 里的相对路径。
改完后执行:
make clean make如果编译通过,会在当前目录生成imx335_cmos.o、imx335_sensor_ctl.o,以及最终的libsns_imx335.a。如果报错imx335_cmos_ex.h: No such file or directory,检查-I包含路径有没有把当前目录加进去。
3.3 把 sensor 注册进 ISP 的 sensor 列表
光编出.a文件还不够,Hi3559 的 ISP 启动时要遍历一个 sensor 列表,找到匹配的 sensor 类型才会加载驱动。这个列表通常在mpp/component/isp/sensor/sensor_list.c或类似文件里。你需要加一行:
extern SENSOR_EXPORT_S g_sns_imx335_obj; // 在 sensor_list 数组里加: { SENSOR_TYPE_IMX335, &g_sns_imx335_obj },SENSOR_TYPE_IMX335这个宏要在hi_sensor.h或sensor_define.h里定义,值不能和已有的冲突。有的 SDK 版本已经预留了 IMX335 的类型号,直接搜IMX335就能找到。如果没有,自己加一个,比如#define SENSOR_TYPE_IMX335 0x40,但要确认这个值没被其他 sensor 占用。
3.4 重新编译整个 MPP 并烧录
sensor 列表改动后,不能只重编 sensor 目录,要回到 MPP 根目录重新编:
cd $(SDK_DIR)/mpp make clean make -j8编完后把生成的ko文件和lib推到板子上。常见做法是用nfs挂载根文件系统,直接替换板子上的libsns_imx335.a和hi3559_isp.ko。如果板子跑的是 squashfs 只读文件系统,就得重新打包固件烧录。
注意:替换
ko后一定要rmmod旧的 ISP 模块再insmod新的,否则内核里跑的还是旧驱动。lsmod | grep isp确认模块版本号变了才算生效。
4. 调出第一帧图像:VI 通道配置与 I2C 时序排查
4.1 用 sample_vi 快速验证 sensor 是否出图
海思 SDK 里自带sample_vi例程,这是验证 sensor 驱动最直接的工具。在板子上跑:
./sample_vi 0 1参数0表示 VI 设备号,1表示启用 ISP 的 sensor 0。如果驱动正常,终端会打印帧率统计,类似vi frame rate: 30.00。如果一直打印vi get frame timeout,说明 VI 没收到数据,问题出在 sensor 初始化或 MIPI 链路上。
先查 I2C:
i2c_read 0x68 0x3000 2这条命令读 IMX335 的 0x3000 寄存器,正常应该返回0x01。如果返回0xff或超时,说明 I2C 没通。检查三件事:sensor 的电源有没有上(1.2V 核心、1.8V IO、2.8V 模拟)、复位引脚有没有拉高、I2C 上拉电阻有没有焊。
4.2 MIPI 时序的示波器验证点
I2C 通了但 VI 还是没帧,大概率是 MIPI 时序问题。用示波器测三个点:
- MIPI CLK 差分对:应该有 400MHz 左右的差分时钟(取决于 IMX335 的 PLL 配置)。
- MIPI DATA0 差分对:在 CLK 稳定后应该有数据突发。
- sensor 的 XVS/FSIN 引脚:应该有 30Hz 的帧同步信号。
如果 CLK 没有,查imx335_cmos_ex.h里的 PLL 寄存器。IMX335 的 PLL 配置涉及 0x3030 到 0x3035 几个寄存器,常见值是0x3030=0x02、0x3031=0x00、0x3032=0x00、0x3033=0x00、0x3034=0x00、0x3035=0x00,但具体值取决于输入时钟是 24MHz 还是 27MHz。如果你板子上的晶振不是 24MHz,这组值必须改。
4.3 曝光和增益的接口对接
出图之后,下一步是调曝光。海思的 ISP 通过cmos.c里的pfn_cmos_set_exposure回调控制 sensor。这个回调的入参是unsigned int exposure和unsigned int gain,但 IMX335 的寄存器不是直接写这两个值,需要换算:
static int imx335_set_exposure(VI_PIPE vi_pipe, unsigned int exposure, unsigned int gain) { unsigned int exp_reg = exposure / 2; // IMX335 一行对应 2 个时钟 unsigned int gain_reg = gain_to_reg(gain); // 查表或公式换算 imx335_write_reg(0x3050, exp_reg >> 16); imx335_write_reg(0x3051, exp_reg & 0xffff); imx335_write_reg(0x3058, gain_reg); return 0; }这里的坑是:不同驱动包里exposure的单位可能不一样。有的用「行数」,有的用「微秒」。如果你发现调曝光时画面亮度变化不对,先确认cmos_ex.h里stSnsExp结构体的u32LinesPer500ms和u32Fps有没有配对。常见做法是拿一个已知亮度的灰卡,从最小曝光逐步加到最大,看画面亮度是否线性变化。如果不是,就是换算公式错了。
4.4 多路 IMX335 的 I2C 地址冲突处理
Hi3559 支持多路 sensor 同时输入,但 IMX335 的 I2C 地址是固定的 0x34,两路接同一个 I2C 总线就会冲突。解决办法有两个:一是用 I2C 多路复用器(如 PCA9548),每路 sensor 挂在不同通道;二是把其中一路的 I2C 地址改掉——IMX335 支持通过 0x300A 寄存器改地址,但需要先给 sensor 上电、在默认地址下写改址寄存器、再复位。第二种方法时序很紧,容易翻车,我一般推荐第一种。
提示:多路场景下,每路 sensor 的
sensor_ctl.c里的 I2C 地址要改成复用器对应的通道地址,而不是 sensor 本身的 0x34。比如 PCA9548 的通道 0 对应 0x70,通道 1 对应 0x71,写寄存器前要先切通道。
5. 避坑与排查:IMX335 在 Hi3559 上最容易翻车的五个点
5.1 现象:I2C 能读到 ID 但 VI 无帧
原因:IMX335 的 ID 寄存器(0x3000-0x3002)在 sensor 上电后就能读,不需要 MIPI 链路工作。所以 I2C 通只说明电源和 I2C 正常,不代表 MIPI 配置对了。
解决:先查imx335_cmos_ex.h里的 MIPI lane 数是否和硬件一致。有的驱动包默认写 4 lane,但你的板子只接了 2 lane,这时候要改sensor_ctl.c里的MIPI_LANE_NUM和cmos_ex.h里的stSnsImageMode的 lane 配置。改完重新编libsns_imx335.a并替换。
5.2 现象:出图但颜色偏绿或偏紫
原因:ISP 的 AWB 没收敛,或者 sensor 输出的 Bayer 顺序和 ISP 配置不匹配。IMX335 默认输出 Bayer RGGB,但有的驱动包里cmos.c的enBayer写成了 BGGR。
解决:在imx335_cmos.c里找到stSnsImageMode结构体,确认enBayer的值。海思的定义是BAYER_RGGB、BAYER_BGGR、BAYER_GRBG、BAYER_GBRG四种。如果颜色不对,先改这个,再跑sample_vi看。如果改了还是偏色,检查 ISP 的 AWB 算法有没有使能,在sample_vi的配置文件里把bAwbEn设为HI_TRUE。
5.3 现象:帧率只有 15fps 而不是 30fps
原因:IMX335 的帧率由 VMAX 和 HMAX 决定,而 VMAX 又和曝光时间联动。如果曝光时间设得太大,VMAX 会自动拉长,帧率就掉下来了。
解决:查cmos_ex.h里的u32Vmax和u32Hmax。5M@30fps 的典型值是 VMAX=0x0A20、HMAX=0x04B0。如果 VMAX 被改大了,帧率必然掉。另外确认u32LinesPer500ms的值,这个值不对会导致曝光换算错误,间接影响帧率。
5.4 现象:编译通过但 insmod 时报 undefined symbol
原因:libsns_imx335.a里引用了海思 ISP 的符号,但链接顺序不对,或者hi3559_isp.ko的版本和 sensor 库不匹配。
解决:先确认hi3559_isp.ko和libsns_imx335.a是同一个 SDK 版本编出来的。如果 SDK 升级过,sensor 库必须重新编。另外在 Makefile 里把-lhi_isp放在-lsns_imx335后面,链接器从左到右解析符号,顺序反了就会报 undefined。
5.5 现象:替换驱动后板子启动卡死
原因:sensor 列表里加了 IMX335,但板子上实际没接 IMX335,ISP 初始化时一直等 sensor 响应,导致启动阻塞。
解决:在sensor_list.c里加一个条件编译或者运行时检测,只有检测到 IMX335 的 ID 才注册。常见做法是在sensor_register函数里先读一次 I2C,读不到就返回错误,让 ISP 跳过这个 sensor。海思的sensor_list机制支持这种「探测失败则跳过」的逻辑,但需要你在cmos.c的pfn_cmos_init里返回非零值。
6. 进阶:用 sensor_cfg 工具生成自定义分辨率寄存器表
6.1 为什么需要自己生成寄存器表
资源包里的寄存器表通常只覆盖 5M@30fps 和 1080P@30fps 两个模式。如果你的项目需要 3M@60fps 或者 4M@25fps,就得自己生成。Sony 的 datasheet 有几百页,手动算 PLL 和时序不现实。海思 SDK 里自带sensor_cfg工具,可以输入分辨率、帧率、MIPI 时钟等参数,自动生成寄存器序列。
6.2 sensor_cfg 的输入参数与输出格式
工具一般在$(SDK_DIR)/mpp/tools/sensor_cfg/下,跑起来是一个交互式命令行:
./sensor_cfg # 选择 sensor 类型:IMX335 # 输入分辨率:2560x1440 # 输入帧率:60 # 输入 MIPI 时钟:594 # 输入 Bayer 顺序:RGGB输出是一个.h文件,里面包含imx335_3m_60fps_regs[]数组和对应的stSnsImageMode结构体。把这个文件替换掉原来的imx335_cmos_ex.h,重新编译即可。
6.3 验证新寄存器表的三个步骤
第一步,用 I2C 逐条对比:把新生成的寄存器序列和 datasheet 里的推荐值对一遍,重点看 PLL 和 MIPI 相关的寄存器。第二步,跑sample_vi看帧率是否达到预期,如果只有一半,检查 VMAX 是不是被工具算大了。第三步,用hi_isp的调试工具看 ISP 的输入统计,确认 Bayer 顺序和位宽(RAW10 还是 RAW12)匹配。
注意:sensor_cfg 生成的寄存器表不一定完全准确,尤其是 MIPI 时钟的计算。我一般会拿示波器实测 MIPI CLK 频率,和工具算出来的值对比,偏差超过 5% 就手动调 PLL 寄存器。
6.4 一个实际踩过的坑
有一次我用 sensor_cfg 生成 4M@25fps 的寄存器表,跑起来帧率只有 12.5fps。查了半天发现是工具的 VMAX 计算逻辑有 bug——它把 VMAX 算成了两倍。后来我手动把 VMAX 除以 2,帧率就正常了。从那以后我每次用工具生成寄存器表,都会先跑一遍sample_vi看帧率,再拿示波器量 XVS 的周期,两个数据对上了才敢往产品里合。希望帮到你。
本文还有配套的精品资源,点击获取