stm32 ADC模数转换器——江协教程踩坑经验分享

江协stm32学习:7-1 ADC模数转换器

一、ADC(Analog-Digital Converter 模拟-数字转换器)简介

1、功能概述

ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁(ADC读取引脚上的模拟电压,转换为一个数据,存在寄存器里,再把数据读取到变量里面来,就可以进行显示、判断、记录等等操作)
※ DAC 数字模拟转换器可以将数字变量转换为模拟电压。但是使用PWM来等效模拟量是比DAC更简单更常用的选择。所以大部分的情况下会倾向于使用PWM,DAC现在主要使用于波形产生。

2、相关结构

  • 12位逐次逼近型ADC,1us转换时间(分辨率:一般用多少位来表示,位数越高,量化结果越精细,对应分辨率就越高;转换时间,也就是转换频率, ad转换是需要花一小段时间的这里1us就表示从Ad转换开始到产生结果需要花1us的时间,对应ad转换的频率就是1MHz,这儿已经是最高转换频率了,再高的就要考虑是否够用了)

  • 输入电压范围:0~3.3V,转换结果范围:0~4095(电压范围和转换结果范围是一一对应的线性关系)

  • 18个输入通道,可测量16个外部和2个内部信号源(外部信号源就是16个gpio口,在引脚上直接接模拟信号就行了;两个内部信号源是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,比如你电脑可以显示一个CPU温度,就可以用ADC读取这个温度传感器来测量;内部参考电压是一个1.2伏左右的基准电压,这个基准电压是不随外部供电电压变化而变化的,所以如果的芯片的外部电压不是标准的3.3V了,那测量外部影响的电压可能就不对,这时就可以读取这个基准电压进行校准,这样就能得到正确的电压值)

  • 规则组和注入组两个转换单元(这个是stm32 ADC的增强功能,普通的AD转化流程是启动一次转换读取值,然后再启动再读值,但是stm32可以列出来一个组,一次性启动一个组,连续转换多个值,并且有两个组,一个是用于常规使用的规则组,一个是用于突发事件的注入组)

  • 模拟看门狗自动监测输入电压范围(ADC通常用于测量光线强度、温度等物理量。当测量值超出预设阈值范围时,如光线强度或温度高于或低于特定值,模拟看门狗可自动触发相应操作。该功能通过监测指定通道的AD值,当数值超过设定的上限或下限阈值时,会触发中断请求。在中断服务程序中,可执行预设的响应操作。这种机制避免了持续轮询和手动判断的繁琐,提高了系统效率。建议深入了解这一功能,以便在实际应用中灵活运用。)

  • STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道

3、基本结构

1)早期8位的逐次逼近型ADC芯片

2)stm32的ADC框图

 二、工作电路

1、基本电路结构

开关控制:使用ADC_Cmd函数,用于给ADC上电

2、输入通道

ADC通道和引脚复用的关系

3、转换模式

单次转换/连续 + 扫描/非扫描

在非扫描模式下,该菜单仅序列一的位置有效,此时菜单的多选功能简化为单选模式。我们可以在序列一位置指定待转换的通道,例如将通道二写入该位置。随后触发转换,ADC将对通道二进行模数转换,转换结果将存储于数据寄存器中,同时EOC标志位被置为1,至此整个转换过程完成。

与上一个模式的区别是,第一次转换结束之后不会停止,而是立刻开始下一轮的转换,然后一直持续下去。

这个就是每一次扫描一整个菜单会有多个数据。

4、触发控制

5、数据对齐

数据右对齐是从低位到高位,数据取出来就可以直接用;左对齐相当于左移了几位,取出来后需要处理,这种用法一般用于不需要那么高的分辨率的时候。

6、转换时间

对于这个参数一般不太敏感,因为AD转换还是很快的,如果不需要非常高速的转换频率,那转换时间就可以忽略。

(采样、保持)+ (量化、编码),可以分成这两大步骤。量化编码就是逐次比较的过程,一般位数越多,花的时间越长。采样保持就是再逐次比较的过程中维持我们的值不变,以免被仍在变化的电压覆盖掉值,导致结果出错。

采样保持的过程中需要闭合采样开关,过一段时间再断开,所以这个过程是需要时间的,叫做采样时间(采样保持花费的时间)。这个时间是可以在程序中配置的,采样时间越大,越能避免一些毛刺信号的干扰,不过转换时间也会延长。12.5个ADC周期,12个=每一位转换对应的转换时间×12位,剩下的0.5是做其他一些东西花的时间。ADC周期就是从RCC分频过来的ADCCLK(最大14MHz)

7、校准

校准过程是固定,只需要在ADC初始化的最后加几条代码即可

  • ADC 有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差

  • 建议在每次上电后执行一次校准

  • 启动校准前,ADC 必须处于关电状态超过至少两个 ADC 时钟周期

8、ADC的外围工作电路

第一个是电位器产生可调电压的电路,第二个是传感器输出电压的电路(大多数传感器的电阻不固定,所以串联一个固定的电阻来分压,便于计算),第三个是针对高电压的情况,通过一次分压来降低电压值,不过这种不是很安全,可以的话其实更加建议采用专门的芯片。

三、程序编写

1、编写步骤

Step 1 RCC开启时钟。ADC外设、GPIO外设和的时钟打开
Step 2 GPIO初始化,配置成模拟输入的模式
Step 3 配置多路开关,把左边的通道接入到右边的规则组列表里
Step 4 配置ADC转换器,单次/连续转换、扫描/非扫描、通道数量、触发源、数据对齐方式(直接用结构体来配置)
Step 5 模拟看门狗(如果需要),有专门的函数用来配置阈值和监测通道。
Step 6 开启中断(如果需要),在中断输出控制里用ITConfig函数开启对应的中断输出,然后再在NVIC里配置优先级。
Step 7 开关控制,ADC_Cmd函数开启ADC。

※ GPIO的引脚模式,可以看外部模块输出的默认电平,如果外部模块空闲默认为输出高电平,选择上拉输入,默认输入高电平;如果外部模块空闲默认为输出低电平,选择下拉输入,默认输入低电平;如果不确定或者外部输出功率很小,可以选择浮空输入。总而言之就是和外部模块保持默认状态一致,防止默认电平打架。

2、库函数

1)规则组的基本配置

函数名

参数及对应含义

函数作用

ADC_DeInit(ADC_TypeDef* ADCx)

ADCx:指定要复位的ADC外设(如ADC1、ADC2等)

将ADC的寄存器重置为默认值

ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct)

ADCx:指定要配置的ADC外设;ADC_InitStruct:指向包含ADC配置参数的ADC_InitTypeDef结构体指针

根据ADC_InitStruct中的参数配置ADC,包括分辨率、转换模式等

ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct)

ADC_InitStruct:指向要初始化的ADC_InitTypeDef结构体指针

将ADC_InitStruct的成员初始化为默认值

ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState)

ADCx:指定ADC外设;NewState:指定ADC的新状态,ENABLE或DISABLE

启用或禁用指定的ADC

ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState)

ADCx:指定ADC外设;NewState:指定DMA的新状态,ENABLE或DISABLE

启用或禁用ADC的DMA功能

ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState)

ADCx:指定ADC外设;ADC_IT:指定ADC中断类型(如ADC_IT_EOC表示转换结束中断);NewState:指定中断的新状态,ENABLE或DISABLE

中断输出控制函数:配置ADC的中断

ADC_ResetCalibration(ADC_TypeDef* ADCx)

ADCx:指定ADC外设

复位校准:重置ADC校准寄存器

ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx)

ADCx:指定ADC外设

获取复位校准状态:检查ADC校准重置状态,返回SET(重置完成)或RESET(重置未完成)

ADC_StartCalibration(ADC_TypeDef* ADCx)

ADCx:指定ADC外设

启动ADC校准

ADC_GetCalibrationStatus(ADC_TypeDef* ADCx)

ADCx:指定ADC外设

检查ADC校准状态,返回SET(校准完成)或RESET(校准未完成)

ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

ADCx:指定ADC外设;NewState:指定是否启动转换,ENABLE或DISABLE

通过软件启动或停止ADC转换

ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx)

ADCx:指定ADC外设

获取软件开始状态:检查ADC是否启动了软件转换,返回SET(转换启动)或RESET(转换未启动)

ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number)

ADCx:指定ADC外设;Number:指定扫描序列中通道的数量

配置ADC扫描模式下通道的数量

ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

ADCx:指定ADC外设;NewState:启用或禁用扫描模式,ENABLE或DISABLE

启用或禁用ADC的扫描模式

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

ADCx:指定ADC外设;ADC_Channel:指定ADC通道(如ADC_Channel_0);Rank:指定通道在序列中的位置;ADC_SampleTime:指定采样时间(如ADC_SampleTime_144Cycles)

配置ADC的常规通道

ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

ADCx:指定ADC外设;NewState:启用或禁用外部触发转换,ENABLE或DISABLE

启用或禁用ADC的外部触发转换

ADC_GetConversionValue(ADC_TypeDef* ADCx)

ADCx:指定ADC外设

获取ADC转换结果的值

ADC_GetDualConversionValue()

获取双通道ADC转换结果的值

2)模拟看门狗

函数名

参数及对应含义

函数作用

ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog)

ADCx:指定ADC外设;ADC_AnalogWatchdog:指定模拟看门狗监控模式

启用或禁用ADC的模拟看门狗功能

ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold)

ADCx:指定ADC外设;HighThreshold:设置看门狗监控的电压上限;LowThreshold:设置看门狗监控的电压下限

配置ADC模拟看门狗的监控电压阈值

ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel)

ADCx:指定ADC外设;ADC_Channel:指定要监控的ADC通道

配置ADC模拟看门狗监控单个通道

ADC_TempSensorVrefintSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel)

ADCx:指定ADC外设;ADC_Channel:指定温度传感器或内部参考电压通道

配置ADC测量温度传感器或内部参考电压

ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG)

ADCx:指定ADC外设;ADC_FLAG:指定要检查的标志位(如ADC_FLAG_EOC表示转换结束标志)

获取指定ADC标志位的状态,返回SET或RESET

ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG)

ADCx:指定ADC外设;ADC_FLAG:指定要清除的标志位

清除指定的ADC标志位

ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT)

ADCx:指定ADC外设;ADC_IT:指定要检查的中断类型(如ADC_IT_EOC表示转换结束中断)

获取指定ADC中断的状态,返回SET或RESET

ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT)

ADCx:指定ADC外设;ADC_IT:指定要清除中断挂起位的中断类型

清除指定ADC中断的挂起位

 四、代码实例

1、AD单通道

void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0引脚初始化为模拟输入
	
	/*规则组通道配置*/
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);		
    //规则组序列1的位置,配置为通道0,指定通道采样时间:需要更快的转换就选择小参数,需要稳定的就选择大的,没要求就随意
    //如果需要配置多个,复制粘贴就好,每个通道也可以设置不同的采样时间
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
	
	/*ADC使能*/
	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);    //这儿使用while是因为标志位清零这件事是硬件电路完成的,不会因为软件里面的等待而被卡死
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
}

/**
  * 函    数:获取AD转换的值
  * 参    数:无
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
}

2、AD多通道

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:AD初始化
  * 参    数:无
  * 返 回 值:无
  */
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
	
	/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
	
	/*ADC使能*/
	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
}

/**
  * 函    数:获取AD转换的值
  * 参    数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
	ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);	//在每次转换前,根据函数形参灵活更改规则组的通道1
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
}

受限于DMA还未讲解,所以容易发生数据覆盖,且由于流程中是在整个列表。扫描完成之后才会产生标志位,所以也无法判断哪些通道已经完成了数据的转换。所以此处我们采取的是仍然使用单通道模式,但是在每一次进入新循环的时候,修改一次通道的值,从而实现多通道的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值