STM32初学入门笔记(3):使用STM32CubeMX配置多通道ADC DMA读取模拟量

模拟信号的读取是我们在做很多项目是都要用到的,而模拟量的读取就要依赖于ADC数模转换器。对于初学者,学习使用ADC可以很大的帮助以后的STM32学习。

目录

ADC简介 :

DMA简介: 

工程开始:

STM32CubeMX配置区:

配置外部时钟:

配置调试:

配置ADC:

配置DMA:

配置串口:

配置工程文件: 

 KEIL编程:

 开启MicroLIB:

添加库函数: 

串口重定向:

定义变量:

while: 

回调函数:

成果展示: 

总结:


ADC简介 :

        ADC可以将模拟信号转换为数字信号,用于采集和处理模拟信号。ADC在嵌入式系统中应用广泛,应用场景包括但不限于电池电量检测、音频数据采集、波形捕获。

DMA简介: 

      DMA全称Direct Memory Access,即直接存储器访问。

      DMA传输可以将数据从一个地址空间直接复制到另一个地址空间,提供外设与储存器或者储存器之间的高速数据传输。

      DMA传输不需要CPU的参与,可以节省大量CPU资源以使程序更加快速高效。

工程开始:

STM32CubeMX配置区:

      打开STM32CubeMX,新建工程

配置外部时钟:

 

  输入需要的频率,敲击回车,STM32CubeMX会自动配置。

配置调试:

 这里一定要配置好,不然会导致芯片自锁

配置ADC:

 开启ADC后时钟树可能会报错,点进去选择Yes自动配置就好了;

 这里要先更改通道数才可以更改其他的参数;

配置DMA:

 

配置串口:

串口的参数使用默认设置就好了。

配置工程文件: 

 然后点击右上角的GENERATE CODE生成文件,点击打开文件,进入keil中

 KEIL编程:

 开启MicroLIB:

关于MicroLIB大家可以自行查找,在此不过多赘述

添加库函数: 

打开main.c,添加库函数,不加会报错

/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

串口重定向:

/* USER CODE BEGIN PTD */

/*串口重定向*/
int fputc(int ch, FILE *f){
	HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);
	return ch;
}

/* USER CODE END PTD */

定义变量:

/* USER CODE BEGIN 0 */

uint32_t ADC1_1, ADC1_2;   //两个通道的ADC
uint32_t ADC1_Value[10];   //ADC数据存放数组
uint8_t ADC1_Flag;         //ADC采集完毕标志位

/* USER CODE END 0 */

 这里把ADC数据存放数组长度定义为了10,因为有两个通道的ADC,所以每个通道各采样10次,轮询模式交错进行(即[ADC1_0; ADC1_1; ADC1_0; ADC1_1…]),最后两个通道累加后求平均值,实际使用如果需要更高的精度,可以采样更多次后求平均值。

初始化 :

  /* USER CODE BEGIN 2 */
 HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1_Value,10);
  /* USER CODE END 2 */

while: 

在while循环中编写主程序(这里直接把一整个while贴上了)

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  if(ADC1_Flag==1)
	  {
		  ADC1_Flag=0;  //清空标志位
		  ADC1_1=0;
		  ADC1_2=0;
		  for(int i=0;i<10;){
			  ADC1_1+=ADC1_Value[i++];
			  ADC1_2+=ADC1_Value[i++];
				//读取ADC值
		  }
		  printf("\n");
		  printf("ADC_IN0(PA0)=%4.0d,Voltage0=%1.4f\r\n",ADC1_1/5,ADC1_1/5*3.3f/4096);
		  printf("ADC_IN1(PA1)=%4.0d,Voltage1=%1.4f\r\n",ADC1_2/5,ADC1_2/5*3.3f/4096);
			//串口打印
		  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1_Value,10);
	  }
		HAL_Delay(1000);   //延时1s
  }
  /* USER CODE END 3 */

回调函数:

主程序写完后还要在下面的用户代码区写回调函数,不然程序将无法运行

/* USER CODE BEGIN 4 */

/*回调函数*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	if(hadc->Instance == ADC1){
		ADC1_Flag=1;
		HAL_ADC_Stop_DMA(&hadc1);  //关闭DMA
	}
}

/* USER CODE END 4 */

这里加了一句关闭DMA的语句,因为发现实际使用中如果不加会出现数值跑飞的情况 

最后贴上完整的main.c代码: 

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/*串口重定向*/
int fputc(int ch, FILE *f){
	HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);
	return ch;
}

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

uint32_t ADC1_1, ADC1_2;   //两个通道的ADC
uint32_t ADC1_Value[10];   //ADC数据存放数组
uint8_t ADC1_Flag;         //ADC采集完毕标志位

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1_Value,10);    //开启DMA传输

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  if(ADC1_Flag==1)
	  {
		  ADC1_Flag=0;  //清空标志位
		  ADC1_1=0;
		  ADC1_2=0;
		  for(int i=0;i<10;){
			  ADC1_1+=ADC1_Value[i++];
			  ADC1_2+=ADC1_Value[i++];
				//读取ADC值
		  }
		  printf("\n");
		  printf("ADC_IN0(PA0)=%4.0d,Voltage0=%1.4f\r\n",ADC1_1/5,ADC1_1/5*3.3f/4096);
		  printf("ADC_IN1(PA1)=%4.0d,Voltage1=%1.4f\r\n",ADC1_2/5,ADC1_2/5*3.3f/4096);
			//串口打印
		  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1_Value,10);
	  }
		HAL_Delay(1000);   //延时1s
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/*回调函数*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	if(hadc->Instance == ADC1){
		ADC1_Flag=1;
		HAL_ADC_Stop_DMA(&hadc1);  //关闭DMA
	}
}

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

这次的程序没有更改任何的头文件和STM32CubeMX生成的初始化函数,全部在main.c中实现,只要在规定的用户代码区写代码,就不会影响后面再次使用STM32CubeMX添加外设或者做更改

成果展示: 

 编译后下载到MCU中,PA9连接USB转串口模块的RXD,PA10连接TXD(因为串口通信要反接),如果MCU与USB转串口用的不是同一路电源,还要把两电源共地(不要把正极也接了,不然可能会烧)。作为试验,我们把PA0接3.3v,PA1接地,打开串口调试助手应该可以看到上面接收到的信息:

一定要注意,ADC的最大输入电压为3.6V,超过会烧MCU(亲身体会,烧了一个ZET6)

总结:

 此方法可以实现快速读取模拟量的同时占用较少的资源,泛用性比较强。

都看到这里了点点关注再走呗

 交流Q群:659512171

物联沃分享整理
物联沃-IOTWORD物联网 » STM32初学入门笔记(3):使用STM32CubeMX配置多通道ADC DMA读取模拟量

发表评论