本文主要解决以下问题:
1、ADC位深到底是什么东西,输出位深是什么,为什么不同位深DCG模式的vmax不一样,为什么14bit的again更小
2、为什么要算again_db,这个有什么意义吗
问题一:ADC位深、输出位深、VMAX、Again范围的关系
1.1 ADC 位深是什么
ADC(模数转换器) 是 sensor 内部的核心部件,把像素里的模拟电压(光子产生的电荷量)转换成数字数值。
一个光子打到像素上,产生一个电子,像素把这些电子积累起来形成电压,ADC 把这个电压量化成数字:
光子 → 电子 → 模拟电压 → ADC → 数字数值
位深决定量化精度:
| ADC 位深 | 量化级数 | 最小可分辨单位 |
|---|---|---|
| 10-bit | 1024 级 | 满量程/1024 |
| 12-bit | 4096 级 | 满量程/4096 |
| 14-bit | 16384 级 | 满量程/16384 |
直白说:ADC 位深越高,同样的亮度范围被切得越细,弱光下能分辨的细节越多,量化噪声越低。
物理原理
IMX989 的 ADC 有一条固定的模拟参考电压 Vref(full-scale voltage),无论哪种模式,这个参考电压不变。变化的是量化步长:
ADC 把 [0, V_ref] 这个模拟电压范围,切成 N 份:
10-bit: 切成 1024 份,每份 = V_ref / 1024
12-bit: 切成 4096 份,每份 = V_ref / 4096
14-bit: 切成 16384 份,每份 = V_ref / 16384
同一个光子量 → 同一个模拟电压 V:
10-bit 输出 = V / (V_ref / 1024) = V × 1024 / V_ref
12-bit 输出 = V / (V_ref / 4096) = V × 4096 / V_ref
14-bit 输出 = V / (V_ref / 16384) = V × 16384 / V_ref
所以:
12-bit 值 = 10-bit 值 × 4 (左移2位)
14-bit 值 = 10-bit 值 × 16 (左移4位)
验证你的例子:
| 光子量 | 模拟电压 | 10-bit | 12-bit | 14-bit |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 半满量程 | 0.5×V_ref | 512 | 2048 | 8192 |
| 满量程 | V_ref | 1023 | 4095 | 16383 |
10-bit 的 512 = 12-bit 的 2048 = 14-bit 的 8192,它们代表同一个模拟电压(半满量程),也就是同一种光子量。
这也解释了 Black Level 的关系
IMX989 的模拟黑电平 ≈ V_ref × (64/1024) (物理上由 OB 电路决定)
10-bit 表示:64
12-bit 表示:64 × 4 = 256
14-bit 表示:64 × 16 = 1024
代码里 MODULES989_BLACK_LEVEL = 1032,比理论的 1024 略高(14bit域同理),是因为实际校准值包含了 sensor 内部模拟电路的额外偏移。
1.2 输出位深是什么,和 ADC 位深的关系
输出位深 是 sensor 通过 MIPI 接口向外(SOC/ISP)传输数据时,每个像素用多少 bit 来表达。
对应寄存器 0x0112/0x0113:
0x0A→ 10-bit 输出0x0C→ 12-bit 输出0x0E→ 14-bit 输出
DCG 模式的特殊性:
普通线性模式,一个像素只有一路 ADC 值,10bit 输出 10bit。
DCG 模式里,同一个像素同时做了两路 ADC 采样(HCG 和 LCG),这两路数据必须都传出去:
线性 10bit: 每像素 = [ADC值 × 10bit] ──→ 输出 10bit
DCG 12bit: 每像素 = [HCG × ? + LCG × ?] 压缩打包 ──→ 输出 12bit
DCG 14bit: 每像素 = [HCG × ? + LCG × ?] 更精细打包 ──→ 输出 14bit
14bit 输出比 12bit 多出的 2bit,用来更精细地表达 HCG/LCG 信号的差异,提升 HDR 合成质量,但代价是数据量更大。
1.3 为什么 VMAX 不一样(这是最容易误解的地方)
根本原因:MIPI 带宽是固定的,数据量增加就必须把帧变短。
三种模式都是 30fps、1500Mbps MIPI,但每帧数据量不同:
线性 10bit:
每像素 10bit
HMAX = 0x5740 = 22336 (每行像素时钟数)
VMAX = 3816
帧时间 = 22336 × 3816 / pixel_clk = 1/30sDCG 12bit:
每像素 12bit(HCG+LCG 打包)
比线性多了 2bit,行数据量多 20%
HMAX 必须变大 = 0xA5A0 = 42400(约是线性的 1.9 倍)
为了保持 30fps,VMAX 必须压小
VMAX = 3216(比线性少了约 600 行)DCG 14bit:
每像素 14bit(HCG+LCG 打包,精度更高)
行数据量比 12bit 还多
PLL 配置也不同(更高的内部时钟用于更精细的 ADC 转换)
VMAX = 2776(最小,因为数据量最大 + 内部 ADC 时间最长)
用一张图理解:
时间轴(1帧 = 1/30s)
│←──────────────── 1帧总时间固定 ────────────────→│
线性 10bit:
│ HMAX=22336 │×│ HMAX=22336 │×│...│ 共 3816 行 │
DCG 12bit:
│ HMAX=42400 │×│ HMAX=42400 │×│...│ 共 3216 行 │
│←── 每行更宽 ──→│ │← 行数更少 →│
DCG 14bit:
│ HMAX=42400 │×│ HMAX=42400 │×│...│ 共 2776 行 │
│←── 每行最宽 ──→│ │← 行数最少 →│
额外原因:14bit ADC 内部需要更多时间做精细量化(逐次逼近型 ADC,14bit 要比较14次,12bit 只要12次),所以内部时序也更紧,VMAX 进一步压缩。
1.4 为什么 14bit 模式的 Again 最大值更小
这是驱动工程师刻意设计的,不是硬件限制。
先看三种模式的 Again 最大值对应的"实际场景增益":
线性模式(无 DCG):
max_again = 65535
实际最大增益 = 65535/1024 ≈ 64xDCG 12bit(曝光比 1:4):
max_again = 16384(LCG 域)
LCG 最大 = 16384/1024 = 16x
HCG 实际 = 16x × 4 = 64x ← 场景增益
min_again 对应 again_db = DCG_1_4_AGAIN_MIN = 12288
LCG 最小 = 1024/1024 = 1x(最低 1x,不能再小)DCG 14bit(曝光比 1:16):
max_again = 4095(LCG 域)
LCG 最大 = 4095/1024 ≈ 4x
HCG 实际 = 4x × 16 = 64x ← 场景增益!和前面一样!
min_again 对应 again_db = DCG_1_16_AGAIN_MIN = 15360
LCG 最小 = 1024/1024 = 1x
关键发现:三种模式折算到场景的最大增益都是约 64x!
14bit 模式 max_again 小(只有 4x LCG),是因为曝光比已经是 1:16 了,LCG 已经足够"弱"。再往大推 gain 会出现以下问题:
场景:亮度均匀的灰卡
LCG 通道:接收到实际光量 = 场景光 / 16
+ 继续加增益 → SNR 极差 → LCG 通道完全被噪声淹没
HCG 通道:正常曝光
+ 高增益 → 很快饱和
HDR 合成:LCG 噪声太大 → 亮区细节丢失 → HDR 失效
结论:曝光比越大,LCG 信号越弱,越不能再叠加大增益
14bit (1:16) 比 12bit (1:4) 的 LCG 信号弱4倍,所以 again 上限低4倍
一句话总结:
DCG 比率越大 → 动态范围越宽 → 但代价是 LCG 更弱 → Again 必须压低以保证 LCG 通道 SNR → 表现为 max_again 更小。
问题二:为什么要算 again_db,这有什么意义
2.1 先理解 AE 算法"看到的世界"
AE(自动曝光)算法在数学上做的事情非常简单,就是一个比例计算:
当前亮度(测光值) = 100
目标亮度 = 50(期望更暗)
需要减少曝光量到 50/100 = 0.5倍
如果当前增益 = 2x,那新增益应该 = 2x × 0.5 = 1xAE 算法必须假设 增益是线性的:
增益翻倍 = 亮度翻倍。它用统一单位表达这个线性增益:1x = 1024,2x = 2048,4x = 4096。
2.2 sensor 寄存器的真实世界
但 IMX989 内部模拟放大电路的物理特性决定了,它的增益公式是反比关系:
真实增益=1638416384−寄存器值
验证几个点:
| 寄存器值 | 真实增益 |
|---|---|
| 0 | 16384/(16384-0) = 1x |
| 8192 | 16384/(16384-8192) = 2x |
| 12288 | 16384/(16384-12288) = 4x |
| 14336 | 16384/(16384-14336) = 8x |
| 15360 | 16384/(16384-15360) = 16x |
| 16128 | 16384/(16384-16128) = 64x |

非常非线性! 寄存器值从 0→8192 增益从 1x→2x,但寄存器值从 14336→16128 增益从 8x→64x。
2.3 如果不做转换,会发生什么
假设 AE 算法直接把"增益值"当寄存器值来用:
场景:画面暗,AE 想把增益从 1x 增加到 2x
AE 内部:again_lin 从 1024 增加到 2048
不做转换,直接写 2048 进寄存器:
实际增益 = 16384 / (16384 - 2048) = 16384 / 14336 = 1.14x ← 完全不对!
AE 期望增加 2x,实际只增加了 1.14x
AE 以为增够了,但画面还是暗
→ AE 继续加 → 寄存器值越来越大但增益增长越来越快 → 振荡!
AE 控制环路就会失控。
2.4 again_db 的意义:完成线性世界到物理世界的翻译
again_db 就是把 AE 的线性期望翻译成 sensor 物理寄存器的翻译结果:
AE 算法(线性世界)
│
│ "我要 4x 增益" again_lin = 4096
│
▼
again_calc_table() ← 你理解它的核心就在这里
│
│ 公式推导:
│ 4096/1024 = 4x 真实增益
│ 4 = 16384 / (16384 - reg)
│ 16384 - reg = 4096
│ reg = 12288
│
▼ again_db = 12288
│
▼
写入寄存器 0x0204/0x0205 = 12288
sensor 物理放大器实际增益 = 16384/(16384-12288) = 4x ✓
again_db 本质上就是要写进寄存器的值,它本身没有"物理意义",它的意义在于:它是反解 sensor 增益公式的数学结果。
2.5 反算 again_lin 的意义
转换之后,代码还做了一步反算:
*again_lin = (AGAIN_FACTOR * AGAIN_AE_FACTOR) / (AGAIN_FACTOR - *again_db);
为什么要反算?
因为
again_db是整数,有精度损失:
AE 期望:3.5x → again_lin = 3584
计算 again_db:
db = 16384 - 16384×1024/3584 = 16384 - 4681.45... = 11702.54
取整 → again_db = 11702
反算实际增益:
actual_gain = 16384 / (16384 - 11702) = 16384 / 4682 = 3.499x
actual again_lin = 3.499 × 1024 = 3583 (不是 3584)
ISP 需要知道实际生效的增益是多少(3583 而不是 3584),才能:
- 正确计算曝光量(曝光时间 × 实际增益)
- 给后级 ISP 算法(降噪、HDR 合成)提供准确的增益信息
- 下一帧 AE 的反馈闭环从正确的起点出发
2.6 DCG 模式的额外翻译层
DCG 模式还多了一个坐标系转换:
AE 算法的视角:LCG 域(从场景角度看的增益)
again_lin = 2048 → LCG 增益 2x
sensor 寄存器控制:物理放大电路(以 HCG 为参考)
HCG 增益 = LCG 增益 × DCG_Ratio
所以计算流程多了一步:
LCG 2x → HCG 8x (12bit, 1:4) → 算 again_db → 写寄存器
LCG 2x → HCG 32x (14bit, 1:16) → 算 again_db → 写寄存器
反算也要除回来:
寄存器算出 HCG 增益 → 除以 DCG_Ratio → 告诉 AE LCG 增益
这就是为什么 DCG 模式的 again_calc_table 里要乘以/除以 DCG_Ratio------不是公式不同,是坐标系不同,需要来回转换。
2.7 一句话总结
again_db是 AE 线性增益期望经过 sensor 增益公式反解后的寄存器值。没有这个转换,AE 闭环就无法正确收敛到目标亮度,会出现画面忽明忽暗、AE 振荡或响应迟钝的问题。
问题三:ADC 位深需要和输出位深保持一致吗?
短答案:不必须一致,但在 IMX989 这个驱动里恰好是一致的。
它们是两个独立概念
| 概念 | 控制什么 | IMX989 寄存器 |
|---|---|---|
| ADC 位深 | 内部模数转换的精度(量化切多少份) | 0x321C |
| 输出位深 | MIPI 接口每个像素传多少 bit | 0x0112/0x0113 |
从初始化序列可以看到它们的对应关系:
| 模式 | ADC (0x321C) |
输出 (0x0112) |
是否一致 |
|---|---|---|---|
| 线性 | 0x00 (10-bit) | 0x0A (10-bit) | ✅ 一致 |
| DCG 12bit | 0x01 (12-bit) | 0x0C (12-bit) | ✅ 一致 |
| DCG 14bit | 0x02 (14-bit) | 0x02 (14-bit) | ✅ 一致 |
为什么这里恰好一致?
因为 DCG 模式下,输出位深必须 ≥ ADC 位深,否则 HDR 信息会丢失。
DCG 模式里每个像素有两路信号(HCG + LCG),需要比线性模式更多的 bit 才能完整编码:
线性 10-bit:
每像素 1 个 ADC 值,10bit 刚好装下 → 输出 10-bit ✓
DCG 12-bit:
每像素 2 个 ADC 值(HCG+LCG),用 12-bit 打包
ADC 也需要 12-bit 精度才能区分 HCG/LCG 差异
→ ADC 12-bit + 输出 12-bit ✓
DCG 14-bit:
曝光比1:16,HCG/LCG差异更大
需要更精细的量化来保留合成信息
→ ADC 14-bit + 输出 14-bit ✓
它们不一致的情况(理论可行但本驱动没有)
在其他 sensor 或场景下,ADC 和输出位深可以不同:
| 场景 | ADC | 输出 | 原因 |
|---|---|---|---|
| 12-bit ADC 降带宽输出 | 12-bit | 10-bit | ADC 高精度采集,低 2bit 丢弃节省 MIPI 带宽 |
| 10-bit ADC 存入宽容器 | 10-bit | 10-bit(但ISP用12/16bit容器处理) | MIPI 传 10bit,ISP 解包后左移到高位宽处理 |
| HDR 压缩输出 | 14-bit | 12-bit | 14bit ADC 转 HDR 后压缩到 12bit 输出 |
你代码里线性模式的实际流程:
IMX989 10-bit 线性模式:
ADC: 10-bit 量化
MIPI: 10-bit 传输(0x0112=0x0A)
ISP: 解包后放入 12-bit 或 16-bit 容器处理
(所以 white_level = 4095,是12bit容器的满量程)
这里 ISP 容器是 12-bit,但实际有效数据只有 10-bit(0-1023 左移2位后是 0-4092),ISP 知道有效位只有10bit,在做处理时会正确识别。
总结核心认知
同一束光打在像素上
│
▼
模拟电压 V(物理量,跟位深无关)
│
├── 10-bit ADC → digital = V × 1024/V_ref (粗量化)
├── 12-bit ADC → digital = V × 4096/V_ref (中量化)
└── 14-bit ADC → digital = V × 16384/V_ref (细量化)
同一个 V,不同位深的数字值不同,但代表的物理量相同
转换关系:简单 ×2^(差值) 移位
ADC 位深决定"看得多细",输出位深决定"传得多宽",ISP容器位深决定"处理多深"。三者可以不同,但 DCG 模式下为了保留 HDR 信息,ADC 和输出保持一致是最常见也最安全的做法。