
简介面向索尼 IMX378 CMOS 图像传感器的软件参考手册围绕相位检测自动对焦PDAF功能展开适合相机驱动、ISP 与嵌入式视觉开发人员查阅。该传感器在有效像素区域内部以 99% 块占用率嵌入左右两类金属屏蔽像素每个 16×32 像素块含 8 个右屏蔽像素和 8 个左屏蔽像素手册据此说明屏蔽像素对齐、相位检测操作模式、控制寄存器与执行序列并详解 AF 窗口设定以及通过 MIPI/I2C 读取相位差数据的方法还涉及模块校准有助于实现快速精准的对焦。资源为 1 个 PDF 文件压缩包约 1.67MB即 v1.0.0 完整电子版章节覆盖相位检测控制序列、固定与灵活 AF 窗口模式、相位差数据输出及水平反转时的符号处理末尾修订历史记录 2015 年 12 月至 2017 年 2 月的更新与功能补充。目前已有 1586 人学习下载可作为 IMX378 开发与 PDAF 调优的常备参考。1. IMX378的PDAF软件参考手册到底在讲什么相位对焦不是玄学IMX378_Software_Reference_Manual_For_PDAF_v1.0.0.pdf这文件名很长但它解决的事情其实只有一个让驱动工程师在对焦这件事上少走弯路。我在做某跨平台系统的主摄驱动时第一次接触这颗传感器习惯性翻寄存器手册找对焦点结果发现IMX378的PDAF并不是把PD像素裸数据直接送出来而是先在sensor内部做一轮相关性统计再把统计值通过寄存器交给上层。软件参考手册跟硬件手册不一样它不跟你讲电位和时序而是告诉你软件要怎么初始化、读哪个地址、数据放在哪、什么时候读才不会拿到半帧脏数据。这份v1.0.0版手册适合正在调IMX378驱动的开发者也适合对接影像算法的工程师——你可以把它当成一条PDAF链路的接口说明而不是对焦光学原理教科书。2. 读懂IMX378 PDAF手册先抓寄存器、数据流、同步三块别急着抄代码2.1 手册里常见的五个功能块驱动代码基本顺着它长软件参考手册一定不是让你从头读到尾的。以我读多份sensor手册的经验IMX378的PDAF手册核心一般按五块组织第一块是全局设置和软件复位告诉你PDAF模块怎样从默认状态恢复第二块是模式选择和窗口配置决定PD像素在画面哪个区域生效第三块是PDAF统计输出格式包括寄存器地址、比特位宽、字节序第四块是同步机制说明统计值什么时候更新、曝光和读出之间是什么时序关系第五块是错误状态一般是错误寄存器或异常统计值。我建议第一次打开PDF时先翻目录把自己正在做的事归到其中某一两块。只是要把PDAF跑起来就先看第一块和第二块跑起来但数据不对重点看第三块的格式解释如果读到的相位差时有时无大概率问题出在第四块同步上。不要先把整张寄存器表抄进驱动再回头查那是事后补课的效率。另外注意Software Reference Manual里的寄存器可能跟Sensor Register Map手册里的寄存器有重叠但描述角度不一样。参考手册往往会给出来龙去脉哪个寄存器是初始化序列的一部分哪个寄存器只能在非streaming状态下写。我习惯的做法是把参考手册里的初始化序列和register map的默认值做一次交叉验证遇到冲突时以当前使用版本的参考手册为准因为它的针对性更强。2.2 画出PDAF数据流比背寄存器快十倍PDAF调试最怕的是什么是你不知道手上这个数据到底是谁产生的。IMX378的PDAF典型数据流可以分成下面几步步骤位置输出/消费方PD像素曝光sensor光电二极管阵列模拟读出行读出与信号分离sensor内部信号处理普通图像与PD通道分离相关性统计计算sensor内部逻辑生成相位差统计值如SAC/PDV统计值写入寄存器sensor寄存器堆驱动通过I2C/CCI读取驱动透传内核I2C层上层对焦算法库对焦算法换算AP端镜头步数 / AF电机指令这张表不用抄但一定要在自己脑子里画一遍。因为调试时会遇到一个问题读到的PDAF值不稳定你很难判断是寄存器读早了同步问题还是统计窗口选得不对窗口配置问题还是数据格式理解错了解析问题。把数据流画出来之后至少能按位置切分责任sensor内部到寄存器这一段归驱动配置管寄存器到算法这一段归I2C时序和解析管。以我的血泪经验大多数PDAF“不动”的现场问题都出在PD像素到寄存器之间不是算法不会算。所以接下来重点是理解手册里统计值更新时间和帧时序的关系。手册一般会写“统计值在frame start之后多少行有效”这个数值非常关键。建议做成驱动里的配置项而不是写死在代码里因为不同分辨率模式下的行数差异很大。2.3 版本v1.0.0在告诉你什么边界要自己探标题里的v1.0.0值得留意。初版手册通常配合第一批工程样片编写覆盖的模式、寄存器布局、初始化序列多半只保证几个主推的预览和拍照模式。我在某个模拟项目X上遇到过翻车用预览模式的PDAF配置套到高分辨率拍照模式sensor切换模式之后统计值完全乱掉。查下来发现手册只在特定mode下验证过其他模式不在v1.0.0承诺范围内。所以拿到v1.0.0这份手册不要默认所有分辨率、所有帧率都支持PDAF。建议在做驱动矩阵时单独列一个“PDAF有效模式”表把手册里明确出现过的分辨率、帧率、binning方式记下来没有提到的模式就降级用对比度对焦别硬把PDAF塞进去。这个习惯能帮你省掉不少线上问题。初版手册还会有一些已知限制通常放在前言或附录里比如统计窗口最大尺寸、最小曝光行数限制、PD像素掩码要求。这些限制不会出现在寄存器表里但会在量产时变成坑。读手册时把这类限制抄在驱动代码头部的注释里是笨办法但很管用。3. 驱动侧落地把IMX378的PDAF配置写进sensor驱动三组代码解决90%初始化3.1 PDAF初始化序列从software reset到stats ready从手册里能提炼出一条公用的初始化顺序先把sensor置于软件复位状态接着设定PDAF模式相关寄存器再配置统计窗口最后使能统计输出。不要在setting table里把PDAF使能放在streaming开启之后很多sensor要求PDAF寄存器在non-streaming状态写入。常见做法是在sensor驱动的set_mode回调里先调用一个独立的PDAF配置函数再走原来的sensor setting写表流程。我自己习惯把它拆成单独文件原因很简单PDAF寄存器组涉及窗口、掩码、统计使能跟普通曝光/增益配置混在一起时后期调试很难定位。下面是最小初始化序列的代码寄存器地址请替换成手册寄存器表里的实际地址我这里只写逻辑占位。/* imx378_pdaf.h */ #define IMX378_PDAF_ENABLE 0x5001 /* 占位以手册为准 */ #define IMX378_PDAF_WIN_X 0x5002 #define IMX378_PDAF_WIN_Y 0x5003 #define IMX378_PDAF_WIN_W 0x5004 #define IMX378_PDAF_WIN_H 0x5005 #define IMX378_PDAF_STATS_ADDR 0x5100 #define IMX378_PDAF_STATS_LEN 12 static const u8 imx378_pdaf_init_table[][2] { { IMX378_PDAF_ENABLE, 0x01 }, /* PDAF模块使能 */ { IMX378_PDAF_WIN_X, 0x04 }, /* 窗口起始列 */ { IMX378_PDAF_WIN_Y, 0x04 }, /* 窗口起始行 */ { IMX378_PDAF_WIN_W, 0xF0 }, /* 窗口宽度 */ { IMX378_PDAF_WIN_H, 0x60 }, /* 窗口高度 */ };static int imx378_pdaf_configure(struct i2c_client *client) { int i, ret; /* 先退出PDAF模块复位再写配置 */ imx378_i2c_write(client, IMX378_PDAF_ENABLE - 1, 0x00); for (i 0; i ARRAY_SIZE(imx378_pdaf_init_table); i) { ret imx378_i2c_write(client, imx378_pdaf_init_table[i][0], imx378_pdaf_init_table[i][1]); if (ret) return -EIO; } /* 统计值更新需要时间至少等1~2帧 */ usleep_range(16600, 33000); return 0; }逻辑说明函数先把PDAF模块的软复位解除再按顺序写入使能和窗口参数。最后的usleep_range是必要的窗口配置要经过sensor内部几行读出才会生效写完后立刻读stats容易拿到全零数据。若当前预览帧率为30fps一帧33ms这里等半帧到一帧是底线我一般会放到两帧避免切换分辨率后时序不稳定。窗口参数注意点X/Y是起始坐标W/H是宽高长宽通常要求偶数对齐否则sensor内部PD像素分组会被截断。如果手册给了窗口对齐限制最好在驱动里做一次W % 2 0之类的校验不要等sensor返回异常再排查。3.2 读取PDAF统计值字节序和读取时机决定数据真伪配置写完下一步就是读统计值。IMX378的PDAF统计值通常不是实时连续更新而是每个帧周期内更新一次读取时机非常关键。下面这段代码封装了一次读取static int imx378_pdaf_read_stats(struct i2c_client *client, s16 *dst, int len) { int ret, i; u8 buf[12]; ret imx378_i2c_read(client, IMX378_PDAF_STATS_ADDR, buf, len); if (ret 0) return ret; /* 手册里统计值一般是大端按实际字节序调整 */ for (i 0; i len; i 2) dst[i / 2] ((s16)buf[i] 8) | buf[i 1]; return len / 2; }逻辑说明读取长度必须是偶数手册里会给出每个统计通道的字节宽度。常见格式是有符号数所以强转成s16避免把负值变成无符号大数。循环里做了大端到CPU小端的转换如果手册写明是小端就把循环改成buf[i] | (buf[i1] 8)。字节序这一点特别容易翻车。某一次我读到所有相位差值都是正数并且伴随固定偏置后来发现是字节序反了负的差值被拆成了错误的两个字节。还有一个隐蔽问题如果驱动在sensor stream on之前调用这个读函数读回来的可能只是寄存器默认填充值不是真实统计。所以读取前一定要确保至少完成过一帧输出。3.3 I2C批量写入封装带一次重试避免偶发NACK配置PDAF时往往要写十多个寄存器逐条写还要处理异常。我通常封装一个批量写表函数把寄存器地址和值做成表统一走I2C写接口。加了重试之后驱动稳定性好很多。struct imx378_reg_val { u16 addr; u8 val; }; static int imx378_write_table(struct i2c_client *client, const struct imx378_reg_val *table, int size) { int i, ret; for (i 0; i size; i) { ret imx378_i2c_write(client, table[i].addr, table[i].val); if (ret 0) { /* I2C偶发NACK重试一次 */ ret imx378_i2c_write(client, table[i].addr, table[i].val); if (ret 0) return ret; } } return 0; }逻辑说明重试逻辑只在写入失败时触发正常情况下不会多占用I2C总线。不要做无限重试因为sensor模式切换有超时预算连续失败两次就返回错误让上层走错误处理比卡在驱动里好。参数size的单位是条目数调用前确保数组长度匹配避免越界。这套写表函数也可以复用到曝光、增益等普通寄存器配置上不局限于PDAF。4. 对焦算法对接把PDAF统计值换算成镜头步数4.1 相位差从哪来统计值不是镜头位移拿到寄存器里的有符号数值后千万不要直接把它当镜头步数喂给AF马达。这个统计值描述的是左右PD像素分组之间的相关偏移量单位是像素或者亚像素不是步进电机步数。手册里会叫它SAC、PDV或者类似的名字本质上都是一个相关性差值。镜头把统计值换算成步数需要经过标定。以我的理解换算链路是这样sensor统计值 → 减offset无穷远基线的中心值 → 乘增益统计值到Lens DAC步数的斜率 → 加dead zone不响应区 → 最终步数。这里的offset和gain每个模组都不一样需要在产线或者实验室标定。v1.0.0手册不会替你生成这些参数它只会告诉你怎么读到原始统计值。4.2 三步换算减offset、乘gain、设dead zone下面是常见的一个换算实现使用定点运算避免浮点性能损耗。struct imx378_pdaf_calib { s16 offset; /* 无穷远位置统计均值 */ u16 gain; /* 统计值到步数增益Q8定点 */ u16 dead_zone; /* 不响应区单位步数 */ }; static int imx378_pdaf_to_step(s16 raw, const struct imx378_pdaf_calib *cal) { s32 v (s32)raw - cal-offset; s32 step; /* gain是Q8格式实际值为 gain / 256 */ step (v * cal-gain) 8; if (abs(step) cal-dead_zone) return 0; /* 限制到镜头行程范围内 */ step clamp(step, -MAX_AF_STEP, MAX_AF_STEP); return step; }逻辑说明先减offset把无限远处统计值拉回零附近再乘gain并右移8位完成Q8定点除法。dead_zone的作用是屏蔽噪声和微小闪烁带来的微步抖动建议设成1到3个实际步数。返回值是带符号的镜头步数正负号对应镜头移动方向。参数选择上offset和gain不能从手册抄需要每个镜头模组单独标定。dead_zone也不是越大越好设大了小对焦点位置会有静区看起来对焦“迟钝”设小了容易在静止场景出现AF微抖。工程上一般用一组不同lux下的实测曲线来定。4.3 同步机制与参数表曝光不同步统计值就是噪声PDAF统计值依赖曝光。如果PD像素曝光时间跟主图像不完全一致统计出来的相位差会和实际画面位置错位。IMX378手册里通常会说明PDAF是否支持与主曝光锁定或者在哪种曝光方式下统计才是可信的。参数具体含义推荐做法offset无穷远基线统计值产线每模组标定保存OTP或calibration文件gain统计值到步数斜率按焦距段分段标定Q8定点dead_zone不响应区1~3步弱光适当调大stats窗口统计区域位置/大小跟随对焦主体建议不小于画面的8%曝光同步主帧与PD通道曝光是否一致手动曝光场景先等AE稳定再读stats我通常会在驱动里把曝光同步开关暴露给上层自动曝光时不做额外干预但手动对焦或者暗光对焦时要求上层先把AE固定下来再连读三帧stats取中值。这样做能让弱光下的对焦成功率明显提升。如果你发现同一场景对焦步数每次读都不一样先检查是不是曝光还在收敛十次里有八次是这个原因。5. PDAF调试避坑笔记五个真实翻车现场现象、原因、解决5.1 PDAF统计数据全零但寄存器看起来都对现象通过I2C读到stats地址全是0主图像正常寄存器设置和手册一致。原因大概率是统计窗口设置在有效像素区域之外或者当前分辨率做了binning后PD像素被隔行读取统计逻辑空转。还有可能是读取时机太早sensor还没完成第一帧统计。解决先把窗口设成传感器输出中心一小块区域比如宽高各取几十像素确认有非0值再扩到实际AF区。同时在驱动里等frame start中断后延时至少一行时间再读stats。如果手边有手电筒照向镜头并左右移动观察数值是否变化能快速判断统计逻辑到底有没有在生产。5.2 对焦闭环震荡相位差符号方向反了现象AF马达来回拉风箱或者每走一步统计值绝对值反而变大。原因PD像素排列有方向性驱动解析时左右相减的顺序和手册给的方向不一致符号就反了。解决在实验室用点光源从固定方向偏移记录统计值变化。如果符号和预期相反就把解析层的相减方向调换或者交换统计窗口里左右分组索引。这个改动一般在驱动解析层完成不需要重写sensor校准参数。符号问题越早发现越省事拖到算法联调阶段会让对方以为镜头马达线性度有问题。5.3 预览流正常拍照后对焦却失效现象预览时AF工作正常按下快门后照片对焦点明显偏移。原因拍照模式下sensor可能切换到不同分辨率的读出模式PDAF窗口配置没有跟随mode更新于是统计窗口仍然指向预览分辨率的坐标但画面比例已经变了主体不在窗口内。解决把PDAF窗口配置做成按mode分辨率归一化切换拍照模式后重新配置窗口并读一次stats做非0校验。另一种情况是拍照流程里驱动把PDAF寄存器复位了需要在capture sequence里保留PDAF配置项对比一下预览和拍照两条set流即可发现。5.4 弱光下PDAF精度下降盲目加大gain导致噪声现象暗光环境对焦比对比度对焦还慢或者来回找。原因PDAF统计值在弱光下信噪比低加大gain后噪声同步放大dead zone没调大于是噪声被当成真实相位差送进算法。解决按环境亮度分档调参数弱光下增大dead_zone降低对统计值的信任度同时融合对比度对焦结果做兜底。v1.0.0手册大概率不会给你弱光参数这块只能靠实拍标定。建议在驱动里预留节点支持运行时切换校准参数产测时再固化。5.5 驱动升级后PDAF行为改变v1.0.0手册没更新现象换新sensor驱动版本后同一寄存器配置下PDAF响应明显变化回看手册内容却没有变化。原因sensor内部固件或微码版本变化导致统计算法行为更新而参考手册发布滞后。解决记录实际模组的sensor ID和驱动commit。出现变化后优先对比两个版本驱动里PDAF初始化序列的差异如果序列完全一致就把问题反馈给传感器原厂要求补充release note。不要自己凭感觉改一堆寄存器参数那会让问题更不可控。6. 进阶验证技巧用一帧PDAF stats给整条链路做体检PDAF调通之后我建议你都做一个自检工具它能在三分钟内区分问题在sensor、驱动还是算法。做法很简单固定镜头朝向均匀亮度墙面循环读取stats寄存器记录基线值然后用一只手在镜头侧面遮挡观察统计值是否发生固定方向的变化。如果遮挡前后统计值纹丝不动说明统计窗口没对准或者数据流在驱动侧断了。下面这段自检代码我一般会放在sensor驱动内部通过debugfs导出一个节点方便QA直接跑不用每次挂调试器。void imx378_pdaf_selftest(struct i2c_client *client) { s16 baseline, covered; imx378_pdaf_configure(client); baseline imx378_pdaf_read_stat(client); /* 用手掌挡住镜头左半侧等待2帧 */ usleep_range(66000, 99000); covered imx378_pdaf_read_stat(client); pr_info(PDAF baseline%d covered%d diff%d\n, baseline, covered, covered - baseline); }逻辑说明baseline是均匀墙面下的统计值一般应在offset附近covered是遮挡后的统计值二者差值的绝对值如果大于一个阈值说明统计链路是通的。阈值可以用4.2节里的dead_zone乘2。若diff接近0下一步就去检查窗口坐标和读取时序别去调算法。我的一个习惯是每次移植新平台都会先把这段自检代码放进驱动等它通过后再去跑产线标定和AF闭环。它帮我排除过至少三次“sensor没问题、驱动读错了地址”的尴尬也省掉了不少和算法工程师扯皮的活儿。记住一个原则PDAF调试最忌讳反复改寄存器参数却不记录现象先相信手册再怀疑驱动最后才怀疑硅片本身。希望这个思路和这些代码能帮到你。本文还有配套的精品资源点击获取