江协科技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初始化也不要忘了加上这几个引脚。

相关推荐
zyhomepage10 分钟前
科技的成就(六十四)
开发语言·人工智能·科技·算法·内容运营
太阳为你照路10 分钟前
关于洛谷中XJS-SINGA科技站点 系统讨论团队的一些介绍
科技
糊涂君-Q1 小时前
Python小白学习教程从入门到入坑------第十九课 异常模块与包【下】(语法基础)
开发语言·python·学习·程序人生·改行学it
爱编程的小新☆1 小时前
Java篇图书管理系统
java·开发语言·学习
EasyCVR2 小时前
宇视设备视频平台EasyCVR视频融合平台果园/鱼塘/养殖场/菜园有电没网视频监控方案
科技·音视频·视频监控·监控视频接入
正在努力的小立2 小时前
STM32 HAL 点灯
stm32·单片机·嵌入式硬件
致奋斗的我们2 小时前
RHCE的学习(7)
linux·服务器·网络·学习·redhat·rhce·rhcsa
孤客网络科技工作室4 小时前
深入学习 Scrapy 框架:从入门到精通的全面指南
学习·scrapy
Whappy0014 小时前
3. STM32之TIM实验--输出比较(PWM输出,电机,四轴飞行器,智能车,机器人)--(实验1:PWM驱动LED呼吸灯)
stm32·嵌入式硬件·机器人