STM32单片机入门学习——第22节: [7-2] AD单通道&AD多通道

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!

本文写于:2025.04.07

STM32开发板学习——第22节: [7-2] AD单通道&AD多通道

前言

   本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
   欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
   在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
   另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!

开发板说明

   本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
   原理图如下
1、开发板原理图
在这里插入图片描述
2、STM32F103C6和51对比
在这里插入图片描述
3、STM32F103C6核心板
在这里插入图片描述

视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。

下图是实物图
在这里插入图片描述

引用

【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
在这里插入图片描述
数据手册
在这里插入图片描述

解答和科普

一、AD单通道

在这里插入图片描述
PA0口,PA0到PB1这10个引脚是ADC的10个通道,所以可以任意接。
在这里插入图片描述
第一步,开启RCC时钟,包括ADC和GPIO的时钟,另外这里ADCCLK的分频器,也需要设置一下。
第二步,配置GPIO,把需要用的GPIO配置成模拟输入的模式。
第三步,配置这里的多路开关,把左边的通道接入到右边的规则组列表里,这个就是我们之前说的点菜,把各个通道的菜,列在菜单里。
第四步,配置ADC转换器了,在库函数里,是用结构体来配置的,可以配置这一大块电路的参数,包括ADC是单次转换还是连续转换,扫描还是非扫描、有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。
如果你需要模拟看门狗,那会有几个函数用来配置阈值和监测通道的,如果你想开启中断,那就在中断输出控制里用ITConfig函数开启对应的中断输出,然后再在NVIC中,配置一下优先级,这样就能触发中断了。
第五步,开关控制,调用一下ADC_Cmd函数,开启ADC。这样ADC就配置完成了。
当然在开启ADC的时候,还可以进行校准,这样可以减小误差。

在ADC工作的时候,如果想要软件触发转换,那会有函数可以触发,如果想读取转换结果,那也会有函数可以读取结果。

配置ADCCLK分频器,可以对APB2的72Mhz时钟选择2、4、6、8分频,输入到ADCCLK,这就是这个函数的作用,是在RCC库函数里面,不要忘了配置。

void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);

在这里插入图片描述

void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);

用来给ADC上电的,也就是开关控制;

void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);

开启DMA输出信号的,如果使用DMA转运数据,那就得调用这个函数;

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

用于控制某个中断,能不能通向NVIC;

void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);

控制校准

void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

软件触发

FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);

不能判断是不是转换结束,转换开始后马上清除此位,这个函数是返回SWSTART的状态,由于在转换开始后立刻就清零了,所以这个函数的返回值和转换是否结束,毫无关系。

FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

判断EOC标志位是否为1,判断转换是否结束;

void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

间断模式

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

是否允许外部触发转换;

uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

ADC获取转换值,就是获取AD转换的数据寄存器,读取转换结果就要使用这个函数,

uint32_t ADC_GetDualModeConversionValue(void);

ADC获取双模式转换值,这个是双ADC模式读取转换结果的函数;

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

模拟看门狗配置

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

时钟开启

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);		//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//开启GPIOA的时钟
}

配置ADC CLOCK

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//分频:72Mhz/6=12Mhz

配置GPIO

	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
	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);

在这里插入图片描述

现在的配置是:在规则组菜单列表的第一个位置,写入通道0这个通道,就是在序列1的位置,写入通道0,如果你还想在序列2的位置写入其他的通道,那就复制一下这个代码,把这个序列数改为2,然后指定你想要的通道,如果还行继续填充菜单,那就在复制进行配置。

ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,2,ADC_SampleTime_55Cycles5);

在这里插入图片描述
结构体初始化ADC

ADC_InitStructure.ADC_ExternalTrigConv=;

在这里插入图片描述
在这里插入图片描述

ADC_InitTypeDef			ADC_InitStructure;
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ;					//独立 
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;				//右对齐
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;	//软件触发没有外部触发	
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;					//连续转换还是单词转换
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;							//扫描模式
	ADC_InitStructure.ADC_NbrOfChannel=1;								//通道数目:总共用到几个通道
	ADC_Init(ADC1,&ADC_InitStructure);
	
	ADC_Cmd(ADC1,ENABLE);
	
	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);							//软件触发,ADC开始转换
	while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET);				//查看是否转换完成 68个周期 5.6us
		
	return ADC_GetConversionValue(ADC1);			//读取DR寄存器,会自动清理EOC标志位,不需要手动清除了
}

在这里插入图片描述

代码
main.c

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

uint16_t ADValue;
float  Voltage;
int main(void)
{
							
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,2,"Hello STM32 MCU");
	OLED_ShowString(2,1,"ADValue:");
	OLED_ShowString(3,1,"Voltage:0.00V");
	while(1)
	{
		ADValue=AD_GetValue();
		Voltage=(float) ADValue/4095 *3.3 ;
		OLED_ShowNum(2,9,ADValue,4);
		OLED_ShowNum(3,9,Voltage,1);
		OLED_ShowNum(3,11,(uint16_t)(Voltage*100)%100,2);
		Delay_ms(100);
	}
}

AD.CH

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);		//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//开启GPIOA的时钟
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//分频:72Mhz/6=12Mhz
	
	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_Mode=ADC_Mode_Independent ;					//独立 
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;				//右对齐
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;	//软件触发没有外部触发	
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;					//连续转换还是单词转换
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;							//扫描模式
	ADC_InitStructure.ADC_NbrOfChannel=1;								//通道数目:总共用到几个通道
	ADC_Init(ADC1,&ADC_InitStructure);
	
	ADC_Cmd(ADC1,ENABLE);
	
	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);							//软件触发,ADC开始转换
	while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET);				//查看是否转换完成 68个周期 5.6us
		
	return ADC_GetConversionValue(ADC1);			//读取DR寄存器,会自动清理EOC标志位,不需要手动清除了
}

#ifndef    __AD_H
#define    __AD_H

void AD_Init(void);
uint16_t  AD_GetValue(void);

#endif

实验现象

AD单通道

连续模式,非扫描
在这里插入图片描述

ADC_InitTypeDef			ADC_InitStructure;
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ;					//独立 
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;				//右对齐
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;	//软件触发没有外部触发	
	ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;					//连续转换还是单词转换
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;							//扫描模式
	ADC_InitStructure.ADC_NbrOfChannel=1;								//通道数目:总共用到几个通道
	ADC_Init(ADC1,&ADC_InitStructure);
	
	ADC_Cmd(ADC1,ENABLE);
	
	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)
{
				
	return ADC_GetConversionValue(ADC1);			
}

在这里插入图片描述

二、AD多通道

在这里插入图片描述
AO分别接在PA1、PA2、PA3口;

在扫描 模式下,你启动列表之后,它里面每一个单独的通道转换完成之后不会产生标志位,也不会触发中断,你不知道某一个通道是不是转换完成了,它只有在整个列表都转换完成后,才会产生一次EOC标志位,才能触发中断,而这时前面的数据已经被覆盖丢失了。
第二个问题是,AD转换是非常快的,几us,手动转移太高,可以使用间断模式,每转换一个通道就暂停一次,等我们手动转运之后,再继续触发,继续下一次转换;只能通过延迟足够的时间,太蛮烦;

在这里插入图片描述
只需要在每次出发转换之前,手动更改一下列表第一个位置的通道就行了,在转换前,先制定一下通道,再启动转换,就可以实现多通道了。
main.c

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

uint16_t AD0,AD1,AD2,AD3;


int main(void)
{
							
	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);
	}
}

AD.ch

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);		//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//开启GPIOA的时钟
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//分频:72Mhz/6=12Mhz
	
	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);	
	

	

	ADC_InitTypeDef			ADC_InitStructure;
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ;					//独立 
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;				//右对齐
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;	//软件触发没有外部触发	
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;					//连续转换还是单词转换
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;							//扫描模式
	ADC_InitStructure.ADC_NbrOfChannel=1;								//通道数目:总共用到几个通道
	ADC_Init(ADC1,&ADC_InitStructure);
	
	ADC_Cmd(ADC1,ENABLE);
	
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1)== SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1)== SET);
}

uint16_t  AD_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)== RESET);				//查看是否转换完成 68个周期 5.6us
		
	return ADC_GetConversionValue(ADC1);			//读取DR寄存器,会自动清理EOC标志位,不需要手动清除了
}

#ifndef    __AD_H
#define    __AD_H

void AD_Init(void);
uint16_t  AD_GetValue(uint8_t ADC_Channel);

#endif

实验现象

AD多通道

问题

总结

本节课主要是学习了AD通道的代码配置,如何对每个部分配置:
第一步,开启RCC时钟,包括ADC和GPIO的时钟,另外这里ADCCLK的分频器,也需要设置一下。
第二步,配置GPIO,把需要用的GPIO配置成模拟输入的模式。
第三步,配置这里的多路开关,把左边的通道接入到右边的规则组列表里,这个就是我们之前说的点菜,把各个通道的菜,列在菜单里。
第四步,配置ADC转换器了,在库函数里,是用结构体来配置的,可以配置这一大块电路的参数,包括ADC是单次转换还是连续转换,扫描还是非扫描、有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。
如果你需要模拟看门狗,那会有几个函数用来配置阈值和监测通道的,如果你想开启中断,那就在中断输出控制里用ITConfig函数开启对应的中断输出,然后再在NVIC中,配置一下优先级,这样就能触发中断了。
第五步,开关控制,调用一下ADC_Cmd函数,开启ADC。这样ADC就配置完成了。
当然在开启ADC的时候,还可以进行校准,这样可以减小误差。
最后启动转换,读取转换完成后的值。
多通道是单此转换,把序列1的位置换成要读的通道,实现了AD多通道的读取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值