STM32 HAL库 ADC+TIM+DMA 3路 1S采样一次电压

一、引言

在很多嵌入式系统应用中,需要对多路模拟信号进行周期性采样,例如在工业控制、环境监测等领域。STM32F407 是一款高性能的微控制器,其丰富的外设资源可以方便地实现这样的功能。通过结合 ADC(模拟 - 数字转换器)、TIM(定时器)和 DMA(直接内存访问),可以高效、稳定地完成多路模拟信号的周期性采样。

二、硬件连接

在使用 STM32F407 进行 ADC 采样时,需要将外部模拟信号连接到对应的 ADC 通道引脚。以下是一个常见的硬件连接示例:

信号 STM32F407 引脚 ADC 通道
模拟信号 1 PA0 ADC1 通道 0
模拟信号 2 PA1 ADC1 通道 1
模拟信号 3 PA2 ADC1 通道 2

三、原理分析

1. ADC(模拟 - 数字转换器)

ADC 的作用是将模拟信号转换为数字信号。STM32F407 的 ADC 具有 12 位分辨率,可对输入的模拟电压进行转换,转换结果范围为 0 - 4095(2^12 - 1)。在本应用中,我们使用 ADC1 来对 3 路模拟信号进行采样。

2. TIM(定时器)

定时器用于产生周期性的触发信号,控制 ADC 的采样频率。在本应用中,我们希望每 1S 采样一次,因此需要配置定时器的周期为 1S。

3. DMA(直接内存访问)

DMA 可以在不占用 CPU 的情况下,将 ADC 转换结果直接传输到内存中。这样可以提高系统的效率,避免 CPU 频繁参与数据传输。

四、代码实现

1. 初始化代码
复制代码
#include "stm32f4xx_hal.h"

// 定义全局变量
// hadc1 用于存储 ADC1 的句柄,通过这个句柄可以操作 ADC1 外设
ADC_HandleTypeDef hadc1;
// hdma_adc1 用于存储 DMA2 流 0 的句柄,用于控制 ADC1 数据的 DMA 传输
DMA_HandleTypeDef hdma_adc1;
// htim2 用于存储定时器 2 的句柄,用于产生 ADC 转换的触发信号
TIM_HandleTypeDef htim2;

// 采样数据数组,用于存储 ADC 转换后的 3 路数据
uint16_t adc_values[3];

// 定时器初始化函数,用于配置定时器 2 产生 1S 的定时信号
void TIM2_Init(void)
{
    // 定义定时器时钟源配置结构体,用于配置定时器的时钟源
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    // 定义定时器主模式配置结构体,用于配置定时器的主模式输出触发等
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    // 指定定时器 2 作为操作对象
    htim2.Instance = TIM2;
    // 定时器预分频器,将定时器时钟频率进行分频
    // 8400 - 1 表示将定时器时钟频率分频为原来的 1/8400
    htim2.Init.Prescaler = 8400 - 1; 
    // 定时器计数模式设置为向上计数,即从 0 开始递增计数
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    // 定时器周期,当计数器计数到这个值时产生更新事件
    // 10000 - 1 结合预分频器实现 1S 的定时
    htim2.Init.Period = 10000 - 1; 
    // 定时器时钟分频,这里设置为不分频
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    // 自动重载预装载功能禁用
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    // 调用 HAL 库函数初始化定时器 2,如果初始化失败则调用错误处理函数
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
    {
        Error_Handler();
    }
    // 配置定时器的时钟源为内部时钟
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    // 应用时钟源配置,如果配置失败则调用错误处理函数
    if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }
    // 配置定时器的主模式输出触发为更新事件,即定时器更新时产生触发信号
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    // 禁用主从模式
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    // 应用主模式配置,如果配置失败则调用错误处理函数
    if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

// DMA 初始化函数,用于配置 DMA2 流 0 进行 ADC 数据的传输
void DMA_Init(void)
{
    // 使能 DMA2 时钟,为 DMA2 外设提供时钟信号
    __HAL_RCC_DMA2_CLK_ENABLE();

    // 指定 DMA2 流 0 作为操作对象
    hdma_adc1.Instance = DMA2_Stream0;
    // 指定 DMA 通道为通道 0
    hdma_adc1.Init.Channel = DMA_CHANNEL_0;
    // 设置 DMA 传输方向为从外设(ADC)到内存
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    // 外设地址不递增,因为 ADC 数据寄存器地址固定
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    // 内存地址递增,因为要将数据依次存储到数组中
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    // 外设数据对齐方式为半字(16 位)
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    // 内存数据对齐方式为半字(16 位)
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    // 设置 DMA 工作模式为循环模式,数据传输完成后自动重新开始
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    // 设置 DMA 传输优先级为低
    hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
    // 禁用 DMA FIFO 模式
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    // 调用 HAL 库函数初始化 DMA,如果初始化失败则调用错误处理函数
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
        Error_Handler();
    }

    // 将 DMA 句柄与 ADC 句柄关联起来,使 DMA 可以为 ADC 服务
    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}

// ADC 初始化函数,用于配置 ADC1 进行 3 路模拟信号的转换
void ADC1_Init(void)
{
    // 定义 ADC 通道配置结构体,用于配置 ADC 通道的参数
    ADC_ChannelConfTypeDef sConfig = {0};

    // 使能 ADC1 时钟,为 ADC1 外设提供时钟信号
    __HAL_RCC_ADC1_CLK_ENABLE();

    // 指定 ADC1 作为操作对象
    hadc1.Instance = ADC1;
    // ADC 时钟预分频,将 ADC 时钟频率进行分频
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    // 设置 ADC 分辨率为 12 位
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    // 使能 ADC 扫描模式,用于多通道转换
    hadc1.Init.ScanConvMode = ENABLE;
    // 禁用连续转换模式,每次转换完成后等待下一次触发
    hadc1.Init.ContinuousConvMode = DISABLE;
    // 禁用不连续转换模式
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    // 设置外部触发转换的边沿为上升沿
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
    // 设置外部触发源为定时器 2 的 TRGO 信号
    hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
    // 设置 ADC 数据对齐方式为右对齐
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    // 设置要转换的通道数量为 3
    hadc1.Init.NbrOfConversion = 3;
    // 使能 DMA 连续请求模式,确保 DMA 可以持续传输数据
    hadc1.Init.DMAContinuousRequests = ENABLE;
    // 设置 EOC 选择为序列转换结束标志
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    // 调用 HAL 库函数初始化 ADC1,如果初始化失败则调用错误处理函数
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    // 配置 ADC 通道 0
    sConfig.Channel = ADC_CHANNEL_0;
    // 设置通道 0 的转换顺序为第 1 个
    sConfig.Rank = 1;
    // 设置通道 0 的采样时间为 3 个 ADC 时钟周期
    sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
    // 应用通道 0 的配置,如果配置失败则调用错误处理函数
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }

    // 配置 ADC 通道 1
    sConfig.Channel = ADC_CHANNEL_1;
    // 设置通道 1 的转换顺序为第 2 个
    sConfig.Rank = 2;
    // 应用通道 1 的配置,如果配置失败则调用错误处理函数
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }

    // 配置 ADC 通道 2
    sConfig.Channel = ADC_CHANNEL_2;
    // 设置通道 2 的转换顺序为第 3 个
    sConfig.Rank = 3;
    // 应用通道 2 的配置,如果配置失败则调用错误处理函数
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

// GPIO 初始化函数,用于配置 GPIO 引脚作为 ADC 的模拟输入引脚
void GPIO_Init(void)
{
    // 使能 GPIOA 时钟,为 GPIOA 外设提供时钟信号
    __HAL_RCC_GPIOA_CLK_ENABLE();

    // 定义 GPIO 初始化结构体,用于配置 GPIO 引脚的参数
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    // 指定要配置的引脚为 PA0、PA1 和 PA2
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;
    // 设置引脚模式为模拟输入模式
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    // 不使用上拉或下拉电阻
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    // 应用 GPIO 引脚配置
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 系统初始化函数,用于初始化整个系统的外设
void System_Init(void)
{
    // 初始化 HAL 库
    HAL_Init();
    // 初始化 GPIO 引脚
    GPIO_Init();
    // 初始化定时器 2
    TIM2_Init();
    // 初始化 DMA2 流 0
    DMA_Init();
    // 初始化 ADC1
    ADC1_Init();
}
2. 主函数
复制代码
int main(void)
{
    System_Init();

    // 启动定时器
    HAL_TIM_Base_Start(&htim2);

    // 启动ADC DMA传输
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_values, 3);

    while (1)
    {
        // 可以在这里处理采样数据
        // 例如将采样数据通过串口发送出去
    }
}
3. 错误处理函数
复制代码
void Error_Handler(void)
{
    while (1)
    {
        // 可以在这里添加错误处理代码,例如点亮LED灯提示错误
    }
}

五、代码解释

1. 定时器初始化

TIM2_Init函数中,我们配置了定时器 2 的预分频器和周期,使其产生 1S 的定时信号。定时器的更新事件(TRGO)将作为 ADC 的外部触发信号。

2. DMA 初始化

DMA_Init函数中,我们配置了 DMA2 的 Stream0,使其将 ADC 的转换结果从外设(ADC)传输到内存(adc_values数组)。使用循环模式可以实现连续的数据传输。

3. ADC 初始化

ADC1_Init函数中,我们配置了 ADC1 的时钟、分辨率、扫描模式等参数。使用外部触发模式,由定时器 2 的 TRGO 信号触发 ADC 转换。配置了 3 个转换通道,分别对应 PA0、PA1 和 PA2 引脚。

4. GPIO 初始化

GPIO_Init函数中,我们将 PA0、PA1 和 PA2 引脚配置为模拟输入模式,用于连接外部模拟信号。

5. 主函数

main函数中,我们首先调用System_Init函数进行系统初始化。然后启动定时器和 ADC 的 DMA 传输。在主循环中,可以对采样数据进行处理,例如将数据通过串口发送出去。

六、调试与测试

在完成代码编写后,可以使用调试工具(如 ST-Link)对程序进行调试。可以在主循环中添加打印语句,将采样数据通过串口输出到电脑上,观察采样结果是否正确。

七、注意事项

  1. 时钟配置:确保系统时钟和外设时钟配置正确,否则定时器和 ADC 可能无法正常工作。
  2. DMA 配置:DMA 的配置需要与 ADC 的配置相匹配,例如数据对齐方式、传输方向等。
  3. 电源和接地:确保模拟信号的电源和接地良好,避免引入噪声干扰。

八、总结

通过结合 STM32F407 的 ADC、TIM 和 DMA 外设,我们可以高效、稳定地实现 3 路模拟信号的周期性采样。定时器用于控制采样频率,DMA 用于数据传输,提高了系统的效率。在实际应用中,可以根据需要对采样频率、通道数量等参数进行调整。

相关推荐
FreakStudio5 小时前
一文速通 Python 并行计算:07 Python 多线程编程-线程池的使用和多线程的性能评估
python·单片机·嵌入式·多线程·面向对象·并行计算·电子diy
SlientICE8 小时前
TCP是什么?不需要!使用ESP32网络层直接通信!
网络·单片机·网络协议·tcp/ip
BW.SU11 小时前
单片机 + 图像处理芯片 + TFT彩屏 触摸开关控件 v1.2
单片机·人机交互·ra8889·开关控件·触摸屏设计
双叶83611 小时前
(51单片机)点阵屏LED显示图片(点阵屏LED教程)(74Hc595教程)
c语言·开发语言·单片机·嵌入式硬件·51单片机
深圳市青牛科技实业有限公司11 小时前
D3502C:一款高性能降压转换器的技术解析,可替代U3502C采用ESOP8封装
单片机·嵌入式硬件·快充·扫地机器人吸尘·筋膜枪电机·驱动轮电机
顾念`12 小时前
履带小车+六轴机械臂(2)
单片机·嵌入式硬件
leoFY12313 小时前
STM32 BOOT设置,bootloader,死锁使用方法
stm32·单片机·嵌入式硬件
czhaii13 小时前
单片机任意普通IO引脚使用定时器扩展外部中断的巧妙方法
单片机·嵌入式硬件
二块烧肉15 小时前
STM32 认识STM32
stm32·单片机·嵌入式硬件