简介:这是一份面向嵌入式开发者的STM32平台ADC/DAC驱动资源,围绕TI公司的ADS131A02(16位Σ-Δ型、最高250ksps、低噪声低失调)与DAC8552(双通道、8位并行输入、串行输出)展开,适合医疗、工业控制、高精度数据采集等需要模拟信号精确测量与输出的项目。压缩包共195个文件,约6.34MB,以C源码、头文件为主,搭配编译生成的o/d/crf等目标及调试文件,同时包含Keil工程配置(uvprojx/uvoptx)、链接脚本、Hex/Axf烧录文件及文本说明,基本构成一套可直接打开查看的完整工程。资源已吸引908人学习,对初学或迁移这两个器件驱动的人员很有参考价值。通过阅读代码可快速掌握ADC的采样率/参考电压配置、DAC输出电压设定及数字量转换逻辑,并借用其中stm32f4xx系列的配置方式接入自己的项目,减少从零编写驱动的时间和排错成本。
1. ADS131A02 与 DAC8552:为什么这颗 ADC 要配一颗 16 位 DAC
在自动测试设备、伺服驱动、传感器模拟器和便携式仪表里,经常出现一种对称需求:一边要把电压信号精确采进来,另一边要同步输出一个电压去激励被测对象。ADS131A02 是 TI 的双通道 24 位 Δ-Σ ADC,ADS131A02 的输入级自带 PGA 和高输入阻抗,适合直接对接传感器;DAC8552 是双通道 16 位电压输出 DAC,SPI 接口,配上外部基准源就能产生毫伏级分辨率信号。很多工程师习惯把它们当成两个独立外设分开调,结果调通单芯片后一拼到同一块板上就碰上中断互相挤占、SPI 总线帧错位、输出毛刺等问题。本文把这两颗器件放到同一条采集回放链路里讲:先看各自寄存器怎么写,再给一个可落地的代码框架,最后讲调试时真正影响波形质量的三个细节。适合手里已经拿到这两颗芯片、准备在 STM32 或其他 SPI 主机上写出可用固件的开发人员。
2. ADS131A02 的 SPI 寄存器配置与连续转换读取实现
2.1 从“命令帧 + 响应数据”理解 ADS131A02 的 SPI 时序
ADS131A02 和普通逐次逼近型 ADC 最大的区别是 SPI 帧里不止有数据,还附带命令和状态回传。主机发一个写寄存器命令,从机在后续帧中返回内部状态;主机连续读数据时,MISO 上出现的实际上是上一次命令周期的转换结果。这一点如果没理解透,很容易出现“读到的第一个数据是上一帧的”“数据错位一个通道”这类问题。
实际工程中不建议完全按字节数去猜帧边界,而是先看数据手册里的 Figure 系列时序图,再用示波器抓一次 CS 拉低期间 MOSI/MISO 的电平变化。ADS131A02 的帧由 8 位命令字和紧随其后的数据字组成,命令字中的 W/R 位决定本次是读还是写,地址位选择寄存器,剩余位填数据。数据输出为两个通道各 24 位数据,主机需要连续发送足够的 SCLK 周期把 LSB 全部移出。
2.2 表驱动方式完成寄存器初始化
初始化寄存器不要上来就写一大段赋值语句,而是用一张配置表,把地址、寄存器名、目标值和注释放在一起。这样既能对照数据手册逐项检查,也方便在调试阶段屏蔽某一项。
typedef struct { uint8_t addr; uint8_t val; const char *name; } ads131a02_cfg_t; static const ads131a02_cfg_t init_tbl[] = { { 0x01, 0x00, "POWER: all channels enabled" }, { 0x02, 0x0D, "CLOCK: 8.192MHz internal, OSR=128" }, { 0x03, 0x00, "ADC: continuous mode, 24-bit" }, { 0x04, 0x00, "CH1_SCALE: gain=1" }, { 0x05, 0x00, "CH2_SCALE: gain=1" }, { 0x06, 0x02, "DRDY: active low, offset calibration off" }, };这段表的含义是:先把通道电源全部打开,再设定内部主时钟与过采样率,随后把转换模式固定为连续转换,并关闭通道增益和偏置校准,最后配置 DRDY 引脚的有效电平。OSR 为 128 时数据输出速率是主时钟除以 OSR 再除以通道数,通常手册会给出对应表格;“OSR=128”直观理解是每个输出样本由 128 个过采样点平均而来,分辨率更高但转换速率会降低。
配置寄存器时要注意每次写完后等待一帧结束再发下一条命令。因为 Δ-Σ ADC 的数字滤波器需要几个输出周期才能稳定,寄存器写得太快,后续命令可能被当成响应数据解析,导致配置没有真正写入。
2.3 读取双通道 24 位数据并做符号扩展
读取时最省事的做法是使用 SPI 的 DMA 模式,由 DRDY 引脚触发一次传输。下面代码演示了在主控端用轮询等待 DRDY 拉低、然后连续读取 6 个字节的方式:
typedef struct { int32_t ch[2]; } ads131a02_sample_t; int ads131a02_read_sample(ads131a02_sample_t *s) { uint8_t tx[6] = {0}; uint8_t rx[6] = {0}; for (int i = 0; i < 10; i++) { if (gpio_get(DRDY_PIN) == 0) break; delay_us(10); } if (gpio_get(DRDY_PIN) != 0) return -1; cs_low(); spi_transfer_buf(tx, rx, 6); cs_high(); for (int i = 0; i < 2; i++) { int32_t v = (rx[i * 3] << 16) | (rx[i * 3 + 1] << 8) | (rx[i * 3 + 2]); if (v & 0x800000) { v |= 0xFF000000; } s->ch[i] = v; } return 0; }逻辑说明:等待 DRDY 为低电平后用 6 个 SPI 时钟周期把两通道数据移出,再把每通道 3 字节拼成一个 24 位有符号数。最关键的符号扩展在if (v & 0x800000)这一行,如果最高位为 1,说明转换结果是负电压,需要把高 8 位全部补 1,才能得到正确的int32_t值。物理电压换算公式是V = code * VREF / (2^23),其中 VREF 是 ADC 参考电压,code 就是这里返回的有符号整数。
需要注意,连续转换启动后的前两个样本不可用。数字滤波器的群延迟会让前几个输出值不反映真实输入,正确做法是启动后先丢弃 2 到 3 个样本,再开始缓存数据。
2.4 DRDY 中断和 SPI DMA 的配合
轮询方式适合低速验证,正式代码里建议把 DRDY 接外部中断,下降沿触发后启动 SPI DMA 读取。关键点在于 DMA 每次读取 6 字节是一次性的,读完后需要重新使能 DRDY 中断,否则下一次采样会被漏掉。
中断优先级设置也有讲究。DRDY 中断应高于普通任务优先级,但低于系统时钟节拍;如果项目里还有 DAC8552 的输出更新中断,建议让 ADC 读取优先级高于 DAC 写入,因为数据手册给出的输出速率一旦超过中断响应能力,丢样本是无法恢复的。可以做一个计数变量,当 ADC 数据就绪事件发生但 DMA 仍忙时,累计丢帧次数,调试时通过串口打印这个值来判断系统是否过载。
3. DAC8552 写时序与双通道输出的最小代码
3.1 DAC8552 的 24 位写帧如何分域
DAC8552 表面上看像普通 SPI DAC,但它不是简单发 16 位数据,而是要求把数据包装成 24 位帧。帧的最高 2 位是控制码,决定数据写入哪个通道以及是否立即更新输出。剩余 22 位中实际有效的只有 16 位 DAC 数据,其他位为低电平占位。
这个设计在工程上的意义是:可以用单一写时序同时完成“选通道”和“传数据”,省去额外的地址引脚。代价是发送一帧需要 3 个字节,SCLK 频率较高时才不会拖慢整体更新率。写代码时最好把控制位定义成宏,而不是散落在业务代码里。
#define DAC8552_CTRL_DAC_A 0x00 #define DAC8552_CTRL_DAC_B 0x01 #define DAC8552_CTRL_BOTH 0x03 void dac8552_write_raw(uint8_t ctrl, uint16_t data) { uint8_t frame[3]; frame[0] = (ctrl << 6); /* 控制码在最高两位 */ frame[1] = (data >> 8) & 0xFF; frame[2] = data & 0xFF; cs_low(); spi_transfer(frame[0]); spi_transfer(frame[1]); spi_transfer(frame[2]); cs_high(); }参数说明:第一个字节的高两位是控制码,低 6 位必须为 0,所以这里(ctrl << 6)之后不需要再做与运算;第二、第三字节是 16 位数据的先后顺序。SPI 模式下 DAC8552 的数据在 SCLK 上升沿锁存,主机配置成 SPI 模式 0 或模式 1 均可,只要保证 CS 在整帧传输期间保持低电平即可。
3.2 电压换算和输出设定
DAC8552 是电压输出型 DAC,输出电压与代码之间近似线性的关系,换算很简单。基准电压 VREF 由外部提供,常见取 4.096V 或 2.5V,LSB 大小为VREF / 65536。要把目标电压转成代码,先做越界检查,再四舍五入。
uint16_t dac8552_volt_to_code(float vout, float vref) { float v = vout; if (v > vref) v = vref; else if (v < 0) v = 0; return (uint16_t)(v / vref * 65535.0f + 0.5f); }这里选择 65535 而不是 65536,是因为 DAC 满量程代码是 0xFFFF,对应输出电压比 VREF 小一个 LSB。四舍五入的加 0.5 能有效减少代码跳动,特别是当目标电压刚好落在两个相邻 LSB 中间时,稳定后的输出不会在最小位上来回振荡。
一个常见误区是忽略 DAC8552 上电后的默认输出。器件刚上电时输出寄存器可能不是 0,如果后续接的是功率放大器,上电瞬间会出现一个不确定的电压尖峰。工程上一般在 DAC8552 初始化时立即写一个已知安全电平,比如 0V 或中量程,再开始后续控制算法。
3.3 用 LDAC 引脚实现两通道同步输出
DAC8552 有 LDAC 引脚,它的作用是控制 DAC 寄存器内容何时真正传到模拟输出端。如果把需要同步输出的两个通道分别写入各自的输入寄存器,然后拉低 LDAC,两个输出在同一时刻更新,避免了逐个写 SPI 造成的时间差。
void dac8552_set_sync(uint16_t code_a, uint16_t code_b) { dac8552_write_raw(0x00, code_a); /* 写入 DAC A 输入寄存器 */ dac8552_write_raw(0x01, code_b); /* 写入 DAC B 输入寄存器 */ gpio_write(LDAC_PIN, 0); /* 同时更新两个输出 */ delay_us(1); gpio_write(LDAC_PIN, 1); }这段代码的关键在于先写完两个通道,再统一触发 LDAC。如果顺序反了,比如写完 A 就立即拉低 LDAC,再写 B,那么 A 的输出会比 B 早一个 SPI 帧时间,在需要同步波形输出的场景下就会产生相位差。LDAC 的低脉冲可以短到亚微秒级,但驱动电容负载时需要根据 DAC8552 输出放大器的压摆率留出适当时间。
4. 把 ADS131A02 和 DAC8552 放进同一套采集回放代码里
4.1 软件架构上的四条数据通路
当两颗芯片同时工作时,主控的 SPI 外设会面临总线占用问题。ADS131A02 需要定期读取,DAC8552 需要根据控制算法实时更新。如果使用同一个 SPI 外设,任何一方的长时间占用都会影响另一方时序。常见做法是把两个芯片挂在不同片选上,共用同一 SCLK/MOSI/MISO,并用一个状态机串行处理读写请求。
| 数据通路 | 方向 | 触发方式 | 实时性要求 |
|---|---|---|---|
| ADS131A02 读取 | 外设到 MCU | DRDY 中断触发 DMA | 必须在一个采样周期内完成 |
| DAC8552 写入 | MCU 到外设 | 控制算法计算完成后主动写 | 越快越好,可容忍一个帧延迟 |
| 串口/日志输出 | MCU 到上位机 | 定时或任务触发 | 低 |
| 回环自检 | ADC 到 DAC | 手动触发 | 低 |
表中前两条是核心路径。为了不让串口日志打断 SPI 时序,最好把日志数据放到另一个缓冲里由任务定期发送,或是使用 DMA 后后台发送。控制算法如果耗时较长,应放到 DRDY 中断之外,只在样本准备好后置一个标志,由主循环或 RTOS 任务去处理。
4.2 DMA 双缓冲和样本处理代码
读取 ADS131A02 的完整流程建议使用双缓冲:当 DMA 正在搬运当前帧时,主控处理上一帧数据。使用双缓冲后,DRDY 中断服务函数里只需要做两件事:启动下一次 DMA 接收,以及把上一帧的索引交给处理函数。
static uint8_t dma_rx_buf[2][6]; static volatile uint8_t cur_buf = 0; static volatile uint32_t sample_cnt = 0; void drdy_isr(void) { spi_dma_receive(dma_rx_buf[cur_buf], 6); cur_buf ^= 1; sample_cnt++; } void app_process_sample(void) { ads131a02_sample_t s; const uint8_t *p = dma_rx_buf[cur_buf ^ 1]; for (int i = 0; i < 2; i++) { int32_t v = (p[i * 3] << 16) | (p[i * 3 + 1] << 8) | p[i * 3 + 2]; if (v & 0x800000) v |= 0xFF000000; s.ch[i] = v; } float vref = 4.096f; float vin_a = s.ch[0] * vref / 8388608.0f; float vin_b = s.ch[1] * vref / 8388608.0f; /* 这里放入控制算法,结果输出到 DAC8552 */ uint16_t out_a = dac8552_volt_to_code(vin_a * 0.5f + 1.0f, vref); uint16_t out_b = dac8552_volt_to_code(vin_b * 0.5f, vref); dac8552_set_sync(out_a, out_b); }说明:drdy_isr负责启动 DMA,主循环或低优先级任务调用app_process_sample处理最近完成的缓冲。注释里的换算把 AD 采样值还原成实际电压,然后按衰减比例回送到 DAC。这种“先采集再回放”的代码结构是很多硬件在环测试项目的基本骨架,不需要在中断里处理浮点,浮点计算全部放到任务级执行,否则中断会占用过多时间。
4.3 时序预算:到底能跑多快的采样率
采样周期的上限由三部分决定:ADC 数据输出的物理限制、SPI 传输耗时、控制算法处理耗时。ADS131A02 在 OSR=128 时输出速率约为几十 kSPS 量级,实际值参考你的主时钟;SPI 传输 6 字节所需时间是6 * 8 / f_spi,如果 SPI 时钟为 12MHz,就是 4 微秒左右。DAC8552 写一帧 3 字节需要 2 微秒。留给处理算法的余量非常充足。
实际工程中真正限制采样率的往往不是 CPU 速度,而是中断调度抖动。在裸机代码里,如果另一个中断长时间占用 CPU,DRDY 中断会延迟响应,DRDY 低电平期间没有被读取的样本会被下一次转换覆盖,最终表现为采集数据周期性跳变。解决方法是把 DRDY 中断优先级提到其他非关键中断之上,并检查中断响应时间是否小于 ADC 输出周期的 10%。
4.4 故障现象和对应排查路径
| 现象 | 大概率原因 | 排查动作 |
|---|---|---|
| ADC 数据第一个通道恒为 0 | 通道符号扩展错误 | 检查拼字节顺序 |
| DAC 输出总是少 1 个 LSB | 满量程用了 65536 | 改成 65535 换算 |
| DAC 两通道不同步 | LDAC 触发时机不对 | 确认写完再拉 LDAC |
| ADC 读数周期性跳大值 | DRDY 中断被阻塞 | 测量 DRDY 低电平时长 |
| ADC 读到的值比实际偏小 | VREF 未接或滤波不足 | 示波器测 REF 脚电压 |
这张表适合在联调阶段对照使用。看到数据异常时先不要改代码,用示波器同时抓 DRDY、CS、SCLK 三路信号,确认帧边界是否正确,再回过来查逻辑。
5. 调试 ADC/DAC 链路时最值得先看的三个信号细节
示波器探头夹上去之前,先明确一点:链路里最容易出卖问题的不是最终波形幅值,而是时序边缘和毛刺。第一个要盯的位置是 CS 下降沿到第一个 SCLK 上升沿之间的建立时间。ADS131A02 和 DAC8552 对 CS 建立时间都有最小要求,如果 MCU 的 GPIO 速度配置过快或过慢,第一个时钟沿采样到的数据可能是无效电平。用示波器把时间轴放到 100ns 每格,观察 CS 拉低后 SCLK 的第一个上升沿,如果边沿上有明显回勾,就要检查 SPI 外设的 GPIO 输出速度档位。
第二个细节是 DRDY 引脚的低电平脉冲宽度。ADS131A02 的 DRDY 拉低表示当前数据已经准备好,但如果在 DMA 启动前 SCLK 就产生上升沿,数据会被破坏。此时可临时把 SPI 时钟频率降到 4MHz,如果问题消失,说明是布局布线引入的串扰,优先在 SCLK 线串联 33Ω 电阻并缩短 SPI 走线长度。
第三个值得花时间做的是把 DAC8552 输出进行电阻分压后直接接回 ADS131A02 输入,构建一个物理回环。用 MCU 产生一个低频正弦波代码表,经过 DAC8552 输出,再让 ADS131A02 采集回来。将采集数据做 1024 点 FFT,读回信号的频谱上如果出现明显的二次谐波,多半是 DAC 输出幅度接近满量程导致放大器非线性;如果出现独立于信号频率的尖峰,需要检查 SPI 帧是否在每次循环里多发了字节。FFT 分辨率不够时,把采样率和 FFT 点数配合起来,让信号频率正好落在某个 bin 的中心,这样可以避开栅栏效应,清楚识别每一个异常频率分量,整套链路是否合格就看这张频谱图上的噪底和谐波高度。
本文还有配套的精品资源,点击获取