STM32 ADC模块采样时机与转换时间详解:获取数据的精准时机解析

在STM32的ADC模块中,**采样时机(Sampling Time)转换时机(Conversion Time)**是ADC工作流程中的两个关键阶段,直接影响采样精度和系统实时性。以下是详细解析:


1. 采样时机(Sampling Time)

(1)定义
  • 采样阶段:ADC对输入信号进行保持和稳定的过程。
  • 采样时间:由ADC_SMPRx寄存器配置,决定采样电容充电时间。
  • (2)配置参数

    STM32F103的采样时间可设置为:

    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)代码示例
    // 配置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. 采样与转换的时序图

    采样阶段                       转换阶段
    |-------- Sampling Time --------|-- 12.5 Cycles --|
    |<----------- Total Conversion Time ------------>|
    

    4. 关键影响因素

    (1)ADC时钟频率
  • 最大14MHz(STM32F103),需分频APB2时钟:
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);  // 72MHz/6=12MHz
    
  • 更高时钟 → 更快转换,但可能降低精度。
  • (2)通道切换延迟
  • 多通道扫描时,每个通道需单独配置采样时间,切换通道会增加额外延迟。
  • (3)触发方式
  • 硬件触发(如定时器):精确控制采样间隔。
  • 软件触发:灵活性高但时序不易控制。

  • 5. 优化策略

    (1)动态调整采样时间

    根据信号类型切换采样时间:

    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)使用注入组中断

    高优先级信号通过注入组立即响应:

    // 过压时紧急采样
    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轮询,提高效率:

    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)

    触发采样的位置
  • 软件触发:在代码中显式调用启动转换函数。
    // 在需要采样的地方(如主循环或定时器回调中)
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);  // 启动规则组采样
    ADC_SoftwareStartInjectedConv(ADC1);     // 启动注入组采样
    
  • 硬件触发:由定时器、外部中断等自动触发,无需手动调用。
    // 配置TIM2触发规则组采样(无需在主循环中操作)
    TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
    
  • 采样时间配置

    在ADC初始化阶段设置:

    // 配置规则组通道的采样时间(在ADC初始化时)
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    

    2. 转换(Conversion)

    转换的触发
  • 转换由硬件自动完成,无需程序干预
  • 转换时间固定为12.5个ADC时钟周期(如14MHz时钟下约0.89μs)。
  • 判断转换完成
  • 轮询方式:检查标志位。
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));  // 规则组
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_JEOC)); // 注入组
    
  • 中断方式:在中断服务函数中处理。
    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)

    规则组数据
  • 轮询读取
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    uint16_t adcValue = ADC_GetConversionValue(ADC1);  // 读取规则组数据
    
  • DMA传输(推荐多通道):
    uint16_t adcValues[3];  // 存储3个通道的数据
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcValues;
    ADC_DMACmd(ADC1, ENABLE);
    // 数据会自动更新到adcValues数组
    
  • 注入组数据
  • 中断读取
    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. 完整流程示例(规则组+注入组)

    初始化配置
    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));
    }
    
    主程序中的采样与读取
    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);  // 模拟其他任务
        }
    }
    
    中断服务函数
    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会被覆盖)。
  • 配置方法
    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传输数据:
    uint16_t adcValues[3];  // 存储多通道数据
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcValues;
    ADC_DMACmd(ADC1, ENABLE);
    

  • 2. 连续转换模式(Continuous Conversion Mode)

    功能
  • 自动重启转换:完成一次转换后立即开始下一次,无需手动触发。
  • 单通道/多通道:可与扫描模式组合使用。
  • 配置方法
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 启用连续转换
    ADC_Init(ADC1, &ADC_InitStructure);
    
    典型应用
  • 实时监控信号变化(如音频采集)。
  • 单通道连续采样示例:
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);  // 启动后自动循环采样
    

  • 3. 组合使用场景

    模式对比
    模式组合 行为 适用场景
    扫描+单次转换 按顺序采样所有通道后停止 定时触发多通道采样(如每1秒1次)
    扫描+连续转换 循环采样所有通道,数据持续更新 实时多通道监测(如电机控制)
    单通道+连续转换 重复采样同一通道 高速信号捕获(如示波器)
    代码示例(扫描+连续转换)
    // 初始化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. 数据覆盖问题

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

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

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

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

    5. 常见问题解答

    Q1:扫描模式必须用DMA吗?
    A1:不是必须,但强烈推荐。DMA可避免数据丢失,若用中断读取,需确保处理速度够快。

    Q2:如何暂停连续转换?
    A2:调用ADC_ContinuousConvModeCmd(ADC1, DISABLE),或直接关闭ADC:

    ADC_Cmd(ADC1, DISABLE);  // 完全停止ADC
    

    Q3:规则组和注入组能同时用扫描模式吗?
    A3:可以!规则组和注入组的扫描相互独立,但注入组会打断规则组当前转换。


    总结

  • 扫描模式:解决多通道自动切换问题,需注意数据存储。
  • 连续转换:实现无缝采样,适合实时性要求高的场景。
  • 组合使用:灵活应对复杂需求(如实时监测多个传感器)。
  • 通过合理配置这两种模式,可以充分发挥STM32 ADC的性能。

    在STM32的ADC模块中,**连续转换模式(Continuous Conversion Mode)单次转换模式(Single Conversion Mode)**的选择直接影响系统的实时性、功耗和采样精度。以下是针对您提到的两点的详细解释:


    1. 连续转换模式 + 硬件触发的精确控制

    工作原理
  • 连续转换模式:ADC完成一次转换后,立即自动启动下一次转换,形成不间断的采样循环
  • 硬件触发(如定时器TIM2):通过外部信号(如定时器更新事件)控制ADC的启动时机,实现固定间隔采样。
  • 为何能精确控制采样间隔?
  • 硬件同步:定时器的时钟源稳定(如内部72MHz晶振),触发信号的间隔误差极小(纳秒级)。
  • 规避软件延迟:相比软件触发(需CPU干预),硬件触发完全由外设自动完成,无调度延迟。
  • 配置示例
    // 配置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)配置代码
    // 配置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传感器)。
  • 信号变化缓慢(如温度、湿度监测)。
  • 折中方案
  • 动态切换模式:根据任务需求灵活调整。
    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)。
  • // 配置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配置
  • 设置为硬件触发模式,选择定时器作为触发源。
  • 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)启用定时器中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);  // 允许更新中断
    NVIC_EnableIRQ(TIM2_IRQn);                 // 启用NVIC中断
    
    (2)中断服务函数示例
    void TIM2_IRQHandler(void) {
        if (TIM_GetITStatus(TIM2, TIM_IT_Update)) {
            // 此处可添加其他任务(如读取ADC数据、控制逻辑)
            // 注意:ADC采样已由硬件自动触发,无需在此启动!
            
            TIM_ClearITPendingBit(TIM2, TIM_IT_Update);  // 清除中断标志
        }
    }
    

    3. 完整代码示例(无中断方案)

    目标:用TIM2以1kHz触发ADC采样,DMA传输数据。
    #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自动传输(推荐方式)
  • 配置步骤
    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)中断中读取
  • 配置步骤
    // 启用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)轮询方式(低效,仅用于测试)
    // 主循环中等待转换完成
    while (1) {
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);  // 定时器触发时无需此句
        while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
        adcValue = ADC_GetConversionValue(ADC1);
    }
    

    3. 完整代码示例(定时器触发+DMA)

    目标:TIM2以1kHz触发ADC1采样PA0,DMA自动传输数据。
    #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)。
  • 数据更新:每次触发后数据更新一次。
  • 适用场景
  • 低频采样:如温度监测(每秒几次)。
  • 电池供电设备:需要最小化功耗。
  • 事件触发采样:如按键按下后采集一次数据。
  • 配置代码
    // 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实现无缝数据流。
  • 配置代码
    // 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. 单次转换模式

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

    5. 需要无缝数据流严格实时性时使用。
    6. 注意:在硬件触发下,连续转换模式的实际采样率仍由定时器决定,但ADC核心会保持活跃状态。

    4. 常见问题解答

    Q1:连续转换模式下,定时器触发还有意义吗?
  • 有意义!连续转换模式只是保持ADC核心处于工作状态,但每次转换的启动时刻仍由定时器精确控制。
  • 若禁用硬件触发(如用软件触发),ADC会以最大速度连续采样(不推荐,可能丢失时序精度)。
  • Q2:单次模式是否会漏掉定时器触发信号?
  • 不会。每次定时器触发都会启动一次新的转换,即使前一次转换已完成。
  • 但需确保两次触发的间隔大于单次转换的总时间(采样时间 + 12.5周期)。
  • Q3:如何动态切换模式?
    // 运行时切换为单次模式
    ADC_ContinuousConvModeCmd(ADC1, DISABLE);
    
    // 运行时切换为连续模式
    ADC_ContinuousConvModeCmd(ADC1, ENABLE);
    

    5. 完整示例(单次转换 + 定时器触发 + DMA)

    #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。
  • 作者:happygrilclh

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 ADC模块采样时机与转换时间详解:获取数据的精准时机解析

    发表回复