当前位置: 首页 > news >正文

STM32F103 ADC多通道采样,用DMA搬运数据到底有多省心?一个完整工程带你上手

STM32F103 ADC多通道采样与DMA数据搬运实战指南

在嵌入式系统开发中,ADC(模数转换器)是连接模拟世界与数字世界的重要桥梁。当我们需要同时采集多个传感器的数据时,如何高效地处理多通道ADC采样成为开发者面临的关键挑战。本文将深入探讨STM32F103系列微控制器的ADC多通道采样技术,重点介绍如何利用DMA(直接存储器访问)实现高效数据搬运,彻底解放CPU资源。

1. 多通道ADC采样的传统实现方式

在嵌入式开发中,多通道ADC采样通常有三种实现方式:轮询模式、中断模式和DMA模式。让我们先了解前两种传统方式的局限性。

轮询模式是最基础的方法,开发者需要手动切换ADC通道并等待每次转换完成。这种方式虽然简单,但存在明显缺点:

  • CPU必须持续等待ADC转换完成,无法执行其他任务
  • 在多通道采样时,代码复杂度随通道数量线性增长
  • 采样速率受限于CPU处理每个通道的时间
// 典型的轮询模式代码示例 for(int i=0; i<CHANNEL_NUM; i++){ ADC_RegularChannelConfig(ADC1, channels[i], 1, ADC_SampleTime_55Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); adcValues[i] = ADC_GetConversionValue(ADC1); }

中断模式相比轮询有所改进,每个通道转换完成后触发中断,CPU可以在等待转换期间处理其他任务。然而,这种方式仍然存在问题:

  • 频繁的中断会带来显著的上下文切换开销
  • 中断服务程序中仍需手动搬运数据
  • 高采样率下可能导致中断风暴,影响系统实时性

提示:在实际项目中,当采样通道超过3个或采样率高于1kHz时,传统方式的效率问题会变得尤为明显。

2. DMA技术原理与优势分析

DMA(Direct Memory Access)是一种无需CPU介入即可实现数据搬运的硬件机制。在STM32中,DMA控制器可以自动完成外设与内存之间的数据传输。

DMA工作流程

  1. 外设(如ADC)产生数据传输请求
  2. DMA控制器接管总线控制权
  3. 数据直接从外设寄存器搬运到指定内存区域
  4. 传输完成后,DMA释放总线控制权

DMA在多通道ADC采样中的优势

特性传统方式DMA方式
CPU占用率接近0
代码复杂度
最大采样率受限接近硬件极限
系统实时性受影响无影响
多通道扩展性优秀

在STM32F103中,ADC与DMA的配合尤为高效。ADC完成每个通道的转换后,会自动触发DMA请求,DMA控制器则将转换结果直接搬运到预设的内存缓冲区。

3. 完整工程搭建:从单次模式到连续模式

让我们从零开始构建一个完整的ADC多通道采样工程,逐步实现从基础到高级的功能。

3.1 硬件准备与初始化

首先配置硬件环境:

  1. 选择ADC通道对应的GPIO引脚(如PC0、PC1)
  2. 配置这些引脚为模拟输入模式
  3. 初始化ADC和DMA外设
// GPIO初始化示例 void ADC_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStruct); }

3.2 基础实现:单次扫描+单次转运

我们先实现最基本的单次扫描模式,适合对实时性要求不高的场景。

ADC配置关键点

  • 设置为扫描模式(ScanConvMode = ENABLE)
  • 禁用连续转换(ContinuousConvMode = DISABLE)
  • 设置正确的通道数量(NbrOfChannel)
  • 为每个规则通道配置序列号和采样时间
// ADC单次扫描模式配置 void ADC_Init_SingleScan(void) { ADC_InitTypeDef ADC_InitStruct = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 12MHz ADC时钟 // 通道配置 ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_55Cycles5); ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; ADC_InitStruct.ADC_ScanConvMode = ENABLE; ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfChannel = 2; ADC_Init(ADC1, &ADC_InitStruct); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); // ADC校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }

配套DMA配置

  • 外设地址设为ADC1->DR
  • 存储器地址指向自定义数组
  • 传输计数器设为通道数量
  • 单次模式(Normal mode)
uint16_t adcValues[2]; // 存储ADC转换结果 void DMA_Init_Single(void) { DMA_InitTypeDef DMA_InitStruct = {0}; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)adcValues; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStruct.DMA_BufferSize = 2; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_High; DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStruct); }

触发采样函数

void ADC_StartConversion(void) { DMA_Cmd(DMA1_Channel1, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel1, 2); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!DMA_GetFlagStatus(DMA1_FLAG_TC1)); DMA_ClearFlag(DMA1_FLAG_TC1); }

3.3 高级实现:连续扫描+循环转运

对于需要持续采样的应用场景,我们可以配置为连续扫描+循环转运模式,实现"一次配置,自动运行"的效果。

配置修改点

  1. ADC配置中启用连续转换模式
  2. DMA配置为循环模式
  3. 移除手动触发逻辑
// 修改ADC配置 ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; // 启用连续转换 // 修改DMA配置 DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; // 循环模式

初始化完成后自动运行

// 在初始化完成后直接启动 DMA_Cmd(DMA1_Channel1, ENABLE); ADC_SoftwareStartConvCmd(ADC1, ENABLE);

在这种模式下,ADC会持续进行转换,DMA自动将结果更新到内存数组,CPU无需任何干预即可获取最新数据。

4. 工程优化与实战技巧

4.1 双缓冲技术实现无抖动采样

对于高精度应用,可以使用DMA双缓冲技术避免读取数据时的竞争条件。

  1. 配置两个缓冲区(BufferA和BufferB)
  2. 设置DMA在填充完一个缓冲区后自动切换
  3. 通过中断或标志位通知CPU处理已完成缓冲区
uint16_t adcBuffer[2][4]; // 双缓冲,每个缓冲4个通道 void DMA_Init_DoubleBuffer(void) { DMA_InitTypeDef DMA_InitStruct = {0}; // ...其他配置同前... DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)adcBuffer[0]; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_BufferSize = 4; DMA_Init(DMA1_Channel1, &DMA_InitStruct); // 启用双缓冲模式 DMA_DoubleBufferModeCmd(DMA1_Channel1, ENABLE); DMA_MemoryTargetConfig(DMA1_Channel1, (uint32_t)adcBuffer[1], DMA_Memory_1); // 启用传输完成中断 DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); NVIC_EnableIRQ(DMA1_Channel1_IRQn); }

4.2 采样时序精确控制

对于多通道采样,各通道的采样时间会影响整体吞吐量。关键参数包括:

  • ADC时钟分频(通常设为PCLK2的6分频)
  • 各通道采样周期数(SampleTime)
  • 总转换时间计算公式:
    总转换时间 = (采样周期 + 12.5个周期) × 通道数

注意:采样周期过短会导致精度下降,过长则限制最大采样率。应根据信号特性权衡选择。

4.3 常见问题排查

数据错位问题

  • 检查DMA存储器地址自增设置
  • 确认ADC通道序列号配置正确
  • 确保DMA缓冲区足够大

采样值不稳定

  • 添加适当的去耦电容
  • 优化PCB布局,减少模拟信号路径上的干扰
  • 使用软件滤波算法(如移动平均)

DMA传输不触发

  • 确认ADC_DMACmd已启用
  • 检查DMA通道与ADC的对应关系
  • 验证DMA和外设时钟已使能

5. 实际应用案例:多传感器数据采集系统

以一个典型的温室监控系统为例,展示DMA在多通道ADC采样中的实际应用。

系统需求

  • 实时采集4路传感器数据(温度、湿度、光照、土壤湿度)
  • 采样率不低于100Hz
  • CPU需要处理通信和显示任务

解决方案

  1. 配置ADC为连续扫描模式,4个通道
  2. DMA设置为循环模式,双缓冲
  3. 主循环中定期处理完整缓冲区的数据
// 传感器数据处理示例 void ProcessSensorData(uint16_t *data) { float temperature = ConvertTemperature(data[0]); float humidity = ConvertHumidity(data[1]); float light = ConvertLight(data[2]); float soil = ConvertSoilMoisture(data[3]); UpdateDisplay(temperature, humidity, light, soil); SendToNetwork(temperature, humidity, light, soil); } // 主循环 while(1) { if(dataReady) { ProcessSensorData(currentBuffer); dataReady = 0; } // 其他任务... }

性能评估

  • CPU占用率从传统方式的~70%降至<5%
  • 系统响应时间提升3倍以上
  • 代码复杂度降低40%

在完成这个项目后,最深刻的体会是DMA配置初期的确需要仔细检查各个参数,但一旦正确配置,系统的稳定性和效率提升非常显著。特别是在处理多通道高频采样时,DMA几乎是不可替代的解决方案。

http://www.jsqmd.com/news/869608/

相关文章:

  • 梳理平凉低耗电太阳能路灯品牌,哪家口碑更好一目了然 - myqiye
  • 深聊靠谱的建筑机电安装工程专业承包一级资质企业,费用怎么算 - mypinpai
  • 用PyTorch手把手实现PGD对抗训练:从FGM的‘一步到位’到‘小步快跑’的实战代码详解
  • 浙江高耐用静电除尘器靠谱厂家分析 科森环境实力稳居前列,旋风分离器/水帘除尘器/滤筒除尘器,静电除尘器批发厂家哪个好 - 品牌推荐师
  • CAN总线电压测试避坑指南:用示波器实测显性/隐性电平,别再被CAN_H和CAN_L的命名误导了
  • 保姆级教程:在Ubuntu 22.04上配置VNC Server,并用VNC Viewer远程桌面(解决加密报错)
  • 2026年PCB行业研究报告
  • 2026靠谱的汽车大屏导航安装店铺排名,为你推荐性价比高的服务 - myqiye
  • 从main.cc到五大视图:手把手拆解QGC的UI启动流程(附QML与C++交互实例)
  • 安科士(AndXe)SPF-10G-T :10G 电口模块,重塑短距网络升级性价比
  • 盘点蓝金灵团队凝聚力、市场份额和产品功能,哪家性价比高 - mypinpai
  • 保姆级教程:在Ubuntu 22.04上用Netplan搞定Bond+VLAN+Bridge混合网络(附H3C交换机配置)
  • 上海婚介所选购指南,梅园婚恋资源丰富度成亮点 - myqiye
  • 告别命令行!用VSCode插件一键搞定ESP-IDF环境(ESP32/S3保姆级教程)
  • 别再只用默认样式了!手把手教你定制LVGL Bar进度条的3种高级视觉效果
  • 从QPLL与CPLL选型到线速计算:一份给Xilinx GTY新手的时钟配置速查手册
  • QMCDecode终极指南:3步解锁QQ音乐加密文件的完整教程 [特殊字符]
  • 别再死记硬背了!图解ASCII码表,轻松掌握C语言字符处理的底层逻辑
  • 告别手动分割!用Python脚本一键生成VOC数据集所需的train.txt和val.txt
  • 告别漫长等待:优化银河麒麟ARM平台Qt源码编译速度的几种思路
  • MDK-7526是什么?基于VHL配体的PROTAC核心组件,泛素连接酶募集剂
  • 手把手教你用AD9834 DDS模块DIY一个可调信号源(附AD原理图/PCB/程序)
  • 可靠的孩子叛逆不上学情绪暴躁矫正机构收费情况揭秘 - myqiye
  • B 题:嵌入式社区养老服务站的建设与优化问题
  • 从AB类到C类:拆解Doherty功放里载波与峰值支路的相位“打架”问题及宽带补偿方案
  • 用GoC画图搞定2018年5月那道‘场记板’编程题,附完整代码和思路拆解
  • 剖析单招培训服务机构性价比,廊坊博大单招费用合理成效好 - myqiye
  • 深聊二手压滤机回收服务怎么选择,哪家高价回收更靠谱 - mypinpai
  • 领导看的是山顶,工程师盯着的是脚下的路
  • 微信小程序逆向分析:从神秘二进制到可读源码的完整指南