STM32的ADC模块中,**采样时机(Sampling Time)**和**转换时机(Conversion Time),获取数据的时机详解

在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时钟:

    c 复制代码
    RCC_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)。
判断转换完成
  • 轮询方式 :检查标志位。

    c 复制代码
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));  // 规则组
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_JEOC)); // 注入组
  • 中断方式 :在中断服务函数中处理。

    c 复制代码
    void 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)

规则组数据
  • 轮询读取

    c 复制代码
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    uint16_t adcValue = ADC_GetConversionValue(ADC1);  // 读取规则组数据
  • DMA传输 (推荐多通道):

    c 复制代码
    uint16_t adcValues[3];  // 存储3个通道的数据
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcValues;
    ADC_DMACmd(ADC1, ENABLE);
    // 数据会自动更新到adcValues数组
注入组数据
  • 中断读取

    c 复制代码
    void 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传输数据:

    c 复制代码
    uint16_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);
典型应用
  • 实时监控信号变化(如音频采集)。

  • 单通道连续采样示例:

    c 复制代码
    ADC_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. 关键注意事项

  1. 数据覆盖问题

    • 规则组扫描时,ADC_DR寄存器会被新数据覆盖,必须使用DMA或中断及时读取。
  2. 触发源选择

    • 连续转换模式下,硬件触发(如定时器)可精确控制采样间隔。
  3. 功耗权衡

    • 连续转换模式功耗较高,电池供电设备建议使用单次转换+定时触发。
  4. 注入组优先级

    • 即使规则组处于连续转换中,注入组触发仍会立即中断当前转换。

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)工作流程
  1. 单次转换模式:每次触发后只采样一次,然后自动停止。
  2. 定时器触发:按需唤醒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传感器)。
  • 信号变化缓慢(如温度、湿度监测)。
折中方案
  • 动态切换模式 :根据任务需求灵活调整。

    c 复制代码
    void 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. 关键注意事项

  1. 无需中断触发ADC:硬件触发本身不需要CPU干预,直接由定时器信号启动ADC。
  2. 中断的用途 :仅当需要在定时器事件中执行额外逻辑(如数据处理、状态机更新)时才启用中断。
  3. DMA推荐:高频采样时,务必使用DMA传输数据,避免CPU轮询开销。

总结

  • 纯硬件触发:配置定时器TRGO + ADC外部触发,无需中断,效率最高。
  • 中断的适用场景:同步执行其他任务(如报警检查、数据打包)。
  • 低功耗设计 :单次转换模式 + 定时器触发,适合电池供电设备。
    在STM32的ADC应用中,定时器触发的是ADC的启动时机 ,即控制何时开始一次完整的"采样+转换"过程。以下是关键概念的分步解释和代码实现:

1. 定时器触发的本质

  • 触发的内容 :定时器触发的是ADC的完整转换流程 ,包括:
    1. 采样阶段 (Sampling Phase):ADC对输入信号进行采样保持(时间由ADC_SampleTime决定)。
    2. 转换阶段(Conversion Phase):ADC将采样值转换为数字量(固定12.5个ADC时钟周期)。
  • 定时器的作用 :精确控制每次"采样+转换"的启动时刻,而非单独控制采样或转换。

2. 数据获取的位置

(1)DMA自动传输(推荐方式)
  • 配置步骤

    c 复制代码
    uint16_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持续占用
典型应用 温度传感器、低速监测 电机控制、音频采集
选择原则
  1. 单次转换模式

    • 需要低功耗非连续采样时使用。
    • 即使定时器触发频率很高(如1kHz),每次触发仍只采样一次。
  2. 连续转换模式

    • 需要无缝数据流严格实时性时使用。
    • 注意:在硬件触发下,连续转换模式的实际采样率仍由定时器决定,但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。
相关推荐
happygrilclh10 分钟前
STM32 定时器主从模式配置解析
stm32·单片机·嵌入式硬件
王光环1 小时前
STM32H743IIT6_ADC采集误差分析与ADC_DMA
stm32·单片机
芯眼2 小时前
STM32启动文件详解(重点)
java·开发语言·c++·stm32·单片机·mybatis
长流小哥3 小时前
STM32 ADC+DMA+TIM触发采样实战:避坑指南与源码解析
stm32·单片机·嵌入式硬件·keil5
道亦无名3 小时前
STM32控制电机
stm32·单片机·嵌入式硬件
真的想上岸啊4 小时前
学习51单片机02
嵌入式硬件·学习·51单片机
sword devil9005 小时前
STM32F407VET6实战:CRC校验
stm32·单片机·嵌入式硬件
小智学长 | 嵌入式6 小时前
单片机-STM32部分:18、WiFi模组
stm32·单片机·嵌入式硬件
KaiGer6668 小时前
AUTOSAR图解==>AUTOSAR_SWS_ICUDriver
单片机·汽车·嵌入式·autosar