【STM32学习笔记】ADC用法总结:轮询模式、DMA模式、扫描模式以及连续转换模式

【STM32学习笔记】ADC用法总结:轮询模式、DMA模式、扫描模式以及连续转换模式

一、ADC简介

ADC(Analog-Digital Converter)模拟-数字转换器,可以将引脚上的连续变化的模拟电压转换为数字变量,建立模拟电路到数字电路的桥梁。

12 位 ADC 是逐次趋近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部 源、两个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续 采样模式下进行。ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。(以STM32F407系列)

二、ADC框图

image-20250616223135238

ADC的框图还是很好理解的,这里不做详细介绍。

三、ADC实验代码解析

实验一:轮询单次采集

1、实验描述

使用STM32F407ZGTx的ADC1的5通道(IN5),使用轮询的方式测量输入的电压值,并通过串口发送出来

2、配置CubeMX
2.1选择芯片

image-20250611215132973

2.2配置调试接口

image-20250611215257216

2.3选择外部高速时钟

image-20250611215401210

2.4配置时钟

image-20250611215530807

2.5设置ADC

image-20250611220815182

2.6开启串口,发送测量值

image-20250611221304815

注:有关串口更详细的介绍,请见:【STM32学习笔记】串口总结:串口轮询模式、串口中断模式、串口DMA模式以及串口接收不定长数据

2.7配置路径

image-20250611221816005

image-20250611221607336

2.8配置debuge

image-20250611230319042

image-20250611230413192

image-20250611230432255

3、逻辑代码

定义变量:

/* USER CODE BEGIN PV */
uint32_t ver = 0;
float voltage;
char message[20] = "";

/* USER CODE END PV */

在main函数中启动规则序列,等待转换完成,获取转换结果,并将结果转换为电压:

while (1)
  {
		//启动规则序列
		HAL_ADC_Start(&hadc1);
		//轮询查看等待转换完成
		HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
		//获取转换结果
		ver = HAL_ADC_GetValue(&hadc1);
		//将结果转换为电压
		voltage = ver * 3.3f/4095.0f;
		
		sprintf(message, "ADC: %d %.2f V", ver, voltage);
		
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
		
		HAL_Delay(500);
		
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

4、实验结果:

image-20250614225341765

实验二:采集内置温度传感器的温度值

1、实验描述

与实验一类似,该实验采集内置温度传感器的温度值

2、配置CubeMX

只需将ADC1的通道配置为内部温度通道,其余配置与实验一相同

image-20250614225905206

3、逻辑代码:

while (1)
  {
		uint32_t ver;
				//启动规则序列
		HAL_ADC_Start(&hadc1);
		//等待转换完成
		HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
		//获取转换结果
		
		ver = HAL_ADC_GetValue(&hadc1);
		//将结果转换为电压
		voltage = ver * 3.3f/4095.0f;
    	//将电压值转换为温度
		temperate = (voltage-0.76f)/0.0025f + 25.0f;
		
		sprintf(message, "ADC: %d %.2fV", ver, temperate);
		
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
		
		HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

4、实验结果

image-20250614230247457

实验三:ADC连续转换模式

1、实验描述

使用ADC的连续转换模式,可以随时采集外部电压

2、配置CubeMX

在实验一的基础上启动连续转换即可

image-20250614232604523

3、逻辑代码

因为开启了连续转换模式,启动函数HAL_ADC_Start就不能放在while循环里了

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(&hadc1);
	//等待转换完成
	HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		//获取转换结果
		ver = HAL_ADC_GetValue(&hadc1);
		//将结果转换为电压
		voltage = ver * 3.3f/4095.0f;
		
		sprintf(message, "ADC: %d %.2f V", ver, voltage);
		
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
		
		HAL_Delay(500);
		
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

4、实验结果

image-20250614233709476

实验四:多通道扫描模式

1、实验描述

多通道扫描模式,结合DMA运输数据

2、配置CubeMX

image-20250614235807329

image-20250614235849004

image-20250614235923105

3、逻辑代码
3.1代码分析

ADC的DMA启动函数为:

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
    /*
    1、参数hadc:ADC句柄的指针
    2、参数pData:指向接收缓冲区的指针
    3、参数Length:要接收数据的数量
    */

进入该函数定义,发现设置了中断回调

image-20250615002633471

点击进入ADC_DMAConvCplt,即DMA传输完成中断,其调用HAL_ADC_ConvCpltCallback(hadc)中断回调函数。

image-20250615002827710

以上函数细节可自行翻阅源码。

所以使用HAL_ADC_Start_DMA开启ADC转换后,我们可以在DMA传输完成中断回调函数里进行数据处理。

3.2代码展示
/* USER CODE BEGIN PV */
uint16_t ver[2];
float voltage;
char message[20] = "";

/* USER CODE END PV */
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 */
	

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		//¿ªÆôת»»
		HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 2);
		HAL_Delay(500);
			
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
	if(hadc == &hadc1){
		
		sprintf(message, "%d %d", ver[0], ver[1]);
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
	
	}

}

/* USER CODE END 4 */
4、实验结果

从结果可以看出,两个数为一组,依次输出,ver[0]为输出电压的转换值,ver[1]为输出内部温度的转换值。

image-20250615003852379

5、实验变式1

将DMA接收数组的大小由2变为4,即ver[2]改为ver[4],即接收到4个数据进入传输完成中断函数HAL_ADC_ConvCpltCallback

那么用一维数组接收数据,接收的数据与采样次数以及转化通道是怎么对应的呢?对应关系如下:

采样0-通道0采样0-通道1采样1-通道0采样1-通道1采样2-通道0采样2-通道1
代码修改:
/* USER CODE BEGIN PV */
uint16_t ver[4];
float voltage;
char message[20] = "";

/* USER CODE END PV */
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 */
	

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		//¿ªÆôת»»
		HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 4);
		HAL_Delay(500);
			
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
	if(hadc == &hadc1){
		
		sprintf(message, "%d %d %d %d", ver[0], ver[1], ver[2], ver[3]);
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
	
	}

}

/* USER CODE END 4 */
输出结果:

image-20250615004941579

从结果可以看出,四个数为一组,依次输出,ver[0]为输出电压的转换值,ver[1]为输出内部温度的转换值,为第一次规则组转换结果;ver[2]为输出电压的转换值,ver[3]为输出内部温度的转换值,为第二次规则组转换结果。

6、实验变式2

将DMA接收数组由一维变为二维,即ver[4]改为ver[ADC_Length] [2],即接收完二维数组的所有数据就会进入传输完成中断回调函数HAL_ADC_ConvCpltCallback;那么用二维数组接收数据,接收的数据与采样次数以及转化通道是怎么对应的呢?对应关系如下:

DMA将ADC数据寄存器(如ADC1->DR)的值按顺序复制到ver中

采样0-通道0采样0-通道1
采样1-通道0采样1-通道1
采样2-通道0采样2-通道1

二维数组的本质:内存中是连续存储的,等同于ver[ADC_Length*2]

这样做的好处是将通道0的采集数据放在一列,通道1的数据放在一列,在ADC_Length次采集后,处理数据时相较于一维数组更容易处理。

代码修改:
/* USER CODE BEGIN PV */
uint16_t ver[3][2];
float voltage;
char message[20] = "";

/* USER CODE END PV */
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 */
	

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		//¿ªÆôת»»
		HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 6);
		HAL_Delay(500);
			
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
	if(hadc == &hadc1){
		
		sprintf(message, "%d %d %d %d %d %d", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
	
	}

}

/* USER CODE END 4 */
输出结果:

image-20250615011747882

实验五:多通道连续转换

1、实验描述

在实验四的基础上,开启ADC多通道扫描模式+连续转换模式

2、配置CubeMX

打开连续转换模式后,相应的DMA设置也应该打开循环模式

image-20250615150806609

image-20250615150829137

3、逻辑代码

因为开启了连续转换模式,启动函数就不能放在while循环里了,同时也不需要在中断回调函数里处理数据。可以随时处理数据

/* USER CODE BEGIN PV */
uint16_t ver[3][2];
float voltage;
char message[30] = "";

/* USER CODE END PV */
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 */
	//¿ªÆôÁ¬Ðø×ª»»ºó£¬Æô¶¯º¯Êý²»ÄÜ·ÅÔÚwhileÑ­»·ÁË
	HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 6);
	

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		//不需要在中断函数里处理数据了
		sprintf(message, "%d %d %d %d %d %d\r\n", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
		HAL_Delay(500);
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
4、实验结果

image-20250615174806186

实验六:定时器触发ADC

1、实验描述

以上5个实验都是使用软件启动,此实验我们采用定时器触发ADC的方式,1s触发一次,基于实验5的双通道扫描模式

2、配置CubeMX

使用tim2,查阅手册,挂载在APB1上,84M

image-20250615195637930

配置好TIM2为1s

image-20250615195707725

开启定时器2的中断

image-20250615182125816

3、逻辑代码
/* USER CODE BEGIN PV */
uint16_t ver[3][2];
float voltage;
char message[30] = "";

/* USER CODE END PV */
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();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
	
	//开启定时器
	HAL_TIM_Base_Start_IT(&htim2);
	//kai
	HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 6);
	

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		
//		sprintf(message, "%d %d %d %d %d %d\r\n", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
//		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	if(htim == &htim2){
		sprintf(message, "%d %d %d %d %d %d\r\n", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
		HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
	
	}

}

/* USER CODE END 4 */
4、实验结果

image-20250615200127569

5、注:

该实验使用轮询方式,在while循环里使用HAL_ADC_GetValue(&hadc1)获取ADC的值,可以更充分地看到1秒出一个数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值