
简介这是一份面向嵌入式开发者的STM32平台ADC/DAC驱动资源围绕TI公司的ADS131A0216位Σ-Δ型、最高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 位 Δ-Σ ADCADS131A02 的输入级自带 PGA 和高输入阻抗适合直接对接传感器DAC8552 是双通道 16 位电压输出 DACSPI 接口配上外部基准源就能产生毫伏级分辨率信号。很多工程师习惯把它们当成两个独立外设分开调结果调通单芯片后一拼到同一块板上就碰上中断互相挤占、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, OSR128 }, { 0x03, 0x00, ADC: continuous mode, 24-bit }, { 0x04, 0x00, CH1_SCALE: gain1 }, { 0x05, 0x00, CH2_SCALE: gain1 }, { 0x06, 0x02, DRDY: active low, offset calibration off }, };这段表的含义是先把通道电源全部打开再设定内部主时钟与过采样率随后把转换模式固定为连续转换并关闭通道增益和偏置校准最后配置 DRDY 引脚的有效电平。OSR 为 128 时数据输出速率是主时钟除以 OSR 再除以通道数通常手册会给出对应表格“OSR128”直观理解是每个输出样本由 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.5VLSB 大小为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 读取外设到 MCUDRDY 中断触发 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 在 OSR128 时输出速率约为几十 kSPS 量级实际值参考你的主时钟SPI 传输 6 字节所需时间是6 * 8 / f_spi如果 SPI 时钟为 12MHz就是 4 微秒左右。DAC8552 写一帧 3 字节需要 2 微秒。留给处理算法的余量非常充足。实际工程中真正限制采样率的往往不是 CPU 速度而是中断调度抖动。在裸机代码里如果另一个中断长时间占用 CPUDRDY 中断会延迟响应DRDY 低电平期间没有被读取的样本会被下一次转换覆盖最终表现为采集数据周期性跳变。解决方法是把 DRDY 中断优先级提到其他非关键中断之上并检查中断响应时间是否小于 ADC 输出周期的 10%。4.4 故障现象和对应排查路径现象大概率原因排查动作ADC 数据第一个通道恒为 0通道符号扩展错误检查拼字节顺序DAC 输出总是少 1 个 LSB满量程用了 65536改成 65535 换算DAC 两通道不同步LDAC 触发时机不对确认写完再拉 LDACADC 读数周期性跳大值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 的中心这样可以避开栅栏效应清楚识别每一个异常频率分量整套链路是否合格就看这张频谱图上的噪底和谐波高度。本文还有配套的精品资源点击获取