7、STM32之ADC(获取某个端口电压并显示)

7、STM32之ADC(获取某个端口电压并显示)_第1张图片
7、STM32之ADC(获取某个端口电压并显示)_第2张图片

文章目录

  • 一、ADC简介
    • 1、逐次逼近型ADC
    • 2、ADC模块框图
    • 3、ADC基本结构
    • 4、转换模式
    • 5、触发控制
    • 6、数据对齐
    • 7、通道采样时间
    • 8、校准
  • 二、代码
    • 1、一些函数
    • 2、ADC初始化
    • 3、实验获取PA1的电压并显示

一、ADC简介

ADC (Analog-Digital Converter) 模拟-数字转换器
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
12位
逐次逼近型ADC**,1us转换时间
输入电压范围∶0-3.3V,转换结果范围∶0~4095。
18个输入通道,可测量16个外部和2个内部信号源规则组和注入组两个转换单元
模拟看门狗自动监测输入电压范围**

12位ADC的值,量化的范围就是0 - 2^12-1 ,即0-4095。位数越高。量化结果就越精细。对应分辨率就越高。
关于这个模拟看门狗,我们可以设定阈值。当AD值高于它设定的上阈值或者低于下阈值时,它就会申请中断,你就可以在中断函数里执行相应的操作。

1、逐次逼近型ADC

STM32的ADC的原理和这个一样,这个是ADC0809的内部结构
7、STM32之ADC(获取某个端口电压并显示)_第3张图片
首先进行通道开关的选择。在左方有个通道选择开关。选择的通道的数字是存在下方的ADDA,ADDB,ADDC中。你想选择IN0-IN7中的哪一个通道,就把那个通道号放在这三个里面。ALE是锁存信号,给ALE置位,上面这里对应的通路开关就可以自动拨好了。
7、STM32之ADC(获取某个端口电压并显示)_第4张图片

然后来到了比较器的上面一根线。
在比较器中用逐次逼近的方法来一一比较
比较器的作用就是判断两个输入信号电压的大小关系。
比较器的上面一根线是一个外部通道输入的,未知编码的电压。
比较器的下面一根线是一个DAC输出的,已知编码的电压。
如果DAC输出的电压比较小。我就增大DAC数据。直到DAC输出的电压和外部通道输入的电压近视相等。这样DAC输入的数据就是外部电压的编码数据了。(这里就是输入到8位三态锁存缓冲器)
7、STM32之ADC(获取某个端口电压并显示)_第5张图片
而这个增大或减小去调整DAC的数据的过程,是那个逐次逼近寄存器SAR在起作用。
它通常会使用二分法进行查找,比如在这里是8位的ADC,就是0-255。第一次比较的时候,我们就给DAC输入255的一半进行比较。第二次比较的时候。再就给128的一半64。就这么继续往下比较。而且128,64,32正好是二进制每一位的位权。也就是说,这个比较的过程,就是对二进制从高位到低位依次判断是1是0的过程。那对于8位的ADC,从高位到低依次判断8次就能找到未知电压的编码。12位的就是判断12次。
7、STM32之ADC(获取某个端口电压并显示)_第6张图片

EOC是End Of Gonvert转换结束信号
START是开始转换。给一个输入脉冲,开始转换。
CLOCK是ADC时钟。
7、STM32之ADC(获取某个端口电压并显示)_第7张图片
下面。VRVF+和VREF-是DAC的参考电压。
比如你给一个数据255。是对应5V还是3.3V呢,就由这个参考电压决定。

这个DAC的参考电压也决定了ADC的输入范围,所以它也是ADC参考电压。
通常参考电压的正极和VCC是一样的,会接一起。参考电的负极和GND也是一样的。也接在一起。

2、ADC模块框图

7、STM32之ADC(获取某个端口电压并显示)_第8张图片
7、STM32之ADC(获取某个端口电压并显示)_第9张图片

3、ADC基本结构

7、STM32之ADC(获取某个端口电压并显示)_第10张图片
左边是输入通道,16个GPO口,外加两个内部的通道。
然后进入AD转换器
AD转换器里有两个组,一个是规则组,一个是注入组
规则组最多可以选中16个通道。注入组最多可以选择4个通道。
然后转换的结果可以存放在AD数据寄存器里,规则组有1个数据寄存器,注入组有4个数据寄存器。
然后下面这里有触发控制。提供了开始转换的START信号。
触发控制可以选择硬件触发和软件触发,硬件触发主要来自于定时器和外部中断触发。
右边这里是来自RCC的ADC时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的
有三个方式可以申请中断,首先规则组和注入组转换完成会有EOC信号,会置一个标志位。其次是看门狗,如果超过阈值,就通过中断输出控制,像NVIC申请中断。

4、转换模式

7、STM32之ADC(获取某个端口电压并显示)_第11张图片
7、STM32之ADC(获取某个端口电压并显示)_第12张图片

7、STM32之ADC(获取某个端口电压并显示)_第13张图片

规则组
7、STM32之ADC(获取某个端口电压并显示)_第14张图片
触发—进入第一个序列位置(选中1个)---- 然后就转换完毕了
此时生成一个EOC的信号
如果要转换就要再次触发

7、STM32之ADC(获取某个端口电压并显示)_第15张图片
触发—进入第一个序列位置(选中1个)---- 此次转换完毕 -----生成EOC ----- 不需要触发继续重复上述过程

7、STM32之ADC(获取某个端口电压并显示)_第16张图片
这个模式也是触发一次用一次
但是一次有好几个序列
所以要给一个通道数目,就是有几个序列能用,有几个就从序列1开始往下排几个
每次触发后,就依次对这前7个(通道数目)进行AD转换,且为了防止数据覆盖,要及时用DMA挪走数据
那7个通道转换完成之后,产生EOC信号,转换结束。

7、STM32之ADC(获取某个端口电压并显示)_第17张图片
就是上述过程,触发了就一直一直转

5、触发控制

7、STM32之ADC(获取某个端口电压并显示)_第18张图片
7、STM32之ADC(获取某个端口电压并显示)_第19张图片

6、数据对齐

7、STM32之ADC(获取某个端口电压并显示)_第20张图片
一般情况下都是右对齐,前面加0。
也有的情况是左对齐

7、通道采样时间

7、STM32之ADC(获取某个端口电压并显示)_第21张图片
在这里插入图片描述

7、STM32之ADC(获取某个端口电压并显示)_第22张图片

8、校准

1、ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。
2、 建议在每次上电后执行一次校准。
3、 启动校准前,ADC必须处于关电状态超过至少两个ADC时钟周期。

二、代码

我使用的板子是正点原子的迷你板
STM32的型号是 STM32F103RCT
有三个ADC
7、STM32之ADC(获取某个端口电压并显示)_第23张图片
7、STM32之ADC(获取某个端口电压并显示)_第24张图片
我们在这里使用ADC1的通道1

1、一些函数

7、STM32之ADC(获取某个端口电压并显示)_第25张图片
7、STM32之ADC(获取某个端口电压并显示)_第26张图片
7、STM32之ADC(获取某个端口电压并显示)_第27张图片
7、STM32之ADC(获取某个端口电压并显示)_第28张图片
7、STM32之ADC(获取某个端口电压并显示)_第29张图片

ADC规则组通道配置

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

是否允许外部触发转换

void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

获取转换值

uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

双ADC模式读取转换结果

uint32_t ADC_GetDualModeConversionValue(void);

7、STM32之ADC(获取某个端口电压并显示)_第30张图片

2、ADC初始化

#include "stm32f10x.h"   

void Adc_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure; 
	
	//1、开启时钟ADC1的时钟和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
	//2、配置分频,因为最大不能超过14MHz 72/6=12
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
	//3、配置GPIOA和ADC结构体 PA1 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	
	ADC_DeInit(ADC1); //复位ADC1
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
	//4、使能ADC1
	ADC_Cmd(ADC1, ENABLE);
	//5、校准并等待校准结束
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
	
}

关于分频
7、STM32之ADC(获取某个端口电压并显示)_第31张图片

7、STM32之ADC(获取某个端口电压并显示)_第32张图片

3、实验获取PA1的电压并显示

首先PA1是ADC通道1
所以这个函数中的ADC_Channel要选择如图所示

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)

7、STM32之ADC(获取某个端口电压并显示)_第33张图片

adc.c

#include "stm32f10x.h"   

void Adc_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure; 
	
	//1、开启时钟ADC1的时钟和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
	//2、配置分频,因为最大不能超过14MHz 72/6=12
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
	//3、配置GPIOA和ADC结构体 PA1 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	
	ADC_DeInit(ADC1); //复位ADC1
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
	//4、使能ADC1
	ADC_Cmd(ADC1, ENABLE);
	//5、校准并等待校准结束
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
	
}

uint16_t AD_GetValue(u8 ch)
{
		//设置指定ADC的规则组通道,一个序列,采样时间
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC1,ADC通道,采样时间为239.5周期	  		
	 
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
	 
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
}

//多获取几次数据求平均值更稳定
uint16_t Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=AD_GetValue(ch);
		delay_ms(5);
	}
	return temp_val/times;
} 	 


main.c

#include "stm32f10x.h"

#include "delay.h"
#include "ADC.h"
#include "lcd.h"

	
 int main(void)
 {	
	float temp;
	u16 adcx;
	delay_init();	    	 //延时函数初始化	  
	LCD_Init();
	Adc_Init(); 
	 	LCD_ShowString(10,70,200,16,16,"AD_GetValue");	
		LCD_ShowString(10,100,200,16,16,"AD_Average");	
		LCD_ShowString(10,150,200,16,16,"ADC_CH1_VOL:0.000V");
  while(1)
	{
		LCD_ShowxNum(110,70,AD_GetValue(ADC_Channel_1),4,16,0);
		LCD_ShowxNum(110,100,Get_Adc_Average(ADC_Channel_1,10),4,16,0);
		
		adcx=Get_Adc_Average(ADC_Channel_1,10);
		temp=(float)adcx*(3.3/4096);
		adcx=temp;
		LCD_ShowxNum(106,150,adcx,1,16,0);//显示整数
		temp-=adcx;
		temp*=1000;
		LCD_ShowxNum(122,150,temp,3,16,0X80); //显示小数		
	}
 }

接3.3v和接GND以及不接是浮空,电平不稳定一直变
7、STM32之ADC(获取某个端口电压并显示)_第34张图片
7、STM32之ADC(获取某个端口电压并显示)_第35张图片
7、STM32之ADC(获取某个端口电压并显示)_第36张图片
4、在不使用DMA的情况下,进行多个通道的AD转换
在这里就是自己选择通道获取一次

#include "stm32f10x.h"   

void Adc_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure; 
	
	//1、开启时钟ADC1的时钟和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
	//2、配置分频,因为最大不能超过14MHz 72/6=12
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
	
	//3、配置GPIOA和ADC结构体                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4| GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	
	ADC_DeInit(ADC1); //复位ADC1
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
	//4、使能ADC1
	ADC_Cmd(ADC1, ENABLE);
	//5、校准并等待校准结束
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
	
}
/*
uint16_t AD_GetValue(u8 ch)
{
		//设置指定ADC的规则组通道,一个序列,采样时间
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC1,ADC通道,采样时间为239.5周期	  		
	 
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
	 
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
}
*/
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
	ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
	return ADC_GetConversionValue(ADC1);
}
//多获取几次数据求平均值更稳定
uint16_t Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=AD_GetValue(ch);
		delay_ms(5);
	}
	return temp_val/times;
} 	 


#include "stm32f10x.h"

#include "delay.h"
#include "ADC.h"
#include "lcd.h"

	
 int main(void)
 {	
	float temp;
	u16 adcx;
	delay_init();	    	 //延时函数初始化	  
	LCD_Init();
	Adc_Init(); 
	 	LCD_ShowString(10,70,200,16,16,"AD_GetValue");	
		LCD_ShowString(10,100,200,16,16,"AD_Average");	
		//LCD_ShowString(10,150,200,16,16,"ADC_CH1_VOL:0.000V");
  while(1)
	{
		LCD_ShowxNum(110,70,AD_GetValue(ADC_Channel_4),4,16,0);
		LCD_ShowxNum(110,100,Get_Adc_Average(ADC_Channel_4,10),4,16,0);	
		
		LCD_ShowxNum(110,170,AD_GetValue(ADC_Channel_5),4,16,0);
		LCD_ShowxNum(110,200,Get_Adc_Average(ADC_Channel_5,10),4,16,0);	
		
	}
 }


你可能感兴趣的:(STM32笔记,stm32,单片机,嵌入式硬件)