
简介面向手机/平板相机模块及嵌入式设备如无人机、智能家居相机开发者的AF驱动源代码包基于C语言实现自动对焦Auto Focus驱动逻辑覆盖对比度检测、相位检测等对焦模式并提供I2C/SPI通信接口便于集成VCM音圈马达与常见图像传感器。资源针对cn3927、dw9714、dw9763、dw9800、pd9215bl等常用驱动芯片整理包含完整的头文件、C源文件与Android.bp构建脚本共计15个文件压缩包约19KB结构紧凑适合直接参考或移植到相机模组项目中。代码内置参数配置、异常检测与日志输出开发者可根据光照环境动态调整步进大小、延迟时间等参数降低联调排错成本。目前已有422人学习/下载既能帮助驱动开发人员快速实现自动对焦功能也可作为学习AF控制算法与驱动分层设计的入门实例。 做手机摄像头模组底层驱动的人几乎都会撞上同一类器件——AF驱动芯片。镜头要自动对焦靠的是VCM音圈马达推动镜片而VCM本身不认电流大小它只认驱动芯片给出来的电流波形这个波形又由一颗芯片内部的DAC寄存器决定。我这些年经手的模组项目里AF驱动芯片出现频率最高的就是CN3927、DW9714、DW9763、DW9800、PD9215BL这几个型号。很多从单片机转来做嵌入式Linux驱动的人第一次看到这些芯片的寄存器手册都会懵明明都是I2C接口为什么有的驱动要写两字节有的要写三字节为什么同一个模组换一颗芯片之后对焦方向反了这篇博客就把我实际移植和调试这几颗AF驱动芯片时沉淀下来的源代码结构、寄存器操作逻辑、移植套路和踩坑经历一次性整理出来。1. 六位数驱动代码后面的型号差异CN3927、DW9714、DW9763、DW9800、PD9215BL怎么选1.1 这些芯片解决了什么问题AF驱动芯片本质上是一颗I2C控制的DAC电流源。主控SoC通过I2C总线把对焦位置对应的数字量写到芯片寄存器里芯片内部的DAC把数字量转换成模拟电压或电流再经H桥或推挽电路施加到VCM马达两端镜头就能移动到指定位置。这个过程看起来简单但实际工程上牵扯到上电时序、I2C时序、寄存器格式、OTP数据读取、马达行程标定等一堆细节。不同型号之间的差异就是这些细节的排列组合。视频通话、扫码、拍照、夜景模式不同场景下对AF马达的行程、速度、稳定性要求差异很大。比如普通扫码模组对AF精度要求低10位DAC的DW9714绰绰有余但高像素旗舰主摄往往需要支持温度补偿、OTP校正的芯片DW9763这类带存储单元的驱动方案就更有优势。做驱动层的人不能只看“能驱动马达”而是要根据模组选型手册来适配寄存器读写逻辑。1.2 五款型号的共性参数与差异点这批芯片虽然厂商不同底层操作模式却高度相似几乎都遵循同样的寄存器写入套路先发一条控制命令再发数据字节最后等待马达稳定。以下表格是我在项目里整理通用架构时参考的常见规格具体参数请务必以每一颗芯片的官方Datasheet为准因为同一型号还有不同封装版本电气特性会略有偏差。型号常见厂商阵营DAC精度OTP支持典型I2C从机地址我的使用印象CN3927国产方案10位左右视批次而定0x18附近常见于中低端模组寄存器布局和DW系列高度相似移植时可先按DW9714套路试DW9714Dongwoon系列10位无0x18最经典的基础款上电就能用适合做AF驱动的入门模板DW9763Dongwoon系列10位有0x18多了OTP空间可以存镜头校正值和出厂信息驱动需要额外处理OTP读取DW9800Dongwoon系列12位常见部分支持0x18附近精度更高适合对焦点更密的马达寄存器位数可能不同PD9215BL韩系/国产兼容10-12位视规格而定0x18附近寄存器写法和DW系列类似但要关注芯片内部LDO电压可调档位从上表能看出5个型号的I2C地址都在0x18附近这也是很多驱动代码能通用移植的基础。但地址相近不代表寄存器细节相同尤其是高字节DAC位数和OTP操作命令必须逐位核对。我在项目里会先建一个“芯片规格速查表”把每颗芯片的关键寄存器地址、DAC位宽、上下电命令、OTP触发方式记录下来这样后面写通用驱动框架时只需要替换一张表就行。2. 从零搭建AF驱动基本框架源代码结构与设备注册2.1 驱动注册和设备树匹配在Linux系统里AF驱动多数以I2C client驱动形式存在。无论上层走V4L2 subdev还是普通字符设备底层都离不开三个核心步骤匹配设备、初始化硬件、设置对焦位置。建立源代码资源时我习惯先把不依赖具体型号的框架搭好再针对不同芯片做适配。一般驱动里会先定义一个i2c_device_id表让内核在I2C总线枚举时能发现设备static const struct i2c_device_id af_driver_id[] { { cn3927, (kernel_ulong_t)cn3927_af_cfg }, { dw9714, (kernel_ulong_t)dw9714_af_cfg }, { dw9763, (kernel_ulong_t)dw9763_af_cfg }, { dw9800, (kernel_ulong_t)dw9800_af_cfg }, { pd9215bl, (kernel_ulong_t)pd9215bl_af_cfg }, { } }; MODULE_DEVICE_TABLE(i2c, af_driver_id);同时也要在of_match_table里加上设备树兼容字符串。设备树节点要确保I2C地址、供电GPIO、复位GPIO、马达方向等参数能被驱动读取到static const struct of_device_id af_driver_of_match[] { { .compatible dongwoon,dw9714, .data dw9714_af_cfg }, { .compatible dongwoon,dw9763, .data dw9763_af_cfg }, { .compatible dongwoon,dw9800, .data dw9800_af_cfg }, { .compatible giantec,pd9215bl, .data pd9215bl_af_cfg }, { .compatible cn,cn3927, .data cn3927_af_cfg }, { } }; MODULE_DEVICE_TABLE(of, af_driver_of_match);之所以把配置指针放在device_id里而不是直接写死在代码里是为了让驱动的probe函数只做通用初始化然后把DAC位宽、寄存器格式、方向标记等差异全部交给配置结构体。这样五颗芯片共用一套probe、remove、runtime_pm函数代码量能减少一半。2.2 配置结构体的设计思路我给每颗芯片准备了一个af_device_cfg结构体里面存的是型号差异比如写位置时命令字节、DAC有效位数、移动方向掩码、是否需要OTP初始化等。这样一个驱动支持多颗芯片时代码里只有数据表在增长逻辑分支基本可以保持恒定。struct af_device_cfg { u8 dac_bits; u8 pos_cmd; u8 vcm_mode_cmd; bool need_otp_init; bool direction_invert; };probe函数里只需要根据device_id里的data字段取出对应配置再用regmap初始化I2C读写通道最后注册字符设备或者V4L2 subdev。只要设计到位后面新增一颗型号无非是添加一个结构体实例连probe都不需要动。这个经验是我从同时维护三个模组项目的痛苦里总结出来的如果没有抽象层每一颗芯片写一个驱动文件后面升级内核版本或者改电源管理策略就是双倍甚至三倍的工作量。3. AF驱动的核心控制逻辑寄存器写入与位置设置3.1 10位DAC位置数据的写入方法AF驱动最常用的操作就是把位置值写到芯片。以DW9714为例它的位置寄存器是一个16位寄存器其中高6位一般为控制位或保留位低10位是DAC值。写入时一般先发命令字节0x00然后发高字节、低字节。在简化实现中很多驱动会直接把位置值套进两字节发送但真正到了移植阶段必须根据Datasheet确认高字节哪几位是控制位哪几位是数据位。我常用的可复用函数是这样写的static int af_write_pos(struct i2c_client *client, u16 pos) { struct af_device_cfg *cfg i2c_get_clientdata(client); u8 buf[3]; if (cfg-direction_invert) pos (1 cfg-dac_bits) - 1 - pos; buf[0] cfg-pos_cmd; buf[1] (pos 8) 0x0F; buf[2] pos 0xFF; return i2c_master_send(client, buf, 3); }这里buf[1]取低4位是因为10位DAC的最高数据位只占高字节的低4位高字节的高4位可能用来设置芯片的其他功能比如快速模式、省电模式。如果不小心把高字节内容全清了马达可能不动或者进入异常状态。3.2 OTP初始化与上电顺序的寄存器差异DW9763这类带OTP的芯片就比DW9714麻烦。OTP区存着镜头马达的出厂校准数据驱动在设置对焦位置前要先读OTP数据并缓存。OTP读取往往依赖时钟引脚有的叫CLK或MCLK芯片只有在指定频率下才能解析OTP数据。我在第一次调DW9763时没给CLK引脚供时钟直接读寄存器结果读回来的都是0xFF折腾了半天才意识到问题。另外这些芯片内部有自己的状态机上电后可能需要先发一个软件复位或者唤醒命令再等几毫秒才能操作DAC寄存器。有的芯片在寄存器写入地址上还区分“数据命令”和“控制命令”顺序错了不会报错但马达就是纹丝不动。最稳妥的做法是在probe函数里按Datasheet的时序图一步步验证先上VDD和VIO再拉复位引脚延时发唤醒命令延时再写一个中间位置比如100确认I2C总线有ACK并且马达有动作最后才继续上层对接。4. 移植和调试中最容易踩的坑I2C地址、方向、电源时序4.1 I2C从机地址的7位和8位陷阱AF驱动芯片的Datasheet里写I2C地址0x18指的是7位地址。但很多SoC的I2C框架在设置client地址时会自动左移一位变成0x30作为8位地址。这个细节在驱动代码里一般不直接暴露但在设备树里容易写错。如果设备树里按0x30填探测时会发现i2cdetect列出的地址是0x18而实际设备可能是0x30。一旦地址对不上驱动probe就失败。我在调试时有个习惯先用i2cdetect扫描总线看芯片到底在哪个地址下回应ACK。然后对比Datasheet确认是7位还是8位表示方法。绝大多数AF驱动芯片都支持7位地址0x18如果设备树里写0x18内核会自动做移位这是正常现象但如果Datasheet给的8位地址是0x30设备树里就要写0x18还是0x30必须看SoC的I2C控制器的约定。这个规则因平台而异不能死记硬背。4.2 马达方向反了怎么办换模组最头疼的问题就是镜头移动方向反了。同一个驱动代码同一个型号芯片换一个模组厂商的镜头VCM的线圈绕向或者磁钢方向可能不一样导致DAC值增大时镜头反而从近处往远处跑。解决方式不能在硬件上改动只能软件里做映射。做法是在配置结构体里加一个direction_invert标志设置位置时执行pos max - pos。问题是这个标志在模组厂文档里往往不明确写只能通过实测判断把位置设成0和最中间值观察能否在近景和远景切换。如果发现200的位置比50的位置更清晰那说明方向反了。我一般会在驱动里加一个调试节点允许运行时直接写一个位置值并读取寄存器回读值快速判断方向映射。4.3 上电时序和I2C无ACK的排查链路有一次在调PD9215BL时I2C总线始终无ACKi2cdetect在0x18地址也看不到设备。我第一反应是设备树地址错了结果反复核对没问题又怀疑I2C总线被占用换了一路总线还是不行。最后用示波器抓电源波形才发现VIO比VDD晚了几百毫秒而这个芯片要求VDD和VIO之间必须在规定时间内上电否则芯片内部逻辑起不来。那次之后我总结出一套排查链路分享出来大家可以参考先看原理图确认VDD、VIO、CLK、RESET引脚是否和驱动配置一致用i2cdetect扫描总线确认设备地址是否出现如果无设备抓I2C波形看SCL/SDA上拉电平是否正常ACK位是否被拉低如果ACK但寄存器读写无响应查配置的寄存器地址和命令字节是否和芯片型号匹配用示波器看写入时I2C时序确认START、STOP条件完整字节之间ACK正常。这套链路帮我在后续几个项目中省了大量时间。尤其对于CN3927这种兼容型芯片它的寄存器布局和标准DW9714略有差异直接照搬DW9714命令可能只能读到ACK但位置不动这时就要回到第4步一条条验证命令字节。4.4 寄存器读回和OTP数据的缓存策略有些驱动要支持断电再恢复这时候OTP数据不能被反复读取因为OTP区有读取寿命限制虽然通常很高但每次上电都读一遍反而增加延时风险。我一般在第一次probe时把OTP数据读到驱动私有结构体里后续对焦设置都用缓存数据计算不再重复读取。如果后续有断电睡眠流程也在resume阶段优先恢复缓存而不是再次跑完整OTP初始化。这里有个小技巧OTP区除了存校正值通常还会有芯片ID或版本号。驱动可以把读取到的ID打印出来既验证了OTP通路又能区分不同批次芯片。我在调试DW9763时通过打印ID确认了手上模组用的是支持温度补偿的版本才决定在驱动里打开温度补偿寄存器。5. 源代码资源的组织方式与复用策略5.1 如何整理一份多芯片共用的AF驱动资源包既然标题里点名了5个型号说明大家需要的是“资源池”不是单个驱动。我在工程上会把源代码资源按照四层来组织第一层公共框架层包含probe、remove、电源管理、字符设备接口第二层I2C操作层包含regmap读写、I2C地址校验、时序封装第三层芯片配置层每个型号一份cfg结构体独立存放方便单芯片专项修改第四层调试工具层包含ioctl命令、位置设置接口、寄存器读写接口。这样做的好处是新增芯片只需要在第三层新增文件其他层几乎不动。之前我把DW9714驱动改成支持DW9763时公共框架一行没改只添加了OTP初始化和校准数据读取逻辑大概花了半天时间就完成了验证。如果每次都复制粘贴一个完整驱动后面维护起来会非常被动。5.2 获取Datasheet和官方参考代码的渠道建议源码资源不能光靠百度文库里零散代码最好找到官方或代理商的原始SDK。Dongwoon系列芯片在模组厂那里使用量很大他们的FAE通常能提供DW9714和DW9763的参考驱动国产CN3927和PD9215BL这两颗找代理商要Linux/RTOS参考代码一般也能拿到。GitHub上也有不少手机内核仓库里面有现成的AF驱动但要注意协议版本和平台适配不能直接搬。我个人的习惯是先建一个基准版本比如基于某颗常见主控平台的DW9714驱动然后在基准版本上做状态机和寄存器差异对比把差异做成表格再根据表格修改配置结构体。这样无论后续平台换到高通、联发科还是展锐驱动逻辑都保持一致只是换一下I2C接口层而已。5.3 不推荐把寄存器命令直接硬编码在应用层很多初学伙伴喜欢写一个应用层脚本直接通过ioctl往I2C地址写数据来调AF。这在早期验证芯片通路上很快但一旦进入量产就必须把寄存器操作下沉到内核驱动或RTOS设备层否则应用的调度波动会导致对焦位置抖动还可能抢占I2C总线影响其他传感器。正确的做法是驱动层提供标准的“设置位置”接口应用层只传递位置值和模式标记寄存器细节全部封装在驱动内部。另外源代码里还要注意对私有数据的保护。AF驱动本身不算特别敏感但厂家给的OTP读取算法和工厂校准协议可能涉及商业保密内容发布源码时不要顺手把这些也发出去。养成用配置结构体隔离厂家私有逻辑的好习惯对以后代码开源或团队分工也有好处。6. 调完这几颗芯片之后留下的一些个人习惯做AF驱动这个方向真正难的不是看懂Datasheet而是把“能用”变成“稳定好用”。我后来每接触一颗新AF芯片都会先按三步走第一搭一个最小验证环境用I2C工具手动读写寄存器确认马达能基本移动第二再用配置结构体把驱动框架跑通做一轮20小时以上的持续对焦压力测试第三把不同芯片的寄存器差异和调试心得记录下来沉淀成团队内部资源。我也很推荐大家把每颗芯片的默认DAC值、最大行程、方向标志、OTP地址都写成宏或者配置项放在驱动源码头部。千万不要在函数后面堆一堆magic number不然三个月以后你自己都看不懂这个0x0A是哪条命令。调驱动这种事最大的成本其实是遗忘成本。如果你们也在做模组底层建议从DW9714这颗开始着手它寄存器简单、资料多作为AF驱动入门的第一块试验板特别适合。等把DW9714的每个寄存器动作都吃透了再去看DW9763的OTP、CN3927的兼容细节、PD9215BL的LDO配置会发现全都是不同层面的同一套思路。驱动代码资源不是越多越好而是越有条理越省心。本文还有配套的精品资源点击获取