STM32CubeMX系列05——ADC(轮询、中断、DMA)

====>>> 文章汇总(有代码汇总) <<<====

1. 所用硬件

正点原子Mini板,主控 STM32F103RCT6.

用到的外设:

  1. 串口1(PA9、PA10)
  2. 任意几个GPIO口(这里用PA1、PA2、PA3,对应ADC通道1、2、3)。

2. 生成工程

2.1. 创建工程选择主控

在这里插入图片描述

2.2. 系统配置

配置时钟源
在这里插入图片描述
配置debug模式(如果需要ST-Link下载及调试可以勾选)
在这里插入图片描述
配置时钟树(可以直接在HCLK那里输入72,然后敲回车会自动配置)
在这里插入图片描述

注意最后的ADC时钟,时钟频率最大14MHZ,因此这里设置6分频,刚好小于14。

2.3. 配置工程目录

在这里插入图片描述在这里插入图片描述

2.4. 配置用到的外设

串口1配置(用于输出结果)
在这里插入图片描述
在这里插入图片描述

3. ADC配置(四选一)

有如下情况:

  1. 单通道轮询
  2. 单通道中断
  3. 多通道轮询
  4. DMA模式(单通道、多通道都能用)

设置说明:

  • ADC_Settings:
    • Data Alignment:
      • Right alignment :转换结果数据右对齐,一般我们选择右对齐模式。
      • Left alignment 转换结果数据左对齐。
    • Scan Conversion Mode:
      • Disabled 禁止扫描模式。如果是单通道 AD 转换使用 DISABLE。
      • Enabled 开启扫描模式。如果是多通道 AD 转换使用 ENABLE。
    • Continuous Conversion Mode:
      • Disabled 单次转换。转换一次后停止需要手动控制才重新启动转换。
      • Enabled 自动连续转换。
    • DiscontinuousConvMode:
      • Disabled 禁止间断模式。这个在需要考虑功耗问题的产品中很有必要,也就是在某个事件触发下,开启转换。
      • Enabled 开启间断模式。
  • ADC_Regular_ConversionMode:
    • Enable Regular Conversions 是否使能规则转换。
    • Number Of Conversion ADC转换通道数目,有几个写几个就行。
    • External Trigger Conversion Source 外部触发选择。这个有多个选择,一般采用软件触发方式。
  • Rank:
    • Channel ADC 转换通道
    • Sampling Time 采样周期选择,采样周期越短,ADC 转换数据输出周期就越短但数据精度也越低,采样周期越长,ADC 转换数据输出周期就越长同时数据精度越高。
  • ADC_Injected_ConversionMode:
    Enable Injected Conversions 是否使能注入转换。注入通道只有在规则通道存在时才会出现。
  • WatchDog:Enable Analog WatchDog Mode 是否使能模拟看门狗中断。当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。

3.1. 单通道轮询

第一步:配置ADC
在这里插入图片描述
第二步:点击生成代码

第三步:串口重定向,在usart.c中添加如下代码。具体的参考上一篇文章串口使用

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{ 
	x = x;
} 

int fputc(int ch, FILE *f)
{  
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

第四步:编写 main.c 代码 其他的什么都不用改

  while (1)
  {
		// 开启ADC
		HAL_ADC_Start(&hadc1);
		// 开始轮询转换
		HAL_ADC_PollForConversion(&hadc1,100);
		// 存储转换的值
		float value = 0;
		// 查询ADC状态
		uint32_t state = HAL_ADC_GetState(&hadc1);
		if (( state & HAL_ADC_STATE_REG_EOC) == HAL_ADC_STATE_REG_EOC)
		{
			// 获取ADC转换结果
			value = HAL_ADC_GetValue(&hadc1);
			printf("adc value:%f \r\n",value/4096.0*3.3);
		}
		else
		{
			printf("adc state %d \r\n",state);
		}
		// 关闭ADC
		HAL_ADC_Stop(&hadc1);
		HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

效果验证
在这里插入图片描述

单通道轮询 在转换时会阻塞直到转换完成。

3.2. 单通道中断

第一步:配置上:在“单通道轮询”实现配置基础上再打开ADC全局中断。
在这里插入图片描述
第二步:点击生成代码

第三步:串口重定向,在usart.c中添加如下代码。具体的参考上一篇文章串口使用

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{ 
	x = x;
} 

int fputc(int ch, FILE *f)
{  
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

第四步:编写 main.c 代码

生成后查看代码,在stm32f1xx_it.c文件中有 ADC1通道2的中断函数ADC1_2_IRQHandler,这个中断函数又调用了HAL_ADC_IRQHandler(&hadc1);

/**
  * @brief This function handles ADC1 and ADC2 global interrupts.
  */
void ADC1_2_IRQHandler(void)
{
  /* USER CODE BEGIN ADC1_2_IRQn 0 */

  /* USER CODE END ADC1_2_IRQn 0 */
  HAL_ADC_IRQHandler(&hadc1);
  /* USER CODE BEGIN ADC1_2_IRQn 1 */

  /* USER CODE END ADC1_2_IRQn 1 */
}

HAL_ADC_IRQHandler(&hadc1);函数在stm32f1xx_hal_adc.c中,这个函数考虑了很多情况,其中调用了HAL_ADC_ConvCpltCallback(hadc);,还是在同一个文件中,这是一个弱函数。根据翻译,很好理解,我们直接重新定义这个方法即可。

/**
  * @brief  Conversion complete callback in non blocking mode 
  * @param  hadc: ADC handle
  * @retval None
  */
__weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hadc);
  /* NOTE : This function should not be modified. When the callback is needed,
            function HAL_ADC_ConvCpltCallback must be implemented in the user file.
   */
}

main.c

/* USER CODE BEGIN PFP */
// 重定义ADC转换完成回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	if(hadc == &hadc1)
	{
		uint16_t adc_value = HAL_ADC_GetValue(hadc);
		printf("refresh adc value:%f \r\n", adc_value/4096.0*3.3);
		// 重新开启ADC中断
		HAL_ADC_Start_IT(&hadc1);
	}
}
/* USER CODE END PFP */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

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

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  
  /* USER CODE BEGIN WHILE */
	// 开启ADC中断
	HAL_ADC_Start_IT(&hadc1);
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

效果和轮询的一样,不过这个会一直执行,而且很快

实际上,这里设置的是单次转换,所以中断只会触发一次,需要再次使用HAL_ADC_Start_IT开启中断。如果需要实时的转换,可以将转换设为连续模式,这样的话ADC转换器便会实时的持续的进行转换,那将是非常消耗CPU的,以至于main将不能正常执行(采样时间太短的话)。

开启中断后,一般需要实现HAL_ADC_ConvCpltCallback函数,在callback中GetValue,也可以在程序其他地方像轮询那样先判断ADC状态,再GetValue。

3.3. 多通道轮询

第一步:ADC配置
多通道时扫描模式会自动打开。要开启“Discontinuous Conversion Mode”。
在这里插入图片描述
第二步:点击生成代码

第三步:串口重定向,在usart.c中添加如下代码。具体的参考上一篇文章串口使用

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{ 
	x = x;
} 

int fputc(int ch, FILE *f)
{  
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

第四步:编写 main.c 代码

HAL_ADCEx_Calibration_Start(&hadc1);
const uint8_t kNbrOfPin = 3;
  while (1)
  {
	for(int i = 0; i < kNbrOfPin; i++)
	{
		HAL_ADC_Start(&hadc1);
		HAL_ADC_PollForConversion(&hadc1, 100);
		float value = 0;
		uint32_t state = HAL_ADC_GetState(&hadc1);
		if (( state & HAL_ADC_STATE_REG_EOC) == HAL_ADC_STATE_REG_EOC)
		{
			value = HAL_ADC_GetValue(&hadc1);
			printf("adc value [%d]:%f\r\n", i,value/4096.0*3.3);
		}
		else
		{
			printf("adc state[%d]:%d\r\n", i, state);
		}
	}
	HAL_ADC_Stop(&hadc1);
	HAL_Delay(200);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

效果验证
在这里插入图片描述

3.4. DMA模式

第一步:ADC配置
在这里插入图片描述
在这里插入图片描述
第二步:点击生成代码

第三步:串口重定向,在usart.c中添加如下代码。具体的参考上一篇文章串口使用

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{ 
	x = x;
} 

int fputc(int ch, FILE *f)
{  
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

第四步:编写 main.c 代码

/* USER CODE BEGIN PFP */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	if(hadc == &hadc1)
	{
		// 使用DMA其实也会运行到这里,也可以将结果在这里输出。
		// 当然此函数也可以不写。
	}
}
/* USER CODE END PFP */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	uint16_t adc_value[3] = {0};
  /* USER CODE END 1 */

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  
  /* Configure the system clock */
  SystemClock_Config();
  
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  
  /* USER CODE BEGIN WHILE */
	HAL_ADCEx_Calibration_Start(&hadc1);
	// enable DMA通道
	// 参数:ADC1、目标缓冲区地址、从ADC外围设备传输到内存的数据长度
	/*
	 * 此处有个大坑,经过测试,DMA中断非常容易进(具体的不知道)
	 *
	 * 如果ADC采样周期短的话,一直在执行中断,
	 * 导致无法执行主程序,因此会卡死在这个函数里面出不去。
	 *
	 * 因此,ADC的采用周期需要长一点。
	 */
	HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_value, 3);
	
  while (1)
  {
	printf("-------------------- \r\n");
	printf("adc value[0]:%f \r\n", adc_value[0]/4096.0*3.3);
	printf("adc value[1]:%f \r\n", adc_value[1]/4096.0*3.3);
	printf("adc value[2]:%f \r\n", adc_value[2]/4096.0*3.3);
	HAL_Delay(1000);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

注意
用到 DMA 的外设,MX_DMA_Init(); 一定要在外设初始化前面,比如这里的 MX_ADC1_Init(); 。

效果验证
在这里插入图片描述

多通道DMA和单通道DMA配置基本相同,只需注意存储AD转换结果的数组,如果有两个通道,数组长度为2,则每个通道的值分别对应数组的每一位;如果数组长度为2的整数倍,如10,则数组内[0] [2] [4] [6] [8]的值对应其中一个通道的AD值,即存储了连续采集5次的AD值,这样可以用多个AD值求平均值。

  • 5
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
您可以按照以下步骤来实现使用 STM32CubeMX 采集 ADC 数据并通过串口屏显示波形: 1. 打开 STM32CubeMX,选择适合您的 STM32 开发板的型号。 2. 在 "Pinout & Configuration" 选项卡中,配置您的 ADC 引脚。选择合适的引脚作为 ADC 输入,并启用 ADC。 3. 在 "Configuration" 选项卡中,配置 ADC 参数。选择采样时间、分辨率、转换模式等参数。 4. 在 "Peripherals" 选项卡中,找到 USART(串口)并启用它。配置串口的波特率、数据位数、停止位等参数。 5. 在 "Middleware" 选项卡中,启用 "FreeRTOS"(如果您计划使用操作系统)和 "USB_DEVICE"(如果您计划使用 USB 串口)。 6. 生成代码并打开生成的项目。 7. 打开生成的主文件(通常是 main.c),在合适的位置添加 ADC 和串口的初始化代码。 8. 在主循环中进行 ADC 数据采集和串口发送操作。您可以使用 HAL 库提供的函数来实现这些操作。 以下是一个简单的示例代码,用于采集 ADC 数据并通过串口发送波形数据: ```c #include "main.h" #include "stdio.h" ADC_HandleTypeDef hadc1; UART_HandleTypeDef huart2; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART2_UART_Init(void); static void MX_ADC1_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init(); MX_ADC1_Init(); uint32_t adc_value = 0; char uart_buffer[20]; while (1) { // 启动 ADC 转换 HAL_ADC_Start(&hadc1); // 等待转换完成 HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); // 读取 ADCadc_value = HAL_ADC_GetValue(&hadc1); // 停止 ADC 转换 HAL_ADC_Stop(&hadc1); // 将 ADC 值转换为字符串 sprintf(uart_buffer, "%lu\r\n", adc_value); // 发送 ADC 值到串口屏 HAL_UART_Transmit(&huart2, (uint8_t *)uart_buffer, strlen(uart_buffer), HAL_MAX_DELAY); HAL_Delay(10); // 可根据需要调整延时时间 } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_OFF; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; 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.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } sConfig.Channel = ADC_CHANNEL_1; // 根据实际情况选择 ADC 通道 sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } } static void MX_USART2_UART_Init(void) { huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } } void Error_Handler(void) { while (1) { } } void SysTick_Handler(void) { HAL_IncTick(); } void EXTI15_10_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_13) { // 处理外部中断事件 } } ``` 请注意,此示例仅提供了基本的框架,您可能需要根据您的需求进行修改和扩展。同时,您需要根据您的具体硬件和引脚配置进行相应的更改。 希望这对您有帮助!如果您有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值