STM32F4xx系列提供的12位ADC是逐次逼近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部源、两个内部源和 V BAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续采样模式下进行。ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。
ADC 具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。
单个ADC框图
ADC 引脚
ADC开关控制
可通过将ADC_CR2寄存器中的 ADON 位置1来为 ADC 供电。首次将 ADON 位置1时,会将ADC从掉电模式中唤醒。
SWSTART 或 JSWSTART 位置 1 时,启动 AD 转换。
可通过将 ADON 位清零来停止转换并使 ADC 进入掉电模式。在此模式式下,ADC 几乎不耗电(只有几 μA)。
ADC时钟
ADC 具有两个时钟方案:
● 用于模拟电路的时钟:ADCCLK,所有 ADC 共用
此时钟来自于经可编程预分频器分频的 APB2 时钟,该预分频器允许 ADC 在 f PCLK2 /2、/4、/6 或 /8 下工作。有关 ADCCLK 的最大值,请参见数据手册。
● 用于数字接口的时钟(用于寄存器读/写访问)
此时钟等效于 APB2 时钟。可以通过 RCC APB2 外设时钟使能寄存器 (RCC_APB2ENR)分别为每个 ADC 使能/禁止数字接口时钟。
通道选择
有 16 条复用通道。可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,该序列可按任意顺序在任意通道上完成。例如,可按以下顺序对序列进行转换:ADC_IN3、ADC_IN8、ADC_IN2、ADC_IN2、ADC_IN0、ADC_IN2、ADC_IN2、ADC_IN15。
● 一个规则转换组最多由 16 个转换构成。必须在 ADC_SQRx 寄存器中选择转换序列的规则通道及其顺序。规则转换组中的转换总数必须写入 ADC_SQR1 寄存器中的 L[3:0] 位。
● 一个注入转换组最多由 4 个转换构成。必须在 ADC_JSQR 寄存器中选择转换序列的注入通道及其顺序。注入转换组中的转换总数必须写入 ADC_JSQR 寄存器中的 L[1:0] 位。
如果在转换期间修改 ADC_SQRx 或 ADC_JSQR 寄存器,将复位当前转换并向 ADC 发送一个新的启动脉冲,以转换新选择的组。
● 对于 STM32F40x 和 STM32F41x 器件,温度传感器内部连接到通道 ADC1_IN16。
内部参考电压 VREFINT 连接到 ADC1_IN17。
● 对于 STM23F42x 和 STM32F43x 器件,温度传感器内部连接到与 VBAT 共用的通道
ADC1_IN18。一次只能选择一个转换(温度传感器或 VBAT)。同时设置了温度传感器和 VBAT 转换时,将只进行 VBAT 转换。
内部参考电压 VREFINT 连接到 ADC1_IN17。
VBAT通道连接到通道ADC1_IN18。该通道也可转换为注入通道或规则通道。
单次转换模式
在单次转换模式下, ADC 执行一次转换 。CONT 位为 0 时,可通过以下方式启动此模式:
● 将 ADC_CR2 寄存器中的 SWSTART 位置 1(仅适用于规则通道)
● 将 JSWSTART 位置 1(适用于注入通道)
● 外部触发(适用于规则通道或注入通道)
完成所选通道的转换之后:
● 如果转换了规则通道:
— 转换数据存储在 16 位 ADC_DR 寄存器中
— EOC(转换结束)标志置 1
— EOCIE 位置 1 时将产生中断
● 如果转换了注入通道:
— 转换数据存储在 16 位 ADC_JDR1 寄存器中
— JEOC(注入转换结束)标志置 1
— JEOCIE 位置 1 时将产生中断
然后,ADC 停止。
连续转换模式
在连续转换模式下, ADC 结束一个转换后立即启动一个新的转换 。CONT 位为 1 时,可通过外部触发或将 ADC_CR2 寄存器中的 SWSTRT 位置 1 来启动此模式(仅适用于规则通道)。
每次转换之后:
● 如果转换了规则通道组:
— 上次转换的数据存储在 16 位 ADC_DR 寄存器中
— EOC(转换结束)标志置 1
— EOCIE 位置 1 时将产生中断
时序图
ADC 在开始精确转换之前需要一段稳定时间 t STAB 。ADC 开始转换并经过 15 个时钟周期后,EOC 标志置 1,转换结果存放在 16 位 ADC 数据寄存器中。
扫描模式
此模式用于扫描一组模拟通道。
通过将 ADC_CR1 寄存器中的 SCAN 位置 1 来选择扫描模式。将此位置 1 后,ADC 会扫描在 ADC_SQRx 寄存器(对于规则通道)或 ADC_JSQR 寄存器(对于注入通道)中选择的所有通道。为组中的每个通道都执行一次转换。每次转换结束后,会自动转换该组中的下一个通道。如果将 CONT 位置 1,规则通道转换不会在组中最后一个所选通道处停止,而是再次从第一个所选通道继续转换。
如果将 DMA 位置 1,则在每次规则通道转换之后,均使用直接存储器访问 (DMA) 控制器将转换自规则通道组的数据(存储在 ADC_DR 寄存器中)传输到 SRAM。在以下情况下,ADC_SR 寄存器中的 EOC 位置 1:
● 如果 EOCS 位清零,在每个规则组序列转换结束时
● 如果 EOCS 位置 1,在每个规则通道转换结束时
从注入通道转换的数据始终存储在 ADC_JDRx 寄存器中。
数据对齐
由于STM32的ADC是12位的,结果存储在16位的数据寄存器中,有4位用不到,所以ADC存在左对齐或右对齐的方式。为方便读数,一般都选择右对齐。
可独立设置各通道采样时间
ADC 会在数个 ADCCLK 周期内对输入电压进行采样,可使用 ADC_SMPR1 和 ADC_SMPR2寄存器中的 SMP[2:0] 位修改周期数。每个通道均可以使用不同的采样时间进行采样。
总转换时间的计算公式如下:
T conv = 采样时间 + 12 个周期
快速转换模式
可通过降低 ADC 分辨率来执行快速转换。RES 位用于选择数据寄存器中可用的位数。每种分辨率的最小转换时间如下:
● 12 位:3 + 12 = 15 ADCCLK 周期
● 10 位:3 + 10 = 13 ADCCLK 周期
● 8 位:3 + 8 = 11 ADCCLK 周期
● 6 位:3 + 6 = 9 ADCCLK 周期
温度传感器和VRENFINT通道框图
要使用传感器,请执行以下操作:
选择 ADC1_IN16 或 ADC1_IN18 输入通道。
选择一个采样时间,该采样时间要大于数据手册中所指定的最低采样时间。
在 ADC_CCR 寄存器中将 TSVREFE 位置 1,以便将温度传感器从掉电模式中唤醒。
通过将 SWSTART 位置 1(或通过外部触发)开始 ADC 转换
读取 ADC 数据寄存器中生成的 V SENSE 数据
使用以下公式计算温度:
温度(单位为 °C)= {(V SENSE — V 25 ) / Avg_Slope} + 25
其中:
— V 25 = 25 °C 时的 V SENSE 值
— Avg_Slope = 温度与 V SENSE 曲线的平均斜率(以 mV/°C 或 μV/°C 表示)
由于ADC寄存器的相关配置相对比较简单,这里直接附上测量温度相关的ADC初始化和中断程序:
void ADC_Init()
{
u32 prigroup = 0;
u32 priority = 0;
RCC- >APB2ENR |= 1< < 8; //开ADC1时钟
ADC- >CCR = 0;
ADC- >CCR |= 1< < 23; //使能温度传感器和 V REFINT 通道
ADC- >CCR |= 1< < 16; //ADCCLK=21mHZ
ADC1- >CR1 = 0;//分辨率12位
ADC1- >CR2 = 0; //数据右对齐
ADC1- >CR2 |= 1< < 10; //通道只要转换结束,则将EOC置1,使能溢出检测
ADC1- >SMPR1 =0; //通道16采样时间3T
ADC1- >SQR3 |= 16< < 0;
ADC1- >CR1 |= 1< < 5; //使能EOC中断
ADC1- >SR &=~ (1< < 1); //清中断标记33
prigroup = NVIC_GetPriorityGrouping(); //得到优先级分组
priority = NVIC_EncodePriority(prigroup,1,2); //优先级编码
NVIC_SetPriority(ADC_IRQn,priority); //设置中断优先级
NVIC_EnableIRQ(ADC_IRQn); //使能ADC中断
ADC1- >CR2 |= 1< < 0; //使能ADC
Delay_us(3);
ADC1- >CR2 |= 1< < 30; //启动AD转换
}
u16 ADC1_Value={0}; //定义二维数组,按行存入三个通道的转换结束
u8 ADC1_OK = 0;
//中断处理函数
void ADC_IRQHandler()
{
if(ADC1- >SR & (1< < 1))
{
ADC1- >SR &=~ (1< < 1); //写0,请标记
ADC1_Value = ADC1- >DR; //按行依次存入各通道的转换结果
ADC1_OK = 1; //采集完成结束标记置1
}
}
接着编写主函数测试
#include 'stm32f4xx.h'
#include 'usart.h'
#include 'delay.h'
#include 'stdio.h'
#include 'ADC.h'
extern u8 ADC1_OK;
extern u16 ADC1_Value;
int main()
{
float wendu = 0;
NVIC_SetPriorityGrouping(7-2);
Usart1_Init(115200);
ADC_Init();
while(1)
{
if(ADC1_OK == 1)
{
ADC1- >SR &=~ (1< < 1); //清中断标记
ADC1_OK = 0;
wendu = 3.3*ADC1_Value/4095; //求温度数字值对应的模拟电压值
wendu = (wendu -0.76)*1000/2.5+25; //计算温度值
printf('温度AD值:%drnwendu = %.lf°Crn',ADC1_Value,wendu);
ADC1- >CR2 |= 1< < 0; //再次开启ADC1
Delay_us(3);
ADC1- >CR2 |= 1< < 30; //触发一次转换
}
Delay_ms(1000);
}
}
从串口可以看到ADC读取的温度值,ADC温度测量成功。
对于电池电压或者其他外部模拟量的测量,配置方法与温度测量类似,这里不再赘述。如果想要让CPU更多地用于算法或者其他功能的处理,同样可以配置DMA进行数据传输。