STM32学习笔记(14)——ADC初步应用

Posted Mount256

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32学习笔记(14)——ADC初步应用相关的知识,希望对你有一定的参考价值。

一、ADC 中断实验

【实现功能】使用 ADC 的一个通道进行测量,并通过中断读取测量结果。

【基本思路】本次实验我们使用 ADC2 的通道 11,查看电路原理图可知,通道 11 对应的是 PC1 口。

结合之前我们所学的内容,因为使用的是是单通道测量,因此工作模式应选择独立模式;连续扫描模式是用于多通道扫描的,因此不使用;由于需要不停地重复采集,因此应使用连续转换模式;不使用外部触发源,而是使用软件触发方式;最后,测量数据使用右对齐方式,以方便程序读取。

除此之外,我们还需要配置 ADC 转换时钟、通道 11 的转换顺序、采样时间,最后开启中断。别忘了每次测量之前,应该对 ADC 进行一次校准。

每测量完一次,便会产生 EOC 中断标志。在中断服务函数内部,即可读取测量结果,并且记得要清除中断标志位,为下一次中断做准备。

【编程要点】

  • 初始化 GPIO:PC1 引脚;
  • 初始化 ADC 结构体;
  • 配置 NVIC 结构体;
  • 配置 ADC 时钟;
  • 配置 ADC 规则通道(转换顺序+采样时间);
  • 使能 ADC 中断、使能 ADC;
  • 校准 ADC,并等待校准完成;
  • 软件触发 ADC 开始工作;
  • 中断服务函数读取测量结果,并清除中断标志位。

部分代码如下:

1. adc.h

#ifndef __ADC_H
#define __ADC_H

#include "stm32f10x.h"

#define ADC_PORT 			GPIOC
#define ADC_PORT_CLK 		RCC_APB2Periph_GPIOC
#define ADC_PIN 			GPIO_Pin_1

#define ADC_x				ADC2
#define ADC_CLK				RCC_APB2Periph_ADC2
#define ADC_CHANNEL			ADC_Channel_11

#define ADC_IRQ				ADC1_2_IRQn
#define ADC_IRQHandler 		ADC1_2_IRQHandler

void ADC_Init_Config(void);

#endif /* __ADC_H */

2. adc.c

#include "adc.h"

static void ADC_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(ADC_PORT_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AIN;    // 复用输入模式
	GPIO_InitStructure.GPIO_Pin 	= ADC_PIN;
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
	
	GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}

static void ADC_Mode_Config(void)
{
	ADC_InitTypeDef ADC_InitStructure;
	
	RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE);
	ADC_InitStructure.ADC_Mode 				= ADC_Mode_Independent;
	ADC_InitStructure.ADC_ScanConvMode 		= DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ExternalTrigConv 	= ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_DataAlign 		= ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel 		= 1;    // 参与转换的通道数为1
	
	ADC_Init(ADC_x, &ADC_InitStructure);
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);       // f(ADC) = PCLK2 / 8,其中PCLK2 = 72MHz
	
	// 配置ADC规则通道的参数,最后两个参数分别是:规则通道的转换顺序 和 采样时间
	ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
	ADC_ITConfig(ADC_x, ADC_IT_EOC, ENABLE);        // 开启EOC中断
	ADC_Cmd(ADC_x, ENABLE);                         // 使能ADC,但此时未使能触发方式,ADC不工作
	
	ADC_StartCalibration(ADC_x);                    // ADC开始校准
	while(ADC_GetCalibrationStatus(ADC_x) == SET);  // 等待ADC校准完成
	
	ADC_SoftwareStartConvCmd(ADC_x, ENABLE);        // 软件触发ADC使其开始工作
}

static void ADC_NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	
	NVIC_InitStructure.NVIC_IRQChannel 						= ADC_IRQ;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 	= 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority 			= 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd 					= ENABLE;
	
	NVIC_Init(&NVIC_InitStructure);
}

void ADC_Init_Config(void)
{
	ADC_Mode_Config();
	ADC_GPIO_Config();
	ADC_NVIC_Config();
}

3. stm32f10x_it.c

#include "stm32f10x_it.h" 
#include "adc.h"

__IO uint16_t ADC_ConversionValue = 0;

void ADC_IRQHandler(void)
{
	if( ADC_GetITStatus(ADC_x, ADC_IT_EOC) == SET ) // 如果 EOC 中断标志位为 1
	{
		ADC_ConversionValue = ADC_GetConversionValue(ADC_x); // 读取测量数据
	}
	ADC_ClearITPendingBit(ADC_x, ADC_IT_EOC); // 清除 EOC 中断标志位
}

4. main.c

#include "stm32f10x.h"
#include "usart.h"
#include "adc.h"

extern uint16_t ADC_ConversionValue;

void Delay(__IO uint32_t nCount)
{
  for(; nCount != 0; nCount--);
} 

int main(void)
{
	float ADC_Result;
	ADC_Init_Config();
	USART_Config();
	printf("\\r\\n开始测试!!!\\r\\n");
	
	while(1)
	{
		ADC_Result = (float)ADC_ConversionValue / 4096 * 3.30; // 提示:最小分辨率为 3.30V / 2^12
		printf("\\r\\n测试结果:%f V (0x%04X)\\r\\n", ADC_Result, ADC_ConversionValue); // 通过串口显示测量结果
		Delay(0xffffee); 
	}
}

大家只需要用杜邦线将 PC1 引脚与待测量引脚连接在一起就行了,数据会在上位机上显示。

二、ADC_DMA 实验

很多情况下,我们不使用中断读取测量数据,主要原因是从中断发起到响应需要一定的滞后时间,并且频繁的响应会拖慢 CPU 的运行速度。使用 DMA 请求则可以解决这个问题。

以下我们分为两个部分:单通道测量和多通道测量。

1. ADC_DMA 单通道实验

【实现功能】使用 DMA 存储 单通道ADC 测量的数据。继续使用 通道11。

【基本思路】回顾一下我们之前所学的 DMA 知识:STM32 有 2 个 DMA ,DMA1 有 7 个通道,DMA2 有 5 个通道,每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。DMA1 的通道 1 对应的是 ADC1,DMA2 的通道 5 对应的是 ADC3,没有专门的通道对应至 ADC2。 因此我们选择使用 ADC1。

现在来解决一下 DMA 的结构体配置问题:

  • 由于数据来源于 ADC_DR 寄存器,因此外设基地址(PeripheralBaseAddr) 应为 ADC_DR 寄存器;
  • 我们用一个变量来存储测量结果,因此存储基地址(MemoryBaseAddr) 应为该变量地址;
  • DMA 的传输方向显然是从外设到存储(DMA_DIR_PeripheralSRC)
  • 显然,由于我们使用的是单通道 ADC,因此均不开启外设和存储基地址的增量模式(PeripheralInc & MemoryInc)
  • 因为在单通道情况下,只使用 ADC_DR 寄存器的低 16 位,所以外设的数据宽度(PeripheralDataSize)和存储的数据宽度(MemoryDataSize) 均为 16 位,即半字(Half Word);
  • 每次采集传输数据只有 1 个,因此传输数据个数(BufferSize) 为 1;
  • 因为是不断采集,工作模式选择循环模式(DMA_Mode_Circular)

在配置完 DMA 后,需要通过函数 ADC_DMACmd 开启 ADC_DMA 请求。

【编程要点】

  • 初始化 GPIO:PC1 引脚;
  • 初始化 ADC 结构体;
  • 初始化 DMA 结构体;
  • 配置 ADC 时钟;
  • 配置 ADC 规则通道(转换顺序+采样时间);
  • 使能 ADC、使能 ADC_DMA;
  • 校准 ADC,并等待校准完成;
  • 软件触发 ADC 开始工作。

部分代码如下:

(1)adc.h

#ifndef __ADC_H
#define __ADC_H

#include "stm32f10x.h"

#define ADC_PORT 			GPIOC
#define ADC_PORT_CLK 		RCC_APB2Periph_GPIOC
#define ADC_PIN1			GPIO_Pin_1
#define ADC_PIN2 			GPIO_Pin_2
#define ADC_PIN3 			GPIO_Pin_3
#define ADC_PIN4 			GPIO_Pin_4

#define ADC_x				ADC1
#define ADC_CLK				RCC_APB2Periph_ADC1
#define ADC_CHANNEL1		ADC_Channel_11
#define ADC_CHANNEL2		ADC_Channel_12
#define ADC_CHANNEL3		ADC_Channel_13
#define ADC_CHANNEL4		ADC_Channel_14

#define ADC_DMA_CLK 		RCC_AHBPeriph_DMA1
#define ADC_DMA_CHANNEL		DMA1_Channel1

#define ADC_IRQ				ADC1_2_IRQn
#define ADC_IRQHandler 		ADC1_2_IRQHandler

void ADC_Init_Config(void);

#endif /* __ADC_H */

(2)adc.c

#include "adc.h"

__IO uint16_t ADC_ConversionValue = 0;

static void ADC_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(ADC_PORT_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
	
	GPIO_InitStructure.GPIO_Pin = ADC_PIN1;
	GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}

static void ADC_Mode_Config(void)
{
	ADC_InitTypeDef ADC_InitStructure;
	
	RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE);
	ADC_InitStructure.ADC_Mode 				= ADC_Mode_Independent;
	ADC_InitStructure.ADC_ScanConvMode 		= DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ExternalTrigConv 	= ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_DataAlign 		= ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel 		= 1;
	
	ADC_Init(ADC_x, &ADC_InitStructure);
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
	ADC_Cmd(ADC_x, ENABLE);
	ADC_DMACmd(ADC_x, ENABLE);                      // 使能ADC_DMA请求
	
	ADC_StartCalibration(ADC_x);                    // 开始校准
	while(ADC_GetCalibrationStatus(ADC_x) == SET);  // 等待校准完成
	
	ADC_SoftwareStartConvCmd(ADC_x, ENABLE);
}

static void ADC_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) (&(ADC_x->DR)); // 外设基地址为ADC数据寄存器
	DMA_InitStructure.DMA_MemoryBaseAddr 	= (uint32_t) (&ADC_ConversionValue); // 存储器基地址为变量地址
	DMA_InitStructure.DMA_DIR 				= DMA_DIR_PeripheralSRC; // 传输源为外设(数据寄存器)
	
	DMA_InitStructure.DMA_BufferSize 		= 1; // 每次传输1个数据
	DMA_InitStructure.DMA_PeripheralInc 	= DMA_PeripheralInc_Disable; // 不使用增量模式
	DMA_InitStructure.DMA_MemoryInc 		= DMA_MemoryInc_Disable; // 不使用增量模式
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 数据宽度:16位
	DMA_InitStructure.DMA_MemoryDataSize 	= DMA_MemoryDataSize_HalfWord; // 数据宽度:16位
	
	DMA_InitStructure.DMA_Mode 				= DMA_Mode_Circular; // 循环模式
	DMA_InitStructure.DMA_Priority 			= DMA_Priority_High;
	DMA_InitStructure.DMA_M2M 				= DMA_M2M_Disable;
	
	DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
	DMA_Cmd(ADC_DMA_CHANNEL, ENABLE);
}

void ADC_Init_Config(void)
{
	ADC_Mode_Config();
	ADC_GPIO_Config();
	ADC_DMA_Config();
}

2. ADC_DMA 多通道实验

现在我们将上面程序修改一下,变成可以同时测量 4 个通道的功能。需要修改的地方其实很少,其实只要理解了结构体每一项的功能,就知道要修改哪里了。

首先当然需要使能 4 个 GPIO(PC1、PC2、PC3、PC4)了,很简单,这里就不放出代码了。然后需要定义一个数组用于存放 4 个测量结果:

__IO uint16_t ADC_ConversionValue[4] = {0, 0, 0, 0};

(1)ADC 通道数、规则通道的配置修改

ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 多通道扫描,开启连续扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 4; // 使用 4 个通道
// 分别对 4 个规则通道配置转换顺序和采样时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL3, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL4, 4, ADC_SampleTime_55Cycles5);

(2)DMA 部分修改

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (ADC_ConversionValue); // 存储基地址为数组的首地址
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 存储基地址选择为增量模式
DMA_InitStructure.DMA_BufferSize = 4; // 传输 4 个数据

三、双 ADC 规则同步实验

【实现功能】使用 ADC1 和 ADC2(不能使用 ADC3)的同步规则模式,同时测量两个引脚(PC1 和 PC2)的电压值。

【基本思路】回顾一下同步规则模式:ADC1 和 ADC2 同时转换一个规则通道组,其中 ADC1 为主,ADC2 为从,ADC1 转换的结果放在ADC_DR低16位,ADC2 转换的结果放在ADC_DR的高16位。

ADC1 测量 PC1 引脚,ADC2 测量 PC2 引脚,每个 ADC 都是单通道测量。由于 ADC2 没有 DMA 功能,所以不需要对 ADC2 使能 DMA 功能。ADC1 采用软件触发方式,ADC2 采用外部触发方式,触发源是 ADC1。

【编程要点】

  • 初始化 GPIO:PC1 和 PC2 引脚;
  • 初始化 ADC 结构体;
  • 初始化 DMA 结构体;
  • 配置 ADC1/2 时钟;
  • 配置 ADC1/2 规则通道(转换顺序+采样时间);
  • 使能 ADC1/2、使能 ADC1_DMA;
  • 校准 ADC1/2,并等待校准完成;
  • 外部触发 ADC2、软件触发 ADC1 开始工作。

1. adc.h

#ifndef __ADC_H
#define __ADC_H

#include "stm32f10x.h"

/*******双ADC规则同步实验*******/

#define ADC_PORT 			GPIOC
#define ADC_PORT_CLK 		RCC_APB2Periph_GPIOC
#define ADC_PIN1			GPIO_Pin_1
#define ADC_PIN2 			GPIO_Pin_2
#define ADC_PIN3 			GPIO_Pin_3
#define ADC_PIN4 			GPIO_Pin_4

#define ADC_DMA_CLK 		RCC_AHBPeriph_DMA1
#define ADC_DMA_CHANNEL		DMA1_Channel1

void ADC_Init_Config(void);

#endif /* __ADC_H */

2. adc.c

#include "adc.h"

__IO uint32_t ADC_ConversionValue = 0;

static void ADC_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
	
	GPIO_InitStructure.GPIO_Pin 	= GPIO_Pin_1; // PC1
	GPIO_Init(ADC_PORT, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin 	= GPIO_Pin_2; // PC2
	GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}

static void ADC_Mode_Config(void)
{
	ADC_InitTypeDef ADC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	ADC_InitStructure.ADC_Mode 				= ADC_Mode_RegSimult;
	ADC_InitStructure.ADC_ScanConvMode 		= DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ExternalTrigConv 	= ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_DataAlign 		= ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel 		= 1;
	
	ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);
	ADC_Init(ADC1, &ADC_InitStructure);
	ADC_Cmd(ADC1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);
	ADC_StartCalibration(ADC1); 
	while(ADC_GetCalibrationStatus(ADC1) == SET); 

	ADC_RegularChannelConfig(ADC2, ADC_Channel_12, 1, ADC_SampleTime_55Cycles5);	
	ADC_Init(ADC2, &ADC_InitStructure);
	ADC_Cmd(ADC2, ENABLE);
	// ADC_DMACmd(ADC2, ENABLE); // ADC2与DMA不相连
	ADC_StartCalibration(ADC2); 
	while(ADC_GetCalibrationStatus(ADC2) == SET); 
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	ADC_ExternalTrigConvCmd (ADC2, ENABLE);
}

static void ADC_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) (&(ADC1->DR));
	DMA_InitStructure.DMA_MemoryBaseAddr 	= (uint32_t) (&ADC_ConversionValue);
	DMA_InitStructure.DMA_DIR 				= DMA_DIR_PeripheralSRC;
	
	DMA_InitStructure.DMA_BufferSize 		= 1; // 每次采集 1 个数据
	DMA_InitStructure.DMA_PeripheralInc 	= DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc 		= DMA_MemoryInc_Disable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
	DMA_InitStructure.DMA_MemoryDataSize 	= DMA_MemoryDataSize_Word;
	
	DMA_InitStructure.DMA_Mode 				= DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority 			= DMA_Priority_High;
	DMA_InitStructure.DMA_M2M 				= DMA_M2M_Disable;
	
	DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
	DMA_Cmd(ADC_DMA_CHANNEL, ENABLE);
}

void ADC_Init_Config(void)
{
	ADC_Mode_Config();
	ADC_GPIO_Config();
	ADC_DMA_Config();
}

3. main.c

#include "stm32f10x.h"
#include "usart.h"
#include "adc.h"

extern uint32_t ADC_ConversionValue;

void Delay(__IO uint32_t nCount)
{
  for(; nCount != 0; nCount--);
} 

int main(void)
{
	float ADC_Result1 = 0, ADC_Result2 = 0;
	ADC_Init_Config();
	USART_Config();
	printf("\\r\\nADC_DMA开始测试!!!\\r\\n");
	
	while(1)
	{
		ADC_Result1 = (float)( (ADC_ConversionValue & 0xFFFF0000) >> 16) / 4096 * 3.30;	// 高16位数据处理
		ADC_Result2 = (float)    STM32学习笔记(12)——定时器初步应用

STM32 ADC学习

STM32CubeMX学习笔记——ADC接口使用

STM32学习笔记(13)——模数转换ADC

STM32学习笔记

STM32 ADC详解