在STM32的ADC模块中,**采样时机(Sampling Time)和转换时机(Conversion Time)**是ADC工作流程中的两个关键阶段,直接影响采样精度和系统实时性。以下是详细解析:
1. 采样时机(Sampling Time)
(1)定义
- 采样阶段:ADC对输入信号进行保持和稳定的过程。
- 采样时间 :由
ADC_SMPRx
寄存器配置,决定采样电容充电时间。
(2)配置参数
STM32F103的采样时间可设置为:
c
typedef enum {
ADC_SampleTime_1Cycles5, // 1.5周期
ADC_SampleTime_7Cycles5, // 7.5周期
ADC_SampleTime_13Cycles5, // 13.5周期
ADC_SampleTime_28Cycles5, // 28.5周期
ADC_SampleTime_41Cycles5, // 41.5周期
ADC_SampleTime_55Cycles5, // 55.5周期
ADC_SampleTime_71Cycles5, // 71.5周期
ADC_SampleTime_239Cycles5 // 239.5周期(用于高阻抗信号)
} ADC_SampleTime;
(3)选择原则
信号类型 | 推荐采样时间 | 原因 |
---|---|---|
低阻抗信号 | 1.5~28.5周期 | 信号源阻抗低(如运放输出),快速稳定。 |
高阻抗信号 | 55.5~239.5周期 | 信号源阻抗高(如温度传感器、分压电路),需更长时间充电。 |
内部通道 | ≥239.5周期 | 内部温度传感器和VREFINT阻抗极高,必须延长采样时间。 |
(4)代码示例
c
// 配置PA0(低阻抗)和温度传感器(高阻抗)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5); // 快速采样
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_239Cycles5); // 慢速采样
2. 转换时机(Conversion Time)
(1)定义
- 转换阶段:ADC核心将采样到的模拟量转换为数字量的过程。
- 固定耗时 :12位分辨率下恒定为12.5个ADC时钟周期(与采样时间无关)。
(2)总转换时间计算
T_{total} = (T_{sampling} + 12.5) \\times \\frac{1}{f_{ADC}}
- 示例 :
- ADC时钟 = 14 MHz,采样时间 = 55.5周期
- 总时间 = (55.5 + 12.5) / 14MHz ≈ 4.86μs
(3)吞吐量限制
- 理论最大采样率 :
f_{max} = \\frac{1}{T_{total}}
- 若
T_total=4.86μs
,则f_max≈205kHz
(单通道)。
- 若
3. 采样与转换的时序图
plaintext
采样阶段 转换阶段
|-------- Sampling Time --------|-- 12.5 Cycles --|
|<----------- Total Conversion Time ------------>|
4. 关键影响因素
(1)ADC时钟频率
-
最大14MHz (STM32F103),需分频APB2时钟:
cRCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz/6=12MHz
-
更高时钟 → 更快转换,但可能降低精度。
(2)通道切换延迟
- 多通道扫描时,每个通道需单独配置采样时间,切换通道会增加额外延迟。
(3)触发方式
- 硬件触发(如定时器):精确控制采样间隔。
- 软件触发:灵活性高但时序不易控制。
5. 优化策略
(1)动态调整采样时间
根据信号类型切换采样时间:
c
void Set_ADC_SampleTime(ADC_TypeDef* ADCx, uint8_t channel, uint8_t isHighImpedance) {
ADC_SampleTime time = isHighImpedance ? ADC_SampleTime_239Cycles5 : ADC_SampleTime_28Cycles5;
ADC_RegularChannelConfig(ADCx, channel, 1, time);
}
(2)使用注入组中断
高优先级信号通过注入组立即响应:
c
// 过压时紧急采样
void ADC1_2_IRQHandler(void) {
if (ADC_GetITStatus(ADC1, ADC_IT_JEOC)) {
uint16_t emergencyData = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1);
ADC_ClearITPendingBit(ADC1, ADC_IT_JEOC);
}
}
(3)DMA传输规则组数据
避免CPU轮询,提高效率:
c
uint16_t adcValues[3];
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcValues;
ADC_DMACmd(ADC1, ENABLE);
6. 实际应用示例
电机电流监测系统
- 规则组 :
- 通道PA0(电流传感器),采样时间7.5周期(低阻抗)。
- 定时器触发1kHz采样,DMA传输数据。
- 注入组 :
- 比较器检测过流时触发PA8中断,注入组采集PA1(备份传感器)。
- 计算总延迟 :
- 采样+转换时间 = (7.5 + 12.5) / 14MHz ≈ 1.43μs
- 1kHz采样率下,CPU负载极低。
总结
- 采样时机:由信号源阻抗决定,需足够时间稳定信号。
- 转换时机:固定12.5周期,与ADC时钟频率直接相关。
- 优化核心:在精度和速度间平衡,优先保证信号完整性。
在STM32的ADC编程中,采样 、转换 和数据获取的流程分布在程序的不同位置,具体取决于触发方式(软件/硬件)和数据读取方式(轮询/DMA/中断)。以下是基于STM32F103的详细说明和代码示例:
1. 采样(Sampling)
触发采样的位置
-
软件触发 :在代码中显式调用启动转换函数。
c// 在需要采样的地方(如主循环或定时器回调中) ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动规则组采样 ADC_SoftwareStartInjectedConv(ADC1); // 启动注入组采样
-
硬件触发 :由定时器、外部中断等自动触发,无需手动调用。
c// 配置TIM2触发规则组采样(无需在主循环中操作) TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
采样时间配置
在ADC初始化阶段设置:
c
// 配置规则组通道的采样时间(在ADC初始化时)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
2. 转换(Conversion)
转换的触发
- 转换由硬件自动完成,无需程序干预。
- 转换时间固定为12.5个ADC时钟周期(如14MHz时钟下约0.89μs)。
判断转换完成
-
轮询方式 :检查标志位。
cwhile (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 规则组 while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_JEOC)); // 注入组
-
中断方式 :在中断服务函数中处理。
cvoid ADC1_2_IRQHandler(void) { if (ADC_GetITStatus(ADC1, ADC_IT_EOC)) { uint16_t data = ADC_GetConversionValue(ADC1); // 规则组数据 ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); } if (ADC_GetITStatus(ADC1, ADC_IT_JEOC)) { uint16_t data = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1); ADC_ClearITPendingBit(ADC1, ADC_IT_JEOC); } }
3. 获取数据(Data Readout)
规则组数据
-
轮询读取 :
cADC_SoftwareStartConvCmd(ADC1, ENABLE); while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); uint16_t adcValue = ADC_GetConversionValue(ADC1); // 读取规则组数据
-
DMA传输 (推荐多通道):
cuint16_t adcValues[3]; // 存储3个通道的数据 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcValues; ADC_DMACmd(ADC1, ENABLE); // 数据会自动更新到adcValues数组
注入组数据
-
中断读取 :
cvoid ADC1_2_IRQHandler(void) { if (ADC_GetITStatus(ADC1, ADC_IT_JEOC)) { uint16_t injData = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1); ADC_ClearITPendingBit(ADC1, ADC_IT_JEOC); } }
4. 完整流程示例(规则组+注入组)
初始化配置
c
void ADC_Init(void) {
// 1. 时钟和GPIO初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 2. 规则组配置(PA0)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_Init(ADC1, &ADC_InitStructure);
// 3. 注入组配置(PA1)
ADC_InjectedChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_Ext_IT15);
// 4. 启用中断
ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLE);
NVIC_EnableIRQ(ADC1_2_IRQn);
// 5. 校准和启动
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
}
主程序中的采样与读取
c
int main(void) {
ADC_Init();
while (1) {
// 规则组:手动触发并读取
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
uint16_t regularData = ADC_GetConversionValue(ADC1);
// 注入组:由PA8按键触发(中断中自动处理)
Delay_ms(100); // 模拟其他任务
}
}
中断服务函数
c
void ADC1_2_IRQHandler(void) {
if (ADC_GetITStatus(ADC1, ADC_IT_JEOC)) {
uint16_t injectedData = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1);
ADC_ClearITPendingBit(ADC1, ADC_IT_JEOC);
// 处理紧急数据(如过压保护)
}
}
5. 关键总结
操作 | 程序位置 | 常用函数/配置 |
---|---|---|
采样触发 | 主循环/定时器回调/中断 | ADC_SoftwareStartConvCmd() |
采样时间 | ADC初始化阶段 | ADC_RegularChannelConfig() |
转换等待 | 轮询/中断 | ADC_GetFlagStatus() 或中断标志 |
数据读取 | 轮询/DMA/中断 | ADC_GetConversionValue() 或DMA数组 |
- 规则组:适合周期性采样,配合DMA提高效率。
- 注入组:用于紧急事件,中断中即时响应。
- 硬件触发:精确控制时序,减少CPU干预。
在STM32的ADC配置中,**连续扫描(Scan Mode)和连续转换(Continuous Conversion Mode)**是两个关键的工作模式,它们决定了ADC如何对多个通道进行采样和数据更新。以下是详细解释:
1. 连续扫描模式(Scan Mode)
功能
- 多通道自动切换:ADC按预设顺序依次采样多个通道(规则组或注入组),无需手动切换。
- 数据存储 :需配合DMA或中断将数据存入数组(规则组数据寄存器
ADC_DR
会被覆盖)。
配置方法
c
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 启用扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 3; // 通道数量
ADC_Init(ADC1, &ADC_InitStructure);
// 设置通道顺序(规则组)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // 第1通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); // 第2通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); // 第3通道
典型应用
-
同时监测多个传感器(如温度、电压、电流)。
-
需配合DMA传输数据:
cuint16_t adcValues[3]; // 存储多通道数据 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcValues; ADC_DMACmd(ADC1, ENABLE);
2. 连续转换模式(Continuous Conversion Mode)
功能
- 自动重启转换:完成一次转换后立即开始下一次,无需手动触发。
- 单通道/多通道:可与扫描模式组合使用。
配置方法
c
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 启用连续转换
ADC_Init(ADC1, &ADC_InitStructure);
典型应用
-
实时监控信号变化(如音频采集)。
-
单通道连续采样示例:
cADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动后自动循环采样
3. 组合使用场景
模式对比
模式组合 | 行为 | 适用场景 |
---|---|---|
扫描+单次转换 | 按顺序采样所有通道后停止 | 定时触发多通道采样(如每1秒1次) |
扫描+连续转换 | 循环采样所有通道,数据持续更新 | 实时多通道监测(如电机控制) |
单通道+连续转换 | 重复采样同一通道 | 高速信号捕获(如示波器) |
代码示例(扫描+连续转换)
c
// 初始化ADC(多通道循环采样)
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_NbrOfChannel = 3;
ADC_Init(ADC1, &ADC_InitStructure);
// 配置DMA自动传输数据
uint16_t adcValues[3];
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcValues;
ADC_DMACmd(ADC1, ENABLE);
// 启动ADC
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
4. 关键注意事项
-
数据覆盖问题
- 规则组扫描时,
ADC_DR
寄存器会被新数据覆盖,必须使用DMA或中断及时读取。
- 规则组扫描时,
-
触发源选择
- 连续转换模式下,硬件触发(如定时器)可精确控制采样间隔。
-
功耗权衡
- 连续转换模式功耗较高,电池供电设备建议使用单次转换+定时触发。
-
注入组优先级
- 即使规则组处于连续转换中,注入组触发仍会立即中断当前转换。
5. 常见问题解答
Q1:扫描模式必须用DMA吗?
A1:不是必须,但强烈推荐。DMA可避免数据丢失,若用中断读取,需确保处理速度够快。
Q2:如何暂停连续转换?
A2:调用ADC_ContinuousConvModeCmd(ADC1, DISABLE)
,或直接关闭ADC:
c
ADC_Cmd(ADC1, DISABLE); // 完全停止ADC
Q3:规则组和注入组能同时用扫描模式吗?
A3:可以!规则组和注入组的扫描相互独立,但注入组会打断规则组当前转换。
总结
- 扫描模式:解决多通道自动切换问题,需注意数据存储。
- 连续转换:实现无缝采样,适合实时性要求高的场景。
- 组合使用:灵活应对复杂需求(如实时监测多个传感器)。
通过合理配置这两种模式,可以充分发挥STM32 ADC的性能。
在STM32的ADC模块中,**连续转换模式(Continuous Conversion Mode)和单次转换模式(Single Conversion Mode)**的选择直接影响系统的实时性、功耗和采样精度。以下是针对您提到的两点的详细解释:
1. 连续转换模式 + 硬件触发的精确控制
工作原理
- 连续转换模式 :ADC完成一次转换后,立即自动启动下一次转换,形成不间断的采样循环。
- 硬件触发 (如定时器TIM2):通过外部信号(如定时器更新事件)控制ADC的启动时机,实现固定间隔采样。
为何能精确控制采样间隔?
- 硬件同步:定时器的时钟源稳定(如内部72MHz晶振),触发信号的间隔误差极小(纳秒级)。
- 规避软件延迟:相比软件触发(需CPU干预),硬件触发完全由外设自动完成,无调度延迟。
配置示例
c
// 配置TIM2触发ADC(1kHz采样率,即1ms间隔)
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_Period = 7200 - 1; // 72MHz / 7200 = 10kHz
TIM_InitStructure.TIM_Prescaler = 0;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // 更新事件触发ADC
// 配置ADC为连续转换+硬件触发
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
ADC_Init(ADC1, &ADC_InitStructure);
效果:ADC以精确的1ms间隔采样,适合需要严格周期性的应用(如数字滤波器、PWM控制)。
2. 连续转换模式的功耗问题
高功耗原因
- ADC持续工作:连续转换模式下,ADC核心和模拟电路(如采样保持电路)始终处于活动状态,电流消耗较大(STM32F103典型值约1mA@14MHz ADC时钟)。
- 频繁数据更新:即使无实际信号变化,ADC仍持续转换,浪费功耗。
单次转换+定时触发的低功耗方案
(1)工作流程
- 单次转换模式:每次触发后只采样一次,然后自动停止。
- 定时器触发:按需唤醒ADC(如每秒1次),其余时间ADC处于低功耗状态。
(2)配置代码
c
// 配置ADC为单次转换+硬件触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
ADC_Init(ADC1, &ADC_InitStructure);
// 配置TIM2低速触发(如1Hz)
TIM_InitStructure.TIM_Period = 72000000 - 1; // 72MHz / 72000000 = 1Hz
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
(3)功耗对比
模式 | 平均电流(示例) | 适用场景 |
---|---|---|
连续转换(1kHz) | ~1.2mA | 实时控制、高速信号处理 |
单次转换+定时(1Hz) | ~50μA | 电池供电的传感器监测 |
3. 如何选择?
选择连续转换模式当:
- 需要高实时性(如电机控制、音频采样)。
- 系统供电充足(如插电设备)。
选择单次转换+定时触发当:
- 设备由电池供电(如IoT传感器)。
- 信号变化缓慢(如温度、湿度监测)。
折中方案
-
动态切换模式 :根据任务需求灵活调整。
cvoid Set_ADCMode(uint8_t isHighSpeed) { ADC_ContinuousConvModeCmd(ADC1, isHighSpeed ? ENABLE : DISABLE); TIM_SetAutoreload(TIM2, isHighSpeed ? 7200 - 1 : 72000000 - 1); // 切换采样率 }
4. 实测建议
- 测量电流:用万用表对比两种模式的功耗差异(注意关闭未用外设)。
- 唤醒时间:单次模式首次转换可能有额外延迟(需重新校准)。
通过合理选择转换模式,可平衡性能与功耗,满足不同应用场景需求。
在STM32的ADC应用中,定时器触发ADC采样 通常不需要在定时器中断服务函数(如TIMx_IRQHandler
)中编写代码,而是直接通过定时器的硬件触发输出(如TRGO)自动控制ADC启动。以下是具体说明和代码示例:
1. 硬件触发ADC的配置流程
(1)定时器配置
- 不依赖中断:仅用定时器的更新事件(UEV)触发ADC,无需进入中断。
- 关键步骤 :
- 设置定时器周期(决定采样间隔)。
- 配置定时器触发输出(TRGO)。
c
// 配置TIM2触发ADC(1kHz采样率,72MHz主频)
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_Period = 72000 - 1; // 72000 / 72MHz = 1ms (1kHz)
TIM_InitStructure.TIM_Prescaler = 0; // 不分频
TIM_InitStructure.TIM_ClockDivision = 0;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
// 配置TIM2更新事件触发TRGO
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
TIM_Cmd(TIM2, ENABLE); // 启动定时器
(2)ADC配置
- 设置为硬件触发模式,选择定时器作为触发源。
c
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO; // TIM2触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换模式(推荐低功耗)
ADC_Init(ADC1, &ADC_InitStructure);
2. 何时需要定时器中断?
如果需要在定时器事件中执行其他任务(如数据处理、状态检查),才需启用中断。此时需:
(1)启用定时器中断
c
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 允许更新中断
NVIC_EnableIRQ(TIM2_IRQn); // 启用NVIC中断
(2)中断服务函数示例
c
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update)) {
// 此处可添加其他任务(如读取ADC数据、控制逻辑)
// 注意:ADC采样已由硬件自动触发,无需在此启动!
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志
}
}
3. 完整代码示例(无中断方案)
目标:用TIM2以1kHz触发ADC采样,DMA传输数据。
c
#include "stm32f10x.h"
uint16_t adcValue; // 存储ADC数据
void ADC_TIM_Config(void) {
// 1. 启动时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO(PA0为ADC输入)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置TIM2(1kHz触发)
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_Period = 72000 - 1; // 1ms间隔
TIM_InitStructure.TIM_Prescaler = 0;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // 更新事件触发ADC
TIM_Cmd(TIM2, ENABLE);
// 4. 配置ADC1(单次转换+定时器触发)
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 5. 配置ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 6. 启用ADC
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
}
int main(void) {
ADC_TIM_Config();
while (1) {
// 数据通过DMA或中断自动更新(此处为轮询示例)
if (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)) {
adcValue = ADC_GetConversionValue(ADC1);
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
}
}
}
4. 关键注意事项
- 无需中断触发ADC:硬件触发本身不需要CPU干预,直接由定时器信号启动ADC。
- 中断的用途 :仅当需要在定时器事件中执行额外逻辑(如数据处理、状态机更新)时才启用中断。
- DMA推荐:高频采样时,务必使用DMA传输数据,避免CPU轮询开销。
总结
- 纯硬件触发:配置定时器TRGO + ADC外部触发,无需中断,效率最高。
- 中断的适用场景:同步执行其他任务(如报警检查、数据打包)。
- 低功耗设计 :单次转换模式 + 定时器触发,适合电池供电设备。
在STM32的ADC应用中,定时器触发的是ADC的启动时机 ,即控制何时开始一次完整的"采样+转换"过程。以下是关键概念的分步解释和代码实现:
1. 定时器触发的本质
- 触发的内容 :定时器触发的是ADC的完整转换流程 ,包括:
- 采样阶段 (Sampling Phase):ADC对输入信号进行采样保持(时间由
ADC_SampleTime
决定)。 - 转换阶段(Conversion Phase):ADC将采样值转换为数字量(固定12.5个ADC时钟周期)。
- 采样阶段 (Sampling Phase):ADC对输入信号进行采样保持(时间由
- 定时器的作用 :精确控制每次"采样+转换"的启动时刻,而非单独控制采样或转换。
2. 数据获取的位置
(1)DMA自动传输(推荐方式)
-
配置步骤 :
cuint16_t adcValue; // 存储ADC数据 // 启用ADC DMA DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adcValue; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1; DMA_Init(DMA1_Channel1, &DMA_InitStructure); ADC_DMACmd(ADC1, ENABLE);
-
数据获取 :
定时器触发ADC后,转换结果会自动通过DMA传输到adcValue
变量,无需程序干预。
(2)中断中读取
-
配置步骤 :
c// 启用ADC转换完成中断 ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); NVIC_EnableIRQ(ADC1_2_IRQn); // 中断服务函数 void ADC1_2_IRQHandler(void) { if (ADC_GetITStatus(ADC1, ADC_IT_EOC)) { adcValue = ADC_GetConversionValue(ADC1); ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); } }
-
数据获取 :
每次转换完成后,在中断中读取ADC_DR
寄存器。
(3)轮询方式(低效,仅用于测试)
c
// 主循环中等待转换完成
while (1) {
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 定时器触发时无需此句
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
adcValue = ADC_GetConversionValue(ADC1);
}
3. 完整代码示例(定时器触发+DMA)
目标:TIM2以1kHz触发ADC1采样PA0,DMA自动传输数据。
c
#include "stm32f10x.h"
uint16_t adcValue; // 存储ADC数据
void ADC_Config(void) {
// 1. 启用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 2. 配置PA0为模拟输入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置TIM2(1kHz触发)
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_Period = 72000 - 1; // 72MHz / 72000 = 1kHz
TIM_InitStructure.TIM_Prescaler = 0;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // 更新事件触发ADC
TIM_Cmd(TIM2, ENABLE);
// 4. 配置DMA
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adcValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 5. 配置ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 6. 配置ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 7. 启用ADC和DMA
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
}
int main(void) {
ADC_Config();
while (1) {
// adcValue已通过DMA自动更新,可直接使用
float voltage = adcValue * 3.3f / 4095; // 转换为电压值
}
}
4. 关键总结
问题 | 答案 |
---|---|
定时器触发的内容 | 触发完整的ADC转换流程(采样+转换) |
采样时刻控制 | 由定时器的触发信号决定 |
数据获取位置 | DMA自动传输到变量 / 中断中读取 / 轮询ADC_DR 寄存器 |
最优方案 | 定时器硬件触发 + DMA传输(无CPU干预,高效低功耗) |
通过这种方式,STM32可以实现精确的定时采样,同时最小化CPU负载。
在STM32的ADC应用中,定时器触发ADC采样时,选择单次转换模式(Single Conversion)还是连续转换模式(Continuous Conversion),取决于具体的应用场景和需求。以下是两者的对比分析和配置建议:
1. 单次转换模式(Single Conversion)
特点
- 工作流程 :
每次定时器触发后,ADC执行一次完整的采样+转换,然后自动停止,等待下一次触发。 - 功耗:较低(仅在触发时激活ADC)。
- 数据更新:每次触发后数据更新一次。
适用场景
- 低频采样:如温度监测(每秒几次)。
- 电池供电设备:需要最小化功耗。
- 事件触发采样:如按键按下后采集一次数据。
配置代码
c
// ADC初始化配置
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO; // TIM2触发
ADC_Init(ADC1, &ADC_InitStructure);
// 启动ADC(首次启动后,后续由定时器自动触发)
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
2. 连续转换模式(Continuous Conversion)
特点
- 工作流程 :
定时器首次触发后,ADC持续循环执行采样+转换 ,直到手动停止。
(注:在硬件触发下,连续转换模式仍依赖定时器的周期性触发。) - 功耗:较高(ADC持续运行)。
- 数据更新:数据持续刷新,速率由定时器触发频率决定。
适用场景
- 高频实时采样:如音频信号处理(>1kHz)。
- 控制环路:如电机PID控制,需要严格周期性的数据更新。
- 多通道扫描:配合DMA实现无缝数据流。
配置代码
c
// ADC初始化配置
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO; // TIM2触发
ADC_Init(ADC1, &ADC_InitStructure);
// 启动ADC(首次启动后,ADC会持续运行,但每次转换仍由定时器触发)
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
3. 关键区别与选择建议
特性 | 单次转换模式 | 连续转换模式 |
---|---|---|
触发后行为 | 采样一次后停止 | 持续采样,直到手动停止 |
功耗 | 低(适合电池供电) | 高(适合持续供电场景) |
数据更新频率 | 由定时器触发频率决定 | 由定时器触发频率决定 |
硬件资源占用 | ADC间歇工作 | ADC持续占用 |
典型应用 | 温度传感器、低速监测 | 电机控制、音频采集 |
选择原则
-
单次转换模式:
- 需要低功耗 或非连续采样时使用。
- 即使定时器触发频率很高(如1kHz),每次触发仍只采样一次。
-
连续转换模式:
- 需要无缝数据流 或严格实时性时使用。
- 注意:在硬件触发下,连续转换模式的实际采样率仍由定时器决定,但ADC核心会保持活跃状态。
4. 常见问题解答
Q1:连续转换模式下,定时器触发还有意义吗?
- 有意义 !连续转换模式只是保持ADC核心处于工作状态,但每次转换的启动时刻仍由定时器精确控制。
- 若禁用硬件触发(如用软件触发),ADC会以最大速度连续采样(不推荐,可能丢失时序精度)。
Q2:单次模式是否会漏掉定时器触发信号?
- 不会。每次定时器触发都会启动一次新的转换,即使前一次转换已完成。
- 但需确保两次触发的间隔大于单次转换的总时间(采样时间 + 12.5周期)。
Q3:如何动态切换模式?
c
// 运行时切换为单次模式
ADC_ContinuousConvModeCmd(ADC1, DISABLE);
// 运行时切换为连续模式
ADC_ContinuousConvModeCmd(ADC1, ENABLE);
5. 完整示例(单次转换 + 定时器触发 + DMA)
c
#include "stm32f10x.h"
uint16_t adcValue;
void ADC_Config(void) {
// 1. 启用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 2. 配置PA0为模拟输入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置TIM2(100Hz触发)
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_Period = 720000 - 1; // 72MHz / 720000 = 100Hz
TIM_InitStructure.TIM_Prescaler = 0;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // 更新事件触发ADC
TIM_Cmd(TIM2, ENABLE);
// 4. 配置DMA(单次传输)
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adcValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 5. 配置ADC(单次转换 + TIM2触发)
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 6. 配置ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 7. 启用ADC和DMA
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
}
int main(void) {
ADC_Config();
while (1) {
// adcValue每10ms自动更新一次(由TIM2触发)
float voltage = adcValue * 3.3f / 4095;
}
}
总结
- 单次转换模式:节能,适合低频或事件触发场景。
- 连续转换模式:实时性强,适合高频或控制环路。
- 硬件触发:无论哪种模式,定时器均可精确控制采样间隔。
- 推荐组合 :
- 低功耗应用:单次转换 + 定时器触发 + DMA。
- 实时控制:连续转换 + 定时器触发 + DMA。