草庐IT

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

叫什么呀 2023-12-29 原文



文章目录

一、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的内部结构

首先进行通道开关的选择。在左方有个通道选择开关。选择的通道的数字是存在下方的ADDA,ADDB,ADDC中。你想选择IN0-IN7中的哪一个通道,就把那个通道号放在这三个里面。ALE是锁存信号,给ALE置位,上面这里对应的通路开关就可以自动拨好了。

然后来到了比较器的上面一根线。
在比较器中用逐次逼近的方法来一一比较
比较器的作用就是判断两个输入信号电压的大小关系。
比较器的上面一根线是一个外部通道输入的,未知编码的电压。
比较器的下面一根线是一个DAC输出的,已知编码的电压。
如果DAC输出的电压比较小。我就增大DAC数据。直到DAC输出的电压和外部通道输入的电压近视相等。这样DAC输入的数据就是外部电压的编码数据了。(这里就是输入到8位三态锁存缓冲器)

而这个增大或减小去调整DAC的数据的过程,是那个逐次逼近寄存器SAR在起作用。
它通常会使用二分法进行查找,比如在这里是8位的ADC,就是0-255。第一次比较的时候,我们就给DAC输入255的一半进行比较。第二次比较的时候。再就给128的一半64。就这么继续往下比较。而且128,64,32正好是二进制每一位的位权。也就是说,这个比较的过程,就是对二进制从高位到低位依次判断是1是0的过程。那对于8位的ADC,从高位到低依次判断8次就能找到未知电压的编码。12位的就是判断12次。

EOC是End Of Gonvert转换结束信号
START是开始转换。给一个输入脉冲,开始转换。
CLOCK是ADC时钟。

下面。VRVF+和VREF-是DAC的参考电压。
比如你给一个数据255。是对应5V还是3.3V呢,就由这个参考电压决定。

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

2、ADC模块框图


3、ADC基本结构


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

4、转换模式


规则组

触发—进入第一个序列位置(选中1个)---- 然后就转换完毕了
此时生成一个EOC的信号
如果要转换就要再次触发


触发—进入第一个序列位置(选中1个)---- 此次转换完毕 -----生成EOC ----- 不需要触发继续重复上述过程


这个模式也是触发一次用一次
但是一次有好几个序列
所以要给一个通道数目,就是有几个序列能用,有几个就从序列1开始往下排几个
每次触发后,就依次对这前7个(通道数目)进行AD转换,且为了防止数据覆盖,要及时用DMA挪走数据
那7个通道转换完成之后,产生EOC信号,转换结束。


就是上述过程,触发了就一直一直转

5、触发控制


6、数据对齐


一般情况下都是右对齐,前面加0。
也有的情况是左对齐

7、通道采样时间


8、校准

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

二、代码

我使用的板子是正点原子的迷你板
STM32的型号是 STM32F103RCT
有三个ADC


我们在这里使用ADC1的通道1

1、一些函数





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);

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));	 //等待校准结束
	
}

关于分频

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

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

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

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以及不接是浮空,电平不稳定一直变


有关STM32之ADC(获取某个端口电压并显示)的更多相关文章

  1. ruby-on-rails - Rails 编辑表单不显示嵌套项 - 2

    我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - 使用 Sublime Text 3 突出显示 HTML 背景语法中的 ERB? - 2

    所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择

  4. ruby-on-rails - link_to 不显示任何 rails - 2

    我试图在索引页中创建一个超链接,但它没有显示,也没有给出任何错误。这是我的index.html.erb代码。ListingarticlesTitleTextssss我检查了我的路线,我认为它们也没有问题。PrefixVerbURIPatternController#Actionwelcome_indexGET/welcome/index(.:format)welcome#indexarticlesGET/articles(.:format)articles#indexPOST/articles(.:format)articles#createnew_articleGET/article

  5. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  6. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  7. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

    我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

  8. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  9. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  10. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

随机推荐