STM32外设AD定时器触发DMA读取模板详解

STM32外设AD-定时器触发 + DMA读取模板

  • 一,方法思路
  • 二,定时器基础与配置
  • 1,定时器时钟源 (Clock Source)
  • 2,预分频器 (Prescaler – PSC)
  • 3,自动重装载寄存器 (Auto-Reload Register – ARR) / 周期 (Period)
  • 4,触发输出 (Trigger Output – TRGO)
  • 三,CubeMX配置
  • 四,代码实现
  • 五,关键优势与注意事项
  • 1,主要优势:
  • 2,注意事项:
  • 一,方法思路

    前面两种方法各有优劣。轮询法效率低,DMA+定时处理法数据处理不够实时。当我们需要以较为固定的频率进行 ADC 采样,并在采集完一批数据块后进行集中处理时,可以结合定时器、DMA 和中断。

    这种方法的思路是:

    1. 定时器 (Timer) 作为"启动信号" (可选,或由软件触发): 可以配置一个定时器以固定频率产生触发信号(TRGO),启动 ADC 转换序列。或者,转换序列由软件直接启动。ADC 配置为外部触发模式(如果使用定时器)或软件触发。
    2. DMA “搬运工”: 配置 DMA 通道,在每次 ADC 转换完成后(或根据触发信号),自动将结果从 ADC 数据寄存器搬运到内存缓冲区 (DMA Buffer)。DMA 通常设置为普通模式 (Normal Mode) 或一次传输后停止。
    3. DMA 中断报告"整批送达": 配置 DMA,使其在完成一次完整的缓冲区传输(填满整个 DMA Buffer)时产生一个传输完成中断 (Transfer Complete Interrupt – TC)。
    4. 中断服务程序 (ISR) “标记完成”: 在 DMA 的 TC 中断服务程序中(通常在 HAL_ADC_ConvCpltCallback 回调中体现):
      4.1 停止 DMA 传输 (HAL_ADC_Stop_DMA)。
      4.2 设置一个标志位 (Flag),通知主循环或后台任务:“一个数据块已采集完成!”
    5. 后台任务处理并重启: 主循环或后台任务检测到标志位后:
      5.1 清空标志位。
      5.2 处理 DMA 缓冲区中的数据(例如,提取、计算、滤波等)。
      5.3 处理完成后,重新启动 ADC 的 DMA 传输 (HAL_ADC_Start_DMA),准备采集下一个数据块。

    类比: 你命令一个机器人 (DMA+ADC) 去收集指定数量 (BUFFER_SIZE) 的样本。机器人收集完毕后,会举起一个牌子 (设置 Flag) 并停下工作 (HAL_ADC_Stop_DMA)。你 (CPU) 看到牌子后,走过去取走机器人收集的所有样本进行处理。处理完后,你再次命令机器人开始新一轮的收集 (HAL_ADC_Start_DMA)。

    这种方式允许在采集间隙处理数据,但处理和重启 DMA 期间可能会丢失连续信号。适用于对数据块进行分析处理,而非严格连续实时处理的场景。

    二,定时器基础与配置

    上面的方法需要定时器精确控制每次数据块采集的启动频率,你需要根据期望的数据块采集间隔来设置定时器的触发频率。如何设置呢?

    1,定时器时钟源 (Clock Source)

    定时器需要一个稳定的时钟源来计数。通常选择内部时钟 (Internal Clock),其频率与 APB 总线时钟相关(例如,如果 APB1 时钟是 72MHz,那么 TIM3 的时钟基频通常也是 72MHz 或其倍频)。

    2,预分频器 (Prescaler – PSC)

    定时器的输入时钟频率可能很高(如 72MHz)。预分频器允许你对输入时钟进行分频,得到一个较低的计数频率 (Counter Clock)。

    Counter Clock = Timer Clock / (Prescaler + 1)

    例如,Timer Clock = 72MHz,Prescaler = 71,则 Counter Clock = 72,000,000 / (71 + 1) = 1,000,000 Hz = 1MHz。这意味着计数器每 1 微秒 (µs) 计一次数。

    3,自动重装载寄存器 (Auto-Reload Register – ARR) / 周期 (Period)

    ARR 决定了计数器从 0 计数到多少时产生一个"溢出"或"更新"事件 (Update Event – UEV),并自动重新从 0 开始计数。这个值决定了更新事件的频率:

    Update Event Frequency = Counter Clock / (ARR + 1)

    继续上面的例子,Counter Clock = 1MHz。如果我们想每 10ms (即 100Hz) 触发一次 ADC 块采集,那么:

    100 Hz = 1,000,000 Hz / (ARR + 1)

    解得 ARR + 1 = 10000,所以 ARR = 9999。

    通过组合 PSC 和 ARR,我们可以精确地配置出所需的触发频率。

    4,触发输出 (Trigger Output – TRGO)

    定时器可以将内部的多种事件(如更新事件 UEV、比较匹配事件等)作为触发信号输出给其他外设(如 ADC、DAC)。我们需要将 TRGO 设置配置为"Update Event",这样每次计数器溢出时,就会产生一个触发信号。

    说明: 此处输入的应为定时器的实际输入时钟频率,通常是经过 APB 总线分频后的频率(例如,若 APB1 时钟为 72MHz 且定时器时钟未被进一步分频,则输入 72),而不是 CPU 的主频(如 180MHz)。请查阅您的 MCU 数据手册和 CubeMX 配置确定正确的定时器时钟源频率。

    三,CubeMX配置

    定时器配置

    基础参数配置


    DMA设置改为自然模式

    开启中断

    总结:
    1,配置 ADC:
    如果使用定时器触发,设置 “External Trigger Conversion Source” 和 “Edge”。
    如果采样多个通道,配置 Scan Conversion Mode 和通道顺序。根据代码 adc_val_buffer[i * 2 + 1] 的用法,似乎配置了至少两个通道进行扫描转换。
    “Continuous Conversion Mode” 应设置为 Disabled。
    配置 DMA (在 ADC 的 DMA Settings 页):
    2,添加 DMA 请求,选择通道。
    设置 Mode 为 Normal。DMA 完成 BUFFER_SIZE 次传输后会自动停止,直到被软件重新启动。
    Peripheral 和 Memory 的 Data Width 通常设置为 Word (32-bit)。
    Memory 地址递增 (Increment Address: Memory)。
    3,启用中断 (在 NVIC Settings 页):
    启用与 ADC 关联的 DMA 通道的中断 (例如 DMA1 Channel1)。
    启用 ADC 全局中断。

    四,代码实现

    // --- 宏定义和外部变量 ---
    #define BUFFER_SIZE 1000        // DMA 缓冲区大小 (总点数)
    
    extern DMA_HandleTypeDef hdma_adc1; // 假设这是 ADC1 对应的 DMA 句柄
    extern ADC_HandleTypeDef hadc1;    // ADC1 句柄
    extern UART_HandleTypeDef huart1; // 用于 my_printf 的 UART 句柄
    
    // --- 全局变量 ---
    uint32_t dac_val_buffer[BUFFER_SIZE / 2]; // 用于存储处理后的 ADC 数据
    __IO uint32_t adc_val_buffer[BUFFER_SIZE]; // DMA 目标缓冲区 (存储原始 ADC 数据)
    
    __IO uint8_t AdcConvEnd = 0;             // ADC 转换完成标志 (一个块完成)
    
    // --- 初始化函数 (在 main 或外设初始化后调用) ---
    void adc_tim_dma_init(void)
    {
        // 启动 ADC 的 DMA 传输,请求 BUFFER_SIZE 个数据点
        // 注意:这里假设 hadc1 已经配置为合适的触发模式 (定时器或软件)
        //       且 DMA 配置为 Normal 模式
        HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_val_buffer, BUFFER_SIZE);
    
        // 显式禁用 DMA 半传输中断 (如果不需要处理半满事件)
        __HAL_DMA_DISABLE_IT(&hdma_adc1, DMA_IT_HT);
    
        // 注意:如果使用定时器触发,需要在此处或之前启动定时器
        HAL_TIM_Base_Start(&htimX); // 替换 htimX 为实际定时器句柄
    }
    
    // --- ADC 转换完成回调函数 (由 DMA TC 中断触发) ---
    // 当 DMA 完成整个缓冲区的传输 (Normal 模式下传输 BUFFER_SIZE 个点) 时触发
    void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
    {
        // 检查是否是由我们关心的 ADC (hadc1) 触发的
        if (hadc->Instance == ADC1) // 或 if(hadc == &hadc1)
        {
            HAL_ADC_Stop_DMA(hadc);
    
            // 设置转换完成标志,通知后台任务数据已准备好
            AdcConvEnd = 1;
        }
    }
    
    // --- 后台处理任务 (在主循环或低优先级任务中调用) ---
    void adc_task(void)
    {
        // 检查转换完成标志
        if (AdcConvEnd)
        {
            // 处理数据: 从原始 ADC 缓冲区提取数据到 dac_val_buffer
            // 示例逻辑:提取扫描转换中第二个通道的数据 (?)
            for(uint16_t i = 0; i < BUFFER_SIZE / 2; i++)
            {
                // 假设 adc_val_buffer[0] 是通道1, adc_val_buffer[1] 是通道2, ...
                dac_val_buffer[i] = adc_val_buffer[i * 2 + 1];
            }
    
            // 打印处理后的数据 (示例)
            for(uint16_t i = 0; i < BUFFER_SIZE / 2; i++)
            {
                // 注意: my_printf 是自定义函数, 需确保其存在且可用
                my_printf(&huart1, "{dac}%d
    ", (int)dac_val_buffer[i]);
            }
    
            // 清理处理后的缓冲区 (可选)
            memset(dac_val_buffer, 0, sizeof(uint32_t) * (BUFFER_SIZE / 2));
    
            // 清除转换完成标志,准备下一次采集
            AdcConvEnd = 0;
    
            // 重新启动 ADC 的 DMA 传输,采集下一个数据块
            // 注意: 需要确保 ADC 状态适合重启 (例如没有错误)
            HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_val_buffer, BUFFER_SIZE);
            // 再次禁用半传输中断 (如果 Start_DMA 会重新启用它)
            __HAL_DMA_DISABLE_IT(&hdma_adc1, DMA_IT_HT);
        }
    }
    

    逻辑分解:
    1,缓冲区定义: adc_val_buffer 用于 DMA 直接写入原始 ADC 数据,dac_val_buffer 用于存储处理后的数据。AdcConvEnd 作为块传输完成的标志。
    2,初始化 (adc_tim_dma_init): 启动 ADC 的 DMA 传输,请求采集 BUFFER_SIZE 个点。禁用半传输中断。注意: 触发 ADC 的定时器(如果使用)也需在此或之前启动。
    3,中断回调 (HAL_ADC_ConvCpltCallback):
    当 DMA 完成 BUFFER_SIZE 次传输后触发。
    设置 AdcConvEnd = 1; 通知后台任务。
    4,后台任务 (adc_task):
    4.1 检查 AdcConvEnd 标志。
    4.2 如果标志为 1:
    处理数据:示例中将 adc_val_buffer 中索引为奇数的元素复制到 dac_val_buffer(这通常意味着提取多通道扫描中的某个特定通道的数据)。
    打印处理结果。
    (可选)清空 dac_val_buffer
    清除 AdcConvEnd 标志。
    重新启动 HAL_ADC_Start_DMA,开始采集下一个数据块。
    再次禁用 HT 中断。

    五,关键优势与注意事项

    1,主要优势:

    1,实现相对简单: 相比 Ping-Pong 缓冲或复杂的环形缓冲区,回调和任务逻辑更直接。
    2,块处理: 适合需要对固定大小数据块进行整体分析或处理的应用(如 FFT 前的数据准备、特定事件后的快照采集)。
    3,低 CPU 占用(采集期间): 数据采集过程由 DMA 完成。

    2,注意事项:

    1,数据丢失: 在 adc_task 处理数据和重新启动 DMA 的期间,ADC 没有在采集数据,会造成数据的不连续性。不适用于需要无缝、连续数据流的应用。
    2,处理时间限制: 后台任务 (adc_task) 处理数据的时间必须小于期望的数据块采集间隔,否则会延迟下一次采集的启动。
    3,DMA 模式: 必须确保 DMA 配置为 Normal 模式,以便在传输完指定数量的点后停止并触发 TC 中断。
    4,多通道数据交错: 如果 ADC 配置为多通道扫描模式,DMA 缓冲区 adc_val_buffer 中将包含所有通道交错的数据。处理时需要根据通道顺序正确提取所需数据(如此示例中提取奇数索引元素)。
    5,重启条件: 在 adc_task 中重新调用 HAL_ADC_Start_DMA 前,应确保 ADC 和 DMA 处于合适的状态(例如,没有发生错误)。

    作者:百里东风

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32外设AD定时器触发DMA读取模板详解

    发表回复