进阶主题一:int_time_accu.offset --- 曝光时间的"零点偏移"
1.1 代码现象
线性模式有一个非零 offset,DCG 模式的 offset 都是 0:
case MODULES989_SENSOR_3840X2160_30FPS_LINEAR_MODE:
ae_sns_dft->int_time_accu.offset = (td_float)(0x9d60 - 64000) / 0x9680;
// = (40288 - 64000) / 38528
// = -23712 / 38528
// ≈ -0.6155
break;
case DCG_12BIT: // offset = 0
case DCG_14BIT: // offset = 0
为什么线性模式有这个奇怪的值?
1.2 先理解"曝光时间"在 sensor 里的精确含义
你写的曝光时间寄存器(0x0202/0x0203)叫 Coarse Integration Time(CIT),单位是"行数"。但 sensor 的实际曝光并不是从行的第0个像素时钟开始的:
一行的时序:
├── 消隐区 (HBlank) ──┤── 有效像素区 ──┤
↑
行有效信号开始
曝光时间 = FINE_INTEG_TIME + COARSE_INTEG_TIME × 行周期
↑ ↑
行内精细曝光时间 按行计的粗曝光时间
(单位:像素时钟) (单位:行数)
AE 算法只控制 Coarse 部分(按行),但实际曝光量 = Coarse + Fine。
1.3 offset 的推导
代码注释已经说明了来源:
/* offset = (FINE_INTEG_TIME - 64000)/LINE_LENGTH_PCK,
FINE_INTEG_TIME:reg0x200_0x201, LINE_LENGTH_PCK:reg0x342_0x343 */
FINE_INTEG_TIME = reg0x200_0x201 = 0x9d60 = 40288(像素时钟单位)64000是一个内部基准值(海思 AE 框架的约定:Fine 部分基准为 64000 像素时钟)LINE_LENGTH_PCK = reg0x342_0x343 = 0x9680 = 38528(每行总像素时钟数)
offset = (40288 - 64000) / 38528 ≈ -0.6155 行
物理含义:由于 FINE_INTEG_TIME 不等于基准值 64000,每行实际多曝光了 -0.6155 行的量。AE 算法在计算"需要多少行曝光"时,必须补偿这个偏差。
DCG 模式为什么 offset = 0? 因为 DCG 模式的 FINE_INTEG_TIME 恰好等于 64000(或者海思框架对 DCG 做了不同的约定),无需补偿。
1.4 如果 offset 设置错误会怎样
画面轻微偏亮/偏暗(偏差小于1行)
AE 收敛速度变慢
多 pipe 时不同 pipe 之间曝光不一致
这是精调参数,不影响功能但影响 AE 精度。
进阶主题二:delay_frame_num 和 cfg2_valid_delay_max --- 寄存器生效时序(一般查sensor文档来决定)
2.1 问题背景
写入 sensor 寄存器后,不是下一帧立即生效,而是有延迟:
帧 N:ISP 写入新曝光值
帧 N+1:sensor 接收到但还没用上(内部流水线还在处理帧N的曝光)
帧 N+2:新曝光值真正生效
2.2 代码中的两个参数
delay_frame_num = 0(每个寄存器上):
sns_state->regs_info[0].i2c_data[0].delay_frame_num = 0;这表示"写寄存器后,希望0帧延迟生效"------但这不是sensor的实际延迟 ,而是告诉 ISP 框架"我希望尽量0帧延迟"。实际延迟由
cfg2_valid_delay_max控制。
cfg2_valid_delay_max = 2:
sns_state->regs_info[0].cfg2_valid_delay_max = 2;
这才是关键 :它告诉 ISP,从写入寄存器到 sensor 实际生效,最多需要 2帧延迟。
2.3 为什么是2帧
帧0 帧1 帧2 帧3
┌───────────┐┌───────────┐┌───────────┐┌───────────┐
│ 正在曝光 ││ 正在读出 ││ ││ │
│ (用旧参数) ││ (用旧参数) ││ 新参数生效 ││ │
└───────────┘└───────────┘└───────────┘└───────────┘
↑
ISP在帧0的中断里写新参数
→ 帧0已经用旧参数在曝光了,改不了
→ 帧1的曝光可能在帧0末尾就开始了,也改不了
→ 帧2才真正用新参数曝光
ISP 框架用这个信息来做 AE 的预测补偿:知道有2帧延迟,AE 算法在计算时就会"提前2帧预判",避免曝光振荡。
2.4 如果设错会怎样
cfg2_valid_delay_max |
后果 |
|---|---|
| 设小了(实际延迟2帧,你写1) | AE补偿不足 → 画面亮度振荡(忽明忽暗) |
| 设大了(实际延迟1帧,你写3) | AE过度补偿 → 响应变慢(切换场景后很久才稳定) |
进阶主题四:AE 精度体系 --- accu_type 和 accuracy
4.1 三种精度类型
代码中出现了两种精度类型:
// Again 精度
ae_sns_dft->again_accu.accu_type = OT_ISP_AE_ACCURACY_TABLE; // 表格型
ae_sns_dft->again_accu.accuracy = 0.00390625; // = 1/256
// Dgain 精度
ae_sns_dft->dgain_accu.accu_type = OT_ISP_AE_ACCURACY_LINEAR; // 线性型
ae_sns_dft->dgain_accu.accuracy = 0.00390625; // = 1/256
// IntTime 精度
ae_sns_dft->int_time_accu.accu_type = OT_ISP_AE_ACCURACY_LINEAR;
ae_sns_dft->int_time_accu.accuracy = 4; // 4行为步进
| 类型 | 含义 | 适用场景 |
|---|---|---|
ACCURACY_LINEAR |
线性步进,最小步长 = accuracy | 曝光时间、数字增益(步长均匀) |
ACCURACY_TABLE |
非线性步进,由 again_calc_table 函数定义 |
模拟增益(步长不均匀) |
4.2 为什么 Again 是 TABLE 型而 Dgain 是 LINEAR 型
Again:前面讲过,IMX989 的 Again 是反比关系,步长不均匀:
寄存器 0 → 8192:增益从 1x → 2x(步长 ≈ 0.001x/step)
寄存器 14336 → 16128:增益从 8x → 64x(步长 ≈ 0.03x/step)
低增益区步长小,高增益区步长大 → 不是线性的
必须用 TABLE 型,让 AE 通过 again_calc_table 查表Dgain:IMX989 的 Dgain 是简单的线性寄存器,步长均匀:
Dgain = dgain_reg / 256
寄存器 256 → 1x
寄存器 512 → 2x
步长固定 = 1/256 ≈ 0.00390625 → LINEAR 型
accuracy = 0.00390625 = 1/256告诉 AE 算法:Dgain 的最小调节粒度是 1/256,AE 不要期望比这更细的调节。
4.3 曝光时间精度 accuracy = 4
ae_sns_dft->int_time_accu.accuracy = 4;IMX989 的曝光时间必须对齐到4行的整数倍:
ae_sns_dft->max_int_time = ((sns_state->fl_std - 48) >> 3) << 3;
// 先减48,再右移3位(÷8),再左移3位(×8)
// 等价于:向下对齐到8的倍数
// 但 accuracy = 4 → 实际步进是4行为什么不能是1行? IMX989 内部的曝光控制电路要求 CIT 对齐到特定倍数(4或8行),否则可能出现行间曝光不均匀(画面出现水平条纹)。
进阶主题五:flicker_freq --- 抗频闪
ae_sns_dft->flicker_freq = 50 * 256; // 50Hz,精度 ×256
背景:交流电光源(日光灯、LED灯)会以市电频率的2倍频闪烁:
- 中国/欧洲:50Hz 市电 → 光源 100Hz 闪烁
- 美国/日本:60Hz 市电 → 光源 120Hz 闪烁
如果曝光时间不是闪烁周期的整数倍,画面会出现水平亮暗条纹(banding):
曝光时间 = 1/100s(整数倍) → 条纹消失 ✅
曝光时间 = 1/150s(非整数倍) → 有条纹 ❌
1行时间 ≈ hmax_times = 1000000000 / (3816 × 30) ≈ 8737 ns
1/100s = 10000000 ns
100Hz 闪烁周期 = 10000000 / 8737 ≈ 1144.5 行
所以曝光时间设为 1144 的整数倍时,banding 消失
AE 算法根据 flicker_freq 自动计算这个"整数倍行数"
×256 是海思框架的固定精度约定 ,内部计算时 50 * 256 = 12800,不会影响结果。
进阶主题六:PIRIS --- 可调光圈
static ot_isp_piris_attr g_piris = {
0, // bStepFNOTableChange
1, // bZeroIsMax
94, // u16TotalStep
62, // u16StepCount
{ 30, 35, 40, ... 512 }, // F-number 映射表
OT_ISP_IRIS_F_NO_1_4, // 最大光圈 F1.4
OT_ISP_IRIS_F_NO_5_6, // 最小光圈 F5.6
1, // iris_type
512, // max_step_reg
32 // min_step_reg
};
PIRIS(Pixel Iris)= 电机驱动可调光圈,不同于 P-Iris(精确光圈),这里是一个步进电机控制的光圈叶片。
工作原理:
AE 算法判断光线太强
→ 减小光圈(增加F值)→ 减少进光量
→ 同时降低Again/Dgain → 减少噪声
AE 算法判断光线太弱
→ 开大光圈(减小F值)→ 增加进光量F-number 映射表的含义:
Step 0 → F值 30 (F1.4附近,最大光圈)
Step 62 → F值 512 (F5.6附近,最小光圈)
Step 94 → 总步数
代码中 max_iris_fno = F1.4,min_iris_fno = F5.6,AE 在这个范围内调节光圈。进光量 ∝ 1/F²,所以 F1.4 到 F5.6 的进光量差了约 16 倍。
进阶主题八:Quick Start --- 快速启动
ae_sns_dft->quick_start.quick_start_enable = g_quick_start_en[vi_pipe];
ae_sns_dft->quick_start.black_frame_num = 0;
Quick Start 是什么?

black_frame_num = 0:快速启动时不跳过黑帧(有些 sensor 启动初期的帧不稳定,需要丢弃几帧)。
什么时候需要 Quick Start:
- 车载产品(倒车影像要求 <200ms 出图)
- 安防产品(快速预览)
- 消费电子(用户对开机速度敏感)
进阶主题九:max_int_time_target vs max_int_time
ae_sns_dft->max_int_time = ((sns_state->fl_std - 48) >> 3) << 3; // 当前帧长下的物理极限
ae_sns_dft->max_int_time_target = 65535; // AE 可以期望的目标上限两者的区别:
| 参数 | 含义 | 谁来执行限制 |
|---|---|---|
max_int_time |
sensor 物理上允许的最大曝光行数 | sensor 驱动(硬件约束) |
max_int_time_target |
AE 算法的目标上限(可以超过 max_int_time,需要降帧来实现) | AE 算法(策略约束) |
场景:用户要求长曝光(慢快门)
max_int_time = 3816 - 48 = 3768 行(30fps 下的物理极限)
max_int_time_target = 65535 行
AE 想要 65535 行曝光 → 超过 3768 → 需要降帧率
→ AE 调用 cmos_slow_framerate_set() 把 VMAX 加大
→ 帧率降低 → 曝光时间可以更长
→ 最终帧率 = 30 × 3768 / 65535 ≈ 1.7fps
xxx_target是 AE 的"理想目标",xxx是"当前硬限制"。当 target > 硬限制时,AE 会自动触发降帧来满足 target。
进阶主题十一:模式切换流程
当从线性模式切换到 DCG 模式时,代码的执行路径:
应用层调用 MPI 设置新模式
│
▼
cmos_set_image_mode()
├── 遍历 g_modules989_mode_tbl 匹配新模式的 fps/分辨率/sns_mode
├── cmos_config_image_mode_param() → 更新 fl_std, fl[0], fl[1]
├── sync_init = TD_FALSE ← 强制下次重新初始化寄存器信息
└── img_mode = 新模式
│
▼
cmos_set_wdr_mode() (如果需要)
└── wdr_mode = OT_WDR_MODE_NONE 或其他
│
▼
ISP 框架检测到 img_mode 变化
├── 调用 modules989_exit() → 关闭 I2C
├── 调用 modules989_init() → 重新初始化(写入新模式寄存器序列)
│ ├── imx989_common_init() → 公共寄存器
│ ├── imx989_dcg_xxx_init_part1() → 模式特定寄存器
│ ├── modules989_default_reg_init() → 写入 AE/AWB 控制的寄存器
│ └── modules989_restart() → 退出 standby
│
▼
下一帧 ISP 调用 cmos_get_sns_regs_info()
├── sync_init == TD_FALSE → cmos_comm_sns_reg_info_init()(全量初始化)
│ └── DCG 模式 → reg_num = 10(多2个 ST_Dgain 寄存器)
└── sync_init = TD_TRUE
关键点:sync_init = TD_FALSE 确保切换模式后,所有寄存器信息表都会被重新构建(因为 DCG 模式多了2个寄存器,如果不重建,只会写8个寄存器,ST_Dgain 就被遗漏了)。
问题一:帧延迟 cfg2_valid_delay_max 是根据什么来的
1.1 它不是你想设多少就设多少------它由 sensor 硬件流水线决定
关键认知:sensor 不是"写完寄存器就立刻生效"的单片机,它是一个持续运行的流水线。
sensor 在任何时刻都在同时做三件事:
时刻 T:
┌─────────────────┐
│ 正在曝光帧 N+1 │ ← 新帧的积累光子阶段已经开始
└─────────────────┘
┌─────────────────┐
│ 正在读出帧 N │ ← 上一帧的数据正在通过 MIPI 传给 ISP
└─────────────────┘
┌─────────────────┐
│ ISP 在帧 N 中断里 │ ← 此时你通过 I2C 写新曝光参数
│ 写新寄存器值 │
└─────────────────┘
1.2 2 帧延迟是怎么来的
IMX989 内部有一个寄存器锁存点(Latch Point),通常在帧消隐区(VBlank)的起始处。sensor 只在这个时刻才会把新寄存器值"拿进来用"。
帧 N VBlank 帧 N+1 VBlank 帧 N+2
┌───────────────┐┌────────────┐┌───────────────┐┌────────────┐┌───────────────┐
│ 读出 + 曝光N+1 ││ 锁存点 ▼ ││ 读出 + 曝光N+2 ││ 锁存点 ▼ ││ 读出 + 曝光N+3 │
└───────────────┘└────────────┘└───────────────┘└────────────┘└───────────────┘
↑
如果 I2C 写入发生在这里之前 → 帧 N+1 用新值 → 延迟1帧
帧 N VBlank 帧 N+1 VBlank 帧 N+2
┌───────────────┐┌────────────┐┌───────────────┐┌────────────┐┌───────────────┐
│ ││ 锁存点 ▼ ││ ││ 锁存点 ▼ ││ │
└───────────────┘└────────────┘└───────────────┘└────────────┘└───────────────┘
↑
如果 I2C 写入发生在这里(锁存点之后)→ 帧 N+2 才用新值 → 延迟2帧
问题在于:I2C 写入的时刻是不确定的 。ISP 帧中断发生 → AE 计算 → I2C 写入,这个时间可能快也可能慢,取决于 CPU 负载。所以最坏情况就是 2 帧延迟。
cfg2_valid_delay_max = 2 就是取最坏情况,确保 AE 算法的预测补偿不会漏算。
1.3 不同 sensor 延迟不同
是的,帧延迟是 sensor 硬件特性,不同 sensor 不一样:
| Sensor | 延迟帧数 | 原因 |
|---|---|---|
| 简单 CMOS(全局快门) | 1 | 没有 rolling shutter,锁存点简单 |
| 典型 Rolling Shutter | 2 | 曝光和读出重叠,锁存点在 VBlank |
| 有 Group Hold 的 Sensor | 1 | Group Hold 保证同一帧内所有寄存器原子生效 |
| 深流水线 Sensor(如某些4K@60fps) | 3 | 内部有多级 FIFO,流水线更深 |
怎么确定你的 sensor 是几帧?
- 查 datasheet:索尼 sensor 通常在时序图里标明"Register Update Timing"
- 实测:写一个寄存器变化(比如突然把曝光时间减半),用示波器/逻辑分析仪抓帧同步信号,数第几帧开始亮度变化
- 经验值:大多数 Sony IMX 系列是 2 帧
问题二:int_time_accu.offset 零点偏移到底怎么来的
2.1 从一个具体场景开始理解
假设你是 AE 算法,你想让画面恰好曝光 100 行的时间。你把 CIT 寄存器写入 100。
但你拿到的画面实际上曝光了 101.046 行的时间。
那多出来的 1.046 行从哪来的?
2.2 Sensor 内部曝光时间的真实公式
IMX989 的曝光由两部分组成:
┌──────────────────────────── 一行的时间 ─────────────────────────────┐
│ │
│ ┌── HBlank ──┬──────── 有效像素区域 ────────┐ │
│ │ 消隐期 │ 像素数据 │ │
│ └────────────┴───────────────────────────────┘ │
│ │
│ 总长度 = LINE_LENGTH_PCK = 0x9680 = 38528 个像素时钟 │
└──────────────────────────────────────────────────────────────────────┘
一帧的曝光:
├── FINE_INTEG_TIME ──┤── CIT × LINE_LENGTH_PCK ──┤
│ 行内精细曝光时间 │ 按行计的粗曝光时间 │
│ (像素时钟单位) │ (行 × 每行像素时钟数) │
总曝光时间(像素时钟单位):
Texposure=FIT⏟精细曝光+CIT×LLPCK⏟粗曝光
其中:
FIT = FINE_INTEG_TIME = reg0x200_0x201 = 0x9d60 = 40288像素时钟LLPCK = LINE_LENGTH_PCK = reg0x342_0x343 = 0x9680 = 38528像素时钟/行CIT= 你写入0x0202/0x0203的值(行数)
换算成"等效行数":
\text{等效曝光行数} = \frac{FIT}{LLPCK} + CIT = \frac{40288}{38528} + CIT \approx 1.046 + CIT \
2.3 那海思 AE 框架怎么处理这个偏差
海思 AE 框架不是完全不知道 FIT 的存在。框架内部有一个假定基准值 :64000 像素时钟。
框架计算曝光时默认认为:
等效曝光=64000LLPCK+CIT
但实际 sensor 的 FIT 不是 64000,是 40288。所以需要告诉框架"纠正一下你的假设":
offset=实际FIT−框架假设FITLLPCK=40288−6400038528=−2371238528≈−0.6155
这个 -0.6155 就是"零点偏移"。
2.4 用数字走一遍完整流程
场景:AE 算法决定需要 200 行的曝光
AE 的内部计算:
期望等效曝光 = 200 行
框架默认:等效曝光 = 64000/38528 + CIT = 1.661 + CIT
需要 CIT = 200 - 1.661 = 198.339 → 取整 CIT = 198
但实际等效曝光 = 40288/38528 + 198 = 1.046 + 198 = 199.046 行
误差 = 200 - 199.046 = 0.954 行!偏暗了!
加入 offset 修正后:
框架知道 offset = -0.6155
实际 FIT 贡献 = 64000/38528 + offset = 1.661 + (-0.6155) = 1.046 行 ✓
等效曝光 = 1.046 + CIT = 1.046 + 198 = 199.046 行
AE 补偿:CIT = 200 - 1.046 = 198.954 → 取整 199
实际等效曝光 = 1.046 + 199 = 200.046 行 ✓ 精度很高!
2.5 为什么 DCG 模式 offset = 0
DCG 模式下,sensor 的曝光机制不同于线性模式:
线性模式:曝光 = FIT + CIT × LLPCK(FIT 和 CIT 都有意义)
DCG 模式 :HCG 和 LCG 的曝光控制逻辑不同,fine integration time 的处理方式也不同。IMX989 在 DCG 模式下,要么 FIT 恰好等于框架假设的 64000,要么 DCG 的 AE 计算方式本身就不依赖这个 offset(DCG 有独立的曝光比计算逻辑),所以 offset = 0。
你也可以这样理解:
| 模式 | 曝光控制方式 | FIT 对 AE 有影响吗 | offset |
|---|---|---|---|
| 线性 | CIT 直接控制总曝光 | 是(FIT ≠ 64000) | 非零 |
| DCG | CIT 控制 HCG,LCG 通过曝光比推导 | 否(曝光比已经隐含了所有时序关系) | 0 |
2.6 如果 offset 设错了会怎样
offset 偏大(比如把 -0.6155 设成 0):
→ AE 以为实际曝光比真值多 0.6155 行
→ AE 会少写 1 行 CIT 来"补偿"
→ 画面整体偏暗约 0.6/3816 ≈ 0.016%(肉眼几乎不可见)
→ 但 AE 收敛精度降低,可能多振荡 1-2 帧才稳定
offset 设反(比如设成 +0.6155):
→ 误差翻倍
→ AE 收敛后亮度偏差约 1.2 行 ≈ 0.03%
→ 配合 flicker 抗频闪时可能导致 banding 条纹
这是精调参数,不是功能开关。设错不会黑屏,但影响 AE 精度和收敛速度。
问题三:快速启动是不是一定选,black_frame_num 是什么
不一定选快速启动,有几个权衡
| 普通启动 | Quick Start | |
|---|---|---|
| 启动速度 | 300-500ms | 50-100ms |
| 实现复杂度 | 简单 | 需要 bootloader 配合 |
| 灵活性 | 每次启动可以动态选择模式 | 模式在 bootloader 就固定了 |
| 功耗 | 启动时才开sensor | bootloader 就开了sensor,持续耗电 |
| 适用场景 | 对启动速度不敏感 | 车载/安防/快速预览 |
不选快速启动的理由:
- 功耗:bootloader 阶段就开始跑 sensor,如果从上电到用户真正看到画面之间有较长等待(比如 Linux 内核启动要 2 秒),sensor 白耗了 2 秒电
- 模式固定:quick start 模式下 sensor 在 bootloader 就初始化了,用户态启动后如果想切换模式(比如从线性切 DCG),还是要重新初始化,quick start 的优势就没了
- 调试困难:bootloader 里的 sensor 初始化代码出错很难调试,没有串口日志没有 ISP 调试工具
black_frame_num 的意义
ae_sns_dft->quick_start.black_frame_num = 0;Sensor 刚启动时,前几帧图像可能不正常:
帧1:行时序还没稳定 → 图像撕裂/花屏
帧2:AE 还没收敛 → 过亮或过暗
帧3:AWB 还没收敛 → 颜色偏差
帧4:基本正常
black_frame_num就是丢弃前 N 帧不显示,等画面稳定后再输出给用户。
black_frame_num = 0 → 第一帧就显示(可能有花屏)
black_frame_num = 2 → 丢弃前2帧,第3帧开始显示
black_frame_num = 5 → 丢弃前5帧,第6帧开始显示(更稳但更慢)
设为 0 是因为 IMX989 配合 Quick Start 时,sensor 在 bootloader 就已经稳定出图了,到用户态时画面已经正常,不需要丢帧。 如果不配 Quick Start,建议设 2-3。