1、STM32F407 的 ADC 介绍
ADC(模数转换器),即将模拟信号转换成数字信号的电路。当然既然有模数转换器,那肯定也有数模转换
器 DAC,即将数字信号转换为模拟信号的电路称为数模转换器,其实就是芯片通过ADC来将模拟量转换为数字量。
例如:
假如3.3V的电压分成平均4096份,提问500份数字量对应的电压是多少?
Voltage = 3.3*500/4096。
ADC的作用就是采集数字量,根据数字量来判断当前的电压是多少。
1.1、基本概念
- 参考电压
参考电压是指测量电压值时,用作参考点的电压值。我板子的参考电压是3.3V
- 精度
8位:256,将参考电压分成256分
10位:1024,将参考电压分成1024分
12位:4096,将参考电压分成4096分
- 转换公式
实际的电压= 采样的数字量*参考电压/采样精度。
1.2、常见的ADC转换器
传感器分为两种:①直接输出模拟电压量;②传感器内部带ADC,主控与这种传感器进行数据交互式,通常会以SPI或者IIC的通讯接口来进行通讯。
STM32内部有ADC控制器,可以实现对模拟电压信号的采集。
2、STM32F407的ADC控制器
12 位 ADC 是逐次趋近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部源,两个内部源和
VBAT 通道的信号。这些通道的 AD 转换可在单次,连续,扫描或不连续采样模式下进行。 ADC 的结果存储在一个
左对齐或右对齐的 16 位数据寄存器中。
ADC 具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。
3、STM32F407 的 ADC 特征
- 可配置 12 位, 10 位, 8 位或 6 位分辨率
- 在转换结束,注入转换结束以及发生模拟看门狗或溢出事件时产生中断
- 单次和连续转换模式
- 用于自动将通道 0 转换为通道”n”的扫描模式
- 数据对齐以保持内置数据一致性
- 可独立设置各通道采样时间
- 外部触发器选项,可为规则转换和注入转换配置极性
- 不连续采样模式
- 双重/三重模式(具有 2 个或更多 ADC 的器件提供)
- 双重/三重 ADC 模式下可配置的 DMA 数据存储
- 双重/三重交替模式下可配置的转换间延迟
- ADC 转换类型
- ADC 电源要求,全速运行时为 2.4V 到 3.6V,慢速运行时为 1.8V
- ADC 输入范围: VREF-≤VIN≤VREF+
- 规则通道转换期间可产生 DMA 请求
4、ADC时钟
ADC 具有两个时钟方案
- 用于模拟电路的时钟: ADCCLK,所有 ADC 共用
此时钟来自于经可编程预分频器分频的 APB2 时钟,该预分频器允许 ADC 在 fPCLK2/2, /4, /6 或/8 下工作。
- 用于数字接口的时钟
此时钟等效于 APB2 时钟。可以通过 RCC_APB2 外设时钟使能寄存器(RCC_APB2ENR)分别为每个 ADC使能/禁止数字接口时钟
5、ADC通道选择
有 16 条复用通道,可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,该序列可
按任意顺序在任意通道上完成。
- 一个规则转换组最多由 16 个转换构成,必须在 ADC_SQRx 寄存器中选择转换序列的规则通道以及顺序。
规则转换组中的转换总数必须写入 ADC_SQR1 寄存器中的 L[3:0]位。
- 一个注入转换组最多由 4 个转换构成。必须在 ADC_JSQR 寄存器中选择转换序列的注入通道及其顺序。
注入转换组中的转换总数必须写入 ADC_JSQR 寄存器中的 L[1:0]位。
如果在转换期间修改 ADC_SQRx 或 ADC_JSQR 寄存器,将复位当前转换并向 ADC 发送一个新的启动脉冲,
以转换新选择的组
6、ADC外部通道与GPIO口的映射关系
STM32F407的ADC控制器的输入通道最多有19路,其中包括16个外部输入通道,3个内部通道。
在使用外部通道输入模拟电压信号时,需要知道ADCx控制器的通道所映射的管脚。
16个外部通道对应的管脚如下表:
举例:使用ADC1通道0采样光敏电阻的电压,需要将PA0配置为模拟输入,具体提代码如下:
RCC->AHB1ENR |= 1<<0;//使能PA时钟
GPIOA->MODER |= 3<<0;//PA0配置为模拟输入
7、转换模式
转换模式有3种:单次模式、循环模式 、扫描模式,这三种模式可以组合使用。
单个通道单次转换 | 一个输入通道,转换模式为单次模式 |
多个通道单次转换 | 输入通道有多个,转换模式为单次模式+扫描模式 |
单个通道连续转换 | 一个输入通道,转换模式为循环次模式 |
多个通道连续转换 | 输入通道有多个,转换模式为循环模式+扫描模式 |
8、相关寄存器
8.1、ADC控制寄存器 1 (ADC_CR1)
AD分辨率位数越大,精度越高,需要的转换时间越长。本章节,我们选择最高的分辨率,以ADC1控制器举例,对应的代码:ADC1->CR1 &=~(3<<24);
注入通道和规则通道都可以用扫描模式,具体扫描哪些通道的模拟信号,需要根据ADC_SQRx和ADC_JSQRx来确定
如果该位置1,当注入通道转换完成,会产生ADC中断异常
如果该位置1,当规则通道转换完成,会产生ADC中断异常
8.2、控制寄存器 2 (ADC_CR2)
该位置1可以启动ADC的规则组模数转换,ADC控制器配置完成后,才可以启动模式转换,以ADC1控制器举例,对应的代码:ADC1->CR2 |=(1<<30);
该位置1可以启动ADC的注入组模数转换,ADC控制器配置完成后,才可以启动模式转换,以ADC1控制器举例,对应的代码:ADC1->CR2 |=(1<<22);
数据对齐常用的是右对齐,也就是AD采样结果从数据寄存器的位0开始存储
以ADC1控制器举例,对应的代码:ADC1->CR2 &=~(1<<11);
用于设置单次模式和连续模式,具体情况具体分析
配置AD的基本内容后,需要将该位置1来启动ADC。
以ADC1控制器举例,对应的代码:ADC1->CR2 |=(1<<0);
8.3、状态寄存器 (ADC_SR)
ADC1->CR2 |=(1<<22);//启动注入通道转换
while(!(ADC1->SR &(1<<2))) ; //阻塞判断转换是否转换完成
ADC1->SR &=~(1<<2);//清零
//读取采样数据
ADC1->CR2 |=(1<<30);//启动规则组通道转换
while(!(ADC1->SR &(1<<1))) ; //阻塞判断转换是否转换完成
//读取采样数据
8.4、通用控制寄存器 (ADC_CCR)
PCLK2 的时钟是84MHZ
举例:将ADC1~3的时钟配置 为84MHZ的8分频,代码如下:
ADC->CCR |= 3<<16;
8.5.、ADC 采样时间寄存器
STM32F407ZGT6支持19个模拟信号通道,每一个通道的采样时间都是可以单独设置的
采样时间寄存器 1 (ADC_SMPR1)
采样时间寄存器 2 (ADC_SMPR2)
通道0~18的采样时间设置与SMP0~SMP18一一对应,即3位对应一个通道,3位有8中情况,具体如下:
举例①:ADC1的通道3设置采样键为480个周期,对应的代码如下:
ADC1->SMPR2 |= 7<<9;
举例①:ADC1的通道16设置采样键为480个周期,对应的代码如下:
ADC1->SMPR1 |= 7<<18;
8.6、ADC规则序列寄存器
规则序列寄存器 1 (ADC_SQR1)
规则序列寄存器 2 (ADC_SQR2)
规则序列寄存器 3 (ADC_SQR3)
.
ADC_SQR1寄存器的位23:20
SQ1写入的是第1次转换的通道编号
SQ2写入的是第2次转换的通道编号
SQ3写入的是第3次转换的通道编号
SQ4写入的是第4次转换的通道编号
SQ5写入的是第5次转换的通道编号
SQ6写入的是第6次转换的通道编号
SQ7写入的是第7次转换的通道编号
SQ8写入的是第8次转换的通道编号
SQ9写入的是第9次转换的通道编号
SQ10写入的是第10次转换的通道编号
SQ11写入的是第11次转换的通道编号
SQ12写入的是第12次转换的通道编号
SQ13写入的是第13次转换的通道编号
SQ14写入的是第14次转换的通道编号
SQ15写入的是第15次转换的通道编号
SQ16写入的是第16次转换的通道编号
注意:通道编号为0~18
举例:
①规则通道转换序列只有一次转换,转换的通道编号是8,对应的代码如下:
ADC1->SQR1 &=~(0XF<<20);
ADC1->SQR3 |=(8<<0);
①规则通道转换序列有两次转换,第一转换的通道编号是12,第二次转换的通道编号是16,对应的代码如下:
ADC1->SQR1 |=(1<<20);
ADC1->SQR3 |=(12<<0);
ADC1->SQR3 |=(16<<5);
8.7、规则数据寄存器 (ADC_DR)
8.8、注入序列寄存器 (ADC_JSQR)
如果转换次数为1次,ADC 将仅转换 JSQ4[4:0] 通道
如果转换次数为2次,ADC 转换通道的顺序为:先是 JSQ3[4:0],而后是 JSQ4[4:0]。
如果转换次数为3次,ADC 将按以下顺序转换通道: JSQ2[4:0]、JSQ3[4:0] 和 JSQ4[4:0]。
如果转换次数为4次,ADC 将按以下顺序转换通道: JSQ1[4:0]、JSQ2[4:0]、 JSQ3[4:0] 和 JSQ4[4:0]。
注意:通道编号为0~18
举例:
①注入通道转换序列只有一次转换,转换的通道编号是8,对应的代码如下:
ADC1->JSQR &=~(3<<20);
ADC1->JSQR |=(8<<15);
①注入通道转换序列有两次转换,第一转换的通道编号是12,第二次转换的通道编号是16,对应的代码如下:
ADC1->JSQR |=(1<<20);
ADC1->JSQR |=(12<<10);
ADC1->JSQR |=(16<<15);
8.9、注入数据寄存器 x (ADC_JDRx) (x= 1..4)
当注入转换只有一次时,采样数据保存到JDR1
9、软件设计
管脚 PA5(STM_ADC)测试滑动变阻器的电压值
寄存器版
#include "stm32f4xx.h"
#include "stdio.h"
void Delay_us(u16 us)
{
SysTick->CTRL &=~(1<<2); //选择时钟源为21MHZ
SysTick->CTRL &=~(1<<1); //禁止滴答中断
SysTick->LOAD = 21*us; //设置重装载寄存器的值
SysTick->CTRL |= (1<<0); //使能滴答定时器
while(!(SysTick->CTRL&(1<<16)));//阻塞判断定时时间是否到达,判断SysTick->CTRL的位
}
/******************** 串口打印函数 ***************************************/
/* fputc是printf最底层的调用函数 */
int fputc(int data,FILE *file)
{
while( !(USART1->SR & (1 << 6)) );//等待发送完成
USART1->DR = data; //发送数据
return data;
}
//初始化 ADC
//这里我们仅以规则通道为例
//我们默认仅开启 ADC1_CH5
void Adc_Init(void)
{
//先初始化 IO 口
RCC->APB2ENR|=1<<8; //使能 ADC1 时钟
RCC->AHB1ENR|=1<<0; //使能 PORTA 时钟
GPIO_Set(GPIOA,PIN5,GPIO_MODE_AIN,0,0,GPIO_PUPD_PU); //PA5,模拟输入,下拉
RCC->APB2RSTR|=1<<8; //ADCs 复位
RCC->APB2RSTR&=~(1<<8); //复位结束
ADC->CCR=3<<16; //ADCCLK=PCLK2/4=84/4=21Mhz,ADC 时钟不要超过 36Mhz
ADC1->CR1=0; //CR1 设置清零
ADC1->CR2=0; //CR2 设置清零
ADC1->CR1|=0<<24; //12 位模式
ADC1->CR1|=0<<8; //非扫描模式
ADC1->CR2&=~(1<<1); //单次转换模式
ADC1->CR2&=~(1<<11); //右对齐
ADC1->CR2|=0<<28; //软件触发
ADC1->SQR1&=~(0XF<<20);
ADC1->SQR1|=0<<20; //1 个转换在规则序列中 也就是只转换规则序列 1
//设置通道 5 的采样时间
ADC1->SMPR2&=~(7<<(3*5)); //通道 5 采样时间清空
ADC1->SMPR2|=7<<(3*5); //通道 5 480 个周期,提高采样时间可以提高精确度
ADC1->CR2|=1<<0; //开启 AD 转换器
}
//获得 ADC 值
//ch:通道值 0~16
//返回值:转换结果
u16 Get_Adc(u8 ch)
{
//设置转换序列
ADC1->SQR3&=0XFFFFFFE0;//规则序列 1 通道 ch
ADC1->SQR3|=ch;
ADC1->CR2|=1<<30; //启动规则转换通道
while(!(ADC1->SR&1<<1));//等待转换结束
return ADC1->DR; //返回 adc 值
}
//获取通道 ch 的转换值,取 times 次,然后平均
//ch:通道编号
//times:获取次数
//返回值:通道 ch 的 times 次转换结果平均值
void usart1_Init()
{
u16 integer ;
u16 fraction;
float USARTDIV;
RCC->AHB1ENR |= 1<<0;//使能GPIOA的时钟 RCC_AHB1ENR
GPIOA->MODER &= ~(3<<18);//清零
GPIOA->MODER |= 2<<18;//设置PA9为复用功能模式MODER
GPIOA->AFR[1]&= ~(0XF<<4);//清零
GPIOA->AFR[1]|= (7<<4);//设置PA9的复用功能为第7复用功能 RXD, AFRH
GPIOA->MODER &= ~(3<<20);//清零
GPIOA->MODER |= 2<<20;//设置PA10为复用功能模式MODER
GPIOA->AFR[1]&= ~(0XF<<8);//清零
GPIOA->AFR[1]|= (7<<8);//设置PA10的复用功能为第7复用功能 RXD, AFRH
RCC->APB2ENR |= 1<<4;//使能串口1模块时钟 RCC_APB2ENR
USART1->CR1 |= 1<<15;//设置串口OVER8为1
USART1->CR1 &= ~(1<<12);//设置串口数据位长度 :1 起始位, 8 数据位
USART1->CR2 &=~(3<<12);//设置串口停止位长度 :1 个停止位
USART1->CR1 &= ~(1<<10);//无校验
//中断不使能
USART1->CR1 |= 1<<3;//发送使能
USART1->CR1 |= 1<<2;//接收使能
USARTDIV = 84000000/8/baudRate;//串口波特率设置:USARTDIV = fCK/(8*(2-OVER8) /波特率
integer = (u16)USARTDIV;
fraction = ((u16)(USARTDIV-integer))<<4;
USART1->BRR |= integer<<4 | fraction;
USART1->CR1 |= 1<<13;//使能串口
}
int main()
{
float adc;
u16 temp;
uSART1_Init();
Adc_Init();
while(1)
{
temp = Get_Adc(ADC_Channel_5);
adc = (temp * 3.3) /4096.0;
printf("%1.4fV\r\n",adc);
delay_ms(500);
}
}
库函数版
void Adc_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟
//先初始化ADC1通道5 IO口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5 通道5
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_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,
ADC_CommonInit(&ADC_CommonInitStructure);//初始化
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个转换在规则序列中 也就是只转换规则序列1
ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
ADC_Cmd(ADC1, ENABLE);//开启AD转换器
}
u16 Get_Adc(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_480Cycles ); //ADC1,ADC通道,480个周期,提高采样时间可以提高精确度
ADC_SoftwareStartConv(ADC1); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
int main()
{
float adc;
u16 temp;
uSART1_Init();
Adc_Init();
while(1)
{
temp = Get_Adc(ADC_Channel_5);
adc = (temp * 3.3) /4096.0;
printf("%1.4fV\r\n",adc);
delay_ms(500);
}
}