简介:面向相机系统软件工程师的IMX378相位检测自动对焦(PDAF)软件参考手册(v1.0.0),由Sony Semiconductor Solutions发布,专门解释相位检测功能的输入/输出接口与控制方法。该传感器在有效像素区域内以99%块占用率嵌入右半屏蔽与左半屏蔽像素,每16×32像素块含8个右屏蔽像素和8个左屏蔽像素,从而实现高精度对焦。包体为单个PDF文档,约1.67MB,共1份文件,体积小巧便于查阅。内容覆盖屏蔽像素区域对齐、相位检测操作概述、控制寄存器与AF窗口配置、相位差数据输出(MIPI/I2C)以及模块校准设置等核心章节,并保留自2015年12月至2017年2月的修订历史,帮助读者追踪寄存器地址与灵活窗口模式等更新。手册适合开发IMX378相机模组或相关嵌入式视觉产品的软硬件人员使用,已有1586人学习下载,可作为实现快速、精确自动对焦方案的直接参考资料。
1. 为什么 IMX378 的 PDAF 手册值得逐行读
IMX378 这颗 CMOS 图像传感器在手机后摄和车载相机里出镜率都不低,但真正把相位检测自动对焦(PDAF)调明白的人,往往都卡在同一处:它不是拨一个开关就能搞定的功能,屏蔽像素布局、AF 窗口坐标换算、MIPI/I2C 两条数据通路和模块校准这四件事必须同时理顺。这份原厂发布的 v1.0.0 版 Software Reference Manual for PDAF,就是围绕 IMX378 讲清楚"相位差数据怎么控、怎么读、怎么校准"的完整说明。适合正在写 PDAF 驱动的工程师、做 ISP 算法的同学,以及负责模组产线校准的同事。别嫌它 2017 年就定稿,同代传感器的 PDAF 寄存器框架到现在还在沿用,把这份 PDF 下载下来对照着调参,比四处搜碎片化的帖子靠谱得多。
2. 屏蔽像素与 PDAF 原理: 16x32 块里的 8+8 结构怎么决定对焦快慢
2.1 左右屏蔽像素布局: 半金属遮挡为什么能测相位
先理解硬件基础。IMX378 在有效像素区域内部嵌入了一批屏蔽像素,每个像素大约有一半面积被金属遮挡。按遮挡方向分成两类:右半被遮挡的叫右屏蔽像素,左半被遮挡的叫左屏蔽像素。手册给出的排列规则是:每个 16 像素 × 32 像素的块内,离散嵌入 8 个右屏蔽像素和 8 个左屏蔽像素,屏蔽区在块内的占用率约 99%。
这个 99% 的块占用率,直观理解是 16×32 的块结构几乎铺满整个有效成像区,而不是只在画面中央埋几个点。这样做的意义在于,无论对焦框落在画面哪个位置,都能就近找到可用的屏蔽像素对参与相位统计;如果只在中心布局,边缘对焦就会变成盲区。
这种"半边遮光"的设计原理不复杂:左屏蔽像素只让左侧光线进入,右屏蔽像素只让右侧光线进入,左右两组像素对同一个物点的响应存在一个固定的视角差。当镜头离焦时,这个视角差会换算成像素域上的错位量,也就是相位差。单位是像素,方向由符号表达。
工程上的第一个隐藏工作是:屏蔽像素分布在有效像素区内部,普通像素做颜色插值和 shading 校正时必须知道它们的确切位置,并把它们排除在插值邻域之外,否则图像上会留下一条条固定的亮线或暗点。有些平台把这步合到 PDAF 像素校正里做,但前提是驱动能准确上报屏蔽像素坐标表。
2.2 相位差与置信度: 算法侧必须成对消费的数据
传感器内部会针对每个指定的 AF 区域做统计处理,输出两个值:Phase Difference(相位差,单位像素)和 Confidence Level(置信度,无量纲)。
相位差代表当前焦点偏离合焦位置的方向和程度。数值的方向性体现在符号上,正负号决定了镜头该往近推还是往远拉。置信度则是这组相位差数据可信度的参考值,手册里的定义很直白:它与 AF 窗口内"易于检测相位差的竖直边缘数量"成正比。
这个设计对算法非常有价值。如果你把 AF 窗口对准一面白墙或者一片天空,相位差数值大概率会来回跳,但置信度会很低。算法侧的常见做法是只把置信度超过阈值的窗口结果喂给对焦控制,否则回退到 CDAF(反差对焦)或者保持当前位置不动作。我在驱动里会把两个值一起读出来打日志,排查对焦抖动时,只盯相位差曲线往往看不出名堂,把置信度叠上去,立刻就能区分"数据不可信"和"马达过冲"两种完全不同的故障。
注意:手册里所有相位差和置信度都是"每个 AF 窗口一组"。窗口设得越细,数据量越大,读取策略要在驱动设计阶段就想清楚,不能拿到数据再规划。
2.3 支持的操作模式: Normal Full / HDR / V2 Binning 全开
手册 Table 2-1 明确列出了相位检测在 IMX378 所有主要工作模式下都受支持,这张表对方案选型影响很大。
| 传感器工作模式 | 相位检测支持 |
|---|---|
| Normal Full | 支持 |
| Normal V2 Binning | 支持 |
| HDR Full | 支持 |
| HDR V2 Binning | 支持 |
这意味着做产品定义时,不需要为了保留 PDAF 而牺牲 HDR 或者 binning,三者的开关是独立的。但"支持"不等于性能完全一致。我实际调试的经验是,binning 模式下的相位差分辨率会受像素合并影响,对焦精度和置信度分布与 Full 模式有明显差异。建议先用 Normal Full 完成 SPC 和 DCC 标定,再去验证 binning 和 HDR 下的行为,不要直接拿 binning 模式做全部标定,否则换模式后对焦手感会变。
3. 控制寄存器与序列: 三个关键地址和一段可抄的启动代码
3.1 0x38a3 AREA_MODE 与 0x3e37 PDAF_CTRL1
相位检测的寄存器数量不多,核心入口就三个。手册 Table 3-1 把它们列得很清楚,这里按驱动视角整理成一张速查表。
| I2C 地址 | 位 | 名称 | 功能描述 |
|---|---|---|---|
| 0x38a3 | [1:0] | AREA_MODE | 0: 固定区域 16x12;1: 固定区域 8x6;2: 自由区域切换(灵活窗口);3: 保留 |
| 0x3e37 | [0] | PDAF_CTRL1 | 0: 相位差数据不从 MIPI 输出;1: 从 MIPI 输出 |
| 0x7c08 ~ 0x7f07 | [7:0] | Phase Difference DATA | I2C 方式读取的相位差数据区,只读 |
一个容易被忽略的细节是 PDAF_CTRL1 只控制 MIPI 输出通路。调试阶段如果你不想解析 MIPI 侧的复杂打包格式,完全可以不置这个位,直接走 I2C 数据区读相位差和置信度。反过来,如果产品要用 MIPI 数据喂实时对焦算法,就必须在出流之前把这一位置 1,否则帧结构正常、图像正常,唯独 PDAF 数据通道是空的。
3.2 相位差数据区: 0x7c08 起 768 字节的只读区域
0x7c08 到 0x7f07 是一整块只读数据区,按 AF 窗口的划分顺序排列相位差和置信度。16x12 固定窗口模式正好是 192 个窗口,这块区域的总长度 768 字节,按每个窗口 4 字节对齐时刚好铺满;8x6 模式只有 48 个窗口,每个窗口对应的字节数就要按手册 Table 6-1 重新确认,不能照搬。
读这块区域我有两个固定习惯。第一,用 burst 方式整块读,不要在窗口边界处拆成单字节散读,否则可能读到跨帧拼接的数据。第二,调试阶段先把原始字节完整打印出来,确认数据区数值确实在随镜头位置变化而变化,再谈解析和算法规避。寄存器配置错了,数据区往往给出的是固定值或者明显乱跳的值,这一步能帮你把问题边界划清楚。
配合 i2c-tools 可以快速做一次存活检查:
# 快速回读 PDAF 关键寄存器, -y 跳过交互确认 i2cget -y 1 0x20 0x3e37 # 确认 PDAF_CTRL1 是否真的写进去了 i2cget -y 1 0x20 0x38a3 # 确认 AREA_MODE 当前值 i2cget -y 1 0x20 0x7c08 # 读第一个窗口的相位差低字节这三个命令在模组刚点亮时跑一遍,能排除掉一大类"寄存器写失败"的底层问题。0x20 是示例地址,实际以模组规格书为准。
3.3 控制序列: SW standby 里写,Streaming 后只读数据
手册第 4 章的相位检测控制序列非常简洁:SW standby → PDAF_CTRL1=1 → Streaming → PD OUT ON。翻译成驱动代码就是下面这段。
// IMX378 PDAF 开启序列, I2C 地址按实际模组总线调整 void pdaf_power_on(uint8_t i2c_addr) { // 1. 进入软 standby, 常见挂在 0x0100 的 bit0 sensor_write(i2c_addr, 0x0100, 0x00); // 2. 选择 AF 区域模式, 0x02 表示自由区域(灵活窗口) sensor_write(i2c_addr, 0x38a3, 0x02); // 3. 打开 MIPI 相位差输出 sensor_write(i2c_addr, 0x3e37, 0x01); // 4. 退出 standby, 开始出流 sensor_write(i2c_addr, 0x0100, 0x01); // 出流后 MIPI 帧内携带相位差数据, 同时 0x7c08 起也可读 }这个顺序不能乱。如果先出流再写 0x3e37,部分批次的 sensor 不会认这次写入,导致 MIPI 侧一直拿不到相位差数据,而 I2C 侧却正常。排查时先回读 0x3e37 确认 bit0 是否真的为 1,再往上层找问题。0x0100 软 standby 是传感器通用惯例,如果某个模组行为不一致,优先查模组的上电时序说明,不要一上来就怀疑这段逻辑。
4. AF 窗口配置: 固定窗口与灵活窗口的寄存器计算和落地写法
4.1 固定窗口: 16x12 / 8x6 的偏移、尺寸与 13 位拆写
固定窗口模式下,你只需要设置第一个格子(左上角)的 X/Y 偏移,以及单个格子的宽高。格子总数由 AREA_MODE 决定:0 对应 16x12,1 对应 8x6。偏移和尺寸都是 13 位无符号数,物理上拆成两个寄存器:高 5 位存在"高位地址",低 8 位存在"低位地址"。
| 含义 | 高 5 位地址 | 低 8 位地址 |
|---|---|---|
| 起始 X 坐标 | 0x38a4 | 0x38a5 |
| 起始 Y 坐标 | 0x38a6 | 0x38a7 |
| 单格宽度 | 0x38a8 | 0x38a9 |
| 单格高度 | 0x38aa | 0x38ab |
// 固定窗口: 第一个格子在 (220,240), 单格 1000x600 pdaf_write13(0x38a4, 0x38a5, 220); // PD_AREA_X_OFFSET pdaf_write13(0x38a6, 0x38a7, 240); // PD_AREA_Y_OFFSET pdaf_write13(0x38a8, 0x38a9, 1000); // PD_AREA_WIDTH pdaf_write13(0x38aa, 0x38ab, 600); // PD_AREA_HEIGHT // 13 位寄存器拆分写入, 先高后低 void pdaf_write13(uint8_t addr_hi, uint8_t addr_lo, uint16_t val) { sensor_write(addr_hi, (val >> 8) & 0x1F); // 高 5 位 sensor_write(addr_lo, val & 0xFF); // 低 8 位 }这里有三件事必须注意。第一,坐标和尺寸的单位都是"输出图像坐标",也就是经过 crop、binning、scaling、mirror、flip 之后的坐标,不是 sensor raw 坐标。手册里专门强调了这一点,实际移植时最容易错的就是拿 raw 坐标直接填。第二,如果窗口整体超出最终图像边界,越界的格子会输出 invalid code,并且不参与相位检测,这不是 bug,是手册规定的行为。第三,我习惯先写高 5 位再写低 8 位,虽然手册没有强约束写顺序,但能避免中间态落在错误窗口里,成本极低。
4.2 灵活窗口: 最多 8 个可重叠区域与 0x1FFF 模式标志
灵活窗口模式的价值在于可以同时关注多个区域,比如人脸框加画面中心区,最多 8 个,允许互相重叠。进入这个模式有一个非常容易漏的条件:必须把 0x38a8 到 0x38ab 这四个宽高寄存器的 13 位值全部写成 0x1FFF,作为"自由区域"的标志。只配置 AREA_EN 是不够的,我第一次移植就栽在这。
以窗口 0 和窗口 1 为例,坐标寄存器的映射关系是每组连续 4 个地址:起始 X、起始 Y、结束 X、结束 Y,每个坐标同样是 13 位拆两个寄存器。
| 含义 | 窗口 0 高 5 位地址 | 窗口 0 低 8 位地址 |
|---|---|---|
| 起始 X | 0x38b4 | 0x38b5 |
| 起始 Y | 0x38b6 | 0x38b7 |
| 结束 X | 0x38b8 | 0x38b9 |
| 结束 Y | 0x38ba | 0x38bb |
// 进入灵活窗口模式: 宽高寄存器写固定值 0x1FFF pdaf_write13(0x38a8, 0x38a9, 0x1FFF); // 模式标志 pdaf_write13(0x38aa, 0x38ab, 0x1FFF); // 模式标志 // 窗口 0: (400,300) 到 (1000,700) pdaf_write13(0x38b4, 0x38b5, 400); pdaf_write13(0x38b6, 0x38b7, 300); pdaf_write13(0x38b8, 0x38b9, 1000); pdaf_write13(0x38ba, 0x38bb, 700); sensor_write(0x38ac, 0x01); // AREA_EN_0 = 1 // 窗口 1: (600,400) 到 (800,600), 与窗口 0 刻意重叠 pdaf_write13(0x38bc, 0x38bd, 600); pdaf_write13(0x38be, 0x38bf, 400); pdaf_write13(0x38c0, 0x38c1, 800); pdaf_write13(0x38c2, 0x38c3, 600); sensor_write(0x38ad, 0x01); // AREA_EN_1 = 1窗口 2 到窗口 7 的寄存器地址按同样的四对一组往后排,手册里列到了 0x38d3,再往后是窗口 4 的起始地址 0x38d4,规律非常明显。重叠窗口的像素会被多个窗口重复统计,这是允许的,但会把置信度分布拉平,做阈值判断时心里要有数。手册没有明确"结束点是否包含在窗口内"这种边界语义,我建议拿到 sensor 后用两个已知尺寸的窗口对着 MIPI 输出做一次校准,确认开闭区间之后再写进代码注释。
4.3 窗口生效时序和 mirror / flip 的组合影响
手册 5.3 节专门讨论了 AF 窗口的反射时序。窗口配置不是写完立刻生效,而是按帧节奏被 sensor 拾取。常见做法是出流前一次性配置完,运行中不频繁改写。如果产品需要动态追踪对焦框,那就要算好帧边界,避免在窗口更新瞬间去读相位差数据,否则读到的可能是新旧窗口混合的结果。
另外,模组为了结构排布经常做水平翻转。手册 6.1 节明确提示,水平反转后相位差数据的符号会反转,原因不复杂:左右屏蔽像素的角色互换了。处理方法是保持寄存器配置不变,在算法侧根据 mirror 状态对读回来的相位差统一取反。我在项目里踩过一次,症状是对焦马达永远朝远离合焦点的方向走,每次开机都是先拉远再拉近,最后定位到就是符号问题。
5. 相位差数据输出、校准与避坑: 五个值得写进文档的坑
5.1 MIPI 与 I2C 两条通路怎么分工
相位差数据可以从 MIPI 随帧输出,也可以从 I2C 直接读,两条通路互不冲突,但使用场景完全不同。
MIPI 通路在 0x3e37 置 1 后生效,数据随图像帧一起带出来,适合实时对焦控制,数据量对后端 ISP 和算法透明。I2C 通路则是 0x7c08 起的只读区,适合调试、产测和低功耗场景。产线校准工具通常纯 I2C 读取,因为不起 MIPI 就能快速验证 SPC 参数是否烧录成功,省掉一套 mipi 解析逻辑。
我建议开发阶段两条通路都开着:MIPI 数据喂算法,I2C 数据打日志,两边数值对得上说明整条链路健康;对不上,就去查帧时序和寄存器配置,而不是先怀疑算法。
5.2 SPC 与 DCC: 校准系数不是玄学,是必须做的
手册第 7 章用一整章讲模块校准,核心是两个系数。SPC(屏蔽像素校正)用来校正屏蔽像素本身的响应差异,不校正的话,画面上会出现固定的颗粒或条纹。DCC(散焦转换系数)单位是 DAC/pixel,作用是把像素域的相位差换算成镜头驱动器的 DAC 步数,对焦控制靠它算出马达要推多远。
校准系数要固化在模组的 EEPROM 或 OTP 里,系统上电后加载到 sensor 或 ISP。手册 7.4 节给了 SPC 参数的设定时机,实际操作中我遇到的翻车场景是产线换了镜头批次但没有重新标定 DCC,结果 PDAF 每次都对焦偏近或偏远,画面永远差一口气。标定件的更换记录和系数版本号,建议在驱动日志里打出来,方便回查。
5.3 常见问题与排查: 现象、原因、解决
问题 1:边缘窗口的相位差全是无效值现象:固定窗口模式下,画面边界附近的格子读回来的数据是 invalid code,置信度也是 0。原因:AF 窗口超出了最终图像边界,手册规定越界格子输出无效码并且不参与检测。解决:写寄存器前先把窗口坐标裁剪到图像尺寸内,最好在驱动里统一做一次 clamp,不要留给上层去猜。
问题 2:开 mirror 后马达一直往反方向推现象:图像正常,对焦框位置也正常,但镜头总是朝远离合焦的方向运动。原因:水平翻转让左右屏蔽像素互换,相位差符号反了,手册 6.1 节专门讲过这一点。解决:根据 mirror 状态对相位差做符号翻转,或者在校准阶段把符号方向写进系数表,二选一并在移植文档里标注清楚,避免后面接手的人重复踩。
问题 3:配置了灵活窗口,出来的还是固定窗口数据现象:AREA_EN_0 到 AREA_EN_7 都置 1 了,MIPI/I2C 读回来的格子数还是 16x12 或 8x6。原因:灵活模式有硬门槛,0x38a8~0x38ab 的宽高寄存器必须整体写 0x1FFF,很多人漏了这一步。解决:回读这四个寄存器,确认高 5 位都是 0x1F、低 8 位都是 0xFF,再检查 AREA_EN。
问题 4:窗口位置偏了一半或者整体错位现象:对焦框实际位置和预期位置差一格,或者差几倍,看起来像坐标缩放错误。原因:13 位坐标拆两个寄存器时,高 5 位和低 8 位写反,或者只写了低 8 位,高位地址保持默认值。解决:统一用拆分函数写入,写完立刻回读拼回 13 位原始值,与预期值比对,这一步在产线测试脚本里也应该保留。
问题 5:出流中改窗口,相位差数据偶发错乱现象:运行中动态更新 AF 窗口后,相位差数据时好时坏,偶尔整组窗口的数据顺序错位。原因:窗口反射时序和帧同步问题,Streaming 状态下写入可能落在帧中间,或者不被本次帧拾取。解决:把窗口配置全部放在 SW standby 阶段完成,运行时只读数据。确需动态切换,就在帧消隐期写入,并等两帧之后再读取,写入窗口用手册 5.3 的时序参数设计。
这五条是我在 IMX378 系列模组调试中反复遇到的高频问题,前三条几乎是每套新模组都会碰到的。把这五条写进你的移植文档,至少能让后面接手的人省下一周的黑盒调试时间。这份手册的 PDF 里带有完整的窗口坐标示例图,调参时对照着看比自己推导快很多。
6. 验证 PDAF 数据的最后一公里: I2C 回读与 MIPI 交叉验证
新模组点亮的当天,我最先做的事不是调对焦,而是验证相位差数据链路。做法是同时打开 MIPI 输出和 I2C 数据区,借助帧同步信号抓一帧 MIPI 里的相位差,再通过 I2C 读同一帧的 0x7c08 数据区,两边对一下数值。这个交叉验证能一次性暴露寄存器配置错误、MIPI 打包偏差、解析字节序三类问题,比单独看一路数据可靠得多。
# i2c_verify_pdaf.py: 16x12 固定窗口模式, 读 0x7c08 起 768 字节 import smbus2 bus = smbus2.SMBus(1) SENSOR_ADDR = 0x20 # 按模组实际 I2C 地址修改 # 768 字节 = 192 窗口 × 4 字节(相位差 2B + 置信度 2B) data = bus.read_i2c_block_data(SENSOR_ADDR, 0x7c08, 192 * 4) for idx in range(192): off = idx * 4 phase_raw = (data[off + 1] << 8) | data[off] conf_raw = (data[off + 3] << 8) | data[off + 2] # 相位差按有符号数处理, 高位为 1 表示负方向 phase = phase_raw - 0x10000 if phase_raw & 0x8000 else phase_raw if idx < 8: # 先打印前 8 个窗口, 完整数据落文件 print(f"win {idx:02d}: phase={phase:5d} conf={conf_raw}")解析格式上有两点要交代清楚。第一,4 字节一组(相位差 2 字节加置信度 2 字节)的假设只对 16x12 固定窗口模式成立,因为 768 字节正好等于 192 乘以 4;换到 8x6 模式,每窗口的实际字节数要以手册 Table 6-1 为准。第二,相位差是否有符号、字节序是大端还是小端,不同固件版本可能有差异,脚本跑出来的值如果和 MIPI 解析值系统性差一个符号,优先怀疑这里。
脚本跑通之后,我会把 I2C 回读值和 MIPI 解析值放在同一张表里逐窗口对比,偏差超过 1 个像素就回去查帧时序,而不是先改算法。从那以后,我每点亮一颗新模组,都会强制走一遍"回读拼接 → MIPI 对比 → mirror 符号检查"这套流程。它帮我抓出过三次产线换料后坐标寄存器地址漂移的问题,也让我养成了先把数据链路验证干净再动对焦算法的习惯。希望帮到你。
本文还有配套的精品资源,点击获取