问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

STM32CubeMX+HAL TIM触发单通道ADC电压采样+DMA传输

创作时间:
作者:
@小白创作中心

STM32CubeMX+HAL TIM触发单通道ADC电压采样+DMA传输

引用
CSDN
1.
https://blog.csdn.net/lxy_11132/article/details/138413844

本文介绍如何使用STM32CubeMX配置STM32F407ZGT6单片机进行ADC电压采样,包括时钟树、定时器、ADC和DMA的配置,以及如何通过中断和DMA实现定时采样和数据传输。

工程架构总体思路

配置时钟树,使得APB1总线频率为42MHz,通过一个输入为APB1的倍频器传给定时器TIM3的时钟频率为84MHz。设置TIM分频系数Prescaler=84-1,自动重装载值ARR=1000-1,使能TIM3中断,使得TIM3发生溢出中断TIM Period Elapsed Interruption的频率为1kHz,即每1ms触发一次定时器溢出中断。

如上图蓝色箭头,该中断信号通过APB1总线传给MPU,MPU保存并暂停当前main函数任务,跳转执行该中断类型对应的中断服务程序,此处将会进入<stm32f4xx_it.c>中的TIM3_IRQHandler()函数,进而进入HAL_TIM_PeriodElapsedCallback(&htim3)函数,这是一个弱定义函数。

如上图绿色箭头,重定义HAL_TIM_PeriodElapsedCallback(&htim3)函数,当TIM3溢出中断事件发生时,启动DMA模式ADC,即执行HAL_ADC_Start_DMA(&hadc1)函数,然后由ADC进行4次连续电压采样。

注意,这里ADC的几个关键参数配置及其原因如下:

  1. Scan Conversion Mode:DISABLE,因为这里只进行是单通道ADC转换;
  2. External Trigger Conversion Source:By Software,也就是需要在程序中自己去启动DMA模式ADC工作,例如此工程中在TIM3发生溢出中断时,通过重定义TIM3_PeriodElapsedCallBack()函数,从软件层面启动ADC工作;当然也可以选择Timer3 Capture Compare Channel1、Trigger Detection on the Rising Edge,这样在捕获到TIM3 Channel1 PWM 上升沿时,启动DMA模式ADC,程序设计思路大同小异。
  3. Continous Conversion Mode:ENABLE。
  4. DMA Continous Request:DISABLE。
  5. DMA mode:Normal模式,不选择Circular模式。

其实我也没太搞懂这三个参数具体含义的差别,在另一篇相似的文章中定时器(PWM输出)触发ADC采样(DMA)——STM32CubeMX,作者第3、5个参数和我的恰好相反,但结果是我们的程序最终都能正常运行。我的大概理解如下:

DMA模式下单通道ADC连续转换模式,配合DMA正常模式,是什么意思?就是在DMA模式下ADC被触发后,执行一轮采样、ADC转换、ADC转换完成事件去触发DMA非循环模式传输,也就是这一次传输完成后传输任务完成,该事件又触发ADC在DMA模式下开始新的一轮同样的工作,直到DMA传输的数据量达到在HAL_ADC_Start_DMA()中设定的传输长度,此处为4,ADC转换任务结束,进入ADC_IRQHandler()中断服务函数;

查阅了书籍《Mastering STM32 - 2nd Edition (Carmine Noviello) 》对这一部分的配置讲解,其对DMA、ADC初始化配置与我的方案一致:

关于DMA模式ADC工作方式的官方介绍:STM32 官方技术文档:Getting started with ADC

4次 “采样随后ADC转换,随后DMA将结果数据从ADC外设寄存器传输到SRAM” 的任务全部完成后,进入ADC_IRQHandler()中断服务函数,进而进入HAL_ADC_ConvCpltCallback(&hadc1)函数,在中断服务函数中:关闭ADC、TIM,,进行数据处理,并将处理结果通过串口发送到上位机。

把上述工程设计转化为实际工程

STM32CubeMX的配置:

(1)RCC配置

(2)时钟树配置

(3)ADC配置

(4)TIM定时器与PWM通道配置

此处的配置选择:

(5)USART串口配置

(6)配置工程,然后Generate Code

Keil MDK用户层Application/User/Core代码:

main.c:

启用中断模式TIM3、启动TIM3 Channel1 PWM输出

/* <main.c> */
/* USER CODE BEGIN PV */
uint16_t ADC_Values[4] = {0};
/* USER CODE END PV */
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
    
    /* 启用中断模式 TIM3 */
    HAL_TIM_Base_Start_IT(&htim3);
    
    /* TIM3 Channel1 开始输出 PWM */
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
    
    /**
        HAL_ADC_Start_DMA()函数的封装太高层次了、太抽象了,其具体功能只能概括为:使能 DMA + 启用 DMA 模式下的 ADC;
        注意"启用"不等于"采样", 具体可查看 ADC 工作原理;
        提示:此时已配置 ADC 为 软件触发 ADC 采样.
    */
    printf("**********By xy Liao**********  \r\n");
  /* USER CODE END 2 */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}  

stm32f4xx_it.c:

完成中断回调函数重定义

/* <stm32f4xx_it.c> */
/* USER CODE BEGIN PV */
extern uint16_t ADC_Values[4];
/* USER CODE END PV */
/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
    /**
        总数据长度为 16bit * 4, 配置的 DMA 传输数据宽度为 HALF_WORD(32 * 0.5 bit);
        故HAL_ADC_Start_DMA 第三个参数————DMA传输长度设置为 4.
    */
    if(htim == &htim3)
    {
        HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_Values, 4);
    }
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if(hadc == &hadc1)
    {
        HAL_ADC_Stop_DMA(&hadc1);
        HAL_TIM_Base_Stop_IT(&htim3);
        float temp = (ADC_Values[0]+ADC_Values[1]+ADC_Values[2]+ADC_Values[3])/4;
        
        printf("ADC Average Result: %.4f  \r\n", temp);
        printf("Actual Voltage: %.4f  \r\n", temp * 3.3 / 4096.0);
        HAL_TIM_Base_Start_IT(&htim3);
    }
}
/* USER CODE END 1 */  

stm32f4xx_hal_msp.c:

完成串口重定向

/* <stm32f4xx_hal_msp.c> */
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
extern UART_HandleTypeDef huart1;
/* USER CODE END PV */
/* USER CODE BEGIN 1 */
/**
  * @brief: 重定向c库函数printf到DEBUG_USARTx
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
 
/**
  * @brief: 重定向c库函数getchar,scanf到DEBUG_USARTx
  */
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}
/* USER CODE END 1 */  

运行效果:

一个BUG:由于115200波特率的printf函数调用的时间量级为0.1-1ms量级,因此在TIM3控制1ms采样周期的条件下,会出现串口调试平台接收一段时间的数据后,发生"串口调试平台 停止响应",因此建议是不要在中断回调函数中使用任何printf、Delay函数,这样非常消耗MCU资源。

参考了以下文章:

STM32CubeMX | HAL库的ADC多通道数据采集(轮训、DMA、DMA+TIM)、读取内部传感器温度

一个严谨的STM32串口DMA发送&接收机制(1.5Mbps波特率)

目前对bug尝试的修复方案:将在中断回调函数中的操作转移到main函数while循环中,将使用printf函数的地方转为sprintf+串口DMA发送,并在发送完成后执行HAL_Delay(50)。或许双缓冲DMA可以实现不用延迟也能使串口调试平台不卡死的效果?等我晚些学习一下双缓冲DMA之后再来这里更新一下它的修复效果。

/* stm32f4xx_it.c */
extern int ADC_ConvEnd;
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if(hadc == &hadc1)
        ADC_ConvEnd = 1;
}
/* main.c */
/* USER CODE BEGIN WHILE */
int ADC_ConvEnd = 0;
int main(void)
{
  /* USER CODE BEGIN 1 */
    char buffer[80];
    uint8_t j = 0;
  /* USER CODE END 1 */
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
    
  HAL_TIM_Base_Start_IT(&htim3);
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        if(ADC_ConvEnd)
        {
            HAL_ADC_Stop_DMA(&hadc1);
            HAL_TIM_Base_Stop_IT(&htim3);
            float temp = (ADC_Values[0]+ADC_Values[1]+ADC_Values[2]+ADC_Values[3])/4;
            
            j = sprintf(buffer, "ADC Average Result: %.4f  \r\n", temp);
            j += sprintf(buffer + j, "Actual Voltage: %.4f  \r\n", temp * 3.3 / 4096.0);
            
            HAL_UART_Transmit_DMA(&huart1, (uint8_t*)buffer, j);
            HAL_TIM_Base_Start_IT(&htim3);
            ADC_ConvEnd = 0;
            HAL_Delay(50);
        }
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}  

修复效果:串口调试平台没有再出现卡死现象

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号