STM32HAL——ADC+中断+DMA

一、STM32 中的 ADC 简介

STM32 微控制器配备了强大的 ADC 模块,可将模拟信号转换为数字信号。ADC 对于采集来自各种传感器(如温度传感器、压力传感器、光传感器等)的模拟数据非常有用。通过 ADC,我们可以将这些模拟信号量化为数字值,以便微控制器进行处理和分析。

二、中断和 DMA 简介

  1. 中断

    • 中断是一种机制,允许微控制器在完成特定任务或发生特定事件时暂停当前执行的任务,转而执行相应的中断服务程序(ISR)。在 ADC 中,我们可以使用中断来通知微控制器 ADC 转换完成,以便及时处理转换结果。
  2. DMA

    • DMA 是一种无需 CPU 干预即可将数据从一个地址空间复制到另一个地址空间的技术。在 ADC 转换中,使用 DMA 可以将 ADC 的转换结果直接存储到内存中,而无需 CPU 逐次读取,大大减轻了 CPU 的负担,提高了系统的效率。

三、使用 STM32 HAL 库实现 ADC + 中断 + DMA 的步骤

  1. 配置 ADC 模块

    • 首先,使用 HAL 库的 ADC_Init 函数配置 ADC 的基本参数,包括分辨率、采样时间、转换模式等。
    • 可以选择不同的 ADC 通道,根据需要选择单通道或多通道模式。对于多通道模式,需要配置扫描模式。
  2. 配置 DMA 模块

    • 配置 DMA 通道,使用 DMA_Init 函数设置 DMA 的源地址(ADC 的数据寄存器)和目的地址(内存缓冲区)。
    • 配置 DMA 的传输方向、数据宽度、传输模式等。对于 ADC,通常使用循环模式,这样可以连续进行数据传输。
  3. 配置中断

    • 在 ADC 中,我们可以配置转换完成中断。通过 HAL_ADC_Start_IT 函数启动 ADC 并启用中断。
    • 在 NVIC(Nested Vectored Interrupt Controller)中使能相应的 ADC 中断通道,确保中断请求能够被正确处理。
  4. 编写中断服务程序

    • 当 ADC 转换完成时,会触发中断服务程序。在中断服务程序中,我们可以调用 HAL_ADC_ConvCpltCallback 函数进行数据处理。
    • 对于 DMA 方式,通常在中断服务程序中进行后续的数据处理,例如对存储在内存缓冲区中的数据进行滤波、计算平均值等。

四、代码示例

#include "stm32f4xx_hal.h"

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
uint16_t adc_buffer[100]; // 存储 ADC 转换结果的缓冲区

// 系统时钟配置
void SystemClock_Config(void);
// ADC 初始化
static void MX_ADC1_Init(void);
// DMA 初始化
static void MX_DMA_Init(void);


int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_DMA_Init();
    MX_ADC1_Init();

    // 启动 ADC 转换
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 100);

    while (1)
    {
        // 主循环可以进行其他操作,例如处理 ADC 数据或控制其他外设
        HAL_Delay(100);
    }
}


// 系统时钟配置
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
    RCC_OscInitStruct.PLL.PLLM = 16;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
    RCC_OscInitStruct.PLL.PLLQ = 4;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK)
    {
        Error_Handler();
    }
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APCLK1Divider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APCLK2Divider = RCC_HCLK_DIV2;
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5)!= HAL_OK)
    {
        Error_Handler();
    }
}


// ADC 初始化
static void MX_ADC1_Init(void)
{
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ENABLE; // 启用扫描模式,用于多通道转换
    hadc1.Init.ContinuousConvMode = ENABLE; // 连续转换模式
    hadc1.Init.DiscreteConvMode = DISABLE;
    hadc1.Init.NbrOfDiscConversion = 1;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 3; // 转换通道数量
    hadc1.Init.DMAContinuousRequests = ENABLE; // 启用 DMA 连续请求
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    if (HAL_ADC_Init(&hadc1)!= HAL_OK)
    {
        Error_Handler();
    }

    // 配置 ADC 通道
    ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel = ADC_CHANNEL_0; // 通道 0
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLINGTIME_3CYCLES;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig)!= HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_1; // 通道 1
    sConfig.Rank = 2;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig)!= HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_2; // 通道 2
    sConfig.Rank = 3;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig)!= HAL_OK)
    {
        Error_Handler();
    }
}


// DMA 初始化
static void MX_DMA_Init(void)
{
    __HAL_RCC_DMA2_CLK_ENABLE();

    hdma_adc1.Instance = DMA2_Stream0;
    hdma_adc1.Init.Channel = DMA_CHANNEL_0;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PIPH_INC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlign = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlign = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR; // 循环模式
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    hdma_adc1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_adc1.Init.MemBurst = DMA_MBURST_SINGLE;
    hdma_adc1.Init.PeriphBurst = DMA_PBURST_SINGLE;
    if (HAL_DMA_Init(&hdma_adc1)!= HAL_OK)
    {
        Error_Handler();
    }

    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}


// 错误处理函数
void Error_Handler(void)
{
    while(1);
}


// ADC 中断服务程序
void ADC_IRQHandler(void)
{
    HAL_ADC_IRQHandler(&hadc1);
}


// ADC 转换完成回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (hadc == &hadc1) {
        // 在这里处理 ADC 转换结果
        // 例如,计算平均值或进行数据滤波
        uint32_t sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += adc_buffer[i];
        }
        uint32_t average = sum / 100;
        // 可以将平均值用于其他操作,如控制输出或发送数据
    }
}

代码解释

  • #include "stm32f4xx_hal.h" : 包含 STM32F4 的 HAL 库头文件,提供了所需的函数和结构体定义。
  • ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; : 分别定义 ADC 和 DMA 的句柄,存储它们的配置信息。
  • uint16_t adc_buffer[100]; : 存储 ADC 转换结果的缓冲区。
  • SystemClock_Config() : 配置 STM32 的系统时钟,使用 HSI 作为时钟源并通过 PLL 进行倍频。
  • MX_ADC1_Init() :
    • 配置 ADC1 的参数,包括分辨率、扫描模式、连续转换模式等。
    • 启用多通道转换(这里使用了通道 0、1、2),设置每个通道的采样时间。
    • 启用 DMA 连续请求,以便 DMA 可以连续将数据从 ADC 转移到内存。
  • MX_DMA_Init() :
    • 初始化 DMA2_Stream0,设置源地址为 ADC1 的数据寄存器,目的地址为 adc_buffer
    • 配置为循环模式,以便连续进行数据传输。
    • 使用 __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); 将 DMA 与 ADC 关联起来。
  • ADC_IRQHandler() : ADC 的中断服务程序,调用 HAL_ADC_IRQHandler 函数处理中断。
  • HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) : ADC 转换完成回调函数,计算存储在 adc_buffer 中的数据平均值。
  • main() :
    • 进行系统初始化,包括系统时钟、DMA 和 ADC 的初始化。
    • 使用 HAL_ADC_Start_DMA 启动 ADC 转换,数据将通过 DMA 存储到 adc_buffer 中。
    • 主循环中可以进行其他操作,这里使用 HAL_Delay 进行简单的延时。

五、总结

使用 STM32 HAL 库实现 ADC + 中断 + DMA 可以高效地进行模拟信号的采集和处理。通过将 ADC 转换结果直接存储到内存缓冲区,减少了 CPU 的干预,提高了系统的响应速度和处理能力。中断服务程序允许我们在 ADC 转换完成后及时处理数据,确保数据的实时性和可靠性。

在实际应用中,根据不同的传感器和系统需求,可以灵活调整 ADC 的通道配置、采样时间、分辨率等参数。此外,还可以对数据处理部分进行扩展,例如使用更复杂的滤波算法或数据融合技术,以提高测量的精度和系统的性能。

请确保在使用该代码时,根据你的 STM32 具体型号和硬件连接进行相应的修改。通过合理利用这些技术,可以为各种嵌入式系统开发提供强大的模拟信号采集和处理功能,例如环境监测系统、工业自动化控制等。

✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进

❤欢迎关注我的知乎:对error视而不见

代码获取、问题探讨及文章转载可私信。

☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。

🍎获取更多嵌入式资料可点击链接进群领取,谢谢支持!👇

点击领取更多详细资料