【sensor】again寄存器层面

本文主要解决以下问题:

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/30s

DCG 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 ≈ 64x

DCG 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 = 1x

AE 算法必须假设 增益是线性的:增益翻倍 = 亮度翻倍

它用统一单位表达这个线性增益: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),才能:

  1. 正确计算曝光量(曝光时间 × 实际增益)
  2. 给后级 ISP 算法(降噪、HDR 合成)提供准确的增益信息
  3. 下一帧 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 和输出保持一致是最常见也最安全的做法。