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通道
-
STM32F407有三路ADC,可配置 12 位、 10 位、 8 位或 6 位分辨率,分别是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的对应) -
通道类型:规则通道和注入通道
什么是注入通道,什么是规则通道?
注入通道:可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像。因此注入通道只有在规则通道存在时才会出现。
规则通道就是很规矩的意思,我们平时一般使用的就是这个通道,或者应该说我们用到的都是这个通道
三 .通道的优先顺序
通道的优先顺序由以下寄存器控制,详见STM32F4XX中文参考手册
-
规则序列通道的优先顺序
说明:SQR1的SQL决定需要控制的通道数,取值范围在1-16。若你想让通道0优先顺序为1,则你在SQR3的SQ1[4:0]位写入0x00就好了。 -
注入通道的优先顺序
四.触发源:告诉ADC开始采集的这个信号就叫触发
详见STM32F4XX中文参考手册:
五.转换时间 -
转换时间=采样周期(自己设置,最大为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 ,用于访问寄存器 -
采样时间
采样时间: 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内的。
- 规则数据寄存器:ADC_ DR寄存器
1.1. 只有16位有效数据,用于存放单个ADC模式转换完成数据
1.2. 根据ADC_CR2:ALIGN位选择数据对齐方式
1.3. 只有一个规则通道数据寄存器,由于数据都是存放在这个数据寄存器中,如果不及时取走,数据就会被覆盖,因此多通道的时候最好使用中断或着DMA 模式。我们可以将每一个通道的数据放入不同数组,再通过DMA得到数据。 - 注入数据寄存器
2.1. 只有16位有效数据,用于存放单个ADC模式转换完成数据
2.2. 根据ADC_CR2:ALIGN位选择数据对齐方式
2.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),第一个采集,采样时间144个周期,一个周期为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);
}
}
本文为观看野火视频学习总结文章