STM32-ADC配置详解及应用。实例:《中断单通道读取ADC》、《DMA多通道读取ADC》

一、ADC介绍

12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。

二、框图分析

在这里插入图片描述
在这里插入图片描述
按照顺序分析:

1、电压输入范围:ADC 输入范围为:VREF- ≤ VIN ≤ VREF+。由 VREF-、VREF+ 、VDDA、 VSSA、这四个外部引脚决定。一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为:0~3.3V。
2、输入通道:可分为注入通道规则通道 前者常用。关于输入通道查询,可以参考《STM32F103xCDE_数据手册》
在这里插入图片描述
下面是总结好的表格:F103x 系列 对应的引脚几乎完全一样 下面是ZET6的
在这里插入图片描述
3、 转换顺序
规则序列
通过控制ADC规则序列寄存器 ADC_SQR1 、ADC_SQR2、ADC_SQR3 来配置规则通道的转换顺序,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果通道 16 想第一次转换,那么在 SQ1[4:0] 写 16 即可。SQR2 控制着规则序列中的第 7 到第12 个转换,对应的位为:SQ7[4:0]~SQ12[4:0],如果通道 1 想第 8 个转换,则 SQ8[4:0] 写 1 即可。SQR1 控制着规则序列中的第 13 到第 16 个转换,对应位为:SQ13[4:0]~SQ16[4:0],如果通道 6 想第 10 个转换,则 SQ10[4:0] 写 6 即可。具体使用多少个通道,由 SQR1 的位L[3:0]决定,最多 16 个通道。

注入序列
ADC注入序列寄存器ADC_JSQR 。注入序列寄存器 JSQR 只有一个,最多支持 4 个通道,具体多少个由 JSQR 的 JL[2:0]决定。如果 JL 的 值小于 4 的话,则 JSQR 跟 SQR 决定转换顺序的设置不一样,第一次转换的不是 JSQR1[4:0],而是 JCQRx[4:0] ,x = (4-JL),跟 SQR 刚好相反。如果 JL=00(1个转换),那么转换的顺序是从 JSQR4[4:0]开始,而不是从 JSQR1[4:0]开始,这个要注意,编程的时候不要搞错。当 JL 等于 4 时,跟 SQR 一样。
在这里插入图片描述
4、触发源选择
通道配置完成,转换顺序也已经确定,下面就是触发源的选择。通过ADC控制寄存器 1(ADC_CR1)、ADC控制寄存器 2(ADC_CR2),选择触发源:
在这里插入图片描述
在这里插入图片描述
5、转换时间

ADC 时钟
ADC 输入时钟 ADC_CLK 由 PCLK2 经过分频产生,最大是 14M,分频因子由 RCC 时钟配置寄存器 RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,一般我们设置 PCLK2=HCLK=72M。一般选择8分频。
在这里插入图片描述
在这里插入图片描述

采样时间:
采样的周期数可通过 ADC 采样时间寄存器 ADC_SMPR1ADC_SMPR2 中的SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0~9,ADC_SMPR1 控制的是通道 10~17。每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5个周期,这里说的周期就是 1/ADC_CLK。
在这里插入图片描述
在这里插入图片描述
ADC 的转换时间跟 ADC 的输入时钟和采样时间有关公式为:Tconv = 采样时间 + 12.5 个周期。当 ADCLK = 14MHZ (最高),采样时间设置为 1.5 周期(最快),那么总
的转换时间(最短)Tconv = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us(常用1.17us)。

6、数据寄存器:一切准备就绪后,ADC 转换后的数据根据转换组的不同,规则组的数据放在 ADC_DR寄存器,注入组的数据放在 JDRx

规则数据寄存器
在这里插入图片描述

注入数据寄存器
在这里插入图片描述

7、中断

转换结束中断:
数据转换结束后,可以产生中断,中断分为三种:规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断。可以根据相应的中断标志位,编写对应的中断服务函数。事件是否发生,可以访问ADC状态寄存器(ADC_SR)
在这里插入图片描述

模拟看门狗中断
当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由 ADC_LTRADC_HTR 设置。例如我们设置高阈值是 2.5V,那么模拟电压超过 2.5V 的时候,就会产生模拟看门狗中断,反之低阈值也一样。
在这里插入图片描述

三、编程相关

在编程的时候,我们只需要配置对用的结构体即可:

ADC_InitTypeDef 结构体

typedef struct
{
    
    
  uint32_t ADC_Mode; 						// ADC 工作模式选择
  FunctionalState ADC_ScanConvMode;			/* ADC 扫描(多通道)
										 		或者单次(单通道)模式选择 */
  FunctionalState ADC_ContinuousConvMode; 	// ADC 单次转换或者连续转换选择
  uint32_t ADC_ExternalTrigConv; 			// ADC 转换触发信号选择
  uint32_t ADC_DataAlign; 					// ADC 数据寄存器对齐格式
  uint8_t ADC_NbrOfChannel;					// ADC 采集通道数
}ADC_InitTypeDef;

ADC_Mode:配置 ADC 的模式,当使用一个 ADC 时是独立模式,使用两个 ADC 时
是双模式,在双模式下还有很多细分模式可选。

ScanConvMode:可选参数为 ENABLE 和 DISABLE,配置是否使用扫描。如果是单通
道 AD 转 换 使 用 DISABLE , 如 果 是 多 通 道 AD 转 换 使 用 ENABLE 。

ADC_ContinuousConvMode:可选参数为 ENABLE 和 DISABLE,配置是启动自动连
续转换还是单次转换。使用 ENABLE 配置为使能自动连续转换;使用 DISABLE 配置为单
次转换,转换一次后停止需要手动控制才重新启动转换。

ADC_ExternalTrigConv:外部触发选择,框图中中列举了很多外部触发条件,可根据
项目需求配置触发来源。一般使用软件自动触发。

ADC_DataAlign:转换结果数据对齐模式,可选右对齐 ADC_DataAlign_Right 或者左
对齐 ADC_DataAlign_Left。一般选择右对齐模式。不然读取数据 还得处理。

ADC_NbrOfChannel:AD 转换通道数目,根据实际设置即可。具体的通道数和通道的
转换顺序是配置规则序列或注入序列寄存器

实例一:中断单通道读取ADC

编程要点

  1. 初始 ADC 用到的 GPIO;
  2. 设置 ADC 的工作参数并初始化;
  3. 设置 ADC 工作时钟;
  4. 设置 ADC 转换通道顺序及采样时间;
  5. 配置使能 ADC 转换完成中断,在中断内读取转换完数据;
  6. 使能 ADC;
  7. 使能软件触发 ADC 转换。
    ADC 转换结果数据使用中断方式读取,这里没有使用 DMA 进行数据传输。

1.初始 ADC 用到的 GPIO

void ADCx_GPIO_Config(void)
{
    
      //以PA1为例
	GPIO_InitTypeDef GPIO_InitStruct;
	//开启GPIO时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	//选择模拟模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
	
	GPIO_Init(ADCx_GPIO_PORT,&GPIO_InitStruct);
}

2.配置ADC

void ADCx_Config(void)
{
    
      //ADC1  Channel_1
	ADC_InitTypeDef ADC_InitStruct;
	//开启ADC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);

	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;		// 只使用一个ADC,属于单模式
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;  		//只有一个通道,关闭扫描模式
	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;		// 连续转换模式
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;// 不用外部触发转换,软件启即可
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;	// 转换结果右对齐
	ADC_InitStruct.ADC_NbrOfChannel = 1;   // 转换通道个数
	//初始化
	ADC_Init(ADC1,&ADC_InitStruct);
	
	//配置ADC时钟N狿CLK2的8分频,即9MHz
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	
	// 配置ADC 通道的转换顺序和采样时间
	ADC_RegularChannelConfig(ADC1,ADC_1_Channel,1,ADC_SampleTime_55Cycles5);
	
	// ADC 转换结束产生中断,在中断服务程序中读取转换值
	ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

	// 开启ADC ,并开始转换
	ADC_Cmd(ADC1,ENABLE);
	
	ADC_ResetCalibration(ADC1);
	// 等待校准寄存器初始化完成
	while(ADC_GetResetCalibrationStatus(ADC1));
	// ADC开始校准
	ADC_StartCalibration(ADC1);
	// 等待校准完成
	while(ADC_GetCalibrationStatus(ADC1));
	
	// 由于没有采用外部触发,所以使用软件触发ADC转换 
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}

3.配置NVIC

void ADC_NVIC_Config(void)
{
    
    
  NVIC_InitTypeDef NVIC_InitStructure;
	// 优先级分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

  // 配置中断优先级
  NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

4.编写中断服务函数

void ADC1_2_IRQHandler(void)
{
    
    	
	
	uint8_t i = 0;
	if (ADC_GetITStatus(ADC_x,ADC_IT_EOC)==SET) 
	{
    
    
		 //读取ADC的转换值
  		ADC_Convert_Value =(float)ADC_GetConversionValue(ADC_x)/4096*3.3;
		ADC_ClearITPendingBit(ADC_x,ADC_IT_EOC); //清除转换完成标志位,等待下一次
	}
}

5.测试函数

uint16_t ADC_USART_Value = 0;
uint16_t ADC_USART_Value2 = 0;
void _ADC_IT_Init(void)
{
    
    
	ADCx_GPIO_Config();
	ADCx_Config();
	ADC_NVIC_Config();
	
}
void  delay(uint32_t count)
{
    
    
	while(count--);
}
int main(void)
{
    
    
	LED_GPIO_Config();
	USARTx_Config();
	ADC_IT_Init();
	printf("\r\n ----这是一个ADC多通道采集实验----\r\n");
    while(1)
	{
    
    
		ADC_USART_Value 2=(float) ADC_USART_Value ; 
		printf("\r\n The current AD value = 0x%04X \r\n", ADC_USART_Value; 
		printf("\r\n The current AD value = %f V \r\n",ADC_USART_Value2); 
		printf("\r\n\r\n");
		delay(0x3fffff);
    }          
}

实例二:DMA多通道读取ADC

编程要点

  1. 初始 ADC 用到的 GPIO;
  2. 设置 ADC 的工作参数并初始化;
  3. 设置 ADC 工作时钟;
  4. 设置 ADC 转换通道顺序及采样时间;
  5. 配置使能 ADC 转换完成中断,在中断内读取转换完数据;
  6. 使能 ADC;
  7. 使能软件触发 ADC 转换。
    这里使用 DMA 进行数据传输,还需配置DMA。

有一点需要注意 仅 ADC1 和 ADC3 有DMA传输功能。分别对应
DMA1 的通道 1
DMA2 的通道 5
在这里插入图片描述
在这里插入图片描述
1.初始 ADC 用到的 GPIO

void ADCx_GPIO_Config(void)
{
    
    
	
	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(ADCx_GPIO_CLK,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStruct.GPIO_Pin = ADC_1_Pin|
     						   ADC_1_Pin|    //ADC_2_Pin和SUART冲突  因此删除
						       ADC_3_Pin|
							   ADC_4_Pin|
							   ADC_5_Pin|
						       ADC_6_Pin|
							   ADC_7_Pin;
	GPIO_Init(ADCx_GPIO_PORT,&GPIO_InitStruct);
}

2.初始 DMA

void ADC_DMA_Config(void)
{
    
    
	DMA_InitTypeDef DMA_InitStruct;
	
	RCC_AHBPeriphClockCmd(DMA_CLK,ENABLE);
	
	//外设基址为:ADC 数据寄存器地址
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)(&(ADC_x->DR));	
	
	// 存储器地址
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)ADC_USART_Value;	
	
	// 数据源来自外设
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; 
	
	//缓冲区大小,应该等于数据目的地的大小
	DMA_InitStruct.DMA_BufferSize = BUFFERSIZE;	
		
	// 外设寄存器只有一个,地址不用递增								
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 
	
	//存储器地址递增
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;	
	
	//外设数据大小为半字,即两个字节			
	DMA_InitStruct.DMA_PeripheralDataSize =	DMA_PeripheralDataSize_HalfWord; 
	
	//内存数据大小也为半字,跟外设数据大小相同
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	
	//循环传输模式
	DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;	
	
	//DMA传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
	DMA_InitStruct.DMA_Priority = DMA_Priority_High;
	
	//禁止存储器到存储器模式,因为是从外设到存储器
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
	
	DMA_Init(DMA_CHANNEL,&DMA_InitStruct);
	
	DMA_Cmd(DMA_CHANNEL,ENABLE);
	
}

3.初始 ADC

void ADCx_Config(void)
{
    
    
	ADC_InitTypeDef ADC_InitStruct;
	//开启ADC时钟
	RCC_APB2PeriphClockCmd(ADCx_CLK,ENABLE);

	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;	// 只使用一个ADC,属于单模式
	ADC_InitStruct.ADC_ScanConvMode = ENABLE;  //6通道开启扫描
	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;// 连续转换模式
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;// 不用外部触发转换,软件开启即可
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;	// 转换结果右对齐
	ADC_InitStruct.ADC_NbrOfChannel = ADC_num;   // 转换通道个数
	//初始化
	ADC_Init(ADC_x,&ADC_InitStruct);
	
	//配置ADC时钟N狿CLK2的8分频,即9MHz
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	
	// 配置ADC 通道的转换顺序和采样时间
	ADC_RegularChannelConfig(ADC_x,ADC_1_Channel,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC_x,ADC_4_Channel,2,ADC_SampleTime_55Cycles5); //A2  A3用于串口
	ADC_RegularChannelConfig(ADC_x,ADC_5_Channel,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC_x,ADC_6_Channel,4,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC_x,ADC_7_Channel,5,ADC_SampleTime_55Cycles5);

	// 使能ADC DMA 请求
	ADC_DMACmd(ADC_x, ENABLE);
	
	// 开启ADC ,并开始转换
	ADC_Cmd(ADC_x,ENABLE);
	
	ADC_ResetCalibration(ADC_x);
	// 等待校准寄存器初始化完成
	while(ADC_GetResetCalibrationStatus(ADC_x));
	// ADC开始校准
	ADC_StartCalibration(ADC_x);
	// 等待校准完成
	while(ADC_GetCalibrationStatus(ADC_x));
	
	// 由于没有采用外部触发,所以使用软件触发ADC转换 
	ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
}

void ADCx_DMA_Init(void)
{
    
    
	ADCx_GPIO_Config();
	ADC_DMA_Config();
	ADCx_Config();
}

4.测试函数

void  delay(uint32_t count)
{
    
    
	while(count--);
}

int main(void)
{
    
    
		LED_GPIO_Config();
		USARTx_Config();
	
		ADCx_DMA_Init();

		//ADC_IT_Init();
	 
	  printf("\r\n ----这是一个ADC多通道采集实验----\r\n");
    while(1)
		{
    
    
			
			printf("\r PA1 value = %f V \r\n",(float)ADC_USART_Value[0]/4096*3.3);
			printf("\r PA4 value = %f V \r\n",(float)ADC_USART_Value[1]/4096*3.3);
			printf("\r PA5 value = %f V \r\n",(float)ADC_USART_Value[2]/4096*3.3);
			printf("\r PA6 value = %f V \r\n",(float)ADC_USART_Value[3]/4096*3.3);
			printf("\r PA7 value = %f V \r\n\n\n\n\n",(float)ADC_USART_Value[4]/4096*3.3);
			delay(0x3fffff);
    }          
}

头文件
#include "bsp_dma.h"

#ifndef _BSP_DMA_H_
#define	_BSP_DMA_H_

#include "stm32f10x.h"

#define	BUFFERSIZE				5
#define DMA_CHANNEL				DMA1_Channel1   //手册
#define DMA_CLK						RCC_AHBPeriph_DMA1


void ADC_DMA_Config(void);

#endif /*_BSP_DMA_H_*/

#include "bsp_adc.h"

#ifndef _BSP_ADC_H_
#define	_BSP_ADC_H_
#include "stm32f10x.h"

#define	ADC_num								5

#define	ADC_x								ADC1
#define ADCx_CLK							RCC_APB2Periph_ADC1

#define ADCx_GPIO_PORT						GPIOA
#define ADCx_GPIO_CLK						RCC_APB2Periph_GPIOA

#define ADC_1_Pin							GPIO_Pin_1
#define ADC_2_Pin							GPIO_Pin_2
#define ADC_3_Pin							GPIO_Pin_3
#define ADC_4_Pin							GPIO_Pin_4
#define ADC_5_Pin							GPIO_Pin_5
#define ADC_6_Pin						    GPIO_Pin_6
#define ADC_7_Pin							GPIO_Pin_7

#define ADC_1_Channel						ADC_Channel_1
#define ADC_2_Channel						ADC_Channel_2
#define ADC_3_Channel						ADC_Channel_3
#define ADC_4_Channel						ADC_Channel_4
#define ADC_5_Channel						ADC_Channel_5
#define ADC_6_Channel						ADC_Channel_6
#define ADC_7_Channel						ADC_Channel_7

extern uint16_t ADC_USART_Value[ADC_num];
extern float ADC_Convert_Value[ADC_num];

void ADCx_GPIO_Config(void);
void ADCx_Config(void);
void ADC_DMA_Config(void);
void ADCx_DMA_Init(void);

#endif /*_BSP_ADC_H_*/

欢迎交流探讨,大家一起进步吧!冲冲冲!

猜你喜欢

转载自blog.csdn.net/qq_45689790/article/details/113862143