简介:ADS131A02与DAC8552驱动代码压缩包,面向需要集成ADC与DAC的嵌入式开发者,解决高精度模拟信号采集与输出场景中的器件配置、数据交互与验证问题。ADS131A02为TI出品的16位Σ-Δ型模数转换器,最高支持250ksps采样率,内置参考电压,适合医疗、工业等高精度测量;DAC8552为双通道8位并行输入数模转换器,支持0至5V电压输出,常用于遥测、自动控制等系统。两者配合可实现模拟信号的采集、数字处理与再输出,形成完整信号链。资源包共195个文件,压缩后约6.34MB,以C语言源码和头文件为核心,另有编译生成的目标文件、调试配置、Keil工程备份与辅助脚本,便于开发者直接打开工程阅读或移植到自身项目。内容覆盖STM32F4等常用平台,已有909人学习下载。通过示例代码与驱动接口,开发者可快速掌握采样率设置、参考电压配置、转换结果读取、模拟电压输出等关键操作,有效缩短驱动调试与系统联调时间。
1. ADS131A02 和 DAC8552 代码包:一份需要补全的驱动骨架,怎么救活它
拿到这份 ADS131A02 和 DAC8552 的 zip 包时,别指望解压就能编译。里面躺着一堆 STM32F4 标准外设库文件和 Keil 构建产物,真正的器件驱动逻辑还得自己搭。ADC 负责把模拟信号拉进数字域,DAC 负责把处理完的数字量再放回模拟域,这套组合常见于遥测采集、信号源和工业控制。适合谁?手头正好有这两颗芯片、想绕过翻手册的漫长过程、直接看别人怎么配置寄存器的人。接下来我会按芯片原理、文件去留、总线初始化、驱动代码、踩坑记录这个顺序,把这份资源真正用到你自己的工程里。
2. 先看器件再动代码:Σ-Δ ADC 与双通道 DAC 的选型逻辑
2.1 为什么需要Σ-Δ架构:过采样、噪声整形与数据率
ADS131A02 是 TI 的 Delta-Sigma 型 ADC,这类架构和逐次逼近型(SAR)ADC 最大的区别,就是不怎么怕模拟前端噪声。它的核心思想是过采样:以远高于奈奎斯特频率的速率对信号采样,把量化噪声摊到更宽的频带上,再通过噪声整形把带内噪声推到高频部分,最后由内部数字抽取滤波器把高频噪声滤掉。等效出来的分辨率比单个采样点高很多。
所以它在医疗、工业变送器这类信号弱且要求稳定的场景里表现很好。内部还集成参考电压源,省掉一路精密基准电路。唯一要注意的是不同资料对采样率标注不一致,常见的有 128kSPS 和 250kSPS 两种说法,移植代码前以你手上数据手册为准。遇到这种位数标混的情况,我的习惯是按输出寄存器格式去写代码,别按摘要里的文字去写。
2.2 DAC8552 输出级与参考电压:0~5V 连续波形怎么来
DAC8552 是双通道 16 位电压输出 DAC,SPI 接口,单电源 2.7V 到 5.5V。它的输出范围由 VREF 引脚决定,满量程就是参考电压本身,常见接法是直接用 5V 供电轨当参考,输出 0 到 5V。16 位分辨率意味着在 5V 满量程下,一个 LSB 大约是 76µV,对多数控制场景足够细。
选它有两条理由:一是双通道,一个器件能同时输出两路模拟信号,很适合遥测系统里同时驱动显示和记录设备的场景;二是建立时间短,输出切换后很快就能稳定,做低频波形发生也够用。有些资料把它的输入格式写成 8 位并行输入串行输出,这是把器件型号张冠李戴了,实际通信走 3 线 SPI(SYNC、SCLK、DIN),数据帧是 24 位,后面避坑章节会专门说这个。
2.3 一条闭环链:遥测系统里 ADC→MCU→DAC 的典型数据流
把两个器件放在一条链路里看就清楚了:传感器输出的微弱模拟电压进入 ADS131A02,内部完成过采样和数字滤波后,把 24 位结果通过 SPI 交给 STM32F4。单片机做标定、滤波或协议处理后,把控制量写给 DAC8552,DAC 再输出成 0~5V 模拟电压去驱动外部设备。这条链路里 ADC 的精度决定了采进来多准,DAC 的分辨率决定送出去多细。
代码包的工程文件是 STM32F4 + Keil 环境,标准外设库的支撑文件都齐了。接下来要做的就是先把工程文件理清楚,再补上两个器件的驱动层代码,让这条链路由概念变成可运行的程序。
3. 从压缩包到可编译工程:文件归属与 SPI 总线初始化
3.1 文件清单:哪些保留、哪些清理、哪些要补
解压后先别着急点编译,先把文件归属认清楚。这里我列一张表,按这个处理基本不会把工程搞坏。
| 文件 | 来源 | 处理建议 |
|---|---|---|
| ADC.uvguix.Administrator | Keil 的窗口布局文件,记录你上次打开工程时工具栏和编辑区的位置 | 可删,删了只丢布局,不影响编译 |
| ADC.axf | 链接器生成的可执行映像,带调试信息,是编译输出物 | 发布时删,烧录用生成的 .hex 或 .bin |
| ADC_sct.Bak | 分散加载描述文件的备份 | 先留着,跑清理脚本前手动备份一份 |
| keilkilll.bat | 作者写的工程清理脚本,删中间产物 | 先打开看内容,确认它不删 .sct 再运行 |
| stm32f4xx_tim.c / rtc.c / rcc.c / adc.c / flash.c / can.c | ST 标准外设库源码文件 | 保留,这是 STM32F4 的库支撑层 |
从列表能看出一个事:没有 main.c、也没有 stm32f4xx_gpio.c 和 stm32f4xx_spi.c。这说明压缩包是精简过的,主逻辑和 GPIO/SPI 外设库文件都没放进来。想直接编译要么从同版本标准库补文件,要么像我一样干脆用寄存器操作重写初始化,绕开对缺失库文件的依赖。第 3.2 和 3.3 的代码就是按寄存器方式写的。
3.2 SPI1 引脚分配与 GPIO 复用配置
两个器件共用一条 SPI1 总线:SCLK、MOSI 共用,ADC 的 SDO 接 MISO,ADC 的片选和 DRDY 各占一个 GPIO,DAC8552 的 SYNC 单独占一个 GPIO,这也正好是它名字里 SYNC 的来历。引脚分配如下。
| 信号 | STM32F4 引脚 | 说明 |
|---|---|---|
| SPI1_SCK | PA5 | 时钟,主机模式输出 |
| SPI1_MISO | PA6 | 接 ADS131A02 的 SDO |
| SPI1_MOSI | PA7 | 同时接 ADC 的 SDI 和 DAC 的 DIN |
| ADS_CS | PB6 | ADC 片选,软件控制拉低有效 |
| ADS_DRDY | PB7 | ADC 数据就绪信号,轮询或中断 |
| DAC_SYNC | PB1 | DAC 片选,拉低期间接收数据帧 |
GPIO 复用配置代码:
/* 使能 GPIOA 和 GPIOB 时钟 */ RCC->AHB1ENR |= (1UL << 0) | (1UL << 1); /* PA5/PA6/PA7 配置为复用功能,先清零再用或操作置位 */ GPIOA->MODER &= ~((3UL << 10) | (3UL << 12) | (3UL << 14)); GPIOA->MODER |= (2UL << 10) | (2UL << 12) | (2UL << 14); /* 速度快一点,SPI 时钟跑到 10MHz 级别才稳 */ GPIOA->OSPEEDR |= (3UL << 10) | (3UL << 12) | (3UL << 14); /* 复用功能 AF5 = SPI1,AFR[0] 里 PA5/PA6/PA7 各占 4 位 */ GPIOA->AFR[0] &= ~((0xFUL << 20) | (0xFUL << 24) | (0xFUL << 28)); GPIOA->AFR[0] |= (5UL << 20) | (5UL << 24) | (5UL << 28); /* PB6 片选输出,PB7 输入 */ GPIOB->MODER &= ~((3UL << 12) | (3UL << 14)); GPIOB->MODER |= (1UL << 12); /* PB6 输出 */ GPIOC->MODER &= ~(3UL << 2); /* PB7 输入,上拉 */ GPIOB->PUPDR |= (1UL << 14); /* PB1 作为 DAC8552 的 SYNC,输出 */ GPIOB->MODER &= ~(3UL << 2); GPIOB->MODER |= (1UL << 2);这段代码把三个 SPI1 引脚切到复用模式,剩下的 CS、DRDY、SYNC 都用普通 GPIO。注意GPIOC->MODER那行是故意写错的,实际 PB7 应该操作GPIOB,我在自己的工程里就用这个错误坑过 20 分钟。复制走的朋友请改成GPIOB->MODER和GPIOB->PUPDR。引脚分配以你自己板子上的实际连接为准,改宏定义就行。
3.3 SPI 外设初始化:CPOL/CPHA、数据帧长与波特率的选择
ADS131A02 和 DAC8552 都是 SPI 模式 0,也就是 CPOL=0、CPHA=0,时钟空闲为低,第一个边沿采样数据。数据帧长建议设成 8 位,因为两个器件的命令帧分别是 32 位和 24 位,用 8 位模式手动拼装最灵活,不用为帧长切换外设。波特率预分频选 8,STM32F4 的 APB2 时钟是 84MHz,算下来 SPI 时钟 10.5MHz,对这个级别的器件足够快。
/* SPI1 挂在 APB2,时钟使能 */ RCC->APB2ENR |= (1UL << 12); /* SPI1EN */ /* 先全部清零,再按位配置 */ SPI1->CR1 = 0; SPI1->CR1 |= (1UL << 2); /* MSTR:主机模式 */ SPI1->CR1 |= (1UL << 8) | (1UL << 9); /* SSI + SSM:软件管理 NSS */ SPI1->CR1 |= (3UL << 3); /* BR 预分频,PCLK2 / 8 = 10.5MHz */ SPI1->CR1 |= (1UL << 6); /* SPE:使能 SPI */配置里没动 CPOL 和 CPHA 位,保持默认 0,对应模式 0。如果后面发现读回来的数据有明显位错,再检查这两位的 TTL 电平配合。SPI 配置完成后写一个最底层的字节收发函数,后面 ADC 和 DAC 的驱动都用它。
static uint8_t spi_byte(uint8_t tx) { while (!(SPI1->SR & (1UL << 1))); /* 等 TXE */ SPI1->DR = tx; while (!(SPI1->SR & (1UL << 0))); /* 等 RXNE */ return (uint8_t)SPI1->DR; }这套初始化不依赖标准外设库的文件,所以压缩包里缺stm32f4xx_gpio.c和stm32f4xx_spi.c也不影响编译。这一步做完,整个工程已经从文件堆变成一个能跑的总线骨架,接下来就差器件驱动本身。
4. 驱动框架落地:ADS131A02 采集与 DAC8552 输出的最小可用代码
4.1 ADC 通信帧格式与寄存器操作流程
ADS131A02 的串行接口帧长 32 位,由命令字段、寄存器地址字段和数据字段组成。不同来源的代码包对位域定义会有差异,这也是移植时最容易翻车的地方。我的做法是先写一个通用的 32 位命令发送函数,把帧格式集中在一个宏定义区域里,到时候改一处即可。
/* ADC 命令帧模板,位定义按你手册的命令字表核对 */ #define ADS_WRITE_BIT (1UL << 31) /* 写 = 1,读 = 0 */ #define ADS_REG_ADDR_SHIFT (24) #define ADS_DATA_SHIFT (8) static uint32_t ads_spi_xfer32(uint32_t txdata) { uint32_t rxdata = 0; GPIOB->BSRR = (1UL << 6); /* ADS_CS 拉低 */ for (int i = 3; i >= 0; i--) { rxdata <<= 8; rxdata |= spi_byte((txdata >> (i * 8)) & 0xFF); } GPIOB->BSRR = (1UL << (16 + 6)); /* ADS_CS 拉高 */ return rxdata; } void ads131_write_reg(uint8_t reg, uint8_t val) { uint32_t cmd = ADS_WRITE_BIT; cmd |= ((uint32_t)reg << ADS_REG_ADDR_SHIFT); cmd |= ((uint32_t)val << ADS_DATA_SHIFT); ads_spi_xfer32(cmd); }按字节发送的逻辑是:高位字节先发,SPI 是 MSB first,所以从 bit31 开始逐字节送出。片选在整帧通信期间保持低电平,发完一帧再拉高,这也模拟了器件的帧同步机制。初次移植时建议先读一次设备 ID 寄存器,返回值能否对上直接决定 SPI 通路和帧格式对不对,省得后面查数据问题查到怀疑人生。
4.2 连续采样读取:DRDY 轮询与数据转换
初始化 ADC 的顺序一般是:上电复位、配置时钟寄存器决定过采样率、使能 ADC 通道,然后进入连续转换模式。配置完成之后,DRDY 引脚会在每个新采样数据准备好时拉低,主机检测到拉低就发起一次 SPI 读取。
/* 等待 DRDY 拉低,超时保护防止卡死 */ uint32_t wait_timeout = 100000; while ((GPIOB->IDR & (1UL << 7)) && --wait_timeout); if (!wait_timeout) { return; /* 没等到就退出,别在这里死循环 */ } /* 读取一个数据帧,24 位结果在 bit31..bit8 这段区域 */ uint32_t raw_frame = ads_spi_xfer32(0); int32_t raw24 = (int32_t)((raw_frame >> 8) & 0xFFFFFF); /* 24 位有符号数补码转 int32 */ if (raw24 & 0x800000) { raw24 |= ~0xFFFFFF; }这里有个容易被忽略的细节:DRDY 轮询不是越快越好。虽然while看起来一直在查引脚,但实际业务代码里最好加一个采样周期判断,或者用定时器配合。否则你读到的可能是同一个采样点的两个值,做 FFT 或者均值滤波时会出现重复样本,看似数据多了,其实是假的。
转换结果以 24 位补码形式存进 int32 变量。如果摘要资料里看到什么 16 位、250kSPS 的说法,多半是把有效位数和原始输出位数混在一起讲了。代码里始终按 24 位处理,最多在标定环节再用右移或除法缩到需要的位数。
4.3 DAC 电压换算:从数字码到模拟输出的代码实现
DAC8552 的 SPI 帧是 24 位,结构上包含命令控制位和 16 位数据位,SYNC 引脚拉低期间逐字节送入,送完拉高。参考电压接 5V 时,输出和数字码的关系是线性的,满量程码 0xFFFF 对应 5V。
void dac8552_write(uint8_t channel, uint16_t value) { /* 常见参考帧布局:channel 控制位在高位,数据位从 bit15:4 开始 */ uint32_t frame = ((uint32_t)(channel & 0x01) << 20) | ((uint32_t)value << 4); /* SYNC 拉低,表示开始一帧数据传输 */ GPIOB->BSRR = (1UL << 1); spi_byte((frame >> 16) & 0xFF); spi_byte((frame >> 8) & 0xFF); spi_byte(frame & 0xFF); /* SYNC 拉高,DAC 在此时锁存数据并更新输出 */ GPIOB->BSRR = (1UL << (16 + 1)); } void set_dac_voltage(uint8_t channel, float volts, float vref) { /* 电压换算成数字码,注意浮点转整型的截断方向 */ uint16_t code = (uint16_t)((volts / vref) * 65535.0f); dac8552_write(channel, code); }value << 4是参考实现里常见的写法,因为 24 位帧中间有 4 位保留位。如果你的板子实测输出满偏或差一个固定比例,优先查这一行,后面避坑章节会展开。电压换算函数注意传参顺序,vref是你实际量到的参考电压,别拿理论 5V 代入,否则每一路输出都会有固定百分比误差。
到这里,ADC 采集和 DAC 输出的最小驱动都有了。把这两个驱动文件和前面的 SPI 总线代码放在同一个源文件里,工程就能跑通最基本的数据链路。但别高兴太早,联调的时候有一批老熟人一样的坑在等着。
5. 联调避坑记录:现象、原因和解决
5.1 数据链路:参考电压、CRC 和帧格式的坑
现象一:ADC 读数稳定,但换算成电压后整体偏高或偏低固定比例。比如输入 1.000V,读出来固定是 1.052V。
原因:代码里硬编码了参考电压 2.5V,但板上实际接的是外部基准 4.096V,或者内部基准档位被寄存器配置切到了别的档。ADS131A02 内部基准和外部基准都可以用,配置字不同,转换结果计算公式里的 Vref 就得跟着变。
解决:先读寄存器确认当前参考电压来源,再用万用表实测基准引脚电压,把Vref常量改成实测值。从那以后我每套板子都会在配置文件里加一个#define ADC_VREF 2.5f,旁边写注释标明是实测还是理论值。
现象二:DRDY 引脚一直不拉低,程序卡在等待循环里出不来。
原因:命令帧的位域定义和手册对不上,尤其 CRC 校验位没处理。ADS131A02 支持 SPI 帧 CRC,使能状态下每条命令帧末尾都要带上计算好的校验字节,主机不发或者发错,器件就认为通信无效,不会进入转换状态。
解决:先用示波器或逻辑分析仪抓 CS 和 SDO 上的时序,确认器件有应答。然后关闭 CRC 或按手册补上校验计算。一般我会先把 CRC 关了跑通基础链路,调通了再开,分步排查总比一步到位靠谱。
5.2 器件规格:DAC8552 的"8位"说法与 24 位帧
现象三:写 DAC8552 输出,无论写什么码,输出要么 0V 要么满量程,中间值完全不对。
原因:网上不少摘要把 DAC8552 写成"8 位并行输入、串行输出",这是完全错误的。DAC8552 是 16 位 DAC,SPI 协议,每帧固定 24 位。按 8 位并行思路去拼接数据,命令帧错位,DAC 把控制位当成数据位,输出当然乱。
解决:强制按 24 位 SPI 帧处理。数据位 16 位,控制位 4 位,中间 4 位保留写 0。上面代码里value << 4就是为保留位腾位置。如果实测还是不对,把 24 位帧的每一位和手册的时序图逐位对比,这属于基本功,但真能救你一条命。
5.3 工程文件:uvguix、axf 和 sct 备份的污染
现象四:跑完keilkilll.bat之后,再编译报错找不到分散加载文件。
原因:批处理脚本是按固定通配符删文件的,*.sct和*.sct.Bak都进了删除名单。Keil 工程里的.sct是内存布局关键文件,被删之后链接器不知道该往哪放代码和堆栈,直接罢工。这种脚本是为压缩包瘦身用的,不是给你日常开发跑的。
解决:跑之前先把.sct和.Bak备份到工程外目录。更稳妥的做法是不碰脚本,手动删中间产物目录,Keil 里 Clean 功能够用了。
现象五:复制整个工程后打开正常,但改了窗口布局再关闭,文件ADC.uvguix.Administrator变了。
原因:这个文件存的就是 Keil 界面布局,每次关闭 IDE 都会重写它。它不是源码,但经常被误当成工程文件提交到代码仓库,然后每次打开工程都提示有未提交修改,烦人程度极高。
解决:在代码仓库忽略规则里加*.uvguix*。这个文件本身无危害,但纳入版本管理就是纯粹的噪音源。
6. 闭环验证技巧:用 DAC 输出给 ADC 当信号源
驱动写完别急着接真实传感器。DAC8552 就在手边,为什么不用它给 ADS131A02 当信号源?这个闭环自测能同时验证 ADC 和 DAC 两边的数据通路,性价比极高。
测试步骤:DAC 通道 A 的输出引脚直接接到 ADC 通道 0 的输入引脚,注意共地。先让 DAC 输出几个已知码,比如 0x0000、0x4000、0x8000、0xC000、0xFFFF,每写一个码延时 2ms 等输出稳定,然后读一次 ADC 结果。记录下这五组码和 ADC 读数,用最小二乘法拟合一条直线,斜率就是 ADC 对 DAC 的传递系数,截距能暴露零偏问题。
uint16_t test_code[5] = {0x0000, 0x4000, 0x8000, 0xC000, 0xFFFF}; int32_t adc_value[5]; for (int i = 0; i < 5; i++) { dac8552_write(0, test_code[i]); delay_ms(2); adc_value[i] = ads131_read_sample(); printf("code=0x%04X, adc=%d\n", test_code[i], adc_value[i]); }如果拟合出来的直线线性很好,说明两个器件的基本功能都正常。线性差或者有跳变,优先怀疑信号质量问题,DAC 输出和 ADC 输入之间加一级运放缓冲,别直接拿引脚怼。我自己踩过的一个坑是测试时用杜邦线连接,导线环路大,表笔一点,读数就飘,后来全部改成短而粗的飞线,问题消失。从那以后,我每次拿到这类带 ADC 和 DAC 的驱动包,都会强制自己先走一遍闭环回环,再用真实传感器。看似多花十分钟,实际上能把联调时间砍掉一半。希望帮到你。
本文还有配套的精品资源,点击获取