江协科技STM32学习- P22 实验-ADC单通道/ADC多通道

🚀write in front🚀

🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

💬本系列哔哩哔哩江科大STM32的视频为主以及自己的总结梳理📚

🚀Projeet source code🚀

💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com

引用:

STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

Keil5 MDK版 下载与安装教程(STM32单片机编程软件)_mdk528-CSDN博客

STM32之Keil5 MDK的安装与下载_keil5下载程序到单片机stm32-CSDN博客

0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_江协科技stm32笔记-CSDN博客

【STM32】江科大STM32学习笔记汇总(已完结)_stm32江科大笔记-CSDN博客

江科大STM32学习笔记(上)_stm32博客-CSDN博客

STM32学习笔记一(基于标准库学习)_电平输出推免-CSDN博客

STM32 MCU学习资源-CSDN博客

stm32学习笔记-作者: Vera工程师养成记

stem32江科大自学笔记-CSDN博客

术语:

|--------------------------------------|---------------------------------------------|
| 英文缩写 | 描述 |
| GPIO:General Purpose Input Onuput | 通用输入输出 |
| AFIO:Alternate Function Input Output | 复用输入输出 |
| AO:Analog Output | 模拟输出 |
| DO:Digital Output | 数字输出 |
| 内部时钟源 CK_INT:Clock Internal | 内部时钟源 |
| 外部时钟源 ETR:External clock | 时钟源 External clock |
| 外部时钟源 ETR:External clock mode 1 | 外部时钟源 Extern Input pin 时钟模式1 |
| 外部时钟源 ETR:External clock mode 2 | 外部时钟源 Extern Trigger 时钟模式2 |
| 外部时钟源 ITRx:Internal trigger inputs | 外部时钟源,ITRx (Internal trigger inputs)内部触发输入 |
| 外部时钟源 TIx:external input pin | 外部时钟源 TIx (external input pin)外部输入引脚 |
| CCR:Capture/Comapre Register | 捕获/比较寄存器 |
| OC:Output Compare | 输出比较 |
| IC:Input Capture | 输入捕获 |
| TI1FP1:TI1 Filter Polarity 1 | Extern Input 1 Filter Polarity 1,外部输入1滤波极性1 |
| TI1FP2:TI1 Filter Polarity 2 | Extern Input 1 Filter Polarity 2,外部输入1滤波极性2 |

正文:

0. 概述

从 2024/06/12 定下计划开始学习下江协科技STM32课程,接下来将会按照哔站上江协科技STM32的教学视频来学习入门STM32 开发,本文是视频教程 P2 STM32简介一讲的笔记。


定时器共四个部分,分为八个小节笔记。本小节为第一部分第一节。

🌳在第一部分,是定时器的基本定时的功能:定时中断功能、内外时钟源选择

🌳在第二部分,是定时器的输出比较功能,最常见的用途是产生PWM波形,用于驱动电机等设备

🌳在第三部分,是定时器的输入捕获功能和主从触发模式,来实现测量方波频率

🌳在第四部分,是定时器的编码器接口,能够更加方便读取正交编码器的输出波形,编码电机测速


1.🚢第一个代码:AD单通道

接线图:

电位器的内部结构是这样的:

左边和右边的两个引脚接的是电阻的两个固定端,中间这个引脚接的是滑动抽头。电位器外边这里有个十字形状的槽,可以拧,往左拧抽头就往左靠,往右拧,抽头就往右靠。所以外围电路这里,我们把左边的固定端接在负极,右边的固定端接在正极,中间就可以输出,从负极到正极可调的电压了,把可调的电压输出接在PA0。

2. 🚢ADC初始化步骤

AD的初始化看这个结构图

ADC初始化的步骤具体的步骤:

**🦄第一步,开启RCC时钟,**包括ADC和GPIO的时钟。另外这里ADC CLK的分频器也需要配置一下。

🦄第二步,配置GPIO, 把需要用的GPIO配置成模拟输入的模式。

🦄第三步,配置多路开关

把左边的通道接入到右边的规则组列表里。这个过程就是我们之前说的点菜,把各个通道的菜列在菜单里。

🦄第四步,配置ADC转换器,库函数里是用结构体来配置的,可以配置这一大块电路的参数。

如果需要模拟看门狗,会有几个函数用来配置阈值和监测通道的。

如果想开启中断,就在中断输出控制里用ITconfig函数开启对应的中断输出,然后再在NVIC里配置一下优先级,这样就能触发中断了。

不过模拟看门狗中断我们本节暂时不用。

**第五步,开关控制,**调用一下ADC_Cmd的函数开启ADC。

这样ADC就配置完成了就能正常工作了。

第六步,校准

当然在开启ADC之后,根据手册里的建议,我们还可以对ADC进行一下校准,这样可以减小误差。在ADC工作的时候,如果想要软件触发转换,会有函数可以触发。如果想读取转换结果,也会有函数,可以读取结果。这个等会介绍扩函数的时候就可以看到了。

3.🚢ADC相关的库函数

首先我们看一下ADC CLK的配置函数,打开这个rcc.h文件,拖到最后。

这个函数是用来配置ADC CLK分频器的。它可以对APB2的72MHz时钟选择二、四、六、八分频,输入到ADC CLK,这就是这个函数的作用。

然后我们找一下ADC的库函数,打开adc.h文件,拖到最后。

三个初始化相关函数

这三个函数和其它模块的库函数一样,都是老朋友,不用多讲了。

ADC_Cmd

这个是用于给ADC上电的,也就是这里的开关控制

ADC_DMACmd

这个是用于开启DMA输出信号的。如果使用DMA转运数据,就得调用这个函数。这个我们下节讲DMA的时候再用。

ADC_ITConfig

中断输出控制,也就是这里用于控制某个中断能不能通过NVIC

四个校准相关函数

接下来这里有四个函数

分别是复位校准、获取复位校准状态、开始校准、获取开始校准状态,这就是用于控制校准的函数。我们在ADC初始化完成之后依次调用就行了。

ADC_SoftwareStartConvCmd

ADC 软件开始转换控制 这个就是用于软件触发的函数了 调用一下就能软件触发转换了 ****,****也就是这里的触发控制,我们目前使用软件触发。

ADC_GetSoftwareStartConvStatus

ADC获取软件开始转换状态,从名字上来看,这个函数好像是判断转换是不是正在进行的。我们是不是可以调用这个函数来判断转换是否已经结束?答案是不行的。这个函数就是用来获取 CR2 SWSTART 这一位。

在手册里可以看到这一位的作用是开始转换规则通道 由软件设置该位 启动转换 转换开始后硬件马上清除此位。

因此,ADC_SoftwareStartConvCmd这个函数就是给SWSTART位置1,以开始转换的。

而ADC_GetSoftwareStartConvStatus这个函数是返回SWSTART的状态。

由于SWSTART位在转换开始后立刻清零了。所以这个函数的返回值跟转换是否结束毫无关系。

那如何才能知道转换是否结束?

我们需要用到下面这个函数:

ADC_GetFlagStatus

获取标志位状态,然后参数给EOC的标志位,判断EOC标志位是不是置1了。如果转换结束 ,EOC 标志位 置1, ****然后调用这函数判断标志位。****这样才是正确的判断转换是否结束的方法。

所以ADC_GetSoftwareStartConvStatus这个函数其实没啥用,我们一般不用,不要被它误导了。

然后下面这两个函数是用来配置间断模式的。

第一个函数是每隔几个通道间断一次。第二个函数是是不是启用间断模式。需要间断模式的话,可以了解一下。

ADC_RegularChannelConfig

DC规则组通道配置,这个函数比较重要。它的作用就是给序列的每个位置填写指定的通道,就是填写点菜菜单的过程

第一个参数是ADCx,第二个ADC channel就是理想指定的通道。第三个rank就是序列几的位置。然后第四个sample time就是指定通道的采样时间。

ADC_ExternalTrigConvCmd

ADC外部触发转换控制,就是是否允许外部触发转换。

ADC_GetConversionValue

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

ADC_GetDualModeConversionValue

之后,ADC获取双模式转换值,这个是双ADC模式读取转换结果的函数,我们暂时不用。

以上这些函数就是对ADC的一些基本功能和规则组的配置。

九个配置ADC注入组的函数

然后接下来这里有一大批函数,里面都带了一个injected,就是注入组的意思。

这一大批函数都是对ADC注入组进行配置的。

三个模拟看门狗配置的函数

然后下面的这三个函数就是对模拟看门狗进行配置的。

第一个是是否启动模拟看门狗。第二个是配置高低阈值。第三个是配置看门的通道。

ADC_TempSensorVrefintCmd

ADC温度传感器内部参考电压控制,这个是用来开启内部的两个通道的。如果你要用这两个通道,得调用一下这个函数开启一下,要不然是读不到正确的结果的。

四个获取或清除标志位函数

分别是获取标志位状态、清除标志位、获取中断状态、清除中断挂起位,这些函数也是常用函数了,不用多说。

看完这些函数我们来开始写代码。

4.🚢代码实现

cpp 复制代码
//配置ADC是工作在独立模式,还是双ADC模式
//独立模式,ADC1和ADC2各转换各的
#define ADC_Mode_Independent                       ((uint32_t)0x00000000)
//其余的都是双ADC模式,相对比较复杂
#define ADC_Mode_RegInjecSimult                    ((uint32_t)0x00010000)
#define ADC_Mode_RegSimult_AlterTrig               ((uint32_t)0x00020000)
#define ADC_Mode_InjecSimult_FastInterl            ((uint32_t)0x00030000)
#define ADC_Mode_InjecSimult_SlowInterl            ((uint32_t)0x00040000)
#define ADC_Mode_InjecSimult                       ((uint32_t)0x00050000)
#define ADC_Mode_RegSimult                         ((uint32_t)0x00060000)
#define ADC_Mode_FastInterl                        ((uint32_t)0x00070000)
#define ADC_Mode_SlowInterl                        ((uint32_t)0x00080000)
#define ADC_Mode_AlterTrig                         ((uint32_t)0x00090000)

ADC校准,如下四个函数对应ADC校准的四个步骤

cpp 复制代码
//1.复位校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
//2.等地复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
//3.开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
//4.等待校准完成
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);

4.1 实验1-单ADC转换

代码 AD.C

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "AD.h"

void AD_Init(void)
{
	//开启RCC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//APB2 GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//APB2 GPIOA时钟
	
	//RCC_ADC Clock预分频值
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);	//RCC_ADC_Clock=72Mhz/6=12Mhz
	
	//GPIOA_Pin0配置为模拟输入
	//GPIOA_Pin0作为ADC1的输入通道1
	GPIO_InitTypeDef gpioInitStructure;
	gpioInitStructure.GPIO_Mode = GPIO_Mode_AIN;
	gpioInitStructure.GPIO_Pin = GPIO_Pin_0;
	gpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpioInitStructure);
	
	//ADC模拟输入多路开关选择
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	
	//ADC_Init初始化
	ADC_InitTypeDef ADC_InitStruct;
	ADC_StructInit(&ADC_InitStruct);
	
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//非外部触发,软件触发方式
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;			//ADC使用非扫描模式
	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;	//ADC使用非连续转换模式(单次模式)
	ADC_InitStruct.ADC_NbrOfChannel = 1;				//ADC规则组转换列表里的数目
	ADC_Init(ADC1, &ADC_InitStruct);
	

	
	
	//ADC开关
	ADC_Cmd(ADC1, ENABLE);
	
	//ADC校准
	ADC_ResetCalibration(ADC1);							//软件置标志位
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);	//当校准完成之后,硬件自动清除标志位
	ADC_StartCalibration(ADC1);							//软件置标志位
	while(ADC_GetCalibrationStatus(ADC1) == SET);		//当校准完成之后,硬件自动清除标志位
}

uint16_t GetValue(void)
{
	uint16_t ADCValue;
	
	//软件ADC触发转换
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	
	//等待ADC完成
	while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
	
	ADCValue = ADC_GetConversionValue(ADC1);	//读取清除EOC标志位
	
	return ADCValue;
}

代码AD.h

cpp 复制代码
#ifndef __AD_H__
#define __AD_H__

void AD_Init(void);
uint16_t GetValue(void);

#endif

代码 main.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"

extern uint16_t Num;

int main(int argc, char *argv[])
{	uint16_t ADValue;
	
	OLED_Init();
	OLED_ShowString(1, 1, "Cnt:");
	
	AD_Init();
	
	OLED_ShowString(1, 1, "ADValue:");
	
	
	while(1)
	{
		ADValue = GetValue();
		OLED_ShowNum(2, 1, ADValue, 6);
		Delay_ms(100);
	}
	
	return 1;
}

实验结果:

如果想显示一下实际的电压值怎么办?

这只需要对这个数据进行一个线性变换就行了。我们在上面定义一个变量表示电压

然后我们将转换的结果再进行一下运算Voltage = (float)ADValue / 4095 * 3.3;,这样就能得到电压值。

另外这里要注意因为AD value是整数,在除4095之后会舍弃掉小数部分,这样会导致计算错误。所以我们先把AD value类型强转为float,这样再除才不会出问题。

由于目前我们的OLED驱动还没有显示浮点数的函数(这个之后讲OLED的时候再加)。目前这里我们如果想显示浮点数,可以用显示整数的函数来操作。

如果直接用显示整数的函数的话,小数就会舍弃掉,所以我们要用两个显示整数的函数,将第二个显示整数的函数的值再进行一下处理变成小数显示出来,也就是先把书扩大100倍,比如原来是1.23,现在就是123,然后再对100取余,就是23,这样就把1.23的小数部分取出来了。

另外由于浮点数是不能取余的,所以(Voltage * 100)要括起来,然后再进行强制类型转换变成整数,再对它取余。这样就可以显示浮点数了。

这里实际上AD值等于4096时才对应3.3V负,会有一个数的偏差,所以AD值最大的4095实际上对应的应该是比3.3V小一丢丢,没有办法达到满量程3.3V,这个是受限于ADC的结构,具体就不再细说了。总之就是认为4095对应3.3V伏可以,认为,4096对应3.3V也可以,只有一点点偏差,也看不出来差别。

如果就只是进行阈值判断数据记录的话,也可以不进行变换,直接使用原始的AD数据,这样也是可以

连续转换模式

目前我们使用的是第一种转换方式:单次转换、非扫描。

我们还可以使用第二种转换方式:连续转换、非扫描。这个模式的好处就是不需要不断的触发,也不需要等待转换完成的。

这种模式只要对程序稍作修改就行。我们要切换为连续转换模式,那么这个参数就要改成enable。

连续转换仅需要在最开始触发一次就行了,所以这里软件触发转换的函数就可以挪到初始化的最后,即在初始化完成之后,触发一次就行了。

所以在这里就不需要判断标志位这行代码了,

直接return数据寄存器的值就行了。这样程序就是单通道连续转换非扫描的模式。

下载程序现象和刚才是一样的,也能实现单通道的AD转换。这就是连续转换非车描的模式。

4.2 实验2,多通道ADC

接线图:

在这里我们使用了四个AD通道,第一个通道还是电位器,接在PA0口。之后上面又接了三个传感器模块,分别是光敏传感器,热敏传感器、反射式红外传感器,它们的vcc和gnd都分别接在面包板的正负极。然后这个AO就是模拟量的输出引脚,三个模块的AO分别接在PA1,PA2和PA3口,加上电位器的PA0,总共是四个输入通道,同样这些GPIO口也是可以在PA0到PB1之间任意选择的。这里就选择前四个。

如何实现多通道采集?

我们首先想到的应该是后面这两种扫描模式

利用这个列表把四个通道都填进去,然后触发转换,这样就能实现多通道了。

这样确实是一种不错的方法,但是有个数据覆盖的问题。

如果想要用扫描模式实现多通道,最好要配合DMA来实现。我们下节讲完DMA之后,再来试一下扫描模式。

那我们一个通道转换完成之后,手动把数据转运出来不就行了吗?为啥非要用DMA来转运?

这个方案看似简单,但是实际操作起来会有一些问题。

  • 第一个问题就是在扫描模式下,启动列表之后,它里面每一个单独的通道转换完成之后,不会产生任何的标志位,也不会触发中断。你不知道某一个通道是不是转换完了。它只有在整个列表都转换完成之后,才会产生一次EOC标志位,才能触发中断。而这时前面的数据就已经覆盖丢失了。
  • 第二个问题就是AD转换是非常快的,刚才我们也计算过转换一个通道,大概只有几微秒。也就是说,如果你不能在几微秒的时间内把数据转运走,数据就会丢失,这对我们程序手动转移数据要求就比较高了。

所以在扫描模式下,手动转移数据是比较困难的。不过比较困难,也不是说手动转运不可行,我们可以使用间断模式,在扫描的时候,每转换一个通道就暂停一次,等我们手动把数据转运走之后再继续触发,继续下一次转换。这样可以实现手动转移数据的功能。

但是由于单个通道转换完成之后,没有标志位。所以启动转换完成之后,只能通过Delay延时的方式,延时足够长的时间,才能保证转换完成,这种方式既不能让我们省心,也不能提高效率。所以我们暂时不推荐使用。

这些方法都不行,我们本节是不是就不能实现多通道了?答案是能实现,而且非常简单,怎么实现?

我们可以使用单次转换 非扫描的模式来实现多通道。只需要在每次触发转换之前 手动更改一下列表第一个位置的通道就行了。

比如,第一次转换,先写入通道0,之后触发,等待、读值。第二次转换,再把通道0,改成通道1,之后触发,等待、读值。第三次转换,再先改成通道二修改......这样在转换前先指定一下通道,再启动转换,就可以轻松的实现多通道转换的功能了。

那么我们本次的代码就比较简单,只需要做一些简单的修改就行了。

我们可以把这个填充通道的这一句代码剪切,

然后放到触发转换之前

然后我们想指定的通道,可以作为成AD_GetValue函数的参数

这样就行了。

这样我们在调用AD_GetValue进行转换时,只需要指定一个转换的通道,返回值就是我们指定通道的结果了。

接下来我们现在要指定的通道是通道0/1/2/3,所以上面这里的GPIO初始化也不要忘了加上这几个引脚。

相关推荐
魔法阵维护师1 小时前
从零开发游戏需要学习的c#模块,第十章(设计模式入门)
学习·游戏·设计模式·c#
jllllyuz1 小时前
STM32 BMP280 I2C通信驱动程序
stm32·单片机·嵌入式硬件
GEO从入门到精通1 小时前
GEO学习能帮我提高AI搜索排名吗?
人工智能·学习
ᰔᩚ. 一怀明月ꦿ2 小时前
MySQL 学习目标
学习·mysql·adb
吃好睡好便好2 小时前
用if…end…语句计算分段函数
开发语言·人工智能·学习·算法·matlab
优信电子2 小时前
基于STM32F103C8T6单片机驱动ACS712模块进行电流检测
stm32·单片机·嵌入式硬件·嵌入式·电流检测·acs712·电流采集
测试狗科研平台2 小时前
VASP软件如何计算电子局域化函数?
科技·云计算·材料工程
爱睡懒觉的焦糖玛奇朵2 小时前
【从视频到数据集:焦糖玛奇朵的魔法工具Video To YOLO Dataset】
人工智能·python·学习·yolo·音视频
刘一说2 小时前
AI科技热点日报 | 2026年5月22日
数据库·人工智能·科技
Dust-Chasing2 小时前
Claude Code源码剖析 - Phase3
开发语言·人工智能·学习