【sensor】ae驱动相关细节(海思)

进阶主题一: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_numcfg2_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_typeaccuracy

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.4min_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 是几帧?

  1. 查 datasheet:索尼 sensor 通常在时序图里标明"Register Update Timing"
  2. 实测:写一个寄存器变化(比如突然把曝光时间减半),用示波器/逻辑分析仪抓帧同步信号,数第几帧开始亮度变化
  3. 经验值:大多数 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,持续耗电
适用场景 对启动速度不敏感 车载/安防/快速预览

不选快速启动的理由

  1. 功耗:bootloader 阶段就开始跑 sensor,如果从上电到用户真正看到画面之间有较长等待(比如 Linux 内核启动要 2 秒),sensor 白耗了 2 秒电
  2. 模式固定:quick start 模式下 sensor 在 bootloader 就初始化了,用户态启动后如果想切换模式(比如从线性切 DCG),还是要重新初始化,quick start 的优势就没了
  3. 调试困难: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。