STM32F103C8T6标准库ADC单通道和多通道(源码+原理)

ADC

ADC 是 “Analog - to - Digital Converter” 的缩写,即模拟数字转换器

简介

单片机只有高电平和低电平两种状态,想要将模拟信号(光,声音等)转为数字信号,来反映模拟信号的大小,需要借助ADC来实现,ADC读取引脚的电压,将读到的值准换成一个数据,并存到寄存器里。

 

输入电压范围,一般是单片机的输入电压的正负极,这个芯片是0~3.3V

12位逐次逼近,0~4095

16个外部,就是16个引脚,可以通过引脚进行输入电压,进行读取,属于这个系列芯片拥有的最多通道数目,但是STM32F103C8T6只有十个外部通道,两个ADC内部通道

其中2个内部,内部温度传感器和内部参考电压(1.2V基准电压固定不变,无论输入电压是多少),

1)可以通过温度传感器读取CPU的温度,

2)内部参考电压可以用来校准,当电源供电不等于3.3V,可能就会出现误差,假如输入的电压为3.0V,那么寄存器的最大值4095对应的就是3V,如果电源输入是3.3V,那么4095就是3.3V,所以可以通过这个电压值进行校准

1微妙转换时间,说明ADC转换的频率是1MHZ,如果对数据读取的频率有更高的要求,就需要考虑其他单片机。

 

规则组和注入组:普通的ADC是启动一次,转换,读值,...然后再启动、转换、读值

32单片机可以进行分组,列一个组,一次性启动一个组,进行多次转换,这就叫做规则组。规则组是常规使用的,注入组是用于突发情况的

 

看门狗自动监测,通常用于测试温度和光度,并且当温度或者光度超过某一个阈值,就会经行对应的操作,说白了看门狗就是用来监测指定的一个通道,超出阈值的范围就会申请中断,可以再中断里头执行一些操作。

ADC0809

这里介绍的是ADC0809,由于以前的单片机功能不是很强大,想要实现ADC需要外挂ADC模块ADC0809是以前一个外挂ADC设备,通过学习这个,了解原理

 

8个信号输入通道,输入一个位置编码的电压

DAC,给定一个电压,输出对应的编码,通过比较器,进行比较,如果比较之后DAC输出的数据比较大,就降低DAC的电压,反之就调高,对于ADC0809,有八位所存缓冲器(就是将电压转换为编码的八位二进制数),

这个比较的过程就是这八位从高到低依次确定的过程,最多八次就能够确定电压的编码。对于stm32F03C8T6有12位,最多12次就能确定输入电压的编码啦~

 

 

 

 

为什么要设计两个ADC呢?

这里涉及到双ADC模式,双ADC可以进一步提高对同一个通道的数据采样频率,也可以分开测试两个通道,(了解即可)

工作模式

连续转换扫描,单次转换扫描,连续转换非扫描,单次转换非扫描

所以在初始化ADC时,需要两个参数,来确定1.转换(连续/单次)和 是否为扫描模式

1)单次转换,非扫描模式

 

在非扫描模式下,只有序列1有效,将选择的通道填入序列1,开始进行转换,转换完成后会置标记为EOC为1,一次转换完成,就可以读取寄存器的数据啦~,想要在进行第二次转换,就需要再次重新手动触发,单次转换是不会进行下一次转换的,如果想要转换另一个通道的数据,就需要提前更换序列填入的通道,在进行转换。

 

2)连续转换,非扫描模式

连续转换,就是会一致转换,当想要读取AD值时,直接读取寄存器的数据就可以了,不需要对标记为进行判断

 

3)单次转换,扫描模式

 

因为是单次转换,在每次完成一次扫描之后,需要手动进行下一次扫描,而且和前面第一种介绍的模式不一样,同时扫描模式,就会用到菜单(多个序列),所以在进行扫描模式下,在初始化ADC的时候就要多配置一个参数,参数表示用到的序列数目,并且每个序列需要指定特定的通道,,通道也可以重复。

详细过程:

假设用到7个序列,在触发之后,就会依次从序列1到7经行AD转换,每读取一次,就会放到寄存器里边,为了防止寄存器的数据被覆盖,就需要结合DMA来进行数据运转,完成7个通道转换完成之后,EOC才会置1

 

4)连续转换,扫描模式

 

在 3 模式基础上,不需要手动进行后面的触发,触发一次后会自动进行下一次转换

 

5)(了解)在扫描模式下,还有一种间断模式,这种模式就是,在读取完几个通道后,会停止扫描,需要手动触发,

(由于篇幅过多,再加这个模式,可能会太多太乱了,所以江科大也没有过多赘述)

 

 

 

触发源

 

 

红色圈是ADC的触发源

有内部时钟源,也有EXTI线11/TIM_8 TRGO事件,在使用这个的时候,需要通过AFIO重映射,来选择具体是 外部引脚,还是内部时钟源 来作为触发源

 

ACD数据对其问题

由于ADC转换的数据是12位的,但是寄存器是16位的,就要解决数据对其问题

有两种数据存放的方法

 

1)将数据放到寄存器的低八位——右对齐(较常用)

在读寄存器的时候,可以直接读出来,就是ADC的值,读取范围12位 0~4095

 

2)将数据放到寄存器的高八位——左对齐

这样,读出来的值就会比原来的大16倍,(二进制左移一位相当于*2,这里左移了4位相当于*16)

 

左对其可以解决精度问题,如果不需要0~4095的精度,可以使用较低的精度,就可以舍弃后面的几位,直接读取前面几位就可以啦!

 

ADC校准

 

校准使用的时候实际是要进行两次校准

1)ADC寄存器校准

2)ADC校准(上面截图)

 

ADC代码实现

单道

一、ADC初始化

void InitADC(void){
	//开启ADC和内部时钟,配置ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//内部时钟6分频,
	
//	配置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);
	
	//配置序列号对应的通道
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//单次转换
	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_NbrOfChannel=1;//选择通道1
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;//不使用扫描模式
	
	ADC_Init(ADC1,&ADC_InitStructure);//ADC初始化
	ADC_Cmd(ADC1,ENABLE);//使能ADC
	
	
	ADC_ResetCalibration(ADC1);//开启ADC寄存器重置校准,
	while( ADC_GetResetCalibrationStatus( ADC1));//确保寄存器的数据已经重置 返回set表示正在重置,reset表示重置完成
	ADC_StartCalibration(ADC1);//ADC较准
	while(ADC_GetCalibrationStatus( ADC1));//确保ADC校准完成 返回set表示正在重置,reset表示重置完成
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
}

1)先开启内部时钟源,和ADC时钟

还要进行ADC时钟配置(分频,最大是14MHZ)

	//开启ADC和内部时钟,配置ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//内部时钟6分频,

2)GPIO,看好通道和GPIO的对应关系,并且进行通道配置

我用的是PA0,对应通道0,在进行配置序列号对应的通道时,第二个参数是通道0

//	配置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);
	
	//配置序列号对应的通道
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);

 

3)配置ADC模块,并且使能ADC

ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//单次转换
	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_NbrOfChannel=1;//选择通道1
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;//不使用扫描模式
	
	ADC_Init(ADC1,&ADC_InitStructure);//ADC初始化
	ADC_Cmd(ADC1,ENABLE);//使能ADC

4)配置完成后进行校准,校准是在初始化进行校准,不是每次调用前校准,上面ADC校准提到建议每次上电时,校准,

  • 校准耗时:ADC 校准需要一定时间,因为它涉及到 ADC 内部的硬件操作和计算,以确定并补偿偏移和增益误差。在初始化时进行校准,可避免每次启动 ADC 都花费额外时间,使系统在后续运行中能更高效地进行模数转换。
  • 稳定性:在正常使用环境下,ADC 的误差特性在短时间内相对稳定。只要系统的环境条件(如温度、电源电压等)没有发生显著变化,一次校准的结果可以在一段时间内保持有效。因此,在初始化时完成校准足以保证 ADC 在后续运行中的准确性。
ADC_ResetCalibration(ADC1);//开启ADC寄存器重置校准,
	while( ADC_GetResetCalibrationStatus( ADC1));//确保寄存器的数据已经重置 返回set表示正在重置,reset表示重置完成
	ADC_StartCalibration(ADC1);//ADC较准
	while(ADC_GetCalibrationStatus( ADC1));//确保ADC校准完成 返回set表示正在重置,reset表示重置完成

ADC使用

uint16_t ADC_GetValue(void){
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
	while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//判断是否已经完成一次转换的标志,EOC置一时,表示已经完成
	return ADC_GetConversionValue(ADC1);//返回寄存器的值
}

main

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "ADC.h"
int main(void){
	OLED_Init();
	InitADC();
	uint16_t value;
	OLED_ShowString(1,1,"value:");
	while(1){
		value=ADC_GetValue();
		OLED_ShowNum(1,7,value,4);
		Delay_ms(500);
	}
}

多道

更改初始化GPIO,匹配通道代代码,移到调用ADC前更换,提前更换通道,在进行ADC转换

void InitADC(void){
	//开启ADC和内部时钟,配置ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//内部时钟6分频,
	
//	配置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);
	
	//配置序列号对应的通道
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//单次转换
	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_NbrOfChannel=1;//选择通道1
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;//不使用扫描模式
	
	ADC_Init(ADC1,&ADC_InitStructure);//ADC初始化
	ADC_Cmd(ADC1,ENABLE);//使能ADC
	
	
	ADC_ResetCalibration(ADC1);//开启ADC寄存器重置校准,
	while( ADC_GetResetCalibrationStatus( ADC1));//确保寄存器的数据已经重置 返回set表示正在重置,reset表示重置完成
	ADC_StartCalibration(ADC1);//ADC较准
	while(ADC_GetCalibrationStatus( ADC1));//确保ADC校准完成 返回set表示正在重置,reset表示重置完成
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
}

在上面的基础上,改一下这个函数,将原先ADC初始化的序列匹配通道代码调下面

将通道作为参数,

uint16_t ADC_GetValue( uint8_t ADC_Channel ){
	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
	while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//判断是否已经完成一次转换的标志,EOC置一时,表示已经完成
	return ADC_GetConversionValue(ADC1);//返回寄存器的值
}

主函数调用时,记得给这个参数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "ADC.h"
int main(void){
	OLED_Init();
	Init_ADC();
	uint16_t value0;
	uint16_t value1;
	uint16_t value2;
	uint16_t value3;

	OLED_ShowString(1,1,"value0:");
	OLED_ShowString(2,1,"value1:");
	OLED_ShowString(3,1,"value2:");
	OLED_ShowString(4,1,"value:"); 
	
	while(1){
		value0=ADC_GetValue(ADC_Channel_0);
		value1=ADC_GetValue(ADC_Channel_1);
		value2=ADC_GetValue(ADC_Channel_2);
		value3=ADC_GetValue(ADC_Channel_3);
		OLED_ShowNum(1,8,value0,4);
		OLED_ShowNum(2,8,value1,4);
		OLED_ShowNum(3,8,value2,4);
		OLED_ShowNum(4,8,value3,4);
		Delay_ms(500);
	}
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值