写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.04.07
STM32开发板学习——第22节: [7-2] AD单通道&AD多通道
- 前言
- 开发板说明
- 引用
- 解答和科普
- 一、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.6usreturn 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 headervoid AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6); //分频:72Mhz/6=12MhzGPIO_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.6usreturn ADC_GetConversionValue(ADC1); //读取DR寄存器,会自动清理EOC标志位,不需要手动清除了
}
#ifndef __AD_H
#define __AD_Hvoid 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 headervoid AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6); //分频:72Mhz/6=12MhzGPIO_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.6usreturn ADC_GetConversionValue(ADC1); //读取DR寄存器,会自动清理EOC标志位,不需要手动清除了
}
#ifndef __AD_H
#define __AD_Hvoid 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多通道的读取。