【STM32】ADC模数转换器

2023-12-14 12:42:53

1?ADC简介

ADC(Analog-Digital Converter)模拟-数字转换器

ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁

STM32是数字电路,只有高低电平,没有几V电压的概念,想读取电压值,就需要借助ADC模数转换器了。DAC则是相反的功能。

12位逐次逼近型ADC1us转换时间

分辨率:一般用多少位来表示,0?~ 2 ^ 12 - 1(量化结果0~4095)
转换时间:也是转换频率,从转换开始到产生结果需要花1us时间,即转换频率是1MHz

输入电压范围:0~3.3V,转换结果范围:0~4095

线性对应

18个输入通道,可测量16个外部和2个内部信号源

16个GPIO口、内部温度传感器(测量CPU的温度)和内部参考电压(1.2V的基准电压)

规则组和注入组两个转换单元

模拟看门狗自动监测输入电压范围

STM32F103C8T6 ADC资源:ADC1ADC210个外部输入通道

1.1?逐次逼近型ADC

ADC0809的内部结构图,独立的8位逐次逼近型ADC芯片。

左边是8路输入通道,通过通道选择开关,选中一路,输入到比较器前进行转换;下面是地址锁存和译码(想选中哪个通道,就把通道号放在这三个引脚上,再给一个锁存信号,上面对应的通路开关就可以自动拨好了,相当于38译码器)

比较器有两个输入端,一个是待测电压,另一个是DAC(数模转换器)的电压输出端;如果DAC输出的电压比较大,就调小DAC数据;如果DAC输出的电压比较小,就增大DAC数据;直到DAC输出的电压和外部通道输入的电压近似相等,这样DAC输入的数据就是外部电压的编码数据了,这就是DAC的实现原理。一般使用二分法调节。

1.2?ADC框图

具体的

有温度传感器、内部参考电压,总共18个输入通道,接到模拟多路开关,其输出接到模数转换器,即逐次比较,转换结果放在数据寄存器里。

普通的的流程是:多路开关选中某一个通道,开始转换,等待转换完成,取出结果。

这里比较高级,可以同时选中多个,而且在转换的时候,还分成了两个组,规则通道组注入通道组其中规则组可以一次性最多选择16个通道,注入组一次最多选择4个通道。规则组的数据寄存器只能存储一个结果,如果不想之前的结果被覆盖,那在转换完成之后,要尽快把结果拿走(使用DMA转运数据);注入组可以存4个结果,不用担心数据覆盖。

左下角是触发转换部分,对于STM32的ADC,触发ADC开始转换的信号有两种:一种是软件触发,程序中调用一条代码;另一种是硬件触发,就是这些触发源。上面是注入组触发源,下面是规则组触发源。这些触发源主要来自定时器,有定时器的各个通道,还有TRGO定时器主模式的输出。定时器可以通向ADC、DAC这些外设,用于触发转换。因为ADC经常需要过一个固定的时间段转换一次,正常的思路是:用定时器,每隔1ms申请一次中断,在中断里手动开始一次转换;但是频繁进中断对程序是有影响的,所以对这种需要频繁进中断,并且在中断里只完成简单的工作的情况,一般都会有硬件的支持。还可以选择外部中断引脚来触发转换。

VREF+、VREF-是ADC的参考电压,决定了ADC的输入电压范围,VDDA、VSSA是ADC的供电引脚,一般情况下VREF+接VDDA,VREF-接VSSA。

右边这里是ADCCLK是ADC的时钟,是用于驱动内部逐次比较的时钟,这个时钟来自ADC的预分频器,而ADC预分频器来自RCC的。

模拟看门狗里面可以存储阈值高限和阈值底限,如果指定了模拟看门狗,并且指定了看门的通道,越限之后,它就会乱叫,在上面申请一个模拟看门狗中断,最后通向NVIC。

对于规则组和注入组,转换完成之后,也会有一个EOC转换完成的信号,EOC是规则组的完成信号,JEOC是注入组完成的信号,这两个信号会在状态寄存器里置一个标志位,读取这个标志位就可以知道是不是转换完成了;同时这两个标志位也可以去到NVIC,申请中断,如果开启了NVIC对应的通道,它们就会触发中断。

1.3?ADC基本结构

左边是输入通道,16个GPIO口外加两个内部通道,然后进入AD转换器。AD转换器里有两个组,一个是规则组,一个是注入组,规则组最多选择16个通道,注入组最多选择4个通道,然后转换的结果存在AD数据寄存器里,其中规则组只有一个数据寄存器,而注入组有4个数据寄存器。下面有触发控制,触发控制可以选择软件触发和硬件触发,硬件触发主要是来自定时器,当然也可以选择外部中断的引脚;右边是来自RCC的ADC时钟CLOCK,ADC逐次比较的功能就是这个时钟推动的。然后上面可以布置一个模拟看门狗用于检测转换结果的范围,如果超出设定的阈值,就通过中断输出控制,向NVIC申请中断。另外规则组和注入组完成之后会有个EOC信号,它会置一个标志位,当然也可以通向NVIC。右下角有个开关控制,在库函数中就是ADC_Cmd,用于给ADC上电的。

1.4?输入通道

通道

ADC1

ADC2

ADC3

通道0

PA0

PA0

PA0

通道1

PA1

PA1

PA1

通道2

PA2

PA2

PA2

通道3

PA3

PA3

PA3

通道4

PA4

PA4

PF6

通道5

PA5

PA5

PF7

通道6

PA6

PA6

PF8

通道7

PA7

PA7

PF9

通道8

PB0

PB0

PF10

通道9

PB1

PB1

通道10

PC0

PC0

PC0

通道11

PC1

PC1

PC1

通道12

PC2

PC2

PC2

通道13

PC3

PC3

PC3

通道14

PC4

PC4

通道15

PC5

PC5

通道16

温度传感器

通道17

内部参考电压

ADC通道和引脚复用的关系。

ADC12_IN0的意思是ADC1和ADC2的IN0都是在PA0上的。以此类推。双ADC模式。

1.5?规则组的转换模式

在ADC初始化中会有两个参数,一个是选择单次转换还是连续转换;另一个是扫描模式还是非扫描

1.5.1?单次转换,非扫描模式

这个列表就是规则组里面的菜单,有16个空位。可以在这里写入要转换的通道,比如通道2,在非扫描的模式下,这个菜单就只有第一个序列1的位置有效。这时菜单同时选中一组的方式就退化成简单地选中一个的方式了,在这里可以在序列1的位置指定想转换的通道,比如通道2,然后触发转换,ADC就会对通道2进行模数转换,过一小段时间后,转换完成,转换结果放在数据寄存器里,同时给EOC标志位置1,整个转换过程就结束了。判断EOC标志位来确定转换是否完成。如果想再启动一次转换,那就需要再触发一次,转换结束,置EOC标志位,读结果。如果想换一个通道转换,那就在转换之前把第一个位置的通道2改成其他通道,再启动转换就可以了。这就是单次转换、非扫描的转换模式。

1.5.2?连续转换,非扫描模式

首先还是非扫描模式。所以菜单列表就只用第一个,然后与上一个单次转换不同的是它在一次转换结束后不会停止,而是立刻开始下一轮的转换,然后一直持续下去。只触发一次就可以转换了。

好处是:开始转换之后不需要等待一段时间,因为一直在转换,所以也不需要手动开始转换,也不用判断是否结束,想要读AD值的时候,直接从寄存器取就是了。

1.5.3?单次转换,扫描模式

单次转换,每触发一次,转换结束后,就会停下来。扫描模式会用到菜单列表,选择通道,可以任意指定,可以重复,初始化结构体中有个参数指定通道数目。这里为了防止数据被覆盖,就需要用DMA及时将数据挪走,7个通道转换完成之后,产生EOC信号,转换结束。再触发下一次,开始新的转换。

1.5.4?连续转换,扫描模式

在上一个模式的基础上变了,就是一次转换完成后,立刻开始下一次转换。

还有间断模式,每隔几个转换,暂停一次需要再次触发才能继续。

1.6?触发控制

这个表是规则组的触发源,有来自定时器的信号,也有来自外部引脚/片上外设的信号,具体需要AFIO重映射。还有软件触发。这些触发信号通过右边寄存器的位来完成。

1.7?数据对齐

ADC是12位的,但是数据寄存器是16位的,因此存在一个数据对齐的问题

数据右对齐(一般是这个)

数据左对齐

有点像大端模式,小端模式

1.8?转换时间

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

STM32 ADC的总转换时间为:TCONV = 采样时间 + 12.5ADC周期(12位)

例如:当ADCCLK=14MHz,采样时间为1.5ADC周期

TCONV = 1.5 + 12.5 = 14ADC周期 = 1μs

1.9?校准

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

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

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

1.10?硬件电路

第一个是电位器产生一个可调的电压,这里电位器的两个固定端,一个接3.3,一个接GND,这样中间的滑动端就可以输出一个0~3.3的可调电压输出了。这里可以接ADC的输入通道,比如PA0口;滑动端往上滑时,电压增大,往下滑时,电压减小。阻值不宜太小。

第二个是传感器输出电压的电路,一般是光敏电阻、热敏电阻、红外接收管、麦克风等。串联分压,当传感器阻值变小时,下拉作用变强,输出端电压就下降;反之,输出端电压增大。

第三个是电压转换电路,比如想测0~5V的VIN电压,但是ADC只能接收0~3.3V的电压,分压。

手册

2?AD单通道

2.1?接线图

2.2?模块封装

按这个初始化

相关库函数

// 在rcc.h中
// 配置ADCCLK分频器,2/4/6/8分频
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);



void ADC_DeInit(ADC_TypeDef* ADCx);                                    // 恢复缺省设置
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);     // 初始化
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);                  // 结构体初始化
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);             // 给ADC上电的

// 中断输出控制,用于某个中断,能不能通往NVIC
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);

// 配置间断模式
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);

// ADC外部触发转换控制
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC获取转换值
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

// ADC获取双模式转换值
uint32_t ADC_GetDualModeConversionValue(void);

// 获取标志位状态/清除标志位状态/获取中断状态/清除中断挂起位
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);

// 带Injected是注入组的函数

版本1:单次转换非扫描

AD.c

#include "stm32f10x.h"                  // Device header

// AD初始化函数
void AD_Init(void)
{
	// 1开启RCC时钟,ADC、GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);			// 72M / 6 = 12M
	
	// 2配置GPIO模拟输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;        // 模拟输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 3配置多路开关,选择规则组的输入通道,指定通道/规则组序列器的次序/通道采样时间
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	
	// 4配置ADC转换器
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;					// 右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;		// 外部触发选择,不使用外部触发
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;						// 工作模式(独立/双ADC)
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;						// 单次/连续转换
	ADC_InitStructure.ADC_NbrOfChannel = 1;									// 通道数目
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;							// 扫描/非扫描转换
	// 单次转换非扫描模式
	// 单次转换/连续转换  扫描/非扫描
	ADC_Init(ADC1, &ADC_InitStructure);
	
	// 5开关控制
	ADC_Cmd(ADC1, ENABLE);
	
	// 6校准
	ADC_ResetCalibration(ADC1);								// 复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);		// 等待复位校准完成
	ADC_StartCalibration(ADC1);								// 开始校准
	while(ADC_GetCalibrationStatus(ADC1) == SET);			// 等待校准完成
}

// 获取转换结果
uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);  				// 软件触发转换
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	// 等待转换完成
	return ADC_GetConversionValue(ADC1);					// 返回转换值
}

版本2:连续转换非扫描

#include "stm32f10x.h"                  // Device header

// AD初始化函数
void AD_Init(void)
{
	// 1开启RCC时钟,ADC、GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);			// 72M / 6 = 12M
	
	// 2配置GPIO模拟输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;        // 模拟输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 3配置多路开关,选择规则组的输入通道,指定通道/规则组序列器的次序/通道采样时间
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	
	// 4配置ADC转换器
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;					// 右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;		// 外部触发选择,不使用外部触发
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;						// 工作模式(独立/双ADC)
//	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;						// 单次/连续转换
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;						// 单次/连续转换
	ADC_InitStructure.ADC_NbrOfChannel = 1;									// 通道数目
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;							// 扫描/非扫描转换
	// 单次转换非扫描模式
	// 单次转换/连续转换  扫描/非扫描
	ADC_Init(ADC1, &ADC_InitStructure);
	
	// 5开关控制
	ADC_Cmd(ADC1, ENABLE);
	
	// 6校准
	ADC_ResetCalibration(ADC1);								// 复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);		// 等待复位校准完成
	ADC_StartCalibration(ADC1);								// 开始校准
	while(ADC_GetCalibrationStatus(ADC1) == SET);			// 等待校准完成
	
	// 加到这里触发一次
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);  				// 软件触发转换
}

// 获取转换结果
uint16_t AD_GetValue(void)
{
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);  				// 软件触发转换
//	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	// 等待转换完成
	return ADC_GetConversionValue(ADC1);					// 返回转换值
}

2.3?主函数

main.c

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

uint16_t ADValue;
float voltage;

int main()
{
	OLED_Init();								// 初始化OLED
	AD_Init();
	
	OLED_ShowString(1, 1, "ADValue:");
	OLED_ShowString(2, 1, "Voltage:0.00v");		// 将转换结果换算成电压值
	
	
	while (1)
	{
		ADValue = AD_GetValue();
		voltage = (float)ADValue / 4095 * 3.3;						// 实际电压值
		OLED_ShowNum(1, 9, ADValue, 4);
		OLED_ShowNum(2, 9, voltage, 1);								// 显示整数部分
		OLED_ShowNum(2, 11, (uint16_t)(voltage * 100) % 100, 2);	// 显示小数
		Delay_ms(100);
	}
}

3?AD多通道

3.1?接线图

使用了4个通道

3.2?模块封装

单次转换非扫描

#include "stm32f10x.h"                  // Device header

// AD初始化函数
void AD_Init(void)
{
	// 1开启RCC时钟,ADC、GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);			// 72M / 6 = 12M
	
	// 2配置GPIO模拟输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;        // 模拟输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 3配置多路开关,选择规则组的输入通道,指定通道/规则组序列器的次序/通道采样时间
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	
	// 4配置ADC转换器
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;					// 右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;		// 外部触发选择,不使用外部触发
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;						// 工作模式(独立/双ADC)
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;						// 连续转换
	ADC_InitStructure.ADC_NbrOfChannel = 1;									// 通道数目
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;							// 非扫描转换
	// 单次转换非扫描模式
	// 单次转换/连续转换  扫描/非扫描
	ADC_Init(ADC1, &ADC_InitStructure);
	
	// 5开关控制
	ADC_Cmd(ADC1, ENABLE);
	
	// 6校准
	ADC_ResetCalibration(ADC1);								// 复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);		// 等待复位校准完成
	ADC_StartCalibration(ADC1);								// 开始校准
	while(ADC_GetCalibrationStatus(ADC1) == SET);			// 等待校准完成
}

// 获取转换结果
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
	// 3配置多路开关,选择规则组的输入通道,指定通道/规则组序列器的次序/通道采样时间
	// 填充通道
	ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);  				// 软件触发转换
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	// 等待转换完成
	return ADC_GetConversionValue(ADC1);					// 返回转换值
}

3.3?主函数

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

uint16_t AD0, AD1, AD2, AD3;

int main()
{
	OLED_Init();       						// 初始化
	AD_Init();
	OLED_ShowString(1, 1, "AD0:");
	OLED_ShowString(2, 1, "AD1:");
	OLED_ShowString(3, 1, "AD2:");
	OLED_ShowString(4, 1, "AD3:");
	
	
	while (1)
	{
		AD0 = AD_GetValue(ADC_Channel_0);
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		
		OLED_ShowNum(1, 5, AD0, 4);
		OLED_ShowNum(2, 5, AD1, 4);
		OLED_ShowNum(3, 5, AD2, 4);
		OLED_ShowNum(4, 5, AD3, 4);
		
		Delay_ms(100);
		
	}
}

文章来源:https://blog.csdn.net/Zhouzi_heng/article/details/134904279
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。