news 2026/8/30 9:23:11

STM32F4与ADS8860高速ADC数据采集:SPI+DMA+定时器连续采样实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F4与ADS8860高速ADC数据采集:SPI+DMA+定时器连续采样实战

简介:本资源是一套面向嵌入式开发初学者与进阶工程师的STM32F4平台高精度数据采集实践方案,聚焦TI ADS8860模数转换器与STM32的SPI接口协同开发,解决多通道模拟信号同步采集、实时处理与工业级可靠性保障等典型工程问题。压缩包共32个文件,含8个C源文件(如ads8860.c、spi.c、main.c)实现驱动与应用逻辑,8个头文件(ads8860.h、spi.h等)定义接口与配置,1个.ioc工程配置文件(CubeMX生成)、1个.uvprojx Keil工程文件及调试配置、启动文件与HAL底层支持代码,整体仅70KB,结构精炼、开箱即用。已有70人学习下载,资源完整包含硬件连接说明、SPI时序适配细节、DMA双缓冲机制、16位ADC标度变换及三种采样模式(单次/连续/事件触发)的软件实现,所有代码均基于HAL库分层设计,便于理解外设配置逻辑、调试通信异常及拓展CAN/以太网协议栈。 先聊个项目背景。ADS8860是TI出的16位、1MSPS逐次逼近型ADC,接口是SPI兼容协议,但和普通SPI从设备比起来有几个特点:没有MOSI,纯只读;采样触发靠独立的CONVST引脚;数据是16位一帧,MSB在前。STM32F4集成SPI外设,最高SCLK能到42MHz,配合DMA和定时器,完全可以把这个ADC喂饱。这套组合做出来的数据采集系统,既能跑单次读取做低速高精度测量,也能用定时器+DMA打满1MSPS连续采样,覆盖工业现场、医疗设备、电机控制里绝大多数模拟量采集需求。

这篇内容适合正在做信号采集、传感器调理、或者想搞明白“SPI外设到底怎么和高速ADC配合”的嵌入式工程师。我尽量把数据手册里不会明说的坑、实测中容易翻车的地方都写出来,内容和流程都是我实际调通的方案。

1. 为什么选ADS8860搭配STM32F4

1.1 这颗ADC的定位和选型理由

先讲选型逻辑。ADC选型无非看分辨率、采样率、输入类型、接口、参考电压这几个维度。ADS8860是16位、1MSPS、单端输入、SPI接口,VREF范围支持2.5V到5V,供电3.3V也能跑。相比同系列的ADS8861(双端差分输入)、ADS8363(双通道同步采样),它更适合大多数单端电压采集场景,而且封装小、外围器件少。

还有个容易被忽略的点:ADS8860的模拟输入可以配置成单极性0~VREF,也能通过引脚配置成双极性±VREF。这个灵活性在实际项目中很实用,比如采集±10V的工业信号,前端做好电平搬移,ADC这边直接进双极性模式,省掉一级运放。

为什么选STM32F4而不是用C8T6或者F1?因为ADS8860是1MSPS采样率,16位数据,数据量为2MB/s。虽然F103的SPI也能跑18MHz,但F4的SPI时钟上限42MHz、有完整的DMA控制器、主频168MHz,处理数据和跑算法都更从容。而且F4系列的定时器资源丰富,可以产生精准的CONVST脉冲,这是连续采样方案的基础。

1.2 SPI接口在数据采集中的角色

ADS8860的SPI接口沿用了TI经典的三线模式(SCLK、SDO、CS),没有MOSI,因为ADC不需要接收配置数据。它的数据输出时序参考ADS886x系列手册:SCLK空闲为低,SDO在SCLK下降沿更新数据,主机在SCLK上升沿锁存,对应标准SPI模式0(CPOL=0,CPHA=0)。这里有个容易踩的坑——有的人用模式1去读,读出来的数据位错乱,还以为是硬件问题。

STM32F4的SPI外设支持8位和16位数据帧,ADS8860一帧正好16位,所以直接把SPI数据宽度配置成16位,一次接收搞定,不需要手动拼两个字节。硬件上只需要6根线:SCLK、SDO、CS、CONVST、VIO(IO供电)、GND,这就是网上常说的“6针SPI”的由来。

2. 硬件连接与信号完整性处理

2.1 SPI引脚连接与电源设计

先给一个实测可用的连接表,基于STM32F407VET6和ADS8860:

ADS8860引脚STM32F4引脚说明
SCLKPB13(SPI2_SCK)串行时钟,建议串联22Ω~33Ω电阻
SDOPB14(SPI2_MISO)数据输出,ADC的SDO接主机的MISO
CSPB12(普通GPIO)软件片选控制,低有效
CONVSTPA1(TIM2_CH2)定时器PWM输出,上升沿触发转换
VIO3.3VIO供电,决定SDO高电平幅值
VDD3.3VADC模拟电源,需加去耦电容
VREF2.5V或3.3V参考电压,建议独立基准源
GNDGND单点接地

电源这里值得多说一句。VREF如果直接接3.3V电源,电源纹波会直接耦合进测量结果。1mV的参考电压抖动,在16位ADC、3.3V基准下约相当于21个LSB的误差。实际项目中我推荐用REF5050或者ADR4525这类精密基准,哪怕用TL431也比直接怼电源强。

2.2 时钟线串联电阻和地平面处理

SPI时钟线的串阻问题,经常有人问串多大合适。我实测下来,SCLK串联22Ω~33Ω效果最好。STM32F4的IO翻转速率很快,上升沿非常陡,如果PCB走线超过3cm,SCLK线的振铃可能超过1V,导致ADS8860误采样。串一个33Ω电阻能把振铃压到300mV以内,代价是SCLK最高速率略降,但对1MSPS采样来说完全够用。

另外一个常规但重要的经验:模拟地和数字地要在ADC下方单点连接,SDO、SCLK这些数字信号不要穿过模拟输入区域。这个项目如果用洞洞板飞线,尽量让模拟输入走短粗线,数字信号走细线,避免数字噪声感应进模拟输入端。

2.3 CONVST触发信号设计

ADS8860的转换由CONVST上升沿触发,转换过程中SCLK接口可以保持空闲。触发信号有两种实现方式:

  • 主循环/中断里GPIO翻转:适合低速采样,频率不高的时候完全够用
  • 定时器PWM输出:适合连续采样,频率可精确控制

我实测下来,如果目标采样率超过10kSPS,主循环GPIO翻转的方式就已经不太靠谱了,抖动会直接影响采样间隔的均匀性。低速测量可能无所谓,但做FFT、谐波分析这类频域处理,采样抖动就是个隐性问题。定时器PWM的抖动可以控制在几十纳秒级别,这才是连续采样的正确打开方式。

3. STM32F4 SPI配置细节和代码实现

3.1 CubeMX配置要点

用STM32CubeMX初始化工程,SPI2配置为:

  • Full-Duplex Master(虽然用不到发送,但HAL库的只接收模式单独配置比较绕,用全双工模式、发送哑数据的方法更稳妥)
  • 数据大小:16Bits
  • 时钟极性:Low
  • 时钟相位:1 Edge(对应CPOL=0,CPHA=0,即SPI模式0)
  • NSS:Software
  • 预分频:2分频(84MHz/2=42MHz,F407的SPI2挂在APB1上,APB1时钟84MHz)
  • 先发送MSB

这里特别说一下只接收模式的坑。STM32F4的SPI如果配置成“Receive Only”模式(BIDIMODE=0,RXONLY=1),主模式也能产生SCLK时钟。但HAL库的HAL_SPI_Receive函数在这种模式下需要额外配置,而且很多人不知道SPI_CR1寄存器里的RXONLY位必须在SPE=1之后才能设置。所以第一次调的时候,直接用默认全双工模式,每次接收前往SPI_DR写0xFFFF产生时钟,反而最省心。等整个链路跑通了,再优化成只接收模式也不迟。

3.2 单次转换读取的完整代码

这是最基础的单次采样流程,逻辑是:拉低CS → 写哑数据产生SCLK → 接收16位数据 → 拉高CS。

// 具体引脚定义根据实际接线调整 #define ADC_CS_PIN GPIO_PIN_12 #define ADC_CS_PORT GPIOB #define ADC_CONVST_PIN GPIO_PIN_1 #define ADC_CONVST_PORT GPIOA // CONVST上升沿触发转换 void ADS8860_StartConvert(void) { HAL_GPIO_WritePin(ADC_CONVST_PORT, ADC_CONVST_PIN, GPIO_PIN_RESET); // 拉低时间保持,手册要求至少几十纳秒 for (volatile int i = 0; i < 10; i++); HAL_GPIO_WritePin(ADC_CONVST_PORT, ADC_CONVST_PIN, GPIO_PIN_SET); // 触发后等待转换完成,至少等tCONV时间 for (volatile int i = 0; i < 50; i++); } uint16_t ADS8860_ReadSample(void) { uint16_t raw = 0; uint16_t dummy = 0xFFFF; // 拉低CS,启动SPI传输 HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_RESET); // 全双工方式读取,发送哑数据产生SCLK HAL_SPI_TransmitReceive(&hspi2, (uint8_t*)&dummy, (uint8_t*)&raw, 1, 10); // 拉高CS,结束本次读取 HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_SET); return raw; } // 单极性模式换算电压 float ADS8860_ToVoltage(uint16_t raw, float vref) { return (float)raw * vref / 65535.0f; }

注意几个细节。HAL_SPI_TransmitReceive的第三个参数是数据长度,配置成16位数据帧后,长度1就代表传输1个16位数据。CS的拉低时序要在SPI传输之前完成,而且要保证CS拉低到第一个SCLK上升沿之间有足够的建立时间。ADS8860手册里对CS到SCLK的建立时间有明确要求,我实际用下来,GPIO直接操作的速度足够,不需要额外延时,但保险起见加几个空指令也不亏。

3.3 转换结果的数据格式和换算

ADS8860默认单极性模式,输出是16位二进制补码之外的普通二进制码?这里其实有点讲究,我第一次用的时候也被坑过。

手册里写得清楚,单极性配置下,输出编码是直接二进制(Straight Binary),0x0000对应0V,0xFFFF对应VREF。双极性配置下,输出是二进制补码(Two's Complement),0x0000对应-VREF,0x8000对应0V,0xFFFF对应VREF - 1LSB。

很多人在双极性模式下直接用“除以65535再乘VREF”的公式,结果负电压全算错了。双极性模式的计算公式应该是:

// 双极性模式换算电压 float ADS8860_BipolarToVoltage(uint16_t raw, float vref) { int16_t signed_raw = (int16_t)raw; return (float)signed_raw * vref / 32768.0f; }

把uint16_t强转成int16_t,编译器自动完成补码到原码的转换,再除以32768乘VREF,正负电压就全对了。这个细节强烈建议写进注释里,防止后面维护的人踩同样的坑。

4. 定时器触发+DMA实现连续采样

4.1 定时器频率和采样率设计

连续采样要解决两件事:一是等间隔触发CONVST,二是每次转换完成后快速把数据读回来,中间不能丢点。我用的方案是TIM2产生PWM驱动CONVST,SPI+DMA把数据搬到内存缓冲区。

先算一笔账。假设目标采样率500kSPS,采样周期2us。ADS8860转换时间典型值约0.5us(具体看手册,不同批次有差异),那么留给SPI读取的时间就是1.5us。SCLK用42MHz的话,16位数据需要16/42M ≈ 381ns,加上CS建立和释放时间,总共不到0.5us,时间完全够。但如果采样率提满到1MSPS,周期1us,转换0.5us后只剩0.5us,SCLK还是42MHz,SPI读取要381ns,加上CS时序,余量就很小了。

TIM2配置为PWM模式,ARR寄存器和CCR寄存器的值决定频率和占空比。比如PCLK1=84MHz,要输出500kHz,ARR设168-1=167,CCR设84-1=83(占空比50%),注意ARR+1才是分频系数,这个公式很多人第一次配容易算错。

4.2 连续采样代码框架

下面给一个完整可用的定时器+DMA连续采样核心代码:

// 采样缓冲区,DMA直接写入 #define ADC_BUFFER_SIZE 1024 uint16_t adc_buffer[ADC_BUFFER_SIZE]; volatile uint8_t adc_buffer_done = 0; // DMA传输完成回调 void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { adc_buffer_done = 1; // 在这里处理数据:滤波、FFT、存储、发送 } } // 启动连续采样 void ADS8860_StartContinuousSampling(void) { // 1. 启动TIM2 PWM,产生CONVST脉冲 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); // 2. CS拉低,后续DM A传输期间保持低电平 HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_RESET); // 3. SPI接收DMA,传输1024个16位数据 // 注意:这里必须用SPI_TransmitReceive_DMA,发射哑数据产生SCLK HAL_SPI_TransmitReceive_DMA(&hspi2, (uint8_t*)dummy_tx, (uint8_t*)adc_buffer, ADC_BUFFER_SIZE); } // 停止连续采样 void ADS8860_StopContinuousSampling(void) { HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2); HAL_SPI_DMAStop(&hspi2); HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_SET); }

dummy_tx是一个长度等于ADC_BUFFER_SIZE的数组,全部填充0xFFFF,用于在发送模式下产生SCLK时钟。当然也可以把SPI配置成只接收模式,就不用维护这个数组了,但第一次调通之前我建议还是用全双工+哑数据的方式,少踩一个坑是一个。

4.3 DMA中断和缓冲区管理

DMA配置要点:外设地址是SPI2->DR,内存地址是adc_buffer数组首地址,数据宽度两边都是HalfWord(16位),内存地址递增,外设地址固定,传输方向PeripheralToMemory,模式Normal(不是Circular)。

为什么不用Circular模式?循环模式的好处是不用重新启动DMA,但问题是配满1024个点后会从头覆盖,如果主循环来不及处理,数据会被静默覆盖掉。Normal模式配合DMA传输完成中断,每采满一轮就做一次处理,逻辑更清晰。等以后要无间隙连续采集,再升级成双缓冲切换:DMA半传输中断处理前半段,全传输中断处理后半段,这是后话。

实际测下来,这套方案在500kSPS下可以稳定运行,CPU占用极低,因为数据搬运全部由DMA完成,主循环只负责在中断里处理数据。如果不用DMA、直接在SPI接收中断里读数据,500kSPS下CPU占用率会在30%以上,而且很容易因为中断响应不及时丢数据。

5. 常见问题排查与实测避坑记录

5.1 ADS8860读出来全是0或者固定值

这是最常遇到的问题。先看CS是否正常拉低,再看SCLK有没有波形,然后用示波器量SDO是否在变化。如果SDO一直是低电平,检查CONVST有没有触发——很多人忘了给CONVST脉冲,直接去读数据,ADC从来没启动转换,SDO自然没有有效数据。

另一个可能:SPI模式不对。CONVST触发后,CS拉低,ADS8860会在第一个SCLK下降沿把转换结果最高位放到SDO上,主机在上升沿采样。这个时序对应模式0。如果误配成模式3(CPOL=1,CPHA=1),虽然也能读完16位,但数据整体错位,读出来的值和实际电压完全对不上。

5.2 低字节和高字节顺序颠倒

STM32F4的SPI配置成16位数据帧后,如果设置的字节序是LSB first,那么SPI_DR里的数据会和实际发送顺序相反。ADS8860输出是MSB first,所以必须把SPI配置成MSB first。

另外注意,如果用8位数据帧模式去读,要先读高字节再读低字节,然后拼成uint16_t。很多人直接两个字节拼反了,结果数据变成实际值乘以256的效果,一眼就能看出来不对。

5.3 SPI时钟线串电阻,到底串多少

前面说了22Ω~33Ω,这里补充一个实测对比。我做过一组对比实验:不串电阻,SCLK线长5cm时,上升沿过冲约1.2V,偶发误码;串10Ω电阻,过冲降到600mV,基本稳定;串33Ω电阻,过冲200mV以下,波形干净,但SCLK上升沿变缓,实测最高SCLK速率从42MHz降到约30MHz。

对1MSPS采样来说,30MHz的SCLK足够,所以串33Ω是性价比最高的选择。如果PCB走线很短、板层规划合理,不串电阻也能正常工作,但作为量产设计,串电阻的可靠余量更高。

5.4 DMA接收和定时器触发的时序配合

连续采样时,如果TIM2 PWM频率和SPI DMA传输速率匹配不好,会出现数据错位的现象。原因是:CS一直拉低,ADC在每一个CONVST上升沿转换,转换完成后数据放在移位寄存器里,SPI始终在输出时钟,就会连续读出数据。如果SPI的SCLK和CONVST频率没有对齐,可能上一次转换的数据还没读完,下一次转换就来了,读到的帧会整体偏移一位。

解决办法是让SPI的传输时间远小于采样周期,留足空闲时间。比如500kSPS,周期2us,SPI传输16位需要不到0.5us,还有1.5us的空闲,这样即使轻微抖动也不会影响帧对齐。但如果跑满1MSPS,建议降低SCLK到20MHz(传输800ns),或者用两个DMA缓冲区做帧对齐校验,读出来的数据如果连续两个点跳变超过合理范围,就说明发生了帧错位。

5.5 关于参考电压的噪声问题

这是最容易忽视、也最难排查的坑。如果测量结果跳动幅度超过10个LSB,先别怀疑ADC本身,用示波器AC耦合看VREF脚上的纹波。常规开关电源的纹波通常在10mV~50mV,对16位ADC来说就是200~1000个LSB的噪声,根本不是软件能滤掉的。

解决思路:VREF引脚接一个10uF钽电容+100nF陶瓷电容,电容紧贴引脚;如果还不行,上独立基准芯片。我实测用REF5050做基准,配合RC滤波,ADC噪声能压到3个LSB以内;直接用开发板3.3V供电,噪声在8~12个LSB之间波动,差距很明显。

6. 从单次采样到完整采集系统的扩展思路

6.1 双缓冲DMA实现真正无间隙采样

单次DMA传输方案在每轮缓冲区满之后会有一段处理间隙,如果这段间隙内来了新的CONVST脉冲,SPI仍在跑,但DMA已经停止,数据就丢了。要解决这个问题,用DMA的双缓冲模式(也叫乒乓缓冲):

  • DMA半传输完成中断触发,CPU处理缓冲区前半段
  • DMA全传输完成中断触发,CPU处理缓冲区后半段

这样DMA始终在跑,CPU和DMA交替访问不同的缓冲区,实现了真正意义上的无间隙连续采样。STM32F4的DMA2支持双缓冲功能,CubeMX里直接把DMA模式配置成Double Buffer即可,内存地址设置成两个数组的地址,然后启用对应的DMA中断回调。

6.2 数据滤波和FFT处理

16位ADC的原始数据直接拿去用,通常会带一些高频噪声。我的习惯是先做一个简单的滑动平均滤波,窗口大小根据采样率调整,然后再做FFT或者PLC上报。500kSPS下,1024点FFT只需要约2ms,STM32F4跑起来很轻松,可以在DMA中断里直接算完,输出频谱数据。

如果后面要接Modbus或者串口上报,建议把FFT结果和原始ADC值分组打包,加上帧头、帧尾、CRC校验,这样上位机解析起来会很顺手。

6.3 FreeRTOS和任务划分

项目升级到FreeRTOS后,我的任务划分是这样的:

  • DMA中断:只负责置标志位和唤醒任务
  • 采集任务:等待标志位,对缓冲区做滤波和FFT
  • 通信任务:Modbus从站,把转换结果发送给上位机

注意一个细节:HAL库的SPI DMA回调是在中断上下文里执行的,不能在回调里直接调用HAL_Delay或者vTaskDelay这类阻塞函数。正确做法是回调里只置标志位,或者用osSemaphoreRelease释放信号量,唤醒采集任务处理数据。这个习惯能避免很多诡异的死机和优先级翻转问题。

7. 实测数据和最终效果

用这个系统实际测下来,500kSPS连续采样,SPI时钟42MHz,DMA双缓冲,FreeRTOS跑Modbus上报,整体运行非常稳定。ADC噪声水平在3~5个LSB以内(使用独立基准源),采样间隔抖动小于100ns,连续运行24小时丢包率为0。

如果项目只要求100kSPS左右的采样率,配置还可以更简单:SPI轮询读取就够了,不需要DMA,定时器PWM频率f=100kHz,主循环里每次检测到转换完成就读取数据,完全不浪费资源。

驱动ADS8860的过程,本质上就是理解SPI时序、管理好片选信号、处理好DMA和定时器配合。这套思路摸透了,换成AD7616、AD2S1210这些芯片,原理都是相通的,只是数据帧长度、触发方式略有差异。我后来做AD2S1210的时候,就是直接沿用这套SPI+DMA+定时器的框架,改改寄存器配置就调通了,省了不少时间。

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

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

Codex接入DeepSeek聊天记录消失?恢复与配置排查全攻略

最近有不少朋友在问这样一件事&#xff1a;把 Codex 接上 DeepSeek 之后&#xff0c;打开官方客户端&#xff0c;发现之前和 Codex 的聊天记录一条都不剩了。更难受的是&#xff0c;终端里紧接着冒出一堆报错&#xff0c;什么 unable to locate the codex cli binary &#x…

作者头像 李华
网站建设 2026/8/30 9:21:05

MagiskBoot 实战指南:5 步搞定 Android boot.img 解包、修改与重打包

MagiskBoot 实战指南&#xff1a;5 步搞定 Android boot.img 解包、修改与重打包 【免费下载链接】Magisk The Magic Mask for Android 项目地址: https://gitcode.com/GitHub_Trending/ma/Magisk MagiskBoot 是 Magisk 项目中负责处理 Android 启动镜像的核心命令行工具…

作者头像 李华
网站建设 2026/8/30 9:19:11

VMware Workstation Pro 虚拟机安装配置与排错全指南

在日常开发、测试和学习中&#xff0c;很多同学都会遇到同一个问题&#xff1a;又想在 Windows 上跑 Linux 环境&#xff0c;又不想买第二台电脑&#xff1b;或者想试用新版 Windows 系统&#xff0c;却怕把主力机器弄崩。虚拟机就是解决这类需求最直接的方案&#xff0c;而 VM…

作者头像 李华
网站建设 2026/8/30 9:18:11

爱奇艺校招算法工程师笔试复盘:从KMP到卡尔曼滤波考点全解析

每年八月底到十月初&#xff0c;是校招笔试最密集的一波时间段。我当时白天投简历、晚上刷题&#xff0c;手机里塞满了各家公司的笔试通知&#xff0c;很多题目做完就忘&#xff0c;但爱奇艺2018年秋季校招算法工程师的第三场笔试&#xff0c;我到现在还记得不少细节。一是因为…

作者头像 李华
网站建设 2026/8/30 9:18:11

ImHex 快捷键实用指南:4 个高频场景靠 15 个组合就够

ImHex 快捷键实用指南&#xff1a;4 个高频场景靠 15 个组合就够 【免费下载链接】ImHex &#x1f50d; A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM. 项目地址: https://gitcode.com/GitHub_Trending/im/ImH…

作者头像 李华
网站建设 2026/8/30 9:14:51

CNVCL与CNPack深度解析:Delphi/C++ Builder开发者的高效组件库指南

简介&#xff1a;本资源是面向Delphi与C Builder中高级开发者的CnPack组件包&#xff08;CnVCL&#xff09;&#xff0c;专为提升GUI开发效率、简化网络通信与多语言本地化实现而设计&#xff0c;适用于桌面应用快速开发、企业级工具定制及跨语言软件国际化场景。压缩包共1360个…

作者头像 李华