八、ADC模数转换

六、ADC模数转换

ADC简介

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

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

逐次逼近型ADC简介

image

“地址锁存和译码”用来选择通道

“IN0~IN7”是8路输入通道,可以输入模拟信号

使用“通道选择开关”选择其中的一路,输入到“比较器”的待测端

“比较器“可以判断两个输入信号的电压大小关系,输出高低电平指示大小。一端是待测端,另一端是”DAC“的电压输出端

给“DAC”一个数据,他就可以输出数据对应的电压,其内部原理是使用加权电阻网络实现

将”比较器“待测端的未知电压与“DAC”输出的已知电压进行比较,当未知电压不等于“DAC”输出的电压时,就调整输入”DAC“的数据来改变”DAC“输出的电压,直到与未知电压近似,此时”比较器“待测端的电压的数字信号就等于输入给”DAC“的数据。比较的方法是:二分法

获取模拟信号对应的数字信号后,将数字信号存放到“8位三态锁存缓冲器”中,并将标志位“EOC”置1,表示成功获取到了数字信号,可以从“8位三态锁存缓冲器”中取值了

STM32的ADC外设

image

  • 规则组:
    • 规则通道:规则组转换单元,一次可以选中16个通道,然后依次进行转换。也可以选择16个通道以下的通道数。
    • 规则通道数据寄存器:存放规则组转换出来的数据,只有一个数据寄存器,所以当规则组同时转换多个通道的数据时,转换完成一个通道就需要立马将该通道转换的数据取出来,否则会被后面通道转换的数据覆盖。一般配合DMA来使用
  • 注入组:
    • 注入通道:注入组转换单元,一次可以选中4个通道,然后依次进行转换。也可以选择4个通道以下的通道数。
    • 注入通道数据寄存器:存放注入组转换出来的数据,有四个数据寄存器,分别对应注入通道连接的4个通道,因此注入组不必担心转换出来的数据被覆盖的情况

ADC的基本结构

image

ADC输入通道

image

规则组的转换模式

单次转换:在完成一次转换后,进行下一次转换时需要重新触发一次

连续转换:在完成一次转换后,进行下一次转换时无需再触发,自动开始转换

非扫描模式:每一次转换只能转换一个通道

扫描模式:每次转换可以转换16个及以下的通道,按照序列一个一个的转换。16个序列中可以放重复的通道

间断模式:在扫描模式下,每隔几个转换就暂停一次,需要再次触发才能继续转换

单次转换,非扫描模式

image

连续转换,非扫描模式

image

单次转换,扫描模式

image

连续转换,扫描模式

image

触发控制

image

EXTSEL:配置寄存器的参数。使用库函数的话给个参数就行

触发源:用于触发AD转换

数据对齐

STM32的ADC外设是12位的,那么转换结果只有12位,但是数据寄存器是16位的,所以数据有两种对齐方式

数据右对齐:低16位写转换出来的数据,高4位补0

一般使用该方式,直接读寄存器就是转换的结果,不需要二次计算

数据左对齐:高16位写转换出来的数据,低4位补0

直接读寄存器的话会比实际值大,对于不需要很高分辨率的情况可以选这种,只读寄存器的高8位,舍弃掉后面4位的精度,相当于8位ADC

image

转换时间

AD转换的步骤:采样,保持,量化,编码

采样、保持:AD在转换时是需要一小段时间的,需要输入的电压稳定不变才能准确的定位。比如用一个小容量的电容存储一下采样的电压,方便后面的量化、编码

量化、编码:逐次比较

STM32 ADC的总转换时间为:TCONV = 采样时间 + 12.5个ADC周期

采样时间是在保持电压时消耗的时间

ADC周期是从RCC分频过来的ADCCLK,最大频率为14MHz

12位的ADC,所以需要12个周期,还有0.5个周期是做其他事情花的时间

例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs

当然,也可以对ADCCLK进行超频,大于14MHz,这样转换时间会更快,但是稳定性会下降

校准

不需要详细了解,只需要在初始化ADC的后面加上几条用于校准的代码即可

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

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

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

可输出不同电压的电路

image

ADC库函数

ADC_InitTypeDef参数

typedef struct
{
  uint32_t ADC_Mode;                      /*!< 选择工作模式,独立模式还是双ADC模式 */
  FunctionalState ADC_ScanConvMode;       /*!< 选择扫描模式或非扫描模式 */
  FunctionalState ADC_ContinuousConvMode; /*!< 单次转换还是连续转换 */
  uint32_t ADC_ExternalTrigConv;          /*!< 选择转换的触发源 */
  uint32_t ADC_DataAlign;                 /*!< 选择数据的对齐,左对齐或右对齐 */
  uint8_t ADC_NbrOfChannel;               /*!< 扫描模式下每一次转换的通道数 */
}ADC_InitTypeDef;

/* ADC_Mode */
ADC_Mode_Independen				// 独立模式
// 下面都是双ADC相关的工作模式
ADC_Mode_RegInjecSimult
ADC_Mode_RegSimult_AlterTrig
ADC_Mode_InjecSimult_FastInterl
ADC_Mode_InjecSimult_SlowInterl
ADC_Mode_InjecSimult
ADC_Mode_RegSimult
ADC_Mode_FastInterl
ADC_Mode_SlowInterl
ADC_Mode_AlterTrig
    
/* ADC_ScanConvMode */
ENABLE		// 扫描模式
DISABLE		// 非扫描模式
    
/* ADC_ContinuousConvMode */
ENABLE		// 连续转换模式
DISABLE		// 单次转换模式

/* ADC_ExternalTrigConv */
ADC_ExternalTrigConv_T1_CC1					// TIM1_CC1事件
    /*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T1_CC2					// TIM1_CC2事件
    /*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T2_CC2					// TIM2_CC2事件
    /*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T3_TRGO				// TIM3_TRGO事件
    /*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T4_CC4					// TIM4_CC4事件
    /*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO		// EXTI线11/TIM8_TRGO事件
    /*!< 适用于ADC1 和 ADC2 */

ADC_ExternalTrigConv_T1_CC3					// TIM1_CC3事件
    /*!< 适用于ADC1, ADC2, ADC3 */
ADC_ExternalTrigConv_None					// 软件控制
    /*!< 适用于ADC1, ADC2, ADC3 */

ADC_ExternalTrigConv_T3_CC1					// TIM3_CC1事件
    /*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T2_CC3					// TIM2_CC3事件
    /*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T8_CC1					// TIM8_CC1事件
    /*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T8_TRGO				// TIM8_TRGO事件
    /*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T5_CC1					// TIM5_CC1事件
    /*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T5_CC3					// TIM5_CC3事件
    /*!< 仅适用于ADC3 */
    
/* ADC_DataAlign */
ADC_DataAlign_Right		// 数据右对齐
ADC_DataAlign_Left		// 数据左对齐

/* ADC_NbrOfChannel */
1~16

函数

// 配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCKL
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);	// 该函数来自rcc.c文件中

// ADC恢复初始化配置
void ADC_DeInit(ADC_TypeDef* ADCx);

// 使用ADC_InitTypeDef结构体中的属性配置ADC
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

// 使用默认值填充ADC_InitTypeDef结构体
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);

// 启动或关闭ADC
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// 启动或关闭DMA
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC的中断输出控制
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);

/* 用于控制校准的函数 */
// 复位校准寄存器
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
// 校准寄存器是否复位完成
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
// 开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
// 是否校准完成
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);

// ADC软件触发转换
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC获取软件开始转换的状态,没啥用,一般不使用
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);

/* 配置间断模式的 */
// 每隔几个通道间断一次
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
// 是否启动间断模式
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC规则组通道配置
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

// 是否允许外部触发转换
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// 获取AD转换的数据寄存器,读取转换结果的
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

// 双ADC模式下读取转换结果
uint32_t ADC_GetDualModeConversionValue(void);

/* 配置注入组相关函数,Injected */
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);

/* 配置模拟看门狗的函数 */
// 是否启动模拟看门狗
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
// 配置看门狗的高低阈值
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
// 配置看门的通道
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);

// ADC温度传感器、内部参考电压控制。开启内部的两个通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);

// 获取转换完成标志位状态,判断转换是否完成。中断函数外使用
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

// 清除转换完成标志位。中断函数外使用
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

// 获取中断状态。中断函数中使用
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);

// 清除中断挂起位。中断函数中使用
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);

案例

AD单通道转换

使用的函数

// 配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCKL
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);	// 该函数来自rcc.c文件中

// 使用ADC_InitTypeDef结构体中的属性配置ADC
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

// 启动或关闭ADC
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// 复位校准寄存器
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
// 校准寄存器是否复位完成
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
// 开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
// 是否校准完成
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);

// ADC软件触发转换
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC规则组通道配置
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

// 获取AD转换的数据寄存器,读取转换结果的
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

// 获取转换完成标志位状态,判断转换是否完成。中断函数外使用
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

// 清除转换完成标志位。中断函数外使用
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

int16_t Speed;

int main()
{
	/* 开启ADC2和GPIOA的时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2 | RCC_APB2Periph_GPIOA, ENABLE);
	
	/* 初始化PA0 */
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;		// 模拟输入模式
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 对72MHz的PAB2时钟进行分频得到ADCCLK,其中ADCCLK最大频率为14MHz
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);	// 6分频
	
	// 规则组配置。PA0对应ADC2的通道0;通道0排在转换序列的第一位;采样、保持的时间55.5个ADC周期
	ADC_RegularChannelConfig(ADC2, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	
	/* 初始化ADC2 */
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;						// 连续转换
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;					// 数据右对齐
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	// 使用软件作为AD转换的触发源
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;						// ADC的工作模式为独立模式,ADC1和ADC2给干各的
	ADC_InitStruct.ADC_NbrOfChannel = 1;								// 每次转换有几个通道。只有在扫描模式下才有效
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;							// 非扫描模式
	ADC_Init(ADC2, &ADC_InitStruct);
	
	// ADC2开始工作
	ADC_Cmd(ADC2, ENABLE);
	
	/* ADC校准 */
	ADC_ResetCalibration(ADC2);								// 复位ADC2的校准寄存器
	while (ADC_GetResetCalibrationStatus(ADC2) == SET);		// ADC2的校准寄存器是否复位完成。复位完成后返回RESET
	ADC_StartCalibration(ADC2);								// ADC2开始进行校准
	while (ADC_GetCalibrationStatus(ADC2) == SET);			// ADC2是否校准完成。校准完成后返回RESET
	
	// 对ADC2进行软件中断,由于是连续转换模式,所以只需要触发一次即可
	ADC_SoftwareStartConvCmd(ADC2, ENABLE);
	
	OLED_Init();
	while(1)
	{
		// AD的规则组是否转换完成,转换完成后返回SET
		while(ADC_GetFlagStatus(ADC2, ADC_FLAG_EOC) == RESET);
		// ADC_GetConversionValue:读取转换结果。该函数就是直接读取DR寄存器,而读取DR寄存器时会自动清除EOC标志位,所以不用手动清除EOC
		OLED_ShowNum(1,1,ADC_GetConversionValue(ADC2), 5);
		Delay_s(1);
	}
}


AD多通道转换

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

int main()
{
	// 开启GPIOA和ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 初始化PA0、PA1、PA2、PA3为模拟输入
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 对APB2的时钟6分频作为ADC的时钟
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	// 初始化ADC1
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;					// 单次转换
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;					// 数据右对齐
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	// 外部触发,软件触发
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;						// ADC独立工作模式
	ADC_InitStruct.ADC_NbrOfChannel = 1;								// 占用规则组16个序列中的1个序列
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;							// 非扫描模式
	ADC_Init(ADC1, &ADC_InitStruct);
	// 开启ADC1
	ADC_Cmd(ADC1, ENABLE);
	
	// 校准ADC
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1) == SET);
	
	// 将用到的四个模拟信号输入通道组成一个列表
	uint8_t ADC_Channel[4] = {ADC_Channel_0, ADC_Channel_1, ADC_Channel_2, ADC_Channel_3};
	
	OLED_Init();
	while(1)
	{
		for(uint8_t i = 0; i<4; i++)
		{
			// 规则组配置
			ADC_RegularChannelConfig(ADC1, ADC_Channel[i], 1, ADC_SampleTime_55Cycles5);
			
			// 软件触发转换
			ADC_SoftwareStartConvCmd(ADC1, ENABLE);
			
			// 检测是否转换完成
			while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
			
			// ADC_GetConversionValue:读取转换的结果
			OLED_ShowNum(i + 1, 1, ADC_GetConversionValue(ADC1), 5);
			Delay_ms(100);
		}
	}
}

posted @ 2024-01-25 22:59  7七柒  阅读(32)  评论(0编辑  收藏  举报