STM32驱动AD5293数字电位器:产线校准回路设计与标定实践
最近在调一台用于传感器产线的校准工装碰到一个特别典型的场景模拟通道里装了一颗多圈电位器负责把输出调到标称值。产线上操作员每天要拧上百次拧多拧少全凭手感同型号产品的出厂一致性很难看。我最早的想法是“用 MCU 加 DAC 去替代电位器”后来发现校准回路里真正需要的其实是一个可编程的电阻而不是一个可编程的电压源。于是把方案改成了 STM32F745ZG 加 AD5293BRUZ-20-RL7——一颗 20kΩ 的 SPI 数字电位器。这篇文章就把我在这个项目里验证过的电路、踩过的坑和校准流程整理出来。本来以为这颗芯片就是“SPI 写个值”这么简单真正做下来才发现数字电位器和机械电位器在工程语义上是两回事机械电位器是“物理旋钮”数字电位器是“带存储器的可变电阻”。理解这一点后面所有设计决策都顺了。1. 从机械电位器翻车现场说起为什么校准回路需要 AD5293 这类器件1.1 数字电位器不是 DAC也不是随便一颗电位器为什么不用 DACDAC 输出的是电压把它接到运放反馈回路里改变增益通常要额外加 V-I 转换电路而且 DAC 的输出阻抗、失调都会引入新的误差。而很多校准场景需要的物理量本身就是“两个端点之间的电阻值”——比如设定基准分压比、调整运放增益、设置传感器激励电流。这些地方直接拿一颗电阻顶上去电路拓扑和原来用机械电位器时完全一致替换成本最低。但市面上大多数数字电位器尤其是音频级、消费级的那批电阻容差给的是 ±20% 甚至 ±30%。拿来调音量没问题放进校准回路就等着翻车——校准的目标是让输出误差小于 0.1%结果电位器本身的误差比被校误差还大两个数量级。所以选型的第一道门槛不是分辨率是容差。1.2 AD5293 参数里真正值钱的三行AD5293 和普通数字电位器的差距用一张表就能看明白参数AD5293常见普通数字电位器电阻容差±1%±20% 或更高温度系数35ppm/°C 典型50~100ppm/°C游标电阻60Ω 典型75~120Ω非易失存储20 次可编程20-TP0 或 EEPROM 类±1% 意味着标称 20kΩ 的芯片实际阻值可能在 19.8kΩ 到 20.2kΩ 之间。这个误差在校准流程里完全可以被两点标定吃掉后面第 4 章会细说。35ppm/°C 的温漂换句话说温度每变化 10°C电阻只变化 0.035%折合到 20kΩ 上是 7Ω大约 1/11 个 LSB。这个水平在工业校准场景够用了不会出现上午校完下午就飘的情况。20-TP 这个指标藏在手册角落里很多人第一次用都会忽略。它表示非易失区最多写 20 次这个限制直接影响了产线流程设计我专门在第 5 章展开。1.3 这颗芯片在标题组合里的位置AD5293BRUZ-20-RL7 是 ADI 的单通道 256 位数字电位器20kΩ 版本SPI 接口。STM32F745ZG 是 ST 的 Cortex-M7 主控144 脚算力对驱动电位器来说是绰绰有余。这两颗 IC 放在一起本质上是把“校准”这个动作从人工操作变成软件操作MCU 通过 SPI 写码值电位器改变电阻测量回路自动验证结果。整条链路是半自动或全自动闭环校准的基础也是工业设备远程维护、批量一致性控制的前提。2. 把 AD5293BRUZ-20-RL7 和 STM32F745ZG 接起来硬件设计与型号解读2.1 型号命名逐段拆解BRUZ-20-RL7 这串后缀在选型时容易看花眼拆开就清楚AD5293系列名单通道数字电位器。B温度等级/版本标识对应 -40°C 到 125°C 工业温度范围。RUTSSOP-16 封装这个小封装在布局时很省地方。Z符合 RoHS无铅。20阻值挡20kΩ。RL7卷带包装适合贴片机量产。选型时最容易搞混的是阻值。AD5293 有 20kΩ、50kΩ、100kΩ 等挡位后缀数字直接对应阻值拿错挡位整个调节范围就全偏了。我项目里选 20kΩ 是因为被校电路的反馈网络需要 10kΩ 级别的电阻20kΩ 分出一半正好落在常用区。2.2 电路连接骨架与电源约束AD5293 的管脚可以分成三组电源、SPI、电阻端子 A/W/B。电源方面模拟侧 V 和 V- 决定内部电阻串的参考电压范围。我用了单 12V 给 VV- 接 GND。注意这颗芯片的 V 和 V- 不只是逻辑电源它们同时也是电阻端子的电压范围限制A、B、W 任意一端的电压都不能超过 V 到 V- 的范围不然内部 ESD 二极管会先导通轻则影响测量重则损坏芯片。这个约束在工业现场特别容易被忽略第 6 章再细聊。逻辑侧 Vlogic 独立供电2.7V 起步3.3V 直接和 STM32 对接不用额外电平转换。GND 要跟 MCU 共地共地不好SPI 时序再对也白搭。SPI 四根线 SCLK、SDI、SDO、CS 接到 STM32 的 SPI1CS 不要用硬件 NSS用普通 GPIO 拉因为同一根 SPI 总线上可能还要挂 ADC、DAC 等其他器件软件控制片选更灵活。去耦电容 0.1µF 必须贴着 V 放大容量电解 10µF 可以稍微远一点。模拟地、数字地在 PCB 上单点汇合校准精度要求越高这个习惯越重要。2.3 为什么控制器选 STM32F745ZG说实话只为了驱动 AD5293随便一颗 8 位 MCU 都够。但一台校准设备不只有电位器——还要跑人机界面、Modbus/串口通信、多通道 ADC 采集、存储校准曲线甚至以后要加 Ethernet 远程维护。STM32F745ZG 有 Cortex-M7 内核、216MHz 主频、大容量 Flash/RAM外设资源丰富144 脚封装的 GPIO 数量也够用。我当时是拿手头现成的 F745ZG 开发板先搭的原型后来确认引脚映射后直接画进主板。项目里实际用到的外设也就是 SPI1电位器、SPI2高精度 ADC、UART上位机、几个 GPIO片选/继电器。选一颗富余量足够的主控后面加功能不用重新画板子这个决策省了很多事。3. SPI 驱动先把命令帧量化成代码3.1 16 位命令帧的结构AD5293 的 SPI 帧是固定的 16 位高 5 位是命令中间 3 位保留位填 0低 8 位是数据。每次 CS 拉低后连续移入 16 个时钟CS 拉高本次命令生效。我在项目里经常用的命令就这几条命令名值作用NOP0x00空操作用于回读时取数写 RDAC0x04改变游标位置易失写 RDAC 并写 Memory0x05改变游标位置并保存到非易失区Memory 恢复到 RDAC0x07把保存值重新加载读 RDAC0x08回读当前游标位置读 Memory0x09回读非易失区保存值不同批次数据手册对命令表的文字描述可能有差别实际用的时候以你手里最新版手册为准。代码里最好把命令定义成宏集中管理不要到处写裸数字。3.2 用 8 位 SPI 发送 16 位帧绕开 HAL 的大小端坑很多人直接在 STM32 上把 SPI 配置成 16 位数据帧然后一次性发一个 uint16_t。这个做法在 CubeMX/HAL 环境下容易踩字节序的坑M7 默认小端HAL 对 16 位数据帧的收发缓冲有高低字节的排列要求一旦顺序反了命令位跑到数据位的位置芯片直接不理你。我的做法很朴素SPI 外设保持 8 位数据帧用软件把 16 位帧拆成两个字节按 MSB first 顺序依次发送。这样字节序完全可控也方便在同一总线上兼容其他 8 位 SPI 器件。驱动代码核心就一个函数#include stm32f7xx_hal.h #define AD5293_CMD_NOP 0x00u #define AD5293_CMD_WR_RDAC 0x04u #define AD5293_CMD_WR_RDAC_MEM 0x05u #define AD5293_CMD_MEM_TO_RDAC 0x07u #define AD5293_CMD_READ_RDAC 0x08u #define AD5293_CMD_READ_MEM 0x09u extern SPI_HandleTypeDef hspi1; #define AD5293_CS_GPIO_PORT GPIOA #define AD5293_CS_PIN GPIO_PIN_4 static void ad5293_cs_low(void) { HAL_GPIO_WritePin(AD5293_CS_GPIO_PORT, AD5293_CS_PIN, GPIO_PIN_RESET); } static void ad5293_cs_high(void) { HAL_GPIO_WritePin(AD5293_CS_GPIO_PORT, AD5293_CS_PIN, GPIO_PIN_SET); } uint16_t ad5293_cmd(uint8_t cmd, uint8_t data) { uint16_t tx_frame ((uint16_t)cmd 11) | ((uint16_t)data 0x00FFu); uint8_t tx_buf[2] { (uint8_t)(tx_frame 8), (uint8_t)(tx_frame 0xFFu) }; uint8_t rx_buf[2] { 0 }; ad5293_cs_low(); HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 2u, 100u); ad5293_cs_high(); return ((uint16_t)rx_buf[0] 8) | rx_buf[1]; }这个函数把命令和数据的拼装集中在了一处。tx_frame 高 5 位是命令低 8 位是要写的码值。发送时先发高字节、再发低字节AD5293 的 SPI 本身就是 MSB first这样和硬件时序完全对齐。SPI 初始化时我用的配置是这样hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_2EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB;这里 CPOL0、CPHA1 对应 SPI Mode 1意思是 SCLK 空闲为低数据在 SCLK 第二个边沿被采样。我第一次按 Mode 0 配结果写进去的码值总是不对查了半天时序图才发现问题。如果你手头手册画的是下降沿采样那就要用 Mode 1 或者 Mode 3以时序图为准。另外 AD5293 对 SCLK 频率不敏感我实际跑在 4.5MHz 左右完全没有问题。3.3 回读验证与链路自检写码值之前先验证 SPI 链路。我提供两个办法第一个是万用表法。让 MCU 和 AD5293 保持上电但 A/B/W 端子不要接任何有源电路写入 D0用万用表量 B-W 电阻应该接近 60Ω 的游标电阻写入 D255量 B-W 电阻应该接近 20kΩ。这两组数值能同时验证 SPI 通信和电阻通道是否正常。第二个是回读法。AD5293 支持把当前 RDAC 值读回来写进去多少读出来多少void ad5293_write_rdac(uint8_t d) { ad5293_cmd(AD5293_CMD_WR_RDAC, d); } uint8_t ad5293_read_rdac(void) { ad5293_cmd(AD5293_CMD_READ_RDAC, 0x00u); /* 发读命令 */ uint16_t rx ad5293_cmd(AD5293_CMD_NOP, 0x00u); /* 下一帧取数 */ return (uint8_t)(rx 0xFFu); }之所以要补一帧 NOP是因为这种 SPI 器件的回读数据是流水线式的读命令本身会占用当前帧结果要在下一帧的 MISO 上才能取回来。如果你发现回读数据恒为 0 或者对不上多数就是少了这一帧。4. 把 20kΩ 变成“校准旋钮”传递函数、两点标定与逐次逼近4.1 256 步到底有多细AD5293 是 8 位分辨率256 个抽头。理想情况下R_WB(D) D / 256 × R_AB R_WD 是 0~255 的码值R_AB 是 20kΩR_W 是游标电阻约 60Ω。所以可调范围不是完整的 0~20kΩ而是约 60Ω 到 19.98kΩ。每步的步进是 20kΩ / 256 ≈ 78Ω。这个粒度做粗调绰绰有余但直接拿来做精密电压微调就不够看了。假设基准 2.5V 直接分压78Ω 的步进可能带来零点几毫伏的电压跳变在某些要求精确到 0.01% 的校准任务里这个跳变已经超了指标。所以实际项目里不能把 20kΩ 整个跨在校准回路上要先用固定电阻把调节窗口压缩再让电位器在窗口内做细调。这就是俗称的“粗调靠固定电阻细调靠电位器”。4.2 用外围电阻网络压缩调节窗口这里给出一个我实际用过的分压微调接法。基准电压 Vref 1.25V经过 100kΩ 固定电阻 R1 接到 AD5293 的 A 端B 端接地W 端接高阻抗缓冲后作为微调电压输出。输出就是V_W(D) ≈ Vref × R_WB(D) / (R1 R_WB(D))带入射算一下D0R_WB≈60ΩV_W ≈ 1.25 × 60 / 100060 ≈ 0.00075V。D255R_WB≈19980ΩV_W ≈ 1.25 × 19980 / 119980 ≈ 0.208V。整个调节跨度约 0.207V平均每一步约 0.81mV。原来 78Ω 的粗步进经过 100kΩ 电阻一压缩变成了约 0.8mV 的细步进。这就是用外围网络换分辨率的思路在 8 位电位器上也能满足不少高端校准需求。如果你的需求是调运放增益可以把电位器放在反馈回路里和固定电阻配合限定增益范围道理一样。关键是先算清楚目标范围和步进能不能接受再定外围电阻阻值。4.3 两点标定先测真实斜率再反算码值哪怕 AD5293 已经把容差做到 ±1%标称 20kΩ 和实际 20.1kΩ 对最终输出还是有影响。产线校准的时候我更推荐直接对每一颗芯片做一次两点标定把真实的电阻斜率测量出来。操作流程让芯片正常上电A/B/W 端子保持悬空写入 D0用四线制万用表测 B-W 电阻 R0。写入 D255测 R255。按线性模型算斜率 k(R255−R0)/255截距 bR0。根据目标电阻 R_target反算码值 D(R_target−b)/k。写入 D验证输出是否落在容差内。这一步的思路其实和给触摸屏做四点校准一模一样先建立物理量和码值之间的映射关系再反过来求码值。AD5293 出厂时 ±1% 的误差被这一步彻底吸收后面即使换一颗芯片只要重新标定校准结果依然一致。如果系统误差不只有增益和失调还带明显非线性那就得引入多点分段标定类似外部 ADC 的三点校准在低、中、高三个点分别测输出分段拟合查表取码值。8 位电位器本身线性度指标不错一般两点就够非线性明显的场景再加中点。如果用在分流器零漂校准这种场景零点测量尤其重要因为零漂的本质就是零点不在原点上标定时必须把 D0 这一端的数据当作真正的基准来看。4.4 逐次逼近法不知道精确模型也能收敛有时候电路里还有运放、电容传递函数不是简单分压反解方程很麻烦。这时候用逐次逼近法最省事思想和 ADC 里的 SAR 一样从最高位开始每步试一个码值看实测输出和目标比大小决定这一位留 1 还是改 0。uint8_t ad5293_cal_sar(float target_voltage) { uint8_t d 0; for (int8_t bit 7; bit 0; bit--) { uint8_t test d | (1u bit); ad5293_write_rdac(test); HAL_Delay(2); /* 等输出稳定 */ float measured read_output_voltage(); /* 外部ADC或万用表读数 */ if (measured target_voltage) { d test; } } return d; }循环结束后的 d 就是最接近目标电压的码值。8 位只需要 8 次迭代每次迭代等 2ms加起来不到 20ms产线上完全能接受。注意循环里的比较方向取决于你的电路是正相关还是负相关。我这个例子是输出电压随 D 增大而增大所以实测值小于目标时保留这一位如果电路接反了比较方向要反过来否则会收敛到完全相反的方向。5. 20 次烧写寿命和上电默认值产线流程里的隐藏雷区5.1 易失写与非易失写先搞清楚再动手AD5293 有两条写路径写 RDAC 只是把游标位置放在易失寄存器里掉电就丢写 RDAC 并写 Memory 会同时把值保存到非易失区上电自动恢复。关键限制是非易失区最多写 20 次。手册里叫 20-TP20-Time Programmable这个数字是硬上限不是写满会自动翻新。第一次用这颗芯片的人最容易在这里翻车——开发阶段每次上电都写一下存储可能几轮调试就把 20 次用完了后面量产烧写直接没额度。所以我的规矩很简单调试期禁止写 Memory全部用易失写只有正式产品在最终测试环节才碰存储命令。测试脚本里也把写存储命令单独隔离出来不跟着普通自检流程跑。提示开发调试阶段千万不要写非易失区20 次额度只能在量产最终环节用。5.2 我把烧写动作放进了哪个环节量产流程上我建议把非易失烧写放在最后产品完成所有调试、老化、功能测试之后在最终校准这一步先把 D 值调到合格范围内并确认稳定再用写 RDAC 并写 Memory 命令固化。固化之后再做一次回读确认读出来的值要和刚才写入的值一致。如果产品有多台测试工序烧写命令一定要放在最后一序。前面任何工序都不允许碰存储区这样即使开发或者中间测试出问题20 次的额度还是满的。5.3 掉电保护与写后回读非易失写入过程对电源稳定性比易失写敏感得多。内部会有类似电荷泵或者熔丝驱动的操作如果写的过程中电源突然掉电结果可能不是“保持原值”而是“不确定值”。为了把这个风险摁住我做了两件事第一硬件上给烧写环节加电源检测MCU 检测到 VDD 低于阈值就不执行写存储命令。 第二写存储后必须回读 Memory和期望值比对不一致立刻报警重新走校准流程。好在 AD5293 的存储读取是可靠的回读本身不消耗写次数可以放心用。5.4 上电默认值和多档位设计上电时 AD5293 会自动把非易失区保存的值加载到 RDAC。所以如果一台设备只有一个校准点烧写一次就能实现“上电即校准”。但很多校准设备有多个量程、多个档位不可能只存一个值。我建议的做法是非易失区只存最常用的默认档位其余档位的码值存到 MCU 的 Flash 或外部存储里开机后由 MCU 根据当前档位动态写入易失 RDAC。MCU 侧也可以用结构体把这些码值做成校准表和产品序列号、校准日期一起管理追查起来特别方便。6. 工业应用现场才遇到的坑电源倒灌、ESD 和布线6.1 A/W/B 端子电压范围不要超轨这是我在现场栽过跟头的地方。AD5293 的 A、B、W 端子电压被严格限制在 V- 到 V 之间。如果我用单 12V 供电V- 是 GND那这三个端子绝对不能被拉到负压也不能超过 12V。问题出在哪呢当数字电位器接在运放的反馈网络里运放上电瞬间或者异常状态下反馈节点可能被拉到负电压。一旦低于 V-芯片内部的 ESD 保护二极管就会导通轻则这次校准数据不对重则芯片直接损坏。处理办法是加保护在 A/B/W 端子上先串联一个几百欧姆的保护电阻再并一个肖特基二极管到电源轨。如果电路允许设计分压网络时让电位器端子永远落在 0~5V 的安全区间比事后保护更干净。6.2 W 端输出阻抗与缓冲问题W 端不是理想电压源它等效于一个几十欧姆的动态电阻会随码值改变。如果后端直接接 ADC 采样ADC 的输入阻抗不够高就会和 W 端分压测出来的电压跟着码值变化而失真。我的做法是所有从 W 端引出的信号先过一级高输入阻抗的运放缓冲再接 ADC 或后级电路。运放输入阻抗做到 MΩ 级别以上W 端电阻的影响可以忽略。这个细节看起来不起眼但校准精度做到 0.1% 以下时漏掉它结果完全不可信。6.3 PCB 布局和现场防护AD5293 的模拟端子和 SPI 数字线在 PCB 上要分开走。SPI 的 SCLK 边沿比较陡如果和 A/B/W 的长距离平行走线容性耦合会干扰模拟信号。我的习惯是 SPI 线走在内层或单独层模拟端子在顶层尽量短中间用地隔离。工业现场还要考虑 ESD。STM32F745ZG 和 AD5293 之间如果通过排线引到前面板排线接口必须加 TVS 管。SPI 线短的话MCU 的 GPIO 内部保护基本够用一旦引出机箱防护等级就不够了。6.4 实测中发现的几个“小字陷阱”最后聊几个数据手册小字里的陷阱手册里的 R_WB 是在 A 端悬空/开路条件下测的如果你把 A 端也接进电路实际等效电阻会受到外部网络影响计算时必须按整个网络一起算。AD5293 的 SDO 支持菊花链但多片级联时每片都要吃满 16 位命令软件发送的比特数是 16×N。如果你只写了一片第二片没处理链路上的数据会错位一帧表现为所有芯片的码值一起乱。手工焊接 TSSOP-16 时引脚间距小连锡后故障很隐蔽用万用表量相邻引脚电阻最靠谱。焊接温度对内部存储没有影响但最好不要用热风枪对着芯片猛吹超过 10 秒。回读命令里的保留位我全部填 0手册没说这几位的严格约束但填 0 最安全不会触发未定义行为。最后给第一次用这颗芯片的朋友一个建议拿到板子先别急着写校准算法把 D 分别设成 0、128、255用万用表量一遍 B-W 电阻。三个点能同时验证 SPI 链路、命令格式和电阻通道是否正常全部对上再往下做。这个习惯我每次换平台、换板子都会做一遍虽然简单但省掉的排查时间远超那几分钟。