笔记10:STM32F407ADC简介+定时器触发ADC1

ADC,即Anolog to Digital Converter,模拟信号到数字信号的转换。
模拟信号:连续的信号,就像连续函数一样的
数字信号:离散的信号,数字信号,就像点一样。
举例:若我们要将3.3V的交流电压,正弦信号转化为数字信号,若3.3V用数字信号100表示,则1.65V则用熟悉信号50表示。
一. 采集信号范围:0-3.3V

ADC 输入范围为: VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、 VDDA 、 VSSA、这四个外部引脚决定.在设计时,已经将VREF-与VSSA(模拟地)接在一起,而VREF+=3.3V,因此输入范围为0-3.3V。
在这里插入图片描述
问题:若想要测超出这个范围的电压怎么办?举例,测量-10V —10V
对输入信号进行外部处理。
-10V->0V 10V->3.3V
在这里插入图片描述
在这里插入图片描述
电阻的确定:先确定其中两个,再确定最后一个。

二. 3路18通道

  1. STM32F407有三路ADC,分别是ADC1,ADC2,ADC3,每一路有18个通道,有16个外部可以看到的通道,有两个内部通道。但是并不是说有三路,每路18个通道就总共有48个(不同)通道,三路有些通道是一样的,加起来外部总的有23个通到可以使用。对于ADC1来说,只有PA6,PB0,PB1,PC2没有被复用过,可以纯净的用作ADC1,其他的I/O口已经在其他地方使用,可能采集的信号会有杂波,使用被复用过的I/O口时,可以在使用之前做一个小测试,将I/O单独接入0V或者3.3V,观察能否采集到这两个极值,如果能的话,就说明基本不会产生杂波影响。(野火版的)
    在这里插入图片描述(野火版F407 ADC与I/O的对应)
    在这里插入图片描述
    (正点原子版F407与I/O的对应)

  2. 通道类型:规则通道和注入通道
    在这里插入图片描述
    什么是注入通道,什么是规则通道?
    注入通道:可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像。因此注入通道只有在规则通道存在时才会出现。
    规则通道就是很规矩的意思,我们平时一般使用的就是这个通道,或者应该说我们用到的都是这个通道
    三 .通道的优先顺序
    通道的优先顺序由以下寄存器控制,详见STM32F4XX中文参考手册
    在这里插入图片描述

  3. 规则序列通道的优先顺序
    在这里插入图片描述
    说明:SQR1的SQL决定需要控制的通道数,取值范围在1-16。若你想让通道0优先顺序为1,则你在SQR3的SQ1[4:0]位写入0x00就好了。

  4. 注入通道的优先顺序
    在这里插入图片描述
    四.触发源:告诉ADC开始采集的这个信号就叫触发
    在这里插入图片描述
    详见STM32F4XX中文参考手册:
    在这里插入图片描述
    五.转换时间

  5. 转换时间=采样周期(自己设置,最大为3,也是最快的)+分辨率个周期(12位分辨率就是12个周期)
    ADC的周期:ADC_ CLK(挂在APB2总线下) : ADC模拟电路时钟,最大值为36M,由PCLK2提供,还可分频,2/4/6/8 , ADC_ CCR的ADCPRE[1:0]设置。PCLK2= 84M。有关ADC_ CLK时钟的具体描述参考datasheet:5.3.21
    数字时钟: RCC APB2ENR ,用于访问寄存器

  6. 采样时间
    采样时间: ADC需要若干个ADC_ CLK周期完成对输入的模拟量进行采样,采样的周期数可通过ADC采样时间寄存器ADC SMPR1和ADC_ _SMPR2中的SMP[2:0]位设置,

ADC_ SMPR2控制的是通道0~9,ADC_ SMPR1控制的是通道10~17。每个通道可以分别用不同的时间采样。其中采样周期最小是3个,即如果我们要达到最快的采样, 那么应该设置采样周期为3个周期,这里说的周期就是1/ADC_ CLK。

在这里插入图片描述
举例:最短的转换时间:Tconv=采样时间+12个周期(12)
PCLK2=84M,ADC_CLK=84/4=21M
Tconv =3+12 = 15周期 = 15/21us=0.71us
六.转换方式

在这里插入图片描述
七.数据寄存器
一切准备就绪后,ADC转换后的数据根据转换组的不同,规则组的数据放在ADC_ DR寄存器,注入组的数据放在JDRx。如果是使用双重或者三重模式那规矩组的数据:是存放在通用规则寄存器ADC_ CDR内的。
在这里插入图片描述

  1. 规则数据寄存器:ADC_ DR寄存器
    在这里插入图片描述
    1.1. 只有16位有效数据,用于存放单个ADC模式转换完成数据
    1.2. 根据ADC_CR2:ALIGN位选择数据对齐方式
    1.3. 只有一个规则通道数据寄存器,由于数据都是存放在这个数据寄存器中,如果不及时取走,数据就会被覆盖,因此多通道的时候最好使用中断或着DMA 模式。我们可以将每一个通道的数据放入不同数组,再通过DMA得到数据。
  2. 注入数据寄存器
    在这里插入图片描述
    2.1. 只有16位有效数据,用于存放单个ADC模式转换完成数据
    2.2. 根据ADC_CR2:ALIGN位选择数据对齐方式
    2.3. 有四个这样的寄存器
  3. ADC_CDR
    在这里插入图片描述

在这里插入图片描述
1-32位有效,双重或者三重ADC使用,必须配合DMA
八.ADC的中断
涉及到的寄存器:
1 ADC _ SR , ADC_ CR1
2 ADC_ HTR , ADC_ LTR

在这里插入图片描述
看门狗中断是用来保护模拟信号范围的。举例:若采集到的信号电压范围为1V-3V,那么我们可以设置阈值上限电压为3V,阈值下限电压为1V,这样超出这个数据范围之外的错误电压信号就可以报警产生中断了。
模拟看门狗如何保护多个通道:
在这里插入图片描述
九.模拟量计算:
怎么根据数据量算出模拟量

1-电压输入范围为: 0~3.3V

2-分辨率为12位

3-最小精度为: 3.3/2^12

4-设数字量为X ,则有模拟量Y =(3.31 2^12)*X

十. 单通道ADC1+定时器3触发并在LCD上显示波形和幅值的程序源码
adc.h文件

#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
#define TableSize 320
void Adc1_Init(void);
void ADC_IRQHandler(void);

#endif 


adc.c文件

#include "adc.h"
#include "delay.h"
#include "timer.h"
u32 ADC_EndFlag;
float Sintable[TableSize];
void Adc1_Init(void)
{
	ADC_InitTypeDef ADC_InitStruct;
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_CommonInitTypeDef ADC_CommonInitStruct;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);						//使能 GPIOA 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); 						//使能 ADC1 时钟
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;										//选择ADC1通道1
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN;									//设置成模拟输入模式
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;								//不使用上拉和下拉
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);	  //ADC1复位
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE);	//复位结束	 

	
	ADC_CommonInitStruct.ADC_Mode=ADC_Mode_Independent;							//ADC独立工作模式
	ADC_CommonInitStruct.ADC_Prescaler=ADC_Prescaler_Div4;						//ADC1挂在APB2=84M,因此ADC时钟频率21MHZ
	ADC_CommonInitStruct.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;			//ADC1通道不采用直接访问DMA模式
	ADC_CommonInitStruct.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cycles;		//两个采样时间之间的延迟,双重或者多重才有效
	ADC_CommonInit(&ADC_CommonInitStruct);
	
	ADC_InitStruct.ADC_Resolution=ADC_Resolution_12b;							//12位分辨率
	ADC_InitStruct.ADC_ScanConvMode=DISABLE;									//采用单通道,因此不需要开启扫描模式
	ADC_InitStruct.ADC_ContinuousConvMode=DISABLE;								//不开启连续采集,否则除开第一次后面的数据采集就不是定时器采集了
	ADC_InitStruct.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_Rising;	//上升沿触发有效
	ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T3_TRGO;			//定时器TIM3触发
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;							//数据右对齐
    ADC_InitStruct.ADC_NbrOfConversion = 1;										//转换通道1个,这个通道是指每个ADC的通道数
	ADC_Init(ADC1,&ADC_InitStruct);
	
	ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE);
	NVIC_InitStructure.NVIC_IRQChannel=ADC_IRQn;                   				//选择TIM3的定时器中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03;      			//抢占优先级为3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;							//子优先级为2
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;					
	NVIC_Init(&NVIC_InitStructure);
	ADC_Cmd(ADC1, ENABLE);														//开启AD转换器
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_144Cycles );	//设置指定 ADC 的规则组通道1(PA1),第一个采集,采样时间5个周期,一个周期为1/21us
}
void ADC_IRQHandler(void)
{
	
	static u32 n=0;
	if(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)!=RESET)						          //等待转换结束		
	{		
		Sintable[n++]=ADC_GetConversionValue(ADC1)*3.3f/4096;			         //返回最近一次 ADC1 规则组的转换结果										//将读取到的数字量存入数组中
	}
										
	
	if(n==TableSize)
	{
		n=0;
		ADC_EndFlag=1;
		ADC_ITConfig(ADC1,ADC_IT_EOC,DISABLE);							//采集TableSize个数据后关闭ADC中断
	}
	ADC_ClearITPendingBit(ADC1,ADC_FLAG_EOC);							//清除中断标志位
}

timer.c文件

#include "timer.h"
#include "math.h"
#include "adc.h"
	
void TIM3_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);				//使能TIM3上的APB1时钟
	
	TIM_TimeBaseInitStruct.TIM_Prescaler=psc;						//TIM3的定时器分频系数
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;		//TIM3为向下计数方式
	TIM_TimeBaseInitStruct.TIM_Period=arr;							//TIM3的自动重装载值
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;			//时钟分频因子
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
	
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);			//定时器2更新触发DAC
	TIM_Cmd(TIM3,ENABLE);											//使能定时器3
	
}


mian.c文件

#include "sys.h"
#include "lcd.h"
#include "adc.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
extern float Sintable[TableSize];
extern u32 SINtable[TableSize];
extern u32 ADC_EndFlag;
void WaveformDisplay()																//在LCD屏幕上显示波型
{
	u32 i;
	u32 m=1;
	for(i=1;i<TableSize;i++)
	{
		LCD_DrawLine(m-1,120-20*Sintable[i-1],m,120-20*Sintable[i]);				//120就是确定一个标准的X轴,20是将实际的电压量数值变大,好在LCD上显示
		m++;
	}
}
float MAX(void)																//读取采集电压中最大的电压
{
	float max1=0;
	u32 i=0;
	for(i=0;i<TableSize;i++)
	{
		if(Sintable[i]>max1)
			max1=Sintable[i];
	}
	return max1;
}
int main(void)
{
	float max1=0;
	u16 adcx;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		//设置系统中断优先级分组2
	delay_init(168);   								    //初始化延时函数
	uart_init(115200);									//初始化串口波特率为115200
	LED_Init();											//初始化LED 
 	LCD_Init();         								//初始化LCD接口
	Adc1_Init();      								    //初始化ADC
	TIM3_Int_Init(4,83);								//5us定时器开始触发						
	LCD_Display_Dir(1);							    	//横屏显示	
	POINT_COLOR=RED; 
	while(1)
	{ 
		if(ADC_EndFlag==1)
		{
			LCD_Clear(WHITE);
			WaveformDisplay();
			LCD_ShowString(30,150,200,16,16,"ADC1_CH1_VOL:0.000V"); //这里显示了小数点
			max1=MAX();
			adcx=max1; 										//赋值整数部分给 adcx 变量,因为 adcx 为 u16 整型
			LCD_ShowxNum(134,150,adcx,1,16,0); 				//显示电压值的整数部分
			max1-=adcx; 									//把已经显示的整数部分去掉,留下小数部分,比如 3.1111-3=0.1111
			max1*=1000;										//小数部分乘以 1000,例如: 0.1111 就转换为 111.1,保留三位小数。
			LCD_ShowxNum(150,150,max1,3,16,0X80); 			//显示小数部分
			ADC_EndFlag=0;
			ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE);			//开启ADC中断,实现实时采集
		}
		LED0=!LED0;
		delay_ms(50);
		
	}
}




本文为观看野火视频学习总结文章

猜你喜欢

转载自blog.csdn.net/weixin_46468790/article/details/107247354