STM32驱动AD5293数字电位器:产线校准回路设计与标定实践
2026/9/11 22:32:10 网站建设 项目流程

最近在调一台用于传感器产线的校准工装,碰到一个特别典型的场景:模拟通道里装了一颗多圈电位器,负责把输出调到标称值。产线上操作员每天要拧上百次,拧多拧少全凭手感,同型号产品的出厂一致性很难看。我最早的想法是“用 MCU 加 DAC 去替代电位器”,后来发现校准回路里真正需要的其实是一个可编程的电阻,而不是一个可编程的电压源。于是把方案改成了 STM32F745ZG 加 AD5293BRUZ-20-RL7——一颗 20kΩ 的 SPI 数字电位器。这篇文章就把我在这个项目里验证过的电路、踩过的坑和校准流程整理出来。

本来以为这颗芯片就是“SPI 写个值”这么简单,真正做下来才发现,数字电位器和机械电位器在工程语义上是两回事:机械电位器是“物理旋钮”,数字电位器是“带存储器的可变电阻”。理解这一点,后面所有设计决策都顺了。

1. 从机械电位器翻车现场说起:为什么校准回路需要 AD5293 这类器件

1.1 数字电位器不是 DAC,也不是随便一颗电位器

为什么不用 DAC?DAC 输出的是电压,把它接到运放反馈回路里改变增益,通常要额外加 V-I 转换电路,而且 DAC 的输出阻抗、失调都会引入新的误差。而很多校准场景需要的物理量本身就是“两个端点之间的电阻值”——比如设定基准分压比、调整运放增益、设置传感器激励电流。这些地方直接拿一颗电阻顶上去,电路拓扑和原来用机械电位器时完全一致,替换成本最低。

但市面上大多数数字电位器,尤其是音频级、消费级的那批,电阻容差给的是 ±20% 甚至 ±30%。拿来调音量没问题,放进校准回路就等着翻车——校准的目标是让输出误差小于 0.1%,结果电位器本身的误差比被校误差还大两个数量级。所以选型的第一道门槛不是分辨率,是容差。

1.2 AD5293 参数里真正值钱的三行

AD5293 和普通数字电位器的差距,用一张表就能看明白:

参数AD5293常见普通数字电位器
电阻容差±1%±20% 或更高
温度系数35ppm/°C 典型50~100ppm/°C
游标电阻60Ω 典型75~120Ω
非易失存储20 次可编程(20-TP)0 或 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 工业温度范围。
  • RU:TSSOP-16 封装,这个小封装在布局时很省地方。
  • Z:符合 RoHS,无铅。
  • 20:阻值挡,20kΩ。
  • RL7:卷带包装,适合贴片机量产。

选型时最容易搞混的是阻值。AD5293 有 20kΩ、50kΩ、100kΩ 等挡位,后缀数字直接对应阻值,拿错挡位整个调节范围就全偏了。我项目里选 20kΩ 是因为被校电路的反馈网络需要 10kΩ 级别的电阻,20kΩ 分出一半正好落在常用区。

2.2 电路连接骨架与电源约束

AD5293 的管脚可以分成三组:电源、SPI、电阻端子 A/W/B。

电源方面,模拟侧 V+ 和 V- 决定内部电阻串的参考电压范围。我用了单 12V 给 V+,V- 接 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 的 SPI1,CS 不要用硬件 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;

这里 CPOL=0、CPHA=1 对应 SPI Mode 1,意思是 SCLK 空闲为低,数据在 SCLK 第二个边沿被采样。我第一次按 Mode 0 配,结果写进去的码值总是不对,查了半天时序图才发现问题。如果你手头手册画的是下降沿采样,那就要用 Mode 1 或者 Mode 3,以时序图为准。另外 AD5293 对 SCLK 频率不敏感,我实际跑在 4.5MHz 左右,完全没有问题。

3.3 回读验证与链路自检

写码值之前先验证 SPI 链路。我提供两个办法:

第一个是万用表法。让 MCU 和 AD5293 保持上电,但 A/B/W 端子不要接任何有源电路,写入 D=0,用万用表量 B-W 电阻,应该接近 60Ω 的游标电阻;写入 D=255,量 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_W

D 是 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))

带入射算一下:

  • D=0:R_WB≈60Ω,V_W ≈ 1.25 × 60 / 100060 ≈ 0.00075V。
  • D=255:R_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Ω 对最终输出还是有影响。产线校准的时候,我更推荐直接对每一颗芯片做一次两点标定,把真实的电阻斜率测量出来。

操作流程:

  1. 让芯片正常上电,A/B/W 端子保持悬空,写入 D=0,用四线制万用表测 B-W 电阻 R0。
  2. 写入 D=255,测 R255。
  3. 按线性模型算斜率 k=(R255−R0)/255,截距 b=R0。
  4. 根据目标电阻 R_target,反算码值 D=(R_target−b)/k。
  5. 写入 D,验证输出是否落在容差内。

这一步的思路其实和给触摸屏做四点校准一模一样:先建立物理量和码值之间的映射关系,再反过来求码值。AD5293 出厂时 ±1% 的误差被这一步彻底吸收,后面即使换一颗芯片,只要重新标定,校准结果依然一致。

如果系统误差不只有增益和失调,还带明显非线性,那就得引入多点分段标定,类似外部 ADC 的三点校准:在低、中、高三个点分别测输出,分段拟合,查表取码值。8 位电位器本身线性度指标不错,一般两点就够,非线性明显的场景再加中点。如果用在分流器零漂校准这种场景,零点测量尤其重要,因为零漂的本质就是零点不在原点上,标定时必须把 D=0 这一端的数据当作真正的基准来看。

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-TP(20-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 链路、命令格式和电阻通道是否正常,全部对上再往下做。这个习惯我每次换平台、换板子都会做一遍,虽然简单,但省掉的排查时间远超那几分钟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询