1. ADC是什么
😈Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。
😠典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。
😬STM32F4的ADC是12位逐次趋近型模数转换器。具有多达19个复用通道,可测量来自16个外部源、两个内部源通道的信号。ADC的结果存储在一个左对齐或者右对齐的16位数据寄存器中。
ADC具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或者下限。
2. ADC主要特性
- 可配置12位、10位、8位或6位分辨率
- 在转换结束、注入转换结束以及发生模拟看门狗或溢出事件时产生中断
- 单次和连续转换模式
- 用于自动将通道0转换为通道“n”的扫描模式
- 数据对齐以保持内置数据一致性
- 可独立设置各通道采样时间
- 外部触发器选项,可为规则转换和注入转换配置极性
- ADC电源要求:全速运行时2.4V到3.6V,慢速运行时1.8V
3. ADC框图
🌕1. 电压输入范围:
表示正模拟参考电压输入,代表ADC高/正参考电压;
表示模拟电源输入,代表模拟电源电压等于;
表示负模拟参考电压输入,代表ADC低/负参考电压;
表示模拟电源接地输入,代表模拟电源接地电压等于;
输入电压: <= VIN <=
和 接地,把和接3V3,表示得到ADC的输入电压范围为0-3.3V;
STM32的ADC只能测0-3.3V的电压,如果超过了这个范围,只能在单片机上改变电压的范围(通过相应芯片的转电平作用),以保证能够被STM32的GPIO口识别。
🌕2. 输入通道:
STM32的每个ADC都具有18个通道,其中外部通道16个:
STM32的ADC有16条复用通道。外部16个通道在转换的过程中又分为规则通道和注入通道。
可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,该序列可按任意顺序在任意通道上完成。
其中规则通道由规则序列寄存器ADC_SQRx(x可以取值为1 2 3)来配置,注入通道由注入序列寄存器ADC_JSQR来配置。
- 一个规则转换组最多由16个转换构成。必须在ADC_SQRx寄存器中选择转换序列的规则通道及其顺序。规则转换组中的转换总数必须写入ADC_SQRx寄存器中的L[3:0]位。
- 一个注入转换组最多由4个转换构成。必须在ADC_JSQR寄存器中选择转换序列的注入通道及其顺序。注入转换组中的转换总数必须写入ADC_JSQR寄存器中的L[1:0]位。
- 顾名思义,两个通道的区别就是:规则通道就是很规矩的意思,我们一般使用的就是这个规则通道。注入可以理解为插入、插队的意思,是一种不安分的通道,有点类似于中断程序;注入通道只有在规则通道存在时才会出现。
🌑3. 转换顺序:
输入通道和注入通道的转换顺序分别由规则序列寄存器ADC_SQRx(x可以取值为1 2 3)和注入序列寄存器ADC_JSQR来配置。
🌙4. 触发源:
触发源相当于一个信号,用来告诉ADC可以开始转换了。
触发方式又分为软件触发和外部事件触发(内部定时器/外部IO)。分别对应于上图中的左侧TIM和右侧TIM。
🌿5. 转换时间:
转换时间:Tconv=采样时间+12个周期
采样时间:ADC需要若干个ADC_CLK周期完成对输入的模拟量进行采样,采样的周期数可通过ADC采样时间寄存器ADC_SMPR1和ADC_SMPR2的SMPx[2:0]位设置,ADC_SMPR2控制的是通道0~9,ADC_SMPR1控制的是通道10~17。每个通道可以分别用不同的时间采样。其中采样周期最小是1.5个,也就是说,我们要达到最快的采样,那么应该设置采样周期为31.5个周期,这里的周期就是1/ADC_CLK。
ADC_CLK:ADC模拟电路时钟,最大值为14M,由PCLK2提供,还可以进行分频,2/4/6/8,RCC_CFGR的ADCPRE[1:0]设置。PCLK2=72M。
数字时钟:RCC_APB2ENR,用于访问寄存器。
ag. 最短的转换时间:Tconv=采样时间+12个周期 (其中PCLK2=72M,ADC_CLK=72/6=12M ,Tconv=1.5+12.5=14=14/12us=1.17us)
ADCCLK=PCLK2/4=84/4=21Mhz, ADC 时钟最好不要超过 36Mhz 具体看芯片的时钟频率
🌍 6. 数据寄存器:
一切准备就绪后,ADC转换后的数据根据转换组不同,规则组的数据存放在ADC_DR寄存器中,注入组的数据放在JDRx寄存器中。
数据寄存器ADC_DR(ADC regular data register)
位31:16 保留,必须保持复位值
位15:0 DATA[15:0]:规则数据(Regular data) 这些位为只读。它们包括来自规则通道的转换结果。
该寄存器的1~16位有效,用于存放独立模式转换完成数据。当在双ADC模式(ADC1和ADC2同时使用)下,ADC1放在低16位上,ADC2放在高16位上。
3.2 ADC时钟
👮用于模拟电路的时钟:ADCCLK,所有ADC共用
此时钟来自于经可编程预分频器分频的APB2时钟,该预分频器允许ADC在、/4、/6或/8下工作。
👮用于数字接口的时钟:(用于寄存器读/写访问)
此时钟等效于APB2时钟。可以通过RCC APB2外设时钟使能寄存器(RCC_APB2ENR)分别为每个ADC使能/禁止数字接口时钟。
3.3 通道选择
STM32的ADC有16条复用通道。外部16个通道在转换的过程中又分为规则通道和注入通道。
可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,该序列可按任意顺序在任意通道上完成。
一个规则转换组最多由16个转换构成。必须在ADC_SQRx寄存器中选择转换序列的规则通道及其顺序。规则转换组中的转换总数必须写入ADC_SQRx寄存器中的L[3:0]位。
一个注入转换组最多由4个转换构成。必须在ADC_JSQR寄存器中选择转换序列的注入通道及其顺序。注入转换组中的转换总数必须写入ADC_JSQR寄存器中的L[1:0]位。
如果在转换期间修改 ADC_SQRx 或 ADC_JSQR 寄存器,将复位当前转换并向 ADC 发送一 个新的启动脉冲,以转换新选择的组。
温度传感器、内部通道
对于STM32F40x系列的开发板,温度传感器内部连接到ADC1_IN16。内部参考电压连接到ADC1_IN17。
注意: 温度传感器、只在主ADC1外设上可用。
3.4 单次转换模式和连续转换模式
在单次转换模式下,ADC执行一次转换。CONT位为0时,可以通过以下三种方式启动此模式:
- 将ADC_CR2寄存器中的SWSTART位置1(仅适用于规则通道)
- 将JSWSTART位置1(适用于注入通道)
- 外部触发(适用于规则通道和注入通道)
如果转化了规则通道:
- 转换数据存储在16位ADC_DR寄存器中
- EOC(转换结束)标志位置1
- EOCIE位置1时将产生中断
如果转换了注入通道:
- 转换数据存储在16位ADC_JCR1寄存器中
- JEOC(注入转换结束)标志置1
- JEOCIE位置1时将产生中断
在连续转换模式下,ADC结束一个转换后立即启动一个新的转换。CONT位为1时,可通过外部触发或将ADC_CR2寄存器中的SWSTRT位置1来启动该模式(仅适用于规则通道)
如果转换了规则通道组:
- 上次转换的数据存储在16位ADC_DR寄存器中
- EOC(转换结束)标志置1
- EOCIE位置1时将产生中断
STM32F4的ADC在单次转换模式下,只执行一次转换,该模式可通过ADC_CR2寄存器的ADON位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道),这时CONT位为0。
以规则通道为例,一旦所选择的通道转换完成,转换结果将被存储在ADC_DR寄存器中,EOC(转化结束)标志将被置位,如果设置了EOCIE,则会产生中断。然后ADC将停止,直到下次启动。
4、内部温度传感器
对应STM32F40x系列的开发板器件(其他的型号开发板对应不同的ADC通道),温度传感器内部连接到ADC1_IN16通道,而ADC1用于将传感器输出电压转换为数字值。不使用时可以将传感器置于掉电模式。
注意:必须将TSVREFE位置1才能同时对两个通道进行转换。ADC1_IN16(温度传感器)和ADC1_IN17(VREFINT)。
主要特性:
- 支持的温度范围(可以通过ADC转换成的数字范围):-40℃到125℃
- 精度:1.5℃
读取温度:
- 选择ADC1_IN16输入通道
- 选择一个采样时间,该采样时间要大于数据手册上指定的最低采样时间
- 在ADC_CCR寄存器中将TSVREFE位置1,以便将温度传感器从掉电模式中唤醒
- 通过将SWSTART位置1开始ADC转换
- 读取ADC数据寄存器中生成的数据
计算温度:
温度(单位℃)={-/Avg_Slope}+25
其中: =25℃时的值 Avg_Slope=温度与曲线的平均斜率
注意:
传感器从掉电模式中唤醒需要一个启动时间,启动时间过后其才能正确输出。ADC在上电后同样需要一个启动时间,因此,为尽可能减少延迟时间,应同时将ADON和TSVREFE位置1。
温度传感器的输出电压随温度线性变化。
5. ADC中断
当模拟看门狗状态位和溢出状态位分别置1时,规则组和注入组在转换结束时可能会产生中断。可以使用单独的中断使能位以实现灵活性。
ADC_SR寄存器中存在另外两个标志,但这两个标志不存在中断相关性:
- JSTRT(开始转换注入组的通道)
- STRT(开始转换规则组的通道)
6. ADC初始化结构体
ADC_InitTypeDef
typedef struct
{
uint32_t ADC_Resolution; //ADC分辨率
FunctionalState ADC_ScanConvMode; //ADC扫描多通道或者ADC单通道模式选择 通过ADC_CR1的SCAN位来配置
FunctionalState ADC_ContinuousConvMode; //ADC单次转换或者连续转换选择 通过ADC_CR2的CON位来配置
uint32_t ADC_ExternalTrigConvEdge; //ADC转换触发信号范围选择
uint32_t ADC_ExternalTrigConv; //ADC外部触发转换模式选择 通过ADC_CR2的EXTTRIG和EXTSEL位来配置
uint32_t ADC_DataAlign; //ADC数据寄存器对齐格式
uint8_t ADC_NbrOfConversion; //ADC采集通道数
}ADC_InitTypeDef;
ADC通道配置
typedef struct
{
uint32_t ADC_Mode; //ADC模式 通过ADC_CR1:DUALMOD位来配置
uint32_t ADC_Prescaler; //ADC预分频值
uint32_t ADC_DMAAccessMode;
uint32_t ADC_TwoSamplingDelay; //ADC多通道模式
}ADC_CommonInitTypeDef;
6.1 ADC相关实验配置
1-独立模式-单通道-中断读取
- 初始化ADC用到的GPIO
- 初始化ADC初始化结构体
- 配置ADC时钟,配置通道的转换顺序和采样时间
- 使能ADC转换完成中断,配置中断优先级
- 使能ADC,准备开始转换
- 校准ADC ADC_StartCalibration ADC_GetCalibrationStatus
- 软件触发ADC,真正开始转换 ADC_SoftwareStartConvCmd
- 编写中断服务函数,读取ADC转换数据
- 编写main函数,把转换的数据打印出来
2-独立模式-单通道-DMA读取
DMA是一种直接存储器,DMA是不占用CPU的,可以一边读取数据,一边处理数据。
- 初始化ADC用到的GPIO
- 初始化ADC初始化结构体
- 配置ADC时钟,配置通道的转换顺序和采样时间
- 使能ADC转换完成中断,配置中断优先级
3-独立模式-多通道-DMA读取
- 初始化ADC用到的GPIO
- 初始化ADC初始化结构体
- 配置ADC时钟,配置通道的转换顺序和采样时间
- 使能ADC转换完成中断,配置中断优先级
配置过程相同,只不过初始化IO口时需要初始化多个IO口通道。并且调用函数ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_480Cycles ); 获取多个通道执行多个功能即可。
7、相关寄存器---看手册 不介绍
8、 库函数配置ADC1的通道5进行AD转换
🍇1. 开启GPIO口时钟和ADC1时钟,设置引脚为模拟输入
例如:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟
注意:对于IO口复用的ADC我们要设置模式为模拟输入,而不是复用功能。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟输入
🍉2. 设置ADC的通用控制寄存器CCR,配置ADC输入时钟分频,模式为独立模式
在库函数中, 初始化CCR寄存器是通过调用ADC_CommonInit来实现的;
typedef struct { uint32_t ADC_Mode; //ADC模式 通过ADC_CR1:DUALMOD位来配置 uint32_t ADC_Prescaler; //ADC预分频值 uint32_t ADC_DMAAccessMode; //DMA模式禁止或者使能相应的DMA模式 uint32_t ADC_TwoSamplingDelay; //ADC两个采样阶段之间的延迟周期数 }ADC_CommonInitTypeDef; 第一个参数ADC_Mode:设置独立模式还是多重模式 第二个参数ADC_Prescaler:设置ADC预分频器,这里设置时一定保证ADC1的时钟频率不超过36MHz 第三个参数ADC_DMAAccessMode:DMA模式禁止或者使能相应的DMA模式 第四个参数ADC_TwoSamplingDelay:设置ADC两个采样阶段之间的延迟周期数
例如:
ADC_CommonInitTypeDef ADC_CommonInitStructure={0}; ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;//不使用DMA ADC_CommonInitStructure.ADC_Mode=ADC_Mode_Independent;//独立模式 ADC_CommonInitStructure.ADC_Prescaler=ADC_Prescaler_Div4;//时钟分频 //ADCCLK=PCLK2/4=84/4=21Mhz,ADC 时钟最好不要超过 36Mhz ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cycles;//采样间隔 //两个采样阶段之间的延迟 5 个时钟 ADC_CommonInit(&ADC_CommonInitStructure);
🍈3. 初始化ADC1参数,设置ADC1的转换分辨率,转换方式,对齐方式,以及规则序列等相关信息
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct) //初始化ADC函数
typedef struct { uint32_t ADC_Resolution; //ADC分辨率 FunctionalState ADC_ScanConvMode; //ADC扫描多通道或者ADC单通道模式选择 通过ADC_CR1的SCAN位来配置 FunctionalState ADC_ContinuousConvMode; //ADC单次转换或者连续转换选择 通过ADC_CR2的CON位来配置 uint32_t ADC_ExternalTrigConvEdge; //ADC转换触发信号范围选择 uint32_t ADC_ExternalTrigConv; //ADC外部触发转换模式选择 通过ADC_CR2的EXTTRIG和EXTSEL位来配置 uint32_t ADC_DataAlign; //ADC数据寄存器对齐格式 uint8_t ADC_NbrOfConversion; //ADC采集通道数 }ADC_InitTypeDef; 第一个参数ADC_Resolution:设置ADC转换分辨率 第二个参数ADC_ScanConvMode:设置是否打开扫描模式 第三个参数ADC_ContinuousConvMode:设置时单次转换模式还是连续转换模式 第四个参数ADC_ExternalTrigConvEdge:设置外部通道的触发使能和检测方式 第五个参数ADC_ExternalTrigConv:设置ADC外部触发转换模式 第六个参数ADC_DataAlign:设置数据对齐方式 是左对齐还是右对齐 第七个参数ADC_NbrOfConversion:设置规则序列的长度 单次转换
例如:
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; //禁止触发检测,使用软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_NbrOfConversion = 1;//1个转换在规则序列中
ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
🍑4. 开启AD转换器
开启AD转换器通过ADC_CR2寄存器控制
ADC_Cmd(ADC1, ENABLE);//开启AD转换器
🍍5. 读取ADC值
例如:
//获取ADC采样的值 u16 Get_Adc_Data(u8 ch) { //设置指定 ADC 的规则组通道,一个序列,采样时间 ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_480Cycles); ADC_SoftwareStartConv(ADC1);//使能指定的 ADC1 的软件转换启动功能 while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//等待转换结束 return ADC_GetConversionValue(ADC1); //返回最近一次 ADC1 规则组的转换结果 }
9、附上ADC代码的实验程序
采集两个IO口上的电压变化 并进行转换 这里接的是火焰传感器和 光照强度
ADC.c
#include "ADC.h"
#include "delay.h"
/************************************************************
* 函数名称:Adc_Init
* 函数说明:ADC1初始化函数
* 形 参:无
* 返 回 值:无
* 备 注:PC0-通道10(ADC123_IN10) PC1--通道11(ADC123_IN11)
*************************************************************/
void Adc_Init(void)
{
//GPIO ADC1 时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
//GPIO参数 PC0 PC1
GPIO_InitTypeDef GPIO_InitStructure={0};
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN;//模拟输入
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1;//引脚配置
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//浮空
GPIO_InitStructure.GPIO_Speed=GPIO_High_Speed;//高速
GPIO_Init(GPIOC,&GPIO_InitStructure);
//ADC通道配置
ADC_CommonInitTypeDef ADC_CommonInitStructure={0};
ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;//不使用DMA
ADC_CommonInitStructure.ADC_Mode=ADC_Mode_Independent;//独立模式
ADC_CommonInitStructure.ADC_Prescaler=ADC_Prescaler_Div4;//时钟分频
//ADCCLK=PCLK2/4=84/4=21Mhz,ADC 时钟最好不要超过 36Mhz
ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cycles;//采样间隔
//两个采样阶段之间的延迟 5 个时钟
ADC_CommonInit(&ADC_CommonInitStructure);
//ADC参数
ADC_InitTypeDef ADC_InitStructure={0};
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//关闭连续转换
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_None;禁止触发检测,使用软件触发
ADC_InitStructure.ADC_NbrOfConversion=1;//1 个转换在规则序列中(ADC转换次数)
ADC_InitStructure.ADC_Resolution=ADC_Resolution_12b;//分辨率
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//非扫描模式
ADC_Init(ADC1,&ADC_InitStructure);
//使能ADC1
ADC_Cmd(ADC1,ENABLE);
}
//获取ADC采样的值
u16 Get_Adc_Data(u8 ch)
{
//设置指定 ADC 的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_480Cycles);
ADC_SoftwareStartConv(ADC1);//使能指定的 ADC1 的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次 ADC1 规则组的转换结果
}
//获取通道 ch 的转换值,取 times 次,然后平均
//ch:通道编号 times:获取次数
//返回值:通道 ch 的 times 次转换结果平均值
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0; u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc_Data(ch); delay_ms(5);
}
return temp_val/times;
}
ADC.h
#ifndef __ADC_H_
#define __ADC_H_
#include "io_bit.h"
void Adc_Init(void);
u16 Get_Adc_Average(u8 ch,u8 times);
#endif
main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "LED.h"
#include "exti.h"
#include "key.h"
#include "usart.h"
#include "timer.h"
#include "ADC.h"
int main()
{
u16 temp,flame;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组2
delay_Init();//延时初始化
Exti_Init();//外部中断初始化
LED_Init();//LED初始化
Usart_Init(115200);
Adc_Init();
while(1)
{
temp=Get_Adc_Average(10,10);//通道10,采样10次取平均值
delay_ms(200);
flame=Get_Adc_Average(11,10);//通道11--火焰传感器
printf("光照强度为:%%%d\r\n",100-(temp*100/4096));//光敏传感器
printf("火焰强度为:%d\r\n",flame);
delay_ms(1000);
}
}