目录
[方向一:Sensor 上电时序](#方向一:Sensor 上电时序)
[IMX989 的完整启动流程](#IMX989 的完整启动流程)
[Sensor 硬件层面(你看不到的部分)](#Sensor 硬件层面(你看不到的部分))
[IMX989 的 Standby/Restart](#IMX989 的 Standby/Restart)
[方向二、ADDR_BYTE 和 DATA_BYTE 的含义](#方向二、ADDR_BYTE 和 DATA_BYTE 的含义)
[为什么 sensor 驱动工程师要懂噪声](#为什么 sensor 驱动工程师要懂噪声)
[1. 光子散粒噪声(Photon Shot Noise)](#1. 光子散粒噪声(Photon Shot Noise))
[2. 读出噪声(Read Noise)](#2. 读出噪声(Read Noise))
[3. 暗电流噪声(Dark Current Noise)](#3. 暗电流噪声(Dark Current Noise))
[ISP 降噪如何利用噪声特性](#ISP 降噪如何利用噪声特性)
方向一:Sensor 上电时序
IMX989 的完整启动流程
从代码中提取的完整时序:
cs
时间线 →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→
Step 1: SOC 端 MIPI 时钟使能
OT_MIPI_ENABLE_MIPI_CLOCK
↓
Step 2: SOC 端 MIPI RX 复位
OT_MIPI_RESET_MIPI
↓
Step 3: SOC 端配置 combo_dev_attr + ext_data_type
↓
Step 4: SOC 端 MIPI RX 解复位(准备接收数据)
OT_MIPI_UNRESET_MIPI
↓
Step 5: Sensor 端上电(sample_comm_vi_start_sensor)
│
├── 5a: 使能 Sensor 时钟(MCLK)
│ OT_MIPI_ENABLE_SENSOR_CLOCK
│
├── 5b: 复位 Sensor(拉低 RESET 引脚)
│ OT_MIPI_RESET_SENSOR
│
└── 5c: 解复位 Sensor(拉高 RESET 引脚)
OT_MIPI_UNRESET_SENSOR
↓
Step 6: ISP 框架调用 modules989_init()
│
├── 6a: 初始化 I2C(open /dev/i2c-X)
│
├── 6b: 写初始化序列(imx989_common_init + 模式特定序列)
│ → 几百个寄存器写入
│
├── 6c: 写运行时寄存器(CIT/GAIN/DGAIN/VMAX)
│ modules989_default_reg_init()
│
├── 6d: 等待 10ms
│ delay_ms(10)
│
└── 6e: 启动 Sensor 出流
modules989_restart() → 写 0x0100 = 0x01
↓
Step 7: MIPI 开始传输数据 → ISP 开始处理 → 画面出现
为什么顺序这么重要
cs
如果先启动 Sensor 再配置 SOC 端:
→ Sensor 已经开始发 MIPI 数据了
→ SOC 端 MIPI RX 还没准备好
→ 数据丢失,前几帧花屏
如果 MCLK 还没稳定就复位解复位:
→ Sensor 内部 PLL 没锁上
→ 寄存器写入可能失败
→ Sensor 不响应 I2C
如果 0x0100 = 0x01 写太早(寄存器还没配完):
→ Sensor 用默认参数出流
→ 帧率/分辨率可能不对
Sensor 硬件层面(你看不到的部分)
cs
PCB 上电时(硬件工程师负责):
│
├── VDD_IO(I/O 供电)= 1.8V ← 最先上
├── VDD_CORE(核心供电)= 1.1V
├── VDD_ANALOG(模拟供电)= 2.8V
├── AVDD(MIPI 供电)= 1.2V
│
└── 所有电源稳定后 → 等待 MCLK
软件接管后(上面的 Step 5 开始):
│
├── 给 MCLK(通常 24MHz 或 37.125MHz)
├── Sensor 内部 PLL 锁定 → 产生内部时钟
│ 内部时钟 = MCLK × PLL 倍频
│ 比如 24MHz × 62.5 = 1500MHz(MIPI 速率)
│
├── RESET 拉高 → Sensor 开始执行内部初始化
│
└── I2C 可用(通常 MCLK 稳定后 1~10ms)
IMX989 的 Standby/Restart
cs
// 暂停出流(不关闭电源)
modules989_standby() → 写 0x0100 = 0x00 // STANDBY 模式
// 恢复出流
modules989_restart() → 写 0x0100 = 0x01 // 恢复出流
这是最轻量的"开关"方式,不需要重新写整个初始化序列。
方向二、ADDR_BYTE 和 DATA_BYTE 的含义
不同的 sensor 有不同的地址/数据宽度组合:
cs
┌──────────┬──────────┬─────────────┬─────────────────────┐
│ Sensor │ADDR_BYTE │ DATA_BYTE │ 一次传输的字节数 │
├──────────┼──────────┼─────────────┼─────────────────────┤
│ IMX989 │ 2 │ 1 │ [AH][AL][D] │
│ OV 系列 │ 2 │ 1 │ [AH][AL][D] │
│ SC 系列 │ 2 │ 1 │ [AH][AL][D] │
│ 某些老 sensor│ 1 │ 1 │ [A][D] │
│ 某些 16bit │ 2 │ 2 │ [AH][AL][DH][DL] │
└──────────┴──────────┴─────────────┴─────────────────────┘
IMX989:
#define MODULES989_ADDR_BYTE 2 ← 寄存器地址是 16bit(2 字节)
#define MODULES989_DATA_BYTE 1 ← 每个寄存器值是 8bit(1 字节)
如果配错了:
cs
ADDR_BYTE 写成 1(应该是 2):
→ 只发了低字节地址
→ sensor 收到错误的寄存器地址
→ 写到了别的地方 → 可能花屏或死机
DATA_BYTE 写成 2(应该是 1):
→ 多发了一个字节
→ sensor 把第二个字节当成下一条命令的地址
→ 后续通信全部错乱
方向三、噪声特性
为什么 sensor 驱动工程师要懂噪声
你以为的画面:
理想:每个像素值 = 真实光照强度
实际的画面:
每个像素值 = 真实光照 + 各种噪声
噪声决定了:
· 画面干不干净
· ISP 降噪模块的参数怎么调
· 增益上限设多少合适
三种主要噪声
1. 光子散粒噪声(Photon Shot Noise)
物理本质:光子到达是随机事件(泊松分布)
即使光照完全均匀,每个像素收到的光子数也不一样:
期望收到 100 个光子
实际可能收到:93, 107, 98, 102, 95, 110...
噪声 = √信号 = √100 = 10
信噪比 SNR = 信号/噪声 = 100/10 = 10
信号越大 → 绝对噪声越大 → 但信噪比越好:
弱光(100 个光子):噪声 = 10,SNR = 10 → 画面有噪点
强光(10000 个光子):噪声 = 100,SNR = 100 → 画面干净
这就是为什么暗光下噪点多的根本原因!
这个噪声无法消除------它是光的物理特性,不是 sensor 的问题。
2. 读出噪声(Read Noise)
来源:sensor 内部的电路噪声
像素 → 放大器 → ADC → 输出
每一步电路都会引入噪声:
· 热噪声(温度越高越大)
· 1/f 噪声(低频噪声)
· 量化噪声(ADC 精度有限)
即使完全没有光(盖上镜头盖),像素值也不是 0:
→ 而是在某个值附近随机抖动
用代码中的数据来看:
#define MODULES989_BLACK_LEVEL 1032 // OB(Optical Black)值
cs
OB 值的含义:
盖上镜头盖(完全没有光)时,像素值 ≈ 1032
但每个像素不是精确的 1032:
像素1: 1030
像素2: 1035
像素3: 1028
像素4: 1034
...
平均值 ≈ 1032(这就是 Black Level)
标准差 ≈ 2~5(这就是读出噪声的大小)
ISP 的 BLC(Black Level Correction)模块就是减去这个值:
cs
原始像素值 = 1032 + 信号 + 噪声
BLC 后 = 0 + 信号 + 噪声 ← 去掉了直流偏置
3. 暗电流噪声(Dark Current Noise)
cs
来源:即使没有光,像素也会因为热效应产生电子
温度每升高 6~7°C → 暗电流翻倍
室温(25°C):暗电流很小,可忽略
高温(60°C):暗电流显著增大,画面出现"热噪点"
长时间曝光时更明显:
· 曝光 1ms:暗电流几乎看不到
· 曝光 1s:暗电流积累很多,画面有明显的亮点
噪声和增益的关系
cs
增益放大信号的同时,也放大了噪声!
原始信号:S = 100,噪声 N = 10(散粒)+ 3(读出)= 13
SNR = 100/13 ≈ 7.7
增益 2x 后:
信号 = 200,噪声 = 26
SNR = 200/26 ≈ 7.7 ← SNR 不变!
但!如果是在暗处,原始信号只有 10:
原始:S = 10,N = 3.16(散粒)+ 3(读出)= 6.16
SNR = 10/6.16 ≈ 1.6 ← 已经很差了
增益 16x 后:
信号 = 160,噪声 = 98.6
SNR = 160/98.6 ≈ 1.6 ← 依然很差
→ 画面看起来就是"一片噪点"
这就是为什么高增益 = 高噪点,而且降噪怎么都救不回来。
噪声特性曲线
每个 sensor 都有一条这样的特性曲线:
cs
噪声
↑
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱╱
│ ╱╱
│ ╱╱
│ ╱╱
│ ╱╱
│ ╱╱╱╱ ← 噪声 = √(散粒² + 读出² + 暗电流²)
│ ╱╱╱╱ 高增益区域噪声急剧上升
│╱╱╱╱
└──────────────────────────────────→ 增益(ISO)
1x 2x 4x 8x 16x 32x 64x
IMX989 的三个模式在这条曲线上的位置不同:
cs
线性模式(10bit):
· 增益范围 1x ~ 64x
· 高增益时噪声明显,但能用
DCG 12bit(HCG:LCG = 4:1):
· HCG 路径天生灵敏度高 4 倍
· 相当于"起点就高",同样场景需要的增益更小
· 所以暗光下噪点更少
· 有效动态范围 ≈ 线性 + 12dB
DCG 14bit(HCG:LCG = 16:1):
· HCG 灵敏度高 16 倍
· 暗光性能最好
· 但 VMAX 更小(2776),最大曝光时间更短
· 亮处可能过曝
ISP 降噪如何利用噪声特性
你的代码里有一个关键参数:
isp_def->noise_calibration = &g_cmos_noise_calibration;
cs
Noise Calibration(噪声标定):
告诉 ISP 在不同 ISO(增益)下,噪声有多大
┌──────────────────────────────────┐
│ ISO 100: 噪声 = 2.0 │
│ ISO 200: 噪声 = 2.8 │
│ ISO 400: 噪声 = 4.0 │
│ ISO 800: 噪声 = 5.7 │
│ ISO 1600: 噪声 = 8.0 │
│ ISO 3200: 噪声 = 11.3 │
└──────────────────────────────────┘
ISP 的 Bayer NR 和 3DNR 模块根据这个表决定:
· 降噪力度多大
· 降噪力度太小 → 噪点残留
· 降噪力度太大 → 细节丢失(涂抹感)
如果噪声标定不准:
cs
标定值偏低(实际噪声 8,你告诉 ISP 是 4):
→ ISP 降噪力度不够
→ 画面噪点残留
→ 用户觉得"画面不干净"
标定值偏高(实际噪声 4,你告诉 ISP 是 8):
→ ISP 降噪过度
→ 画面细节被抹掉
→ 用户觉得"画面像水彩画"
噪声标定怎么做
cs
标准方法(需要实验室环境):
1. 盖上镜头盖(全黑环境)
2. 拍 100 帧 RAW 图
3. 对每个像素计算标准差 → 这就是读出噪声
4. 在不同光照下重复 → 得到散粒噪声曲线
5. 在不同增益下重复 → 得到完整的噪声特性
实际操作:
· Sony FAE 通常会提供一份噪声标定数据
· 你也可以用 IQ 工具自己标定
· 标定结果写入 g_cmos_noise_calibration
你可以做的验证实验
# 在设备上 dump 一帧 RAW 图(盖上镜头盖)
# 然后用工具分析:
# 1. 平均值 ≈ Black Level(应该接近 1032)
# 2. 标准差 ≈ 读出噪声大小
# 3. 最大值 - 最小值 ≈ 噪声峰峰值
# 如果不盖镜头盖,对着均匀白墙拍:
# 1. 看画面的均匀性
# 2. 四角比中心暗 → 需要 LSC 校正
# 3. 局部有色偏 → 需要 CCM 校正
噪声知识总结
| 噪声类型 | 来源 | 能否消除 | 和增益的关系 |
|---|---|---|---|
| 光子散粒噪声 | 光的量子特性 | ❌ 物理限制 | 信号越大,绝对噪声越大,但 SNR 越好 |
| 读出噪声 | sensor 电路 | ⚠️ 可通过降噪减弱 | 被增益等比放大 |
| 暗电流噪声 | 热效应 | ⚠️ 可通过降温/减法减弱 | 温度越高越大,长曝光更明显 |
一句话总结
噪声是画面的"底线"------sensor 的物理噪声决定了画面质量的上限,ISP 降噪只能在这个上限之内做优化。理解噪声特性,你才能设合理的增益上限、调正确的降噪参数、解释为什么暗光下噪点多。