资讯动态

STM32F1x ADC三通道连续转换:规则组扫描+DMA稳定采集实战

发布时间:2026/10/16 22:55:02 来源:尧图企业网站定制
简介本资源为STM32F1x系列ADC三通道连续转换的完整工程代码面向具备一定嵌入式基础的开发者与学习者帮助解决多通道模拟量实时采集与DMA高效搬运的工程实现问题。压缩包共309KB以C语言源码与工程配置文件为主涵盖ADC初始化、通道配置、DMA传输、软件触发及中断处理等关键模块可直接编译运行并移植到工业自动化、环境监测、电机控制等数据采集场景。代码围绕12位ADC、连续转换模式与DMA免CPU干预展开清晰呈现采样时间、转换序列、时钟分频等参数设置并涉及功耗优化与输入电压安全等实践要点便于读者对照理解多通道采集的完整链路与排错思路。目前已有423人学习下载适合需要快速搭建STM32F1x多通道ADC采集框架的开发者参考借鉴。1. STM32F1x ADC三通道连续转换为什么“能跑”和“跑得稳”是两回事很多人第一次在STM32F1x上配ADC多通道代码烧进去串口能打印出三个值就觉得搞定了。但把板子放到电机旁边、或者把采样率往上提一档数据立刻开始跳——通道之间串扰、首采样值偏大、DMA搬着搬着就停了。STM32F1x的ADC三通道连续转换完整工程代码核心要解决的不是“怎么让ADC出数”而是“怎么让三路模拟量在连续转换模式下稳定、可复现地出数”。它适合正在做数据采集板、电机电流采样、多路传感器巡检的嵌入式工程师也适合刚学完寄存器配置、想搞明白规则通道序列和DMA配合的新手。下面这套工程代码我会按“规则组扫描DMA搬运”这条最稳的路线拆开讲参数怎么算、坑在哪、怎么验证都落到能直接抄的粒度。2. 规则组扫描加DMA三通道连续转换的工程骨架2.1 为什么选规则组扫描而不是注入组或单次转换STM32F1x的ADC有规则组和注入组两套通道序列。规则组最多16个通道按你写的顺序依次转换注入组最多4个可以打断规则组。三通道连续采集常见做法是规则组扫描加DMA理由有三条第一规则组序列长度灵活三个通道刚好占满不需要注入组那种“插队”逻辑第二连续转换模式下ADC一轮转换结束自动开始下一轮CPU完全不参与DMA把每次EOC产生的数据搬走第三注入组虽然可以单独触发但三路等间隔采样用注入组反而要处理JDR寄存器的读取顺序代码更绕。我一般会这样配ADC1的规则序列长度设为3序列1到3分别对应三个通道扫描模式使能连续转换模式使能DMA使能且循环模式打开。这样ADC自由跑DMA循环搬CPU只在需要时去缓冲区取最新值。注意STM32F1x的ADC1和ADC2共用DMA1通道1如果你同时用ADC2得错开或改用双ADC模式三通道场景一般只用ADC1就够了。2.2 时钟与采样时间两个决定数据质量的参数STM32F1x的ADC时钟来自PCLK2分频最大不能超过14MHz。常见做法是PCLK272MHzADC预分频设为6得到12MHz。采样时间每个通道独立设置范围从1.5到239.5个ADC时钟周期。采样时间越长对高阻抗信号源越友好但总转换时间会拉长。总转换时间公式Tconv (采样周期 12.5) / ADC时钟。以12MHz、采样时间55.5周期为例Tconv (55.512.5)/12MHz 5.67微秒。三通道一轮就是17微秒左右换算成采样率约58.8kHz。如果你要测50Hz工频信号这个速率绰绰有余如果要测开关电源的电流纹波可能得把采样时间压到7.5周期但此时信号源阻抗必须足够低否则采样电容充不满读数会偏小。提示采样时间不是越小越好。信号源内阻大于10kΩ时建议采样时间不低于55.5周期否则相邻通道之间会因为采样电容残留产生串扰。2.3 最小工程代码从GPIO到DMA的完整配置下面这段代码基于标准外设库寄存器版本思路一致。假设三个通道是PA0、PA1、PA2对应ADC_Channel_0、1、2DMA缓冲区是uint16_t adc_buf[3]。#include stm32f10x.h #define ADC_BUF_SIZE 3 uint16_t adc_buf[ADC_BUF_SIZE]; void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // PA0/PA1/PA2 模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); } void ADC_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize ADC_BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; // 循环模式ADC连续跑 DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); } void ADC_Config(void) { ADC_InitTypeDef ADC_InitStructure; ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 3; ADC_Init(ADC1, ADC_InitStructure); // 规则序列1~3采样时间55.5周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); ADC_DMACmd(ADC1, ENABLE); // 使能ADC的DMA请求 ADC_Cmd(ADC1, ENABLE); // ADC校准先复位校准再启动校准等校准完成 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发连续模式下只触发一次 }逻辑说明GPIO配成模拟输入是必须的否则引脚上的数字输入缓冲会引入漏电流。DMA的源地址固定为ADC1-DR目标地址是adc_buf搬运长度3循环模式保证搬完一轮自动回到起点。ADC配置里扫描模式和连续转换模式都使能规则序列按通道0、1、2排好。校准步骤不能省F1x的ADC校准能明显改善零点偏移。最后软件触发一次之后ADC就自由运行了。参数说明ADC_SampleTime_55Cycles5是采样时间对应55.5个ADC时钟周期DMA_BufferSize必须等于规则序列长度否则DMA搬完指定个数就停循环模式也救不回来ADC_NbrOfChannel设为3和序列长度一致。2.4 验证数据是否真的在连续更新烧进去之后别只看串口有没有数。我一般会在主循环里做两件事第一用另一个定时器每100ms翻转一个GPIO同时记录adc_buf的值用逻辑分析仪看翻转沿和串口输出是否对应第二把三个通道分别接GND、VDD、和中间电平看adc_buf[0]是否接近0adc_buf[1]是否接近4095adc_buf[2]是否在中间。如果通道顺序错乱多半是规则序列配置的顺序和DMA缓冲区索引没对上。如果三个值都在跳先检查模拟输入引脚有没有被其他外设复用。3. 采样率、通道串扰与DMA搬运三个必须算清楚的账3.1 采样率怎么算以及为什么不能只看ADC时钟很多人以为采样率就是ADC时钟除以转换周期但三通道连续转换下实际每个通道的更新率是总转换率的三分之一。总转换率 ADC时钟 / (采样周期 12.5)。以12MHz、55.5周期为例总转换率约176.5kHz单通道更新率约58.8kHz。如果你在DMA完成中断里读数据中断频率就是总转换率三通道场景下中断会非常密集所以更推荐用DMA循环模式加主循环轮询或者用DMA传输完成中断但把缓冲区开大。注意STM32F1x的ADC在连续转换模式下EOC标志每次转换都会置位DMA请求也随之产生。如果DMA没使能或者缓冲区大小设错EOC会一直置位ADC可能停止转换。这是最常见的“跑着跑着不动了”的原因。3.2 通道串扰的根源与采样时间的关系通道串扰表现为通道0接GND通道1接VDD结果通道0的读数不是0而是几十。根源是ADC内部的采样保持电容在切换通道时没有完全放电。F1x的ADC采样电容约8pF如果信号源内阻高或者采样时间太短上一次通道的电荷会残留到下一次采样。解决办法有两个一是增加采样时间让电容充分充放电二是在两个通道之间插入一个“牺牲”通道比如把采样时间最长的通道放在中间或者用注入组做隔离。我一般会把采样时间统一设到55.5周期以上如果信号源内阻超过10kΩ还会在引脚外加一个100nF的电容到地效果立竿见影。3.3 DMA搬运的边界条件与缓冲区设计DMA循环模式下缓冲区大小必须等于规则序列长度。如果你设了3个通道但DMA_BufferSize6DMA会搬6次才回卷结果adc_buf[3]到[5]是下一轮的数据索引就乱了。另一个坑是DMA的传输宽度ADC_DR是16位DMA_PeripheralDataSize和DMA_MemoryDataSize都必须设成HalfWord设成Word会导致地址错位。还有DMA的优先级如果低于其他外设在高负载场景下可能丢数据建议设成High或VeryHigh。// 主循环里安全读取三通道值的写法 uint16_t ch0, ch1, ch2; do { ch0 adc_buf[0]; ch1 adc_buf[1]; ch2 adc_buf[2]; } while (ch0 ! adc_buf[0]); // 简单防撕裂要求不高的场景够用这段防撕裂逻辑利用的是DMA循环搬运时如果读的过程中发生了回卷adc_buf[0]会变。更严谨的做法是用DMA传输完成中断在中断里拷贝一份到影子缓冲区。但三通道场景下主循环轮询加一次校验通常就够了。4. 避坑与排查三通道连续转换最常见的五个翻车现场4.1 现象三个通道读数完全一样或者顺序错乱原因规则序列配置的顺序和DMA缓冲区索引没对应或者ADC_NbrOfChannel设成了1。解决检查ADC_RegularChannelConfig的第三个参数Rank是否按1、2、3递增DMA_BufferSize是否等于3。如果用的是寄存器版检查SQR1的L[3:0]位是否设为2表示3个转换。4.2 现象第一个通道读数总是偏大后面两个正常原因ADC校准后第一次转换的残留电荷或者采样时间太短。解决在启动连续转换前先手动触发一次单次转换并丢弃结果或者把采样时间加到55.5周期以上。我遇到过一批板子PA0靠近电源引脚耦合了开关噪声后来在PA0和地之间并了1nF电容才压住。4.3 现象DMA搬了几轮就停了ADC还在跑但缓冲区不更新原因DMA传输完成中断里没有清除标志或者DMA模式设成了Normal而不是Circular。解决检查DMA_InitStructure.DMA_Mode是否为DMA_Mode_Circular如果用了中断在中断服务函数里调用DMA_ClearITPendingBit。另外ADC的DMA使能位ADC_DMACmd必须在ADC_Cmd之前或之后都行但必须在软件触发之前。4.4 现象采样率上不去或者数据跳变严重原因ADC时钟超过14MHz或者采样时间设得太小导致信号源驱动能力不足。解决用RCC_ADCCLKConfig把ADC预分频设到6或8确保ADC时钟不超过14MHz。如果信号源是运放输出检查运放的建立时间是否满足采样时间要求。我一般会在运放输出和ADC引脚之间串一个100Ω电阻再并一个1nF电容构成抗混叠滤波。4.5 现象低功耗模式下ADC停止工作原因STM32F1x在Stop或Standby模式下ADC时钟被关闭唤醒后需要重新校准和启动。解决如果应用需要低功耗建议在进入低功耗前关闭ADC和DMA唤醒后重新执行校准和软件触发。不要指望ADC在低功耗模式下继续连续转换F1x的ADC没有这个能力。5. 进阶技巧用定时器触发替代软件触发让采样率精确可控软件触发在连续模式下只触发一次之后ADC自由跑采样率由ADC时钟和采样时间决定精度依赖内部RC振荡器温漂不小。如果你要做同步采样或者精确控制采样率更稳的做法是用定时器触发。以TIM2的CC2事件触发ADC为例把ADC_ExternalTrigConv设为ADC_ExternalTrigConv_T2_CC2连续转换模式关掉扫描模式保持使能DMA循环模式不变。这样每个定时器事件触发一轮三通道扫描采样率完全由定时器决定。void TIM2_Trigger_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 72MHz / (711) 1MHz 计数频率 TIM_TimeBaseStructure.TIM_Period 99; // 100个计数 100us 10kHz TIM_TimeBaseStructure.TIM_Prescaler 71; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 输出比较模式CC2事件不映射到引脚 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PulseWidth; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Disable; TIM_OCInitStructure.TIM_Pulse 50; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC2Init(TIM2, TIM_OCInitStructure); TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_OC2Ref); TIM_Cmd(TIM2, ENABLE); }逻辑说明TIM2配置成1MHz计数周期99即每100us产生一次OC2REF事件对应10kHz触发频率。ADC配置里把连续转换关掉外部触发选T2_CC2这样每次触发ADC按规则序列扫一遍三个通道DMA搬三个数。参数说明TIM_Prescaler71对应72分频TIM_Period99对应100个计数两者共同决定触发周期。注意触发频率不能超过ADC总转换时间的倒数否则会丢触发。以55.5周期、12MHz为例一轮三通道需要约17us触发周期必须大于17us即频率低于58.8kHz。用定时器触发后采样率的精度取决于晶振而不是ADC内部时钟做FFT或者同步采样时优势明显。我一般会在定时器触发的基础上再开一个DMA传输完成中断在中断里置一个标志位主循环检测到标志位再处理数据这样既保证了采样率又避免了主循环轮询的延迟。最后说个血泪教训调ADC多通道时先别急着上RTOS或者复杂滤波把裸机下的三通道数据用逻辑分析仪和串口同时看一遍确认通道顺序、采样率、串扰都符合预期再往上叠功能。很多“玄学”问题其实都是采样时间不够或者DMA缓冲区大小写错。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑