简介:面向电子设计竞赛(电赛)中的数字电位器控制场景,这份资源以STM32F103单片机为控制核心,提供AD5272数字电位器的I2C总线驱动与工程实现方案。内容覆盖I2C接口初始化、寄存器配置、数据帧构造与阻值读写等关键环节,并详细处理了起始/停止信号、设备地址与读写控制、阻值回读以及通信异常排查思路,还包含C语言源程序、HAL/标准外设库文件以及完整的编译产物(HEX/AXF),适合参赛选手或嵌入式初学者对照学习。压缩包共211个文件,以.c/.h源文件、.o/.crf编译中间文件、.d依赖文件及.s启动文件为主,另有工程配置文件(.uvprojx/.uvoptx)、链接映射文件(.map/.sct)、可直接烧录的.hex固件和说明文档(.pdf),整体大小仅6.37MB,便于对照源码、配置和产物进行定位与调试。已有271人学习下载,既可用于理解数字电位器的I2C驱动原理,也可作为电赛项目或嵌入式课程设计的工程骨架直接复用,能有效缩短原型开发与调试时间。
1. 电赛里怎么用 stm32f103 把 AD5272 数字电位器当精密器件用
AD5272 名字里带“数字电位器”,第一印象是和机械电位器一样拧一下改个阻值,但电赛里真正值钱的不是“能写阻值”,而是把 CODE 数值、万用表实测电阻、运放增益或恒流源输出三者校准成一条可复现的曲线。很多队伍第一天就能把 SPI 例程跑通,第三天卡在“CODE 从 0 写到 255,输出电压只变了一小段”,问题往往不在 SPI,而在物理接法:把 A/B/W 三个脚接反,或者把 AD5272 当作精密电阻直接串联进主回路。这篇文章按 stm32f103 最小系统到运放电路的顺序,把寄存器帧结构、标准外设库与 HAL 驱动、增益调整和校准方法一次说清,最后给出万用表验证和几个容易忽略的边界行为,适合 F103 工程能编译但第一次碰 AD5272 的参赛者,也适合用 HAL 库调不通时回头对照寄存器层行为的老手。
2. AD5272 SPI时序与寄存器模型:16bit命令帧先读懂
2.1 数字电位器和普通电阻的差别
AD5272 内部是一个电阻串加 MOS 开关树,把 A 到 B 之间分成 256 段,SPI 写入的 8 位 CODE 决定 W 抽头落在哪一段。手册上端到端电阻 R_AB 有 20k、50k、100k 三种后缀,程序完全一样,差别在电阻值范围。注意它的绝对精度并不高,端到端容差通常在 ±20% 到 ±30%,但分压比做得很准,长期漂移也远小于机械电位器。
所以电赛中正确的用法有两类:第一类是把它接成“分压器”,从 A 输入电压,从 W 取分压,输出电压约等于 CODE/256 乘输入电压;第二类是把它接成“可变电阻”,只使用 A 和 B 两个端子,W 悬空,串在运放反馈回路里改变增益。最容易犯的错误是把 A/B/W 三个脚按普通电阻那样随便接,导致 CODE 和阻值的变化方向和你预期相反,或者 W 端浮动产生额外噪声。
W 与 B 之间的阻值可以近似写成:
R_WB ≈ (CODE / 256) × R_AB + R_WCODE 等于 0 时 R_WB 并不等于 0,而是几十到几百欧姆的开关导通电阻 R_W。这个残阻在增益电路里影响很大,后面校准时会专门处理。
2.2 写 RDAC 的 16bit 帧结构
AD5272 的 SPI 写入帧固定为 16 位:高 4 位是命令,中间 4 位是地址,低 8 位是数据。和 AD5270/5271 的 20 位帧不一样,不要混用。常用命令如下:
| 命令 | 值 | 地址 | 数据 | 说明 |
|---|---|---|---|---|
| 写 RDAC | 0x01 | 0x00 | 8 位阻值码 | 电赛最常用 |
| 读 RDAC | 0x02 | 0x00 | 0x00 | 需要 SDO 引脚配合 |
| 写 TCON | 0x04 | 0x00 | 控制位 | 上电默认可用,先别动 |
比如要把 CODE 设为 128,实际发送的第一个字节是(0x01 << 4) | 0x00 = 0x10,第二个字节是0x80。CS 引脚必须在整个 16 位传输期间保持低电平,传输结束后拉高,帧与帧之间建议留至少 1 微秒。若 CS 在传输中途抖动,AD5272 会把半截数据当成命令,表现就是 CODE 写进去但输出完全不对。
TCON 寄存器控制内部开关和钳位功能,上电默认状态已经能让 RDAC 正常工作,电赛代码里一般不用初始化它。如果需要把输出钳制到地或者断开输出,再查数据手册的 TCON 位域定义,普通增益调节用不到。
2.3 挂在 stm32f103 的哪条 SPI 上
stm32f103 有 SPI1 和 SPI2,SPI1 挂在 APB2 时钟上,最高可以跑到 18MHz;SPI2 挂在 APB1 上,最高 18MHz 但需要额外分频。AD5272 的 SCLK 上限远高于 18MHz,所以瓶颈在 F103 这边。推荐用 SPI1,引脚对应 PA5 的 SCK、PA7 的 MOSI、PA4 做片选,PA6 的 MISO 在只写模式下可以空着。
SPI 模式建议先用 Mode 0,也就是 CPOL 为低、CPHA 为第一个边沿采样。AD5272 在 SCLK 上升沿锁存数据,Mode 0 正好匹配。如果你的工程里还挂了 NRF24L01 这类同样用 Mode 0 的器件,两者可以直接共享 SPI1 总线,只要片选分开即可。如果写入后实测输出不变化,再把 CPHA 改成第二个边沿试一次,这个坑在国产同封装芯片上更容易出现。
3. stm32f103 用标准外设库与 HAL 驱动 AD5272 的最小工程
3.1 在 stm32f103 最小系统上接线
电赛常见的 stm32f103 最小系统板,不外接复杂外设时,只需要 PA4 到 PA7 四根线就能驱动 AD5272。推荐接线方式如下:
| AD5272 引脚 | 接到 F103 | 说明 |
|---|---|---|
| VDD | 3.3V | 不要接 5V,逻辑电平参考 VDD |
| GND | GND | 共地必须可靠 |
| SCLK | PA5 | SPI1 时钟 |
| SDI | PA7 | SPI1 MOSI |
| CS | PA4 | GPIO 推挽输出 |
| SDO | 悬空 | 只写模式下不需要读回 |
| A / B / W | 按电路功能接 | 分压或可变电阻两种接法 |
一个容易忽略的细节:AD5272 的 VDD 如果接 5V,输入高电平门槛会接近 3.5V,F103 的 3.3V GPIO 输出可能达不到,导致 SPI 有时候能写进去有时候不能。把 VDD 接 3.3V,逻辑电平匹配最稳妥。CS 线建议在靠近 AD5272 引脚处加一个 10k 上拉到 VDD,防止 F103 复位期间 GPIO 处于浮空状态时误触发写操作。
3.2 标准外设库 V3.5 下的初始化和写函数
国内电赛工程里最常见的还是标准外设库 V3.5.0,初始化部分分成 GPIO 和 SPI 两步。GPIO 配置重点是把 SCK 和 MOSI 设为复用推挽,CS 设为普通推挽输出,并且默认拉高。
void AD5272_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK PA5, MOSI PA7 复用推挽 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // CS PA4 普通推挽,空闲高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); }SPI1 的配置要确认几个参数:主模式、8 位数据、CPOL 低、CPHA 第一个边沿、16 分频。72MHz 主频下 16 分频得到 4.5MHz 的 SCLK,对 AD5272 来说足够,而且长线连接时低速率更稳定。
void AD5272_SPI_Init(void) { SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }SPI_NSS 设为硬件 NSS 输出时,如果代码里又不小心操作了 SSI 位,CS 引脚会被外设自动拉低,和手动控制冲突。统一用软件 NSS,CS 完全由 GPIO 控制,逻辑最简单。
发送一帧数据时,先拉低 CS,再发送两个字节,最后拉高 CS。
uint8_t AD5272_SendByte(uint8_t byte) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, byte); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI1); } void AD5272_WriteRDAC(uint8_t code) { GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS 拉低 AD5272_SendByte((0x01 << 4) | 0x00); // 写 RDAC 命令 + 地址 AD5272_SendByte(code); // 阻值码 GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS 拉高 for (volatile uint32_t i = 0; i < 200; i++); // 帧间隔 }先发送的字节是高 4 位命令拼上低 4 位地址,(0x01 << 4) | 0x00得到 0x10。有些例程喜欢用 16 位变量拼成0x1000 | code再一次性发出去,这样必须把 SPI 设置成 16 位数据模式,但 AD5272 的帧本质是两个 8 位字节,16 位模式下字节序容易搞反,建议始终用 8 位模式发两个字节。
3.3 HAL 库版本的写函数和 16 位数据的坑
用 CubeMX 生成工程时,SPI1 的初始化由MX_SPI1_Init完成,需要手工确认的只有CPOL为 Low、CPHA为 1Edge。写 RDAC 的代码比标准库更简洁:
void AD5272_WriteRDAC_HAL(uint8_t code) { uint8_t buf[2]; buf[0] = (0x01 << 4) | 0x00; // 命令 + 地址 buf[1] = code; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, buf, 2, 10); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); HAL_Delay(1); }HAL 库里有一个隐藏坑:HAL_SPI_Transmit只能按句柄里配置的数据宽度发送,如果 CubeMX 里把Data Size设成 16 位,那么buf里的两个 8 位字节会被当成一个 16 位字一次性送出,AD5272 收到的位序完全错乱。解决办法就是保持 8 位数据宽度,传两个字节。
HAL 库的HAL_Delay(1)其实有点奢侈,1 毫秒对 AD5272 来说足够,但如果你的程序里还要做按键扫描或串口收发,这个延时会影响响应。可以把延时改成空循环,或者把 CS 拉高后的间隔压缩到几微秒,AD5272 的 CS 高电平最小时间只有几十纳秒,GPIO 操作本身已经满足,只有连续快速写帧时才需要显式等待。
4. 实战:AD5272 构建可程控增益和恒流源校准
4.1 运放 PGA 电路里的 AD5272 接法
电赛信号源和仪表放大器题目里,最常见的需求是程控增益。用一个运放搭同相放大,增益公式是1 + R2 / R1,把 AD5272 接在 R1 的位置,也就是 A 端接地、B 端接运放反相输入端、W 悬空,CODE 越大 R_WB 越小,增益越高。
这个接法有一个需要注意的物理事实:CODE 从 0 变到 255 时,R_WB 的变化范围不是从 0 到 R_AB,而是从 R_W 到 R_AB + R_W。假设 AD5272-50 实际端到端电阻是 48k,R_W 是 80 欧姆,那么 CODE 接近 255 时增益会被 R_W 限制住,无法做到理论上的无穷大。如果题目要求增益范围很宽,可以在 AD5272 的 A 和 B 两端并联一个固定电阻,压缩阻值变化范围,同时降低 R_W 对高增益档的影响。
分压器接法用在可调参考电压或恒流源设定电压上更直接。A 接参考电压,B 接地,W 输出电压约等于CODE / 256 × VREF,配合运放跟随器使用。这个接法的分压比精度比可变电阻接法好,因为分压比只取决于内部电阻串的匹配,不受端到端容差影响。
4.2 增益档位跳变和用户控制程序的骨架
按键控制增益时,最简单的是每按一次 CODE 步进 1,但 256 档对电赛人机交互来说太细。更实用的做法是每按一次步进 8,长按时连续步进,同时把当前增益换算成 CODE 后通过串口打印。
static uint8_t code = 128; void AD5272_Step(int8_t delta) { int16_t new_code = code + delta; if (new_code > 255) { new_code = 255; } else if (new_code < 0) { new_code = 0; } code = (uint8_t)new_code; AD5272_WriteRDAC(code); }这里把code先转成int16_t再比较,避免uint8_t回绕。很多参赛选手直接写code += delta,当 code 等于 255 时加 1 变成 0,增益瞬间掉到底部,如果后面挂着功率放大电路,输出会出现一个很大的毛刺。步进函数里做钳位,是电赛现场最容易忽略但最重要的一行逻辑。
串口指令解析可以用 GET 命令配合strcmp,这是标准库字符串函数里最直观的写法:
if (strcmp((char *)rx_buffer, "up") == 0) { AD5272_Step(8); } else if (strcmp((char *)rx_buffer, "down") == 0) { AD5272_Step(-8); } else if (strncmp((char *)rx_buffer, "set", 3) == 0) { uint8_t target = (uint8_t)atoi((char *)rx_buffer + 4); AD5272_WriteRDAC(target); }串口中断接收时要注意 stm32f103 的串口 1 挂在 APB2,串口 3 挂在 APB1,CubeMX 生成代码后两者的中断服务函数名不同,回调入口也不一样。调试阶段建议用串口 1,和 SPI1 同属 APB2 时钟域,波特率误差更容易控制。用strcmp之前要保证rx_buffer以\0结尾,DMA 接收不定长数据时这一条常常被漏掉。
4.3 恒流源校准流程
恒流源题目里,AD5272 通常放在运放同相输入端作为设定电压,通过采样电阻把电压转换成电流。由于 R_AB 容差和参考电压误差,理论计算出的 CODE 和实际电流总是有偏差,必须在现场做两点校准。
校准步骤一般是这样:先写入一个中间值 CODE,比如 128,用万用表或 ADC 测出实际电流,再写入另一个值,比如 64,测出第二组数据,然后线性插值出目标电流对应的 CODE。
uint16_t AD5272_Calibrate(uint16_t target_code, uint16_t measured_code, uint16_t actual_code) { int32_t new_code; new_code = (int32_t)target_code * (int32_t)actual_code / (int32_t)measured_code; if (new_code > 255) new_code = 255; if (new_code < 0) new_code = 0; return (uint16_t)new_code; }这个函数假设 CODE 与电流近似线性,AD5272 本身是线性锥度,在恒流源电路里成立。注意measured_code不能为 0,否则除法崩溃。实际测试中,一次校准后误差通常能收敛到 1% 以内,想要更高精度就再迭代一次。迭代时用第二次实测值替换measured_code,而不是把两次数据做平均,因为数字电位器的分压比在中间段最准,两端略差。
如果题目要求断电后保持增益设置,AD5272 支持把数据写入内部 EEPROM,但电赛一般不推荐在代码里频繁擦写,EEPROM 有写入次数限制。可以把校准后的 CODE 存在 F103 的 Flash 备用扇区,上电时先读出来再写 AD5272,这样既保留现场校准结果,又不消耗 AD5272 的寿命。
5. 下板验证:用万用表给 AD5272 写码的三个检查点
拿到转接板或自己画的 PCB 后,不要直接接运放,先把 AD5272 当独立器件验证。万用表拨到电阻档,表笔夹在 W 和 B 两端,烧录一个简单程序循环写入 CODE 0、128、255,每次间隔 1 秒。
第一个检查点是看阻值是否单调。CODE 从 0 到 255 过程中,阻值应该持续向同一方向变化,如果中间有回头或跳变,先怀疑 SPI 时序里的毛刺,用示波器看 CS 拉低瞬间 SCLK 上有没有抖动。第二个检查点是看 CODE 0 时的最小阻值,这个值就是 R_W,它决定了运放增益电路的最高增益档。第三个检查点是看 CODE 255 时的阻值是否接近 R_AB 标称值,如果差得很多,可能是你买的 AD5272 后缀不是预期阻值,也可能 A/B 两个脚接反了。
常见故障现象和排查顺序如下:
| 现象 | 可能原因 | 检查方向 |
|---|---|---|
| 阻值完全不变 | CS 没有拉低或帧格式错 | 示波器抓 CS 与 SCLK |
| CODE 0 时阻值几十千欧 | 测的是 A 到 B 而不是 W 到 B | 确认表笔位置 |
| 输出有跳变但不单调 | SPI 速率过高或线太长 | 分频降到 4.5MHz |
| 上电瞬间输出异常 | F103 复位期间 CS 浮空 | CS 加上拉电阻 |
一个容易被忽视的边界行为是 AD5272 的通带带宽。它内部是 MOS 开关和电阻串,存在寄生电容,信号频率超过几百千赫兹后,W 端输出会出现明显衰减和相移。如果你在信号源题目里想用 AD5272 做 DAC 输出幅度的程控衰减,建议把信号频率控制在几十千赫兹以内,更高的频率就走固定电阻衰减网络加模拟开关切换的经典方案。
验证完上述三点,把 AD5272 从万用表上拆下来,按照 4.1 节的 PGA 电路焊到运放反馈回路里,重新跑一遍 CODE 扫描,记录输出电压,你会发现实际输出电压和理论值之间有一个固定比例的偏差,这就是端到端电阻容差造成的,用 4.3 节的校准函数迭代两次就能消除。
本文还有配套的精品资源,点击获取