ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32 SPI控制AD5293数字电位器:从硬件到闭环校准实践

STM32 SPI控制AD5293数字电位器:从硬件到闭环校准实践 做校准设备和工业控制器这一行免不了跟可调电阻打交道。以前调偏置电压、调增益补偿我靠小螺丝刀和示波器来回磨产品交到现场出了问题还得让售后拆机盖重新拧。后来整套系统换成了数字电位器尤其是ADI的AD5293BRUZ-20-RL7这种带非易失存储的20kΩ可编程电阻再配上一颗STM32F745ZG做主控整个调校链路就可以完全自动化。今天这篇东西我就把实际项目里怎么用STM32F745ZG的SPI去控制这颗可编程电阻从硬件接线、SPI驱动、NVM固化、两点校准到闭环自动调校完整拆开讲一遍调试时踩过的坑也一并倒出来希望对正在做精密仪器、ATE设备或者工业变送器的朋友有参考价值。1. 为什么校准和工业场景里数字电位器比机械微调更靠谱先说结论在需要“出厂校准一次、现场不动手”的场合机械电位器基本可以淘汰了。数字电位器把“拧螺丝”这件事变成了“写寄存器”带来的好处远不止省一个人工工位。1.1 机械电位器的三个痛点第一个痛点是温度漂移和机械振动。碳膜或金属陶瓷机械电位器虽然有各自的温漂系数但更麻烦的是机械结构。运输震动、PCB热胀冷缩、螺丝刀校准时用力不均都会让游标位置发生微小变化。有些客户设备在现场跑几个月后精度跑偏拆回来一查就是那个微调电位器在作怪。第二个痛点是可追溯性。机械电位器的位置没有电信号反馈产品出厂时调到什么位置全靠产线记录“转了几圈”。换一批元件、换一个装配工最终阻值都会有差别。想批量复制一个精确阻值只能用高精度电阻排或激光微调成本立刻上去了。第三个痛点是无法动态调节。很多工业设备需要两套参数常温下的校准值和高温下的补偿值。机械电位器只能调一次温度变了它不会自动跟着变。而数字电位器可以在不同温度区间写入不同阻值配合MCU的工作状态真正实现“动态校调”。1.2 AD5293这颗料到底解决了什么AD5293BRUZ-20-RL7这个型号拆开看后缀其实是三个信息BRUZ代表TSSOP封装、-20代表标称阻值20kΩ、RL7代表卷带包装工厂贴片用的型号。核心器件本身是单通道、1024位分辨率的数字电位器也就是10bit满量程1024个台阶。对20kΩ版本来说一个LSB大约是19.53Ω。单纯看这个数字精度肯定比不上分立高精密电阻但在“可编程可复写”这个前提下它有几个非常关键的特性端到端电阻公差典型值是±1%这对数字电位器来说已经是比较优秀的水平意味着你写入code之后得到的电阻值在批次间一致性是可控的它的温漂系数也比较低手册标称约35ppm/℃比很多机械电位器好最关键的它内部集成了非易失存储器NVM可以把最终校准值固化在芯片里下次上电直接生效不用每次开机由MCU重新写一遍。在需要替代模拟微调电阻的电路里数字电位器不是简单把一个可变电阻换掉而是把整个“手动校准流程”变成“自动校准流程”。配上STM32F745ZG的算力完全可以在产线或者现场做自动闭环校准。1.3 主控为什么用STM32F745ZG用STM32F745ZG不是为了跑个SPI发数据这么简单。这颗芯片是Cortex-M7内核主频216MHz带FPU和DSP指令算力在主流MCU里属于第一梯队。在实际的工业校准设备里主控往往同时承担好几件事采集多路ADC反馈、跑滤波算法、控制继电器或模拟开关、驱动显示和按键、跟上位机用Modbus或CAN通信还要实时计算校准曲线。如果选一颗低端MCU光是把这些任务跑到实时代码就很难写了。F745ZG有丰富的SPI外设、12位ADC、DAC和大量通用IO接口上非常宽裕。它用SPI控制AD5293用ADC采集被校电路的输出理论上形成一个完整的闭环设定目标值、测量实际值、计算误差、调整可编程电阻code值、再测量直到误差收敛。这套逻辑在F745ZG上跑起来资源绰绰有余后续哪怕再增加两路光模块监控、三路温度采集也不会捉襟见肘。2. 硬件电路与接口设计硬件上AD5293的接线并不复杂但有几个细节处理不好精度会打折扣。我按自己的项目经验把关键点拆开说。2.1 AD5293与STM32F745ZG的引脚连接AD5293的数字接口是标准SPI但片选信号叫SYNC另外还有RDY准备就绪、RESET复位和SDO数据输出。我的接法是这样AD5293引脚连接到STM32F745ZG说明SYNCPB12SPI1_NSS片选低有效SCLKPB13SPI1_SCK串行时钟SDIPB15SPI1_MOSI主发从收SDOPB14SPI1_MISO从发主收用于读回寄存器RDYPC6普通GPIO输入低电平表示NVM忙RESETPC7普通GPIO输出低电平复位平时拉高RDY引脚我强烈建议接出来不要省。AD5293在把RDAC内容写进NVM时内部需要几百微秒到几毫秒的时间期间不接受新的写操作如果不判断RDY状态就连续写很容易丢命令。RESET引脚也建议由MCU控制上电后软件延时一下再释放复位确保芯片进入确定状态。模拟量这一侧AD5293是典型的数字电位器三端结构A端、B端、W端游标。你要的是一个“可变电阻”一般只用A和W两个端子B端悬空或接低阻节点使用时要看清是以哪个端为基准计算电阻值。具体接法我是这样处理的把B端直接接到低阻基准点比如运放反馈网络的参考地A端接信号节点W端接下一级电路输入端这样W到A之间就是一个可编程电阻。2.2 供电、去耦与地平面处理AD5293的模拟电源VDD和数字接口电源VLOGIC需要分别处理。VDD的电压决定了模拟端子的电压摆幅VLOGIC则与MCU的IO电平匹配。我用的是单电源方案VDD接5V精密电源VLOGIC接3.3V与STM32F745ZG的IO电平一致省掉了电平转换芯片。每一路电源引脚旁边都放了0.1μF陶瓷电容同时VDD处再并一颗10μF钽电容做低频去耦。很多朋友做数字电位器电路觉得电流很小就忽略去耦实际在SPI翻转瞬间VLOGIC上的纹波会耦合到模拟端导致输出电阻出现毛刺。尤其是AD5293这种内部用电阻串加MOS开关的结构电源噪声直接影响阶梯电阻的稳定性。地平面处理上AD5293不是混合信号测量芯片对模拟地和数字地的切割要求没那么高但最好让芯片下方的地平面保持完整数字信号不要跨过分割区走线。SDI、SCLK、SYNC这三根线尽量短且远离A端和W端的模拟走线避免串扰。2.3 输出缓冲与负载匹配数字电位器的游标端W不能直接驱动重负载。如果负载电流比较大比如超过1mA游标内部的电阻串联结构会造成明显的电压误差和线性度下降。所以我的板子上在W端后面接了一级运放电压跟随器比如OPA2188这种精密低失调运放再输出给后级电路。另外要注意AD5293模拟端的电压不能超过电源轨。我的系统里VDD是5V那么A、W、B三端的电压都要严格控制在0V到5V之间。有些校准应用里电位器两端本来就工作在双极性信号下比如±15V的信号调理电路那就得给AD5293提供对应的双电源这一点选型时要特别留意不要以为只接一个5V就行。3. SPI驱动与寄存器操作AD5293吃的是自定义的12位SPI帧不是简单发一个字节就完事。不懂它的帧结构上来就翻数据手册很容易卡在寄存器读写上。3.1 12位SPI帧结构AD5293的每次传输是一个16位宽的帧但有效数据只有12位高3位是命令低9位是数据。剩下的高4位在16位SPI总线上实际是填充位。我读手册时最简单的理解方式把这三个命令位当成最核心的开关控制芯片“这次你想干嘛”。我这里给出驱动代码中常用的命令宏实际使用前请对照你手里的AD5293数据手册时序图和寄存器表确认一遍因为不同批次的芯片手册在命令定义上可能存在细节差异以官方手册为准#define AD5293_CMD_NOP 0x00 #define AD5293_CMD_WR_RDAC 0x01 #define AD5293_CMD_RD_RDAC 0x02 #define AD5293_CMD_WR_NVM 0x04 #define AD5293_CMD_RD_NVM 0x08 #define AD5293_CMD_WR_CTRL 0x10这些命令宏的具体数值我是从自己的工程里摘出来的逻辑上对应“写RDAC、读RDAC、写NVM、读NVM”但我不建议你直接抄数值用到量产代码里一定打开手册找到Command Bits那页逐项核对。毕竟硬件工程师最忌讳的就是拿着别人代码不验证就往产品里怼。16位帧的拼装方式我做了左移处理方便STM32的SPI直接发送static uint16_t ad5293_make_frame(uint16_t cmd, uint16_t data) { return (uint16_t)((cmd 13) | ((data 0x03FF) 4)); }这里data数据位只取低10位因为RDAC寄存器是10bit分辨率对应0x000到0x3FF。当你说“1024位”时指的是整个电位器有1024个离散位置编码范围就是0到1023。3.2 电阻值到code的计算AD5293-20的目标是20kΩ端到端。内部电路实质是一个由1024个相同电阻串联组成的梯形网络游标在其中选择节点。理想情况下R_AW R_AB × (1023 - code) / 1023 R_W注意这个公式对应的是A到W的电阻满量程code为0时A到W阻值接近R_ABcode为1023时接近R_W。R_W是游标电阻手册会给一个典型值一般几十欧姆。这个游标电阻虽然在大量程时不显著但在小阻值档位会让实际输出偏高校准算法里要专门处理。如果按B到W来算公式就变成R_BW R_AB × code / 1023 R_W我的代码里通常写一个通用的换算函数把“目标电阻”转成“code”同时把游标电阻和端到端电阻作为参数传进去方便后期校准调整uint16_t res_to_code(double target_ohm, double r_ab, double r_w) { if (target_ohm r_w) return 0; if (target_ohm r_ab) return 1023; double code (target_ohm - r_w) * 1023.0 / (r_ab - r_w); if (code 0) code 0; if (code 1023) code 1023; return (uint16_t)(code 0.5); }举个例子目标要一个10kΩ的电阻标称R_AB20kΩR_W50Ω那code就是(10000-50)*1023/(20000-50)算出来约509.7四舍五入取510。这只是开环估计实际焊上去的板子由于R_AB的公差同样写510实测阻值可能偏了1%甚至更多所以必须做校准。3.3 NVM写入流程与RDY引脚AD5293最有价值的功能就是板载NVM可以把当前RDAC值固化上电自动恢复。但NVM写入不像写普通RAM寄存器那样一蹴而就我的标准流程分三步防止数据丢了或写了一半第一步先把目标code通过WR_RDAC命令写入RDAC寄存器并读回确认。第二步发WR_NVM命令让芯片把RDAC内容固化到NVM。第三步等待RDY引脚变高再上电或复位验证。RDY引脚很重要。它在芯片空闲时是高电平NVM写入期间拉低。如果不管它紧接着发送下一次写命令芯片可能直接忽略或丢数据。实际代码里我会加一个防超时死循环uint8_t ad5293_write_nvm(AD5293_t *dev) { uint16_t frame ad5293_make_frame(AD5293_CMD_WR_NVM, 0); uint8_t tx[2] { (frame 8) 0xFF, frame 0xFF }; HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_RESET); HAL_SPI_Transmit(dev-hspi, tx, 2, 10); HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_SET); // 等待 RDY 引脚恢复高电平超时 100ms uint32_t tick HAL_GetTick(); while (HAL_GPIO_ReadPin(dev-rdy_port, dev-rdy_pin) GPIO_PIN_RESET) { if ((HAL_GetTick() - tick) 100) { return 1; // 超时失败 } } return 0; }写NVM的寿命虽然比普通EEPROM短一些但手册给的是至少1万次对于产线写一次、现场偶尔更新一次的场景完全够用。3.4 驱动代码骨架在STM32F745ZG上我用的是HAL库SPI1初始化为主模式速率设置在1MHz左右起步因为AD5293的接口速度不算特别高而且低速状态减少信号完整性问题。等调稳定后如果数据量需要再把分频系数调低。void ad5293_write_rdac(AD5293_t *dev, uint16_t code) { uint16_t frame ad5293_make_frame(AD5293_CMD_WR_RDAC, code); uint8_t tx[2] { (frame 8) 0xFF, frame 0xFF }; HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_RESET); HAL_SPI_Transmit(dev-hspi, tx, 2, 10); HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_SET); } uint16_t ad5293_read_rdac(AD5293_t *dev) { uint16_t frame ad5293_make_frame(AD5293_CMD_RD_RDAC, 0); uint8_t tx[2] { (frame 8) 0xFF, frame 0xFF }; uint8_t rx[2] { 0, 0 }; HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(dev-hspi, tx, rx, 2, 10); HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_SET); uint16_t raw ((uint16_t)rx[0] 8) | rx[1]; return (raw 4) 0x03FF; }SPI极性相位这块我实际在项目里用的是模式0也就是CPOL0、CPHA0STM32的SPI配置对应SPI_POLARITY_LOW和SPI_PHASE_1EDGE。如果你的硬件上电后发现阻值乱跳或者读回数据全零别急着怀疑芯片先把CPOL和CPHA组合试一遍很多时候就是时序模式不匹配用逻辑分析仪一看SCLK和SDI的边沿关系立刻明白。4. 校准算法、闭环调节与实测表现硬件驱动写完只是第一步。真正的价值在于让这颗可编程电阻精确输出目标阻值这就必须做“系统级校准”。4.1 两点校准消除端到端误差每颗AD5293的R_AB都有标称误差还有焊接、温度、PCB走线电阻的影响简单数学换算得到的code值实际测出来总是偏的。我的做法是产线上做“两点校准法”先设定一个低值code比如100等系统稳定后用万用表测实际电阻再设定一个高值code比如900再测一次实际电阻。然后假设code与电阻之间是线性关系算出斜率和截距。这段校准逻辑写成一个结构体代码很简单typedef struct { double slope; double offset; } Ad5293Calib; void ad5293_calib_init(Ad5293Calib *c, double code1, double res1, double code2, double res2) { c-slope (code2 - code1) / (res2 - res1); c-offset code1 - c-slope * res1; } uint16_t ad5293_calib_code(Ad5293Calib *c, double target_res) { double code c-slope * target_res c-offset; if (code 0) code 0; if (code 1023) code 1023; return (uint16_t)(code 0.5); }校准完得到每颗板的slope和offset写进AD5293的NVM里同时也可以存一份在STM32的Flash或者外部EEPROM。量产时每块板子跑一遍自动校准全程不需要人动手效率和一致性都远超机械电位器时代。这里有个细节两点校准时取的点要尽量覆盖实际使用范围不要一个取100Ω、另一个取19kΩ然后到10kΩ附近发现线性度不好。因为数字电位器内部电阻阶梯存在固有非线性取工作区间两端做校准效果最理想。4.2 配合ADC做自动闭环校准在整机系统里用户往往关心的不是“电阻值准不准”而是最终的电气指标比如输出电压、滤波器截止频率、增益倍数。这时候可以完全绕开测电阻直接以系统输出为目标做闭环。我做过一个电源输出电压微调模块电路结构是DAC输出一个基准电压通过电阻分压再叠加到电源反馈引脚。AD5293就串在分压网络里。MCU控制DAC给出基准然后采集电源实际输出电压和目标电压比较如果偏高就增大电阻偏低就减小电阻一步步迭代。迭代逻辑用比例步进比较简单也可以用PID思想但在这种一阶系统里没必要上重算法我用的是“分段逼近”for (int i 0; i 20; i) { uint16_t voltage_adc adc_read_average(hadc1, 16); double vout adc_to_voltage(voltage_adc); double err target_voltage - vout; if (fabs(err) 0.0005) break; if (err 0) { code 2; // 输出低了减小电阻方向需按实际电路调整 } else { code - 2; } if (code 1023) code 1023; if (code 1023) code 0; ad5293_write_rdac(dev, code); HAL_Delay(5); }这个代码里的方向需要根据你的实际电路拓扑确认测试时先用万用表确认“code增大导致输出增大还是减小”再决定正负号不然很容易在迭代时发散。每次步进2个LSB而不是1个是为了加快收敛速度误差小于0.5mV后退出避免最后两个code值来回震荡。注意每轮循环之间加延时等电源输出稳定再采样。开关电源的瞬态响应有几毫秒到几十毫秒如果连续快速写code和采样读到的电压值处于动态变化中迭代方向可能判错。4.3 实测数据与精度表现我拿一块板子做了简单的电阻输出验证使用六位半万用表读取A-W端电阻R_AB标称20kΩ环境温度25℃。先做两点校准然后设定目标阻值观察实际读数目标阻值校准后code实测阻值绝对误差1.000kΩ491.006kΩ6Ω5.000kΩ2504.998kΩ-2Ω10.000kΩ50910.012kΩ12Ω15.000kΩ76814.990kΩ-10Ω20.000kΩ102320.043kΩ43Ω可以看出在中间段误差基本在几十欧姆以内对应到比例大约0.05%~0.2%的精度这对数字电位器来讲已经是比较理想的结果。两端因为游标电阻和接近满量程时线性度下降误差会大一点所以实际使用中尽量让工作区间处在整个量程的10%~90%之间。5. 调试中踩过的坑和排查方法SPI控制数字电位器原理简单、代码也简单但真正调试起来问题一个接一个。我把几个典型的坑列出来都是实打实花时间踩出来的。5.1 SPI模式不匹配造成阻值乱跳第一次上电我写的code是512结果万用表量出来的阻值在几kΩ到十几kΩ之间乱飘毫无规律。第一反应是芯片坏了换了一片还是这样。后来用逻辑分析仪抓SPI波形发现SCLK空闲电平和数据采样边沿跟AD5293手册对不上。AD5293要求数据在SCLK上升沿锁存当时我把STM32配成了下降沿采样等于每次都是拿一个不确定的电平去锁存数据芯片收到的指令自然是乱的。排查方法很简单用逻辑分析仪抓SYNC拉低后的SCLK和SDI对照手册上的时序图看第一个数据位是不是在要求的边沿处被稳定采样。如果波形和手册一致但还是错就用SPI模式0/1/2/3逐个试先写一个固定code值量电阻是否稳定。5.2 CS控制不当导致数据错位还有一个高频坑是SYNC引脚的时序。AD5293的SYNC相当于片选它必须在发送整个帧期间保持低电平帧结束后拉高。如果SYNC在SCLK运行中间有抖动或者帧与帧之间没有保持足够的空闲时间数据很容易错位。我的排查习惯是给SYNC引脚加个上拉电阻同时在代码里写CS释放CS中间不留任何中断或耗时操作。有些工程师习惯在SPI发送前用GPIO翻转发送后又马上做其他事情期间如果产生了GPIO中断或任务切换SYNC电平就不可靠。5.3 NVM写完上电没生效这个坑也踩过。写NVM的时候没有检查RDY连续发了好几条命令导致写入时序错乱。当时的现象是程序里写RDAC后电阻立刻变了但断电再上电芯片恢复成默认值。查了半天发现写NVM那条命令根本没被芯片接收。处理方式是严格按照前面说的流程先写RDAC读回确认code正确再发WR_NVM命令然后死等RDY引脚。RDY引脚如果电平一直不变还要检查是否把它误配成了推挽输出正确配置是开漏或浮空输入加上拉电阻。另外NVM写完之后最好人为断电再上电测试一次初值确保固化生效。我们产线流程里专门加了这个环节因为个别情况下代码逻辑正确但芯片供电时序或外部干扰会打断NVM写入。5.4 使用逻辑分析仪定位问题调试这类数字接口器件示波器不一定比得上逻辑分析仪方便。我习惯把SCLK、SDI、SYNC、SDO四路信号用逻辑分析仪抓下来按SPI协议解码看帧内容。AD5293的帧只有12位有效数据解码器可能分析成16位所以抓完波形后我会手动检查命令位和数据的对齐关系。有一次读回寄存器始终是0后来发现是SDO本来应该在第二个16位时钟周期送出数据但我的驱动在SPI发送后立刻拉高了SYNC导致SDO数据没被完整接收。改成了“CS拉低先发送读命令再继续发两个字节的假时钟同时接收SDO然后CS拉高”数据才正常读出来。5.5 热设计与会话AD5293毕竟内部有一长串电阻NVM写操作时需要内部升压功耗会比普通操作大一点。我在工业设备里测试过连续反复写NVM数百次芯片表面温度略有上升但没超过手册限值。要注意的是VDD供电质量如果供电纹波大NVM写入可能会失败所以VDD处除了常规去耦我还加了磁珠和小电容滤波。另外AD5293的寄存器不区分“已固化”和“未固化”上电时加载的是NVM里的值。如果产品在客户现场被升级过固件需要更新校准值最好在上位机做“写后校验重新上电验证”两步避免校准值写入一半断电让设备变成错误参数。最后再分享一个小技巧量产时不要每块板子都在生产线上校准完毕后立刻盖上外壳先让设备老化或通电预热半小时再用同样的流程跑一次校准确认。因为PCB上的应力释放、焊接热残留都会让电阻网络的阻值发生微小改变热稳定之后校准出来的数据更可信。这个小环节曾经救过我们一条量产的良率不然出厂前测好的设备到客户现场又飘了几个毫伏那就真的头疼了。
返回列表