news 2026/9/4 0:54:23

ADS131多通道ADC驱动开发:SPI配置、寄存器初始化与24位数据解析实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ADS131多通道ADC驱动开发:SPI配置、寄存器初始化与24位数据解析实战

简介:面向STM32F401开发者的一份轻量级驱动源码,专注于通过硬件SPI接口控制TI ADS131A04高精度四通道同步采样ADC,适用于医疗、工业测量等需要多通道同时采集的场景。压缩包内共2个文件,包含一个C源文件和一个H头文件,总大小仅2KB,代码精简,可直接移植到基于STM32的嵌入式项目中。文件封装了ADS131_Init()、ADS131_StartConversion()和ADS131_ReadData()等核心函数,覆盖SPI主模式配置、寄存器初始化、命令字与地址读写以及转换结果读取;同时保留增益、采样率、滤波器、低功耗模式等参数调整入口,开发者可结合数据手册按需修改,并可根据实际连接调整SCK、MISO、MOSI、NSS引脚映射。已有4997人学习下载,适合正在调试STM32+ADS131A04数据采集卡的工程师,既能快速跑通基础通信,也能深入理解SPI与Σ-Δ型ADC协同工作的细节,有效缩短驱动开发与排错时间。 做多通道同步采集项目时,我被ADS131这颗ADC吸引住了。24位分辨率、四路同步采样、内部的PGA放大,还能直接挂在SPI总线上,用在电力测控和振动监测这类场景里是真的香。但真正把ADS131代码工程从Datasheet里扒出来跑通后,我发现寄存器配置、SPI帧格式、数据解析这些环节,不亲手调一遍,根本不知道坑在哪。这篇我就拿实际项目里的代码作为示例代码讲解,从初始化到读取,再到大伙儿容易翻车的调试点,完整过一遍,给准备用这颗片子的朋友一份能直接参考的实操记录。

1. 项目背景与整体设计思路

1.1 为什么选ADS131系列,而不是其他ADC

当时项目需求很直接:要同时采集四路模拟信号,通道之间的相位差要控制在微秒级,每路信号动态范围还比较大,普通12位、16位ADC根本不够用。ADS131系列最大的卖点就是多通道同步采样,芯片内部每个通道都有独立的24位Delta-Sigma调制器和数字滤波器,四个通道的采样时刻由同一个时钟驱动,天然保证了相位一致性。这比用多个单通道ADC再用软件对齐要靠谱得多,软件同步做得再好,也会受中断抖动影响。

ADS131家族里,ADS131M04是四通道,M06六通道,M08八通道,而E08这类带更丰富诊断功能的型号适合工控和电力保护场景。我这次用的是ADS131M04,四通道对于三相电压电流加一个备用通道来说刚好够用。芯片支持每通道独立配置PGA增益,输入范围可以通过配置去适配不同的传感器输出,前端不用堆太多调理电路,这一点对PCB尺寸敏感的产品很友好。

1.2 示例代码讲解:驱动层和应用层怎么分

拿到ADS131代码工程,千万不要一口气把寄存器配置、SPI收发、数据解析全部塞到主循环里。我最后重构出来的结构是三层:最底层是平台相关的SPI读写接口,中间层是ADS131的芯片驱动,负责寄存器读写、初始化、数据帧解析,最上层是应用逻辑,比如计算有效值、功率、做FFT。这样换MCU平台的时候,只需要替换底层那几十行SPI代码,中间驱动层和应用层基本不用动。

很多朋友刚开始调这种ADC,习惯写一个超长函数,从拉CS到发命令再到读6个通道全部写完,结果换一颗MCU或者调一个引脚就要整体重来。我在项目里把SPI收发封装成platform_spi_transfer(uint8_t *tx, uint8_t *rx, uint16_t len),后续所有寄存器读写都基于这个函数实现,代码整洁程度完全不一样。ADS131的数据手册和例程其实已经给了很好的代码框架,但TI的参考代码偏重演示,直接扔到工程里会很臃肿,需要自己裁剪。

2. 硬件接口要点与寄存器初始化代码

2.1 SPI接口配置:引脚分配和时序模式

ADS131M04的控制接口是标准SPI,我用的是STM32F103平台,硬件SPI1做主模式。接线上,SCLK、MOSI、MISO、CS这四根线是常规操作,但还有个DRDY引脚很重要,它是数据准备好标志,每次ADC转换结束会拉低产生一个脉冲,主机可以用它来判断什么时候读数据。DRDY建议接一个外部上拉电阻,开漏输出会更保险,直接用推挽输出也行,但要注意电平匹配。

SPI模式这里我得多说一句。ADS131M04手册里推荐的配置是CPOL=0、CPHA=1,也就是SPI Mode 1,时钟空闲为低,数据在第二个边沿采样。之前有同事按惯性配成Mode 0,结果移位寄存器读出来的数据总是错位。这个时序模式影响的是数据能否在正确的边沿被锁存,一旦配错,读出来的数据经常是正常的镜像或者低位串到高位,现象很迷惑。SPI时钟速率我也压的比较保守,用的4MHz,这个速度在几十厘米的排线连接下依然很稳定。

2.2 寄存器写入与回读的代码实现

初始化ADS131,本质就是往一堆寄存器里写配置。驱动底层我用宏把寄存器地址定义好,比如模式寄存器MODE、时钟寄存器CLOCK、增益寄存器GAIN、配置寄存器CFG,具体地址以你手里那版Datasheet为准。下面这段是我裁剪简化后的写法:

#define ADS131_REG_MODE 0x00 #define ADS131_REG_CLOCK 0x01 #define ADS131_REG_GAIN 0x02 #define ADS131_REG_CFG 0x03 static void ads131_write_reg(uint8_t reg, uint8_t val) { uint8_t tx_buf[3] = {0}; uint8_t rx_buf[3] = {0}; tx_buf[0] = 0x00 | reg; // 写寄存器命令 tx_buf[1] = 0x00; tx_buf[2] = val; ads131_cs_low(); platform_spi_transfer(tx_buf, rx_buf, 3); ads131_cs_high(); }

写寄存器时我习惯带上回读校验,尤其是量产烧录阶段,寄存器没写进去,后面数据全是乱码,排查起来非常浪费时间。回读命令的代码结构类似,把命令字节改为读取类型,然后多读几个字节取有效寄存器值,对比写入值是否一致。回读校验不用每次初始化都跑,但调试阶段一定要开着。

初始化序列一般是这样:拉低RESET复位,等待DRDY稳定,然后依次配置时钟寄存器选择采样率、增益寄存器设置PGA倍数、配置寄存器决定是否开启内部基准和断线检测。每个寄存器写完最好加个延时,别指望芯片瞬间消化完命令。配置完成后,ADS131就会按设定好的采样率持续转换,DRYD引脚开始周期性产生脉冲,这时候核心任务就从配置芯片变成了怎么把数据搬回MCU。

3. 数据采集与解析的核心代码

3.1 SPI帧结构与连续读取模式

ADS131M04的SPI读数帧是固定的:主机发一帧数据过去,芯片就会回一帧数据回来,里面包含四个通道的24位原始码。用一句话讲就是“你问它要数据,它把上次采样结果一并吐给你”。实际代码里就是拉低CS,连续发8个字节(全0即可),同时接收8个字节,前两个字节是状态字,后面六个字节对应三个通道。如果读四通道,就需要再扩展字节数。

这里最容易出错的是连续读取时CS和DRDY的配合。最稳妥的流程是等待DRDY变低,然后立刻启动SPI读写,读完后等到CS拉高完成一次完整事务。千万不要在主循环里不管DRDY直接怼SPI请求,那样读出来的很可能是同一份旧数据,或者刚好卡在数据更新的中间时刻,得到位与位之间的拼接错乱。我在调试时曾遇到过通道值反复跳变,后来加上逻辑分析仪才看到DRDY脉冲和SPI片选时序错开了半个周期。

为了保持代码的可读性,我用一个结构体来存放解析结果:

typedef struct { int32_t ch_data[4]; uint16_t status; } ads131_sample_t; static void ads131_read_samples(ads131_sample_t *sample) { uint8_t tx_buf[11] = {0}; uint8_t rx_buf[11] = {0}; uint8_t i; ads131_cs_low(); platform_spi_transfer(tx_buf, rx_buf, 11); ads131_cs_high(); sample->status = ((uint16_t)rx_buf[0] << 8) | rx_buf[1]; for (i = 0; i < 4; i++) { int32_t raw = ((uint32_t)rx_buf[2 + i * 3] << 16) | ((uint32_t)rx_buf[3 + i * 3] << 8) | rx_buf[4 + i * 3]; if (raw & 0x800000) { raw |= 0xFF000000; // 24位有符号扩展为32位 } sample->ch_data[i] = raw; } }

代码里最关键的是那步符号扩展。AD位数是24位,但MCU的寄存器是32位,如果最高位是1,直接转成int32_t就是个正数,必须手动把bit23的值扩展到bit31。这个失误很容易犯,我一开始就把满量程负值读成了八千多万的正数,当时还以为是芯片坏了。

3.2 24位原始码到物理量的换算

拿到原始码只是第一步,工程上最后要的是电压、电流、功率这些物理量。换算公式不复杂:实际电压 = 原始码 × 基准电压 / (2^23 × PGA增益)。以内部基准1.2V、PGA增益为1举例,LSB对应电压是1.2V / 8388608,算出来约143nV。这个分辨率看数字还行,但实际系统噪声、板材漏电、参考源波动早就盖过它了,所以别迷信理论分辨率。

换算时还得分清楚原始码是两补数还是偏移二进制。ADS131输出的是二进制补码,满量程正数对应0x7FFFFF,满量程负数是0x800000。我在代码里这么换算:

float ads131_raw_to_voltage(int32_t raw, float vref, float pga_gain) { return ((float)raw * vref) / (8388608.0f * pga_gain); }

如果PGA配置成4倍,有效输入范围会缩小到原来的四分之一,但LSB分辨率提升四倍。要用多大的PGA,得根据传感器输出幅度来定,目的是让信号尽量填充ADC的满量程,而不是一进来就开大增益,否则噪声也会跟着放大,实测效果反而不如小增益。

3.3 通过DMA+中断把刷新率拉满

SPI阻塞式读取在采样率不高时没问题,但一旦ADS131跑到16kSPS以上,主循环里到处是等待,MCU其他任务基本干不了。我后来的方案是SPI DMA加DRDY外部中断:DRDY拉低进入中断,中断里启动DMA传输,DMA传输完成再把数据丢进环形缓冲区,主循环直接消费解析好的数据。这样CPU占用率明显降下来,采样率跑满32kSPS也轻轻松松。

DMA传输要特别注意缓冲区字节序,MCU是小端模式,SPI数据寄存器里的一字节还是原样搬过来的,排列顺序和代码里手写字节拼接是一样的。我在HAL库里踩过一个小坑:DMA接收缓冲是uint8_t数组,但后续解析用指针强转成uint32_t,结果字节序反了,通道0和通道1的数据像被交换过一样。后来规规矩矩按字节位移组合,这个问题就消失了。

4. 实际调试中遇到的坑与排查方法

4.1 DRDY信号怎么接才稳

DRDY看似简单,实际上好几个项目都在这个脚上吃过亏。这颗芯片的DRDY是开漏输出,意味着它只能拉低,不能主动输出高电平,必须靠外部上拉电阻抬到逻辑高。如果你直接接MCU的推挽输入引脚,没有上拉,信号波形就会是浮空和低电平来回切,SPI读取时机完全乱套。

我用的上拉电阻是10k欧姆,接到MCU同电压域,实测上升沿稍微有点缓,但对低速读取没影响。如果你把采样率拉到32kSPS还想跑SPI DMA,建议上拉电阻换成4.7k,上升沿更利落,DRDY触发中断的抖动也更小。还要在DRDY上就近加一个RC滤波,比如100欧姆串联加1nF对地电容,能滤掉来自数字电源的毛刺,防止误触发中断。

4.2 数据跳变与噪声干扰

有个现象我印象特别深:ADS131在实验室台面上测试数据很稳,一旦装进设备机箱,数值就开始鬼畜。排查到最后发现是开关电源的噪声从模拟电源引脚串进去了。ADS131的数字电源和模拟电源是分开的引脚,手册明确建议分别走线并加磁珠或者RC隔离。我当时只图省事,把AVDD和DVDD并在一起接LDO,结果数字噪声全灌进模拟电路。

处理办法是在AVDD和DVDD两个引脚附近各加一个1uF陶瓷电容和100nF小电容并联,模拟地平面和数字地单点连接,采样值立马安静不少。软件上也可以做一层防护,用滑动平均滤波或者中值滤波,但前提是硬件把底噪压下去,否则软件滤波只是把问题盖住了。ADS131本身还有内部断线检测功能,通过往输入通道注入一个小电流判断传感器是否脱落,初始化的时记得把CH_CFG寄存器里的断线检测打开,对现场诊断帮助很大。

4.3 寄存器读写失败的排查思路

寄存器读写不成功,是新手最容易卡住的地方,而且现象五花八门。有的是初始化后DRDY不拉低,有的是读回状态字全0xFF,有的是配置了增益却完全没生效。排除流程我一般这么走:先用示波器看CS、SCLK、MOSI波形,确认时序边沿是否干净;再用逻辑分析仪抓一次完整的寄存器写操作,对比Datasheet时序图,看CS有没有提前拉高、命令字节和数据字节中间是否有过长延时。

第二个常被忽略的点是读寄存器的字节长度。有些寄存器是8位,有些是16位,如果统一按一种长度去读,高字节或者低字节就会错位。我最后写驱动时,给每个寄存器都定义了一个长度表,读写函数从表里取长度,而不是靠外部传入参数,避免调用层没传对的尴尬。如果你手里的ADS131代码是从别处移植来的,一定要仔细核对寄存器地址和长度定义,不同型号之间寄存器表并不完全一致。

5. 工程化扩展与个人实际体会

5.1 双芯片级联实现更多通道同步

如果四通道不够用,ADS131系列提供了级联模式,可以把两片甚至更多片挂在同一条SPI总线上,通过CLKIN同步时钟和SYNC/RESET信号实现多片严格同步。我做过一个八通道的采集模块,就是两片M04级联,初始化时先同步复位所有芯片,再配置成相同寄存器值。这样两片芯片的所有通道都由同一个时钟驱动,通道间相位差几乎可以忽略,很适合振动阵列或者电能质量分析这类场景。

级联时的坑主要是片选和芯片识别,多片挂在同一条总线上,每个芯片要有独立的CS引脚,否则无法单独寻址。还有在SPI通信时,如果命令帧发遍了所有CS,很容易把不相关的芯片一并唤醒。我的做法是先拉低目标芯片的CS,再操作,命令结束立刻拉高,不给其他芯片任何可乘之机。

5.2 与上位机联调的经验

调试前期我习惯把MCU的解析结果通过串口发到上位机,直接在电脑上看波形。写了一个简单的Python脚本,从串口读二进制数据,解析成电压值再实时画出来。这一步特别有用,能直观看到通道间相位、噪声底、直流偏置,比整天盯着一堆十六进制数舒服太多。实测下来,ADS131稳定工作后,短接输入通道的噪声码值通常在正负几个LSB以内,如果远超这个范围,硬件方面得重新审视。

最后再说一点个人体会:ADS131本身底子不差,但代码和硬件必须要配合好。你光把寄存器配置写对,地线铺得稀烂,数据一样没法看;你把硬件做得再漂亮,SPI帧时序错了也白搭。尤其是24位数据解析和符号扩展这种细节,看似不起眼,却能让你排查到怀疑人生。调试时尽量用回读命令把寄存器配置实时打印出来核对一遍,再用逻辑分析仪看DRDY和CS的配合,很多问题能提前拦下来。这颗芯片跑了大半年,现在再看ADS131代码,已经不是当初那堆令人发怵的寄存器操作,而是一套稳定的采集模块,能让我把更多精力放在上层算法上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 11:06:02

外景 宫殿建筑庭院3d模型

本项目为前几天收费帮学妹做的一个项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 宫殿建筑庭院3d模型 地址&#xff1a;本地PC端运行&#xff08;或WebGL端部署链接&…

作者头像 李华
网站建设 2026/9/4 1:39:35

Android逆向入门:MT管理器核心功能与App目录结构详解

很多刚接触 Android 逆向或应用修改的朋友&#xff0c;可能都听说过“MT管理器”这个名字&#xff0c;但面对其复杂的界面和众多的功能按钮&#xff0c;往往感到无从下手。网上的教程要么过于零散&#xff0c;要么直接跳到高深的破解环节&#xff0c;对新手极不友好。本文将从零…

作者头像 李华
网站建设 2026/9/2 11:02:39

在 PC 上运行 Switch 游戏:Ryujinx 模拟器的安装、首通与调优实操

在 PC 上运行 Switch 游戏&#xff1a;Ryujinx 模拟器的安装、首通与调优实操 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx 没有 Switch 主机&#xff0c;却想在电脑上玩它上面的游戏…

作者头像 李华
网站建设 2026/9/2 11:02:23

Python提取PDF表格:四大主流库对比与实战指南

在处理财务单据、科研论文、年报和合同的时候&#xff0c;PDF 里的表格是最让人头疼的部分&#xff1a;数据眼睛能看懂&#xff0c;程序却拿不到。很多人的第一反应是手动复制到 Excel&#xff0c;遇到几十页上百页的文件&#xff0c;或者每天都要处理批量 PDF&#xff0c;手动…

作者头像 李华
网站建设 2026/9/2 11:01:49

Diffusers:3 行代码跑通文生图的扩散模型工具箱

Diffusers&#xff1a;3 行代码跑通文生图的扩散模型工具箱 【免费下载链接】diffusers &#x1f917; Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch. 项目地址: https://gitcode.com/GitHub_Trending/di/diffusers …

作者头像 李华