简介:面向嵌入式开发者,这份资源提供了基于STM32的AD5422数模转换器(DAC)驱动完整工程,并兼容适配AD5412单通道版本,适用于工业控制、仪器仪表、数据采集等需要高精度模拟电压输出的应用场景。压缩包共90个文件,核心为C源码和头文件(.c/.h),另含启动汇编文件、工程备份、调试记录及说明文档等,整体约300KB,结构紧凑、模块清晰,便于直接分析移植到自有工程。已有1644人学习下载,驱动实现了完整的SPI通信流程,包括引脚与模式初始化、参考电压及电源配置、控制字与数据字发送、状态读取和超时/CRC错误处理,并附带亲测可用的AD5422测试工程。开发者可基于此快速搭建驱动框架,用于AD5412时仅需调整配置结构体并设置单通道模式,大大缩短工业控制或仪器仪表项目的DAC集成周期。
1. 项目概述与方案选型解析
1.1 AD5422 是什么,解决什么问题
做工业控制的朋友对 4-20mA 电流环一定不陌生。传感器变送、PLC 模拟量输入、阀门定位器控制,几乎所有工业现场都靠这个标准传输信号。原因很直接:电流信号不像电压信号那样容易受线长压降影响,而且断线直接变成 0mA,故障诊断也方便。那 MCU 侧要输出这种标准信号,不能靠 DAC 引脚直接驱动,因为电流太小、驱动能力也不够,所以需要专门的信号链芯片。
AD5422 就是一颗 16 位、单通道、可编程电流/电压输出 DAC。它能把 SPI 写入的数字量转成 4-20mA、0-20mA 或 0-24mA 电流,也能切换成 0-5V、0-10V 等电压输出,内部还带精密基准源和故障检测。一颗芯片就把信号链里 DAC、基准、放大器、保护电路全包了,PCB 面积和 BOM 成本都省很多。
这个项目要做的,就是在 STM32 上把 AD5422 的驱动完整跑起来。为什么选 STM32?一是开发环境成熟,标准库、HAL 库都能用,CubeMX 配置 SPI 也就几分钟的事;二是各路教程和调试资料多,遇到问题好查。对于刚接触工业模拟输出的开发者来说,这套组合上手成本相对低,踩坑也容易定位。
1.2 方案整体架构与设计思路
整个系统分三层:应用层、驱动层、硬件层。应用层只管填 DAC 数值,比如从 PID 控制器拿一个 0 到 65535 的数字量;驱动层封装 AD5422 的寄存器读写、初始化序列、量程配置;硬件层就是 STM32 的 SPI 外设和 AD5422 芯片本身。
设计驱动时我的习惯是:底层 SPI 字节收发函数先独立出来,不跟具体芯片逻辑耦合。这样以后换芯片或者改 SPI 引脚,只动底层那几行,上层寄存器配置完全不用碰。寄存器读写各封装一个函数,再往上才是“写电流输出”“配输出范围”这类高语义接口。分层干净了,调试也省心。
选型方面还要补充一点:如果项目对成本极度敏感,也可以用 PWM 加 RC 滤波加运放搭一个模拟输出,但线性度、温漂、分辨率都跟 AD5422 不是一个级别。16 位 DAC 的单调性和低增益误差是 PWM 方案很难追上的,尤其是在 4-20mA 需要千分之几精度的场合,用集成 DAC 才是可靠解。
2. 硬件连接与电路设计要点
2.1 SPI 接线与引脚分配
AD5422 的 SPI 是三线加一根帧同步,标准模式。按项目最常用的配置,STM32 的 SPI1 做主机:
| STM32 引脚 | AD5422 引脚 | 功能说明 |
|---|---|---|
| PA5 (SPI1_SCK) | SCLK | 串行时钟,空闲极性可配置 |
| PA7 (SPI1_MOSI) | SDIN | 数据输入,MSB 在前 |
| PA4 (GPIO 输出) | SYNC | 帧同步,低电平有效 |
| PA6 (SPI1_MISO) | SDO | 数据回读输出,可选接 |
SYNC 脚特别重要。AD5422 靠它确定一帧数据的边界:SYNC 拉低,SCLK 开始采 SDIN 上的 16 位数据;传完一个 16 位寄存器指令后,SYNC 拉高,芯片才开始执行这次写入。很多第一次调试的人把 SCLK、SDIN 接对了但 SYNC 一直拉低,结果数据全都错位,输出怎么都不对。
SDO 是回读用的。如果项目里不需要读寄存器状态、不需要做故障诊断,SDO 可以不接,省一个 GPIO。但我强烈建议接上——后面排查“输出为什么乱跳”“寄存器到底写没写进去”这类问题时,回读功能能省你半天时间。
2.2 电源与基准电压设计
AD5422 的供电分两路:模拟电源 AVDD 和数字电源 DVDD。AVDD 范围挺宽,12V 到 33V 都能工作,常见做法是直接用 24V 工业电源。DVDD 一般接 3.3V 或 5V,取决于 STM32 的 IO 电平,建议和 MCU 同一路电源,避免电平不匹配。
基准电压可以选内部 2.5V 基准,也可以外部注入。我的建议是:没有特殊精度要求时,直接用内部基准,省电路;要求精度达到 0.1% 以上、或者希望温漂可控时,外接精密基准源,比如 REF195 这类。板上如果有 ADC 同时采集其他信号,基准的干净程度直接影响整机精度,这点在设计 PCB 时就要想好。
去耦电容别省。AVDD、DVDD 两端都要放 0.1uF 和 10uF 的组合,布局尽量贴近芯片电源引脚。这个芯片输出是模拟信号,电源纹波会直接耦合到电流环上,去耦没做好,示波器上看输出就是毛刺一串。
2.3 输出负载与保护电路
电流输出模式下,AD5422 的 IOUT 引脚直接接负载回路。现场负载可能是 PLC 的 250Ω 采样电阻,也可能是长电缆。芯片规格书上说 Compliance Voltage 最高能到 AVDD - 2.5V 左右,也就是说 24V 供电时输出端能抗到接近 21V,带几百欧负载轻轻松松。
保护方面,建议在 IOUT 和负载之间串一个反向二极管,防止现场误接正负电源把芯片打坏。同时并联一个小电容,一般 1nF 到 10nF,滤掉高频噪声。但注意电容不要太大,太大影响信号带宽。
电压输出模式的负载端接法不太一样,需要看芯片手册里的内部放大器结构,输出端和 SENSE 引脚配合接法,负载电阻一般 5kΩ 以上比较稳。这部分如果你的项目只用到电流输出,可以先不管。
3. 驱动代码架构与核心实现
3.1 AD5422 寄存器体系速览
写驱动之前,必须先把寄存器地图搞清楚。AD5422 的寄存器是 16 位宽,高 3 位是寄存器地址,低 13 位是数据。常用寄存器就那几个:
| 寄存器 | 地址 | 功能 |
|---|---|---|
| NOP | 0x00 | 空操作,常用来发起一次回读时序 |
| Control | 0x01 | 工作模式、省电、故障清除等 |
| Output Range | 0x02 | 配置电流/电压输出范围 |
| DAC Data | 0x03 | 16 位 DAC 数据 |
| Clear | 0x04 | 清零模式选择 |
| Status | 0x05 | 故障状态标志 |
写 DAC 数据就是往 0x03 寄存器写一个 16 位数据。注意它不支持 24 位帧格式,所有通信都是标准的 16 位 x 2 段,要么通过 SYNC 区分,要么在回读时先写 NOP 再读。
3.2 SPI 底层通信函数
底层我按最常见的裸机方式写,没有上 DMA。DMA 对于这种单通道、低频更新的 DAC 场景收益不大,反而引入缓冲管理复杂度。代码结构这样拆:
static void ad5422_spi_write_frame(uint16_t frame) { GPIO_ResetBits(SYNC_PORT, SYNC_PIN); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, frame); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); SPI_I2S_ReceiveData(SPI1); GPIO_SetBits(SYNC_PORT, SYNC_PIN); } static uint16_t ad5422_spi_read_frame(uint16_t frame) { uint16_t rx = 0; GPIO_ResetBits(SYNC_PORT, SYNC_PIN); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, frame); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); rx = SPI_I2S_ReceiveData(SPI1); GPIO_SetBits(SYNC_PORT, SYNC_PIN); return rx; }SYNC 每次传输都要拉低拉高,这是 AD5422 和其他 SPI 芯片不太一样的地方。如果直接用 SPI 硬件的片选功能,要注意片选拉高时机对不对,有些 MCU 的 NSS 在传输结束后自动拉高,时机不一定满足芯片要求。实测下来 GPIO 模拟 SYNC 反而更稳,时序完全自己控制。
3.3 初始化流程详解
初始化顺序很重要,网上不少例程顺序都不对,容易留下隐患。我整理了一份稳定的初始化序列:
void ad5422_init(void) { ad5422_spi_init(); // 配置 SPI 引脚和模式 ad5422_reset(); // 拉低 RESET 引脚复位 ad5422_set_output_range(AD5422_RANGE_4_20MA); // 配置输出范围为 4-20mA uint16_t ctrl = AD5422_CTRL_OUTPUT_EN | // 使能输出 AD5422_CTRL_SR_CLOCK_64; // Slew Rate 可选,默认关闭 ad5422_write_reg(AD5422_REG_CTRL, ctrl); ad5422_write_reg(AD5422_REG_DAC_DATA, 0x8000); // 先输出中间值 }第一步复位,这个别省略。上电后芯片内部寄存器不一定是确定状态,拉一下 RESET 引脚保证从干净状态开始跑。第二步配输出范围,这个寄存器要优先于输出使能,否则芯片可能在错误的范围下瞬间输出一个大电流。第三步使能输出,最后再写 DAC 数据。
初始化完成后建议做一次回读验证。读 Control 寄存器和 Output Range 寄存器,确认写进去的数值对,再往下跑业务逻辑。这一步特别适合放在产品自检流程里,比裸奔试输出可靠得多。
3.4 关键函数代码示例
写 DAC 输出的核心就一行:
void ad5422_set_dac(uint16_t value) { ad5422_write_reg(AD5422_REG_DAC_DATA, value); }这里有个细节,16 位 DAC 数据是左对齐还是右对齐,芯片手册里有明确说明。AD5422 的数据位是 DB15:DB0,直接写入即可,不需要移位。
输出范围配置稍微复杂一点,因为寄存器里每一位的含义需要对照手册。我把常用范围整理成了宏:
#define AD5422_RANGE_0_5V (0x01) #define AD5422_RANGE_0_10V (0x02) #define AD5422_RANGE_0_20MA (0x03) #define AD5422_RANGE_4_20MA (0x04) #define AD5422_RANGE_0_24MA (0x05)这里可以留意一下,0-24mA 这个范围常用于一些特殊变送器需求,4-20mA 是最常见的安防和仪表标准,0-20mA 适合一些相对旧的控制系统。选型时先确认好现场接口,别到联调时才发现电流范围不匹配。
3.5 回读功能与状态诊断
AD5422 支持回读,这是排查问题的大杀器。回读时序是:先写一个 NOP(0x00 地址),后面跟一个地址帧,芯片会把该地址的数据放在下一帧的 SDO 上。驱动里我这样封装:
uint16_t ad5422_read_reg(uint8_t addr) { ad5422_spi_write_frame(0x0000); // NOP,刷新回读缓冲 uint16_t value = ad5422_spi_read_frame((uint16_t)(addr << 13)); return (value & 0x1FFF); }这个函数配合逻辑分析仪或者示波器抓 SDO,基本能定位绝大多数“寄存器没写进去”的问题。比如你写了一个 DAC 数据,但串口显示输出的电压不对,回读一下 DAC Data 寄存器,如果读到的值跟你写的对不上,要么是 SPI 时序问题,要么是 SYNC 没控制好,要么是 SDO 线上拉电阻冲突。三种原因都能通过回读来区分。
4. 实操过程与调试记录
4.1 STM32CubeMX 配置 SPI
如果用的是 HAL 库,CubeMX 里配置 SPI1 为主模式,参数设置建议:波特率预分频 16 或 32,控制在 5MHz 以内;时钟极性 CPOL=Low,相位 CPHA=2Edge。但这里有个坑,不同 AD5422 版本对时序要求略有差异,手册里看到的是 SCLK 空闲为低,数据在下降沿锁存,如果发现通信不稳定,试着把 CPHA 改成 1Edge 再测一次,不影响硬件。
配置完成后生成工程,在 main.c 里调用前面写的 ad5422_init 就行。用标准库的话就手写 GPIO 和 SPI 外设初始化,整体代码量也不大。
4.2 编译烧录与基本验证
编译烧录这一步没什么特别的,但要提醒一句:先别急着接 AD5422 的电源和负载,先把 SPI 线上波形抓出来看。用逻辑分析仪抓 SYNC、SCLK、SDIN 三根线,确认写帧时的时序跟芯片手册上的一致,特别是 SYNC 拉低后 SCK 的第一个沿到数据第一个 bit 之间的建立时间。
我调这块板子时,第一次抓波形就发现问题——SCLK 空闲电平搞反了,导致 SDIN 上的数据在错误沿被锁存。把 CPOL 和 CPHA 调对之后,再抓波形就完全正常。
4.3 输出信号验证与调整
硬件和驱动都正常后,把 DAC 值写成 0x8000,对应中间值,万用表或者示波器量 IOUT 对地电压。接 250Ω 负载电阻,中间值应该量到 3V 左右——因为 4-20mA 范围中间是 12mA,12mA 乘以 250Ω 就是 3V。这是一个很好的 sanity check,如果数值对不上,先查负载电阻精度和供电电压。
如果发现某个特定 DAC 值时输出非线性,大概率是 slew rate 限制或者输出级电容影响了建立时间。AD5422 的 Slew Rate 寄存器里可以配置压摆率,默认关闭是最快速的,但如果负载电容大,可能要在建立时间和稳定性之间做个折中。调试时从关闭开始,有问题再慢慢加。
4.4 与上位机联调时的一个实用技巧
在实际项目中,DAC 数值往往由 PID 或者协议栈算出来。建议在驱动层做一个“影子寄存器”,每次写完后更新影子变量,上位机读当前输出时直接返回影子值,不用每次都 SPI 回读,实时性好很多。
static uint16_t shadow_dac = 0x8000; void ad5422_set_dac(uint16_t value) { ad5422_write_reg(AD5422_REG_DAC_DATA, value); shadow_dac = value; } uint16_t ad5422_get_dac(void) { return shadow_dac; }另外,SPI 写寄存器前建议关中断,防止写一半被高优先级中断打断,导致帧错位。虽然 AD5422 对帧完整性有一定容错,但这是个成本极低的好习惯。
5. 常见问题与排障心得
5.1 寄存器配置了,输出就是不动
这是最常见的问题。排查顺序:先万用表量 AVDD、DVDD 电压对不对,再看 SYNC 引脚电平是否正常,然后用逻辑分析仪抓 SPI 波形。如果波形正常但输出还是不动,大概率是输出范围寄存器没配对,或者 Output Enable 位没置 1。
有一个容易忽略的小细节:AD5422 的 Control 寄存器第 9 位是 Output Enable,第 10 位是 Slew Rate Enable。如果你只写了 DAC Data 寄存器,没有把 Output Enable 置位,芯片是不会有输出的。查这块板子时我一度怀疑芯片坏了,结果就是软件少置了一位。
5.2 SPI 通信失败怎么定位
SPI 通信失败的典型表现是:回读的数据全是 0xFFFF 或者 0x0000。先按这个顺序排查:
| 可能原因 | 排查方法 |
|---|---|
| SCLK 极性/相位错误 | 逻辑分析仪抓波形,对比手册时序图 |
| SYNC 时序不对 | 确认 SYNC 每帧都拉高一次 |
| SDIN 上无数据 | 示波器量 MOSI 引脚,确认 IDF 配置正确 |
| 电源异常 | 量 AVDD、DVDD、GND 是否稳定 |
| RESET 被拉低 | 确认复位引脚不是悬空状态 |
还有个小坑:AD5422 的 SPI 是 16 位帧,如果 STM32 的 SPI 配置成 8 位帧,每次只发 8 位,寄存器地址和数据就对不上,回读自然全乱。CubeMX 里 SPI 默认是 8 位,记得改成 16 位。
5.3 电流输出精度不够怎么办
精度不够时,先看基准电压。内部基准虽然够用,但温漂和初始精度都有限。如果系统跑下来误差超过 0.5%,建议外接精密基准。其次是校准,AD5422 自带失调和增益校准寄存器,可以先把零点校准到 4mA 精确值,再校满量程 20mA。
校准流程建议这样:用高精度万用表测实际输出电流,算误差,然后写失调寄存器修正零点,再修正增益。重复两次就能收敛到 0.1% 以内。注意校准的时候要等温度稳定,冷机校准和热机校准结果可能会差几个 LSB。
5.4 独家避坑笔记
最后分享几个只有实际做项目才会遇到的小问题。
第一,AD5422 的 SDO 引脚如果不回读,悬空或者接上拉都行,但如果接了上拉电阻,注意不要影响 SPI 总线的时序。
第二,PCB 布局时,IOUT 走线尽量短粗,避开数字信号线。我见过一块板子,IOUT 和 SPI 时钟线平行走了 3 厘米,输出纹波直接从示波器上爆表,改成垂直布线后恢复正常。
第三,产品低温测试时,如果输出偏差变大,多半不是驱动软件问题,而是板上的电阻温漂。输出负载电阻要用低温漂薄膜电阻,万不可用普通贴片,否则 4-20mA 环路误差在温差大时远超规格。
根据我个人的习惯,每次拿到一块新板子,我都是先把回读功能调通,再去看输出信号。回读通了,代表芯片通信链路健康,后面再怎么折腾都不会出现“写了半天不知道芯片状态”的尴尬局面。这套 AD5422 驱动做好之后,后面换 AD5412(单电流输出)或者 AD5755(多通道)时,寄存器布局很接近,代码改造量很小,算是做一次受益多次。
本文还有配套的精品资源,点击获取