【sensor】新增sensor如何修改(海思)
目录
换 Sensor 实战全流程
总览:哪些能复用,哪些必须重写
第1步:创建新目录和文件
第2步:改 cmos.h — 全是宏定义,查手册填写
第3步:改 sensor_ctl.c — 完全重写初始化序列
3.1 I2C 读写函数 — 改个 I2C 地址就行
3.2 初始化序列 — 从 FAE 拿到
3.3 standby/restart — 改寄存器地址
第4步:改 cmos.c — 最核心,改得最多
4.1 寄存器地址 — 查手册
4.2 模式表 — 填入新值
4.3 AE 参数 — 逐项计算(最关键)
4.4 Again 映射表 — 最容易出错
4.5 Black Level — 从手册查
4.6 AWB 参数 — 需要校准
4.7 导出对象 — 改名字
第5步:改 cmos_ex.h — ISP 调优参数
第6步:改 Makefile
第7步:应用层对接 — 让系统加载新驱动
实战排坑清单
总结:信息获取渠道
二:MIPI 配置
为什么不在 sensor 驱动里
你项目里的 MIPI 配置
层次1:ext_data_type — MIPI 扩展数据类型
层次2:combo_dev_attr — SOC 端 MIPI 接收器配置
层次3:vi_dev_attr — SOC 端 VI 接口配置
MIPI 配置的关键参数解读
换 sensor 时 MIPI 配置怎么改
配错 MIPI 的典型症状
换 Sensor 实战全流程
假设你要把 IMX989 换成IMX662(举个例子),我从零开始讲。
总览:哪些能复用,哪些必须重写
┌───────────────────────────────────────────────────────────┐
│ 换 Sensor 改动量评估 │
│ │
│ 直接复用(不改或微调) 必须重写(依赖新sensor) │
│ ────────────────────── ────────────────────────── │
│ cmos.c 的回调框架结构 cmos.h 的宏定义 │
│ cmos.c 的全局变量声明 sensor_ctl.c 的初始化序列 │
│ sensor_register_callback() AE gain 映射表 │
│ sensor_unregister_callback() AE 参数(VMAX/again/dgain) │
│ regs_info 双缓冲机制 AWB Planck 曲线参数 │
│ ISP 框架调用流程 Black Level 值 │
│ Makefile(微调名字) 寄存器地址定义 │
│ cmos_ex.h 的 ISP 模块结构 DNG/CFA 参数 │
│ ────────────────────── ────────────────────────── │
│ 约 40% 代码可复用 约 60% 需要重写 │
└───────────────────────────────────────────────────────────┘
第1步:创建新目录和文件
SDK路径下:
.../sensor/hi3519dv500/
├── modules989/ ← 旧的 IMX989 驱动
└── imx662/ ← 新建目录
├── imx662_cmos.h
├── imx662_cmos.c
├── imx662_cmos_ex.h
├── imx662_sensor_ctl.c
└── Makefile
实战建议:复制 modules989 整个目录,然后全局替换名字,再逐个文件改内容。
第2步:改cmos.h— 全是宏定义,查手册填写
// ========== 旧(IMX989)→ 新(IMX662)对照 ========== // I2C 地址:查新 sensor 手册的 Slave Address #define IMX662_I2C_ADDR 0x1A // IMX989 是 0x34,IMX662 不同 // 地址/数据字节:大多数 Sony sensor 都是 2字节地址+1字节数据 #define IMX662_ADDR_BYTE 2 // 通常不变 #define IMX662_DATA_BYTE 1 // 通常不变 // 最大帧长:查手册 VMAX 的位宽 #define IMX662_FULL_LINES_MAX 0xFFFFF // 20bit,通常不变 // VMAX 值:查手册每种模式的推荐 VMAX #define IMX662_VMAX_1920X1080P30_LINEAR (1125) // 从手册查 #define IMX662_VMAX_1920X1080P60_LINEAR (1125) // 从手册查 // 分辨率:查手册的有效像素阵列 #define SENSOR_IMX662_WIDTH 1920 // IMX662 是 1080P sensor #define SENSOR_IMX662_HEIGHT 1080 // 模式枚举:根据新 sensor 支持的模式定义 typedef enum { IMX662_SENSOR_1920X1080_30FPS_LINEAR_MODE, IMX662_SENSOR_1920X1080_60FPS_LINEAR_MODE, IMX662_MODE_BUTT } imx662_res_mode; // 模式表结构:完全复用,不需要改 typedef struct { td_u32 ver_lines; td_u32 max_ver_lines; td_float max_fps; td_float min_fps; td_u32 width; td_u32 height; td_u8 sns_mode; ot_wdr_mode wdr_mode; const char *mode_name; } imx662_video_mode_tbl;这是最直接的文件,每一行都要改,因为全是 sensor 硬件参数。
关键数据来源:
| 参数 | 查手册位置 |
|---|---|
| I2C 地址 | "Serial Communication Interface" 章节 |
| VMAX 值 | "Readout Mode" 章节,每种模式的 Frame Length |
| 分辨率 | "Image Size" 章节 |
| FULL_LINES_MAX | "Frame Length" 寄存器的位宽 |
第3步:改sensor_ctl.c— 完全重写初始化序列
这是工作量最大的文件。初始化序列必须从 Sony FAE 拿到,不能自己写。
3.1 I2C 读写函数 — 改个 I2C 地址就行
// 这部分逻辑完全一样,只改宏名和 I2C 地址
td_s32 imx662_write_register(ot_vi_pipe vi_pipe, td_u32 addr, td_u32 data) {
i2c_data.dev_addr = IMX662_I2C_ADDR; // 只改这里
i2c_data.reg_addr = addr;
// ... 其余不变
}
3.2 初始化序列 — 从 FAE 拿到
// FAFA 提供的通常是这样一个表(可能叫 "IMX662_Register_Set_*.xlsx"):
//
// Address | Value | Description
// --------|--------|------------
// 0x3000 | 0x01 | STANDBY
// 0x3002 | 0x00 | XMSTA
// 0x3004 | 0x01 | XVSIE
// 0x0112 | 0x0A | ADC bit depth = 10bit
// 0x0340 | 0x04 | VMAX[15:8]
// 0x0341 | 0x65 | VMAX[7:0] = 1125
// ... | ... | ...
// 0x0100 | 0x01 | MODE_SEL = streaming
// 把这个表翻译成代码:
static td_s32 imx662_linear_1080p30_10bit_init_part1(ot_vi_pipe vi_pipe) {
td_s32 ret = TD_SUCCESS;
ret += imx662_write_register(vi_pipe, 0x3000, 0x01);
ret += imx662_write_register(vi_pipe, 0x3002, 0x00);
ret += imx662_write_register(vi_pipe, 0x0112, 0x0A);
ret += imx662_write_register(vi_pipe, 0x0340, 0x04);
ret += imx662_write_register(vi_pipe, 0x0341, 0x65);
// ... 几百行 ...
return ret;
}
实操经验:
FAE 给的寄存器表可能有 200-500 行
不要试图理解每一行——很多是模拟电路偏置、时序微调
你只需要识别出关键寄存器:
0x0100 → MODE_SEL(streaming 控制)
0x0112/0x0113 → 位深
0x0340/0x0341 → VMAX
0x0202/0x0203 → CIT(曝光时间)
0x0204/0x0205 → AGain
0x020E/0x020F → DGain
其余全部按表照抄
3.3 standby/restart — 改寄存器地址
void imx662_standby(ot_vi_pipe vi_pipe) { imx662_write_register(vi_pipe, 0x0100, 0x00); // 大多数 Sony sensor 一样 g_standby[vi_pipe] = TD_TRUE; } void imx662_restart(ot_vi_pipe vi_pipe) { imx662_write_register(vi_pipe, 0x0100, 0x01); g_standby[vi_pipe] = TD_FALSE; }第4步:改cmos.c— 最核心,改得最多
4.1 寄存器地址 — 查手册
// 不同 sensor 曝光/增益寄存器地址不同! // 必须查新 sensor 的手册 // IMX989: // IMX662(示例,需查手册确认): #define CIT_H 0x0202 #define CIT_H 0x0202 // Sony 系列通常一样 #define CIT_L 0x0203 #define CIT_L 0x0203 #define AGAIN_H 0x0204 #define AGAIN_H 0x0204 // Sony 系列通常一样 #define AGAIN_L 0x0205 #define AGAIN_L 0x0205 #define DGAIN_H 0x020E #define DGAIN_H 0x020E // Sony 系列通常一样 #define DGAIN_L 0x020F #define DGAIN_L 0x020F #define VMAX_H 0x0340 #define VMAX_H 0x0340 #define VMAX_L 0x0341 #define VMAX_L 0x0341Sony 系列的寄存器地址高度统一,IMX989/IMX662/IMX678/IMX585 大部分共用。但不能想当然,必须确认。
4.2 模式表 — 填入新值
const imx662_video_mode_tbl g_imx662_mode_tbl[IMX662_MODE_BUTT] = { {1125, 0xFFFFF, 30, 0.5, 1920, 1080, 0, OT_WDR_MODE_NONE, "IMX662_1920X1080_30FPS_LINEAR"}, {1125, 0xFFFFF, 60, 0.5, 1920, 1080, 1, OT_WDR_MODE_NONE, "IMX662_1920X1080_60FPS_LINEAR"}, };4.3 AE 参数 — 逐项计算(最关键)
static td_void cmos_get_ae_comm_default(...) { // ① full_lines_std:从模式表取 ae_sns_dft->full_lines_std = sns_state->fl_std; // 不用改 // ② flicker_freq:看地区,中国/欧洲=50Hz,美国/日本=60Hz ae_sns_dft->flicker_freq = 50 * 256; // 不用改 // ③ hmax_times:必须重新算 // 公式:1e9 / (VMAX × fps) ae_sns_dft->hmax_times = (1000000000) / (1125 * 30); // ≈ 29630 ns // ④ again_accu:查手册 AGain 步进精度 // Sony sensor 通常 1/256 步进 = 0.00390625 ae_sns_dft->again_accu.accu_type = OT_ISP_AE_ACCURACY_TABLE; ae_sns_dft->again_accu.accuracy = 0.00390625; // 通常不变 // ⑤ lines_per500ms:重新算 ae_sns_dft->lines_per500ms = 1125 * 30 / 2; // = 16875 // ... 其余参数逐项填入 } static td_void cmos_get_ae_linear_default(...) { // ⑥ max_int_time:重新算 ae_sns_dft->max_int_time = ((1125 - 48) >> 3) << 3; // = 1072 // 注意:48 是 IMX989 的值,IMX662 可能不同!查手册 "CIT limit" // ⑦ AGain 范围:查手册 ae_sns_dft->max_again = 65535; // 查手册 "Analog Gain Range" ae_sns_dft->min_again = 1024; // ⑧ DGain 范围:查手册 ae_sns_dft->max_dgain = 65535; ae_sns_dft->min_dgain = 256; // ⑨ init_exposure:先用默认值跑起来,再调 ae_sns_dft->init_exposure = 30000; // 先给一个合理的估计值 // ⑩ offset:查手册 FINE_INTEG_TIME ae_sns_dft->int_time_accu.offset = (td_float)(FINE_TIME - 64000) / LINE_LENGTH; // 查手册 0x200/0x201 和 0x342/0x343 的默认值 }4.4 Again 映射表 — 最容易出错
不同 sensor 的 AGain 映射公式完全不同!
IMX989(Sony 1.x 系列):
again_db = 16384 - 16384 × 1024 / again_lin
公式来源:Sony 提供的 "Gain Conversion" 应用笔记
IMX662(可能不同):
可能是线性映射:again_db = again_lin × 某系数
也可能是分段映射
必须从 Sony FAE 拿到转换公式!
如果你用错了公式,AE 会算错增益,画面要么过曝要么过暗,而且 AE 永远收敛不了。
4.5 Black Level — 从手册查
#define IMX662_BLACK_LEVEL 1024 // 10bit: 1024, 12bit: 4096, 查手册 "Optical Black Level"4.6 AWB 参数 — 需要校准
// Planck 曲线参数:必须用新 sensor + 实际镜头在标准光源下拍摄校准 // 不能直接抄! awb_sns_dft->p1 = ???; // 需要校准 awb_sns_dft->p2 = ???; // 需要校准 awb_sns_dft->q1 = ???; // 需要校准 awb_sns_dft->a1 = ???; // 需要校准 // ...校准方法:用标准光源箱(D65/A/CWF/TL84),每个光源下拍一张 RAW,计算 R/G/B 比值,拟合 Planck 曲线。这通常是 IQ 团队的工作,但你至少要知道这些值不是拍脑袋定的。
4.7 导出对象 — 改名字
ot_isp_sns_obj g_sns_imx662_obj = { .pfn_register_callback = sensor_register_callback, .pfn_un_register_callback = sensor_unregister_callback, .pfn_standby = imx662_standby, .pfn_restart = imx662_restart, .pfn_mirror_flip = TD_NULL, .pfn_set_blc_clamp = TD_NULL, .pfn_write_reg = imx662_write_register, .pfn_read_reg = imx662_read_register, .pfn_set_bus_info = imx662_set_bus_info, .pfn_set_init = sensor_set_init };第5步:改cmos_ex.h— ISP 调优参数
这是工作量最大但优先级最低的文件。可以先用"保底值"跑起来,后面慢慢调。
实操策略:
1. 先把旧 sensor 的 cmos_ex.h 整个复制过来
2. 只改确定不同的参数(Black Level、DPC 阈值)
3. 编译、跑通、确认 AE/AWB 基本正常
4. 后续再逐模块做 IQ 调优
cmos_ex.h 各模块的优先级:
| 优先级 | 模块 | 原因 |
|---|---|---|
| 🔴 必须改 | BLC (Black Level) | 值不对 → 全画面偏色 |
| 🔴 必须改 | DPC (坏点校正) | 不同 sensor 坏点特征不同 |
| 🟡 建议改 | CCM (色彩矩阵) | 不同 sensor 光谱响应不同 |
| 🟡 建议改 | Noise Calibration | 不同 sensor 噪声特性不同 |
| 🟢 可延后 | Gamma, Sharpen, NR | 通用性较强,不影响功能 |
| 🟢 可延后 | DRC, Dehaze, LDCI | 跟场景关系大,跟 sensor 关系小 |
第6步:改 Makefile
# 只需要改输出名和源文件名 OUTPUT_NAME = libsns_imx662 # 源文件列表 SRCS := imx662_cmos.c imx662_sensor_ctl.c # 其余路径、编译选项完全复用第7步:应用层对接 — 让系统加载新驱动
你的应用层代码(不在 sensor 驱动目录里):
// 旧: extern ot_isp_sns_obj g_sns_modules989_obj; ot_mpi_isp_sensor_reg_callback(vi_pipe, &g_sns_modules989_obj); // 新: extern ot_isp_sns_obj g_sns_imx662_obj; ot_mpi_isp_sensor_reg_callback(vi_pipe, &g_sns_imx662_obj);实战排坑清单
按时间顺序,你一定会遇到这些问题:
Day 1:编译通过,但黑屏 ├─ 检查 I2C 地址是否正确(用 i2cdetect 扫一下) ├─ 检查 MIPI 配置(lane 数、速率、continuous clock) ├─ 检查 sensor 是否正确初始化(读 0x0000/0x0001 确认通信) └─ 检查 VMAX/位深是否匹配 ISP 配置 Day 2:有图像但严重偏色 ├─ Black Level 值不对 → 先确认 sensor 输出的 OB 值 ├─ CFA Pattern 配反了 → RGGB vs GRBG vs GBRG vs BGGR └─ AWB 参数全错 → 先关掉 AWB,用固定增益看原始色彩 Day 3:图像亮度不对/闪烁 ├─ Again 映射公式错误 → 打印 AE 算出的 again 和实际写入的寄存器值 ├─ init_exposure 不合理 → 手动设一个已知正确的值 └─ offset 不对 → 导致曝光时间跳变 Day 4:帧率不对 ├─ VMAX 值不匹配 → 检查模式表和 sensor 实际输出 ├─ MIPI 速率不匹配 → sensor 和 SOC 的 MIPI 配置要一致 └─ pixel clock 计算错误 → 影响 hmax_times Day 5+:进入 IQ 调优阶段 ├─ 噪声水平 → 校准 noise_calibration 表 ├─ 色彩准确度 → 校准 CCM ├─ 动态范围 → 调 DRC/Gamma └─ 细节表现 → 调 Sharpen/Demosaic总结:信息获取渠道
| 你需要的信息 | 去哪里找 |
|---|---|
| 寄存器初始化序列 | Sony FAE 提供(最关键!) |
| 增益转换公式 | Sony "Gain Conversion Note" |
| VMAX / 帧长限制 | Sensor Datasheet "Readout Mode" |
| 曝光时间范围 | Sensor Datasheet "Integration Time" |
| Black Level 值 | Sensor Datasheet "Optical Black" |
| CFA Pattern | Sensor Datasheet "Color Filter Array" |
| I2C 地址 | Sensor Datasheet "Serial Interface" |
| MIPI 配置 | Sensor Datasheet "MIPI CSI-2" |
| AWB 校准参数 | 自行校准(标准光源箱) |
| ISP 调优参数 | 自行调优(IQ 工具) |
一个 sensor 驱动工程师的核心能力 = 从 FAE 和手册获取正确信息 → 准确填入框架 → 快速排错验证。
二:MIPI 配置
MIPI 配置不在 sensor 驱动目录里,而是在应用层的sample_comm_vi.c中。这是很多人找不到的地方。
为什么不在 sensor 驱动里
sensor 驱动(modules989/)负责:
→ 通过 I2C 控制 sensor 内部寄存器
→ 提供回调给 ISP/AE/AWB
MIPI 配置(sample_comm_vi.c)负责:
→ 配置 SOC 这端的 MIPI CSI-2 接收器
→ 不是配置 sensor 的 MIPI 发送器!
它们是两端:
sensor 端:MIPI 发送器(由初始化序列里的寄存器配置,已经写死在 sensor_ctl.c)
SOC 端:MIPI 接收器(由 sample_comm_vi.c 配置)
你项目里的 MIPI 配置
在sample_comm_vi.c中,IMX989 相关的 MIPI 配置涉及三个层次:
层次1:ext_data_type — MIPI 扩展数据类型
static ext_data_type_t g_mipi_ext_data_type_imx989_10bit_8m_nowdr_attr = { .devno = 0, // MIPI 设备号 .num = MIPI_NUM, // 通道数 .ext_data_bit_width = {10, 10, 10}, // 10bit RAW 数据 .ext_data_type = {0x30, 0x30, 0x30} // MIPI CSI-2 Data Type };0x30是什么:
MIPI CSI-2 规范定义的 Data Type 编码:
0x2b = RAW10 (10bit RAW)
0x2c = RAW12 (12bit RAW)
0x2d = RAW14 (14bit RAW)
0x30 = ???
0x30 在标准 CSI-2 规范里其实是 "User Defined Data Type 8"
但海思框架用 0x30 表示一种自定义打包格式(可能是压缩 RAW 或嵌入式数据)
层次2:combo_dev_attr — SOC 端 MIPI 接收器配置
IMX989 没有单独的 combo_dev_attr,它使用了default 分支(第779行):
default:
(td_void)memcpy_s(combo_attr, sizeof(combo_dev_attr_t),
&g_mipi_4lane_chn0_sensor_os08a20_12bit_8m_nowdr_attr, sizeof(combo_dev_attr_t));
也就是说 IMX989复用了 OS08A20 的 MIPI combo 配置:
static combo_dev_attr_t g_mipi_4lane_chn0_sensor_os08a20_12bit_8m_nowdr_attr = { .devno = 0, // MIPI device 0 .input_mode = INPUT_MODE_MIPI, // MIPI 输入模式 .data_rate = MIPI_DATA_RATE_X1, // 单速率(非双速率) .img_rect = {0, 0, WIDTH_3840, HEIGHT_2160}, // 图像窗口 .mipi_attr = { DATA_TYPE_RAW_12BIT, // 这里写的是12bit! OT_MIPI_WDR_MODE_NONE, // 无WDR {0, 1, 2, 3, -1, -1, -1, -1} // 4 lane: Lane0~3 } };层次3:vi_dev_attr — SOC 端 VI 接口配置
static ot_vi_dev_attr g_mipi_raw_dev_attr = { .intf_mode = OT_VI_INTF_MODE_MIPI, // MIPI 接口模式 .work_mode = OT_VI_WORK_MODE_MULTIPLEX_1, // 1路复用 .component_mask = {0xfff00000, 0x00000000}, // 12bit掩码(高12位有效) .scan_mode = OT_VI_SCAN_PROGRESSIVE, // 逐行扫描 .data_type = OT_VI_DATA_TYPE_RAW, // RAW数据输入 .in_size = {WIDTH_3840, HEIGHT_2160}, // 输入尺寸 .data_rate = OT_DATA_RATE_X1, // 数据速率 };MIPI 配置的关键参数解读
换 sensor 时,这些参数必须匹配新 sensor:
┌──────────────────────────────────────────────────────────────────┐ │ combo_dev_attr_t 各字段含义 │ │ │ │ input_mode: │ │ INPUT_MODE_MIPI ← 正常 MIPI CSI-2 │ │ INPUT_MODE_SDI ← SDI(Broadcast) │ │ INPUT_MODE_BT656 ← 并行接口 │ │ │ │ data_rate: │ │ MIPI_DATA_RATE_X1 ← 单速率(每lane 1像素周期传1样本) │ │ MIPI_DATA_RATE_X2 ← 双速率(DDR,每lane传2样本/周期) │ │ │ │ img_rect: │ │ {x, y, width, height} ← SOC 端裁剪窗口 │ │ 必须和 sensor 输出尺寸一致 │ │ │ │ mipi_attr: │ │ DATA_TYPE_RAW_10BIT / 12BIT / 14BIT ← 必须匹配 sensor 输出 │ │ OT_MIPI_WDR_MODE_NONE ← 无 WDR │ │ OT_MIPI_WDR_MODE_VC ← VC(Virtual Channel)WDR │ │ lane_id[] ← MIPI lane 映射表 │ │ {0,1,2,3,-1,-1,-1,-1} = 使用 4 lane,Lane编号 0~3 │ │ {0,1,-1,-1,-1,-1,-1,-1} = 使用 2 lane │ │ -1 = 该位置不使用 │ └─────────────────────────────────────────换 sensor 时 MIPI 配置怎么改
典型步骤: 1. 查新 sensor 手册的 MIPI CSI-2 章节 → lane 数(2-lane / 4-lane) → 输出位深(10bit / 12bit / 14bit) → 数据速率模式(X1 还是 X2) → continuous clock 是否开启 2. 在 sample_comm_vi.c 里新增一个 combo_dev_attr_t: static combo_dev_attr_t g_mipi_2lane_chn0_sensor_imx662_10bit_1080p_nowdr_attr = { .devno = 0, .input_mode = INPUT_MODE_MIPI, .data_rate = MIPI_DATA_RATE_X1, .img_rect = {0, 0, WIDTH_1920, HEIGHT_1080}, // 改分辨率 .mipi_attr = { DATA_TYPE_RAW_10BIT, // 改位深 OT_MIPI_WDR_MODE_NONE, {0, 1, -1, -1, -1, -1, -1, -1} // 改lane数(2 lane) } }; 3. 在 switch(sns_type) 里加 case 分支 4. ext_data_type 也要加对应的 5. vi_dev_attr 可能需要改 in_size 和 component_mask配错 MIPI 的典型症状
症状 可能原因
───────────────────────────────────────────────────────
黑屏/无数据 lane 映射错误或位深不匹配
画面花屏/条纹 MIPI 速率不匹配
图像偏移/裁切不对 img_rect 设置错误
偶发性丢帧 data_rate 模式错误
MIPI LP/HP 错误日志 sensor 端的 MIPI 连续时钟配置与 SOC 端不一致
MIPI 配置是最容易踩坑的地方——sensor 端和 SOC 端必须完全对称,任何一点不匹配都会导致黑屏。而且黑屏时几乎没有任何调试信息,只能通过 SOC 的 MIPI 错误计数器日志来排查。
