【STM32】ADC数模转换器
【STM32】ADC数模转换器
- 一、ADC数模—模数转换器
- 二、逐次逼近型 ADC
- 2.1 ADC0809芯片
- 2.2 STM32里的ADC
- 三、ADC基本结构
- 3.1 基本结构图
- 3.2 ADC通道和引脚复用关系
- 3.3 规则组的4种转换模式
- 3.4 触发控制(规则组的触发源)
- 3.5 其它有关细节
- 四、ADC程序
- 4.1 ADC+单通道(单次转换非扫描)
- 4.2 ADC+多通道
一、ADC数模—模数转换器
-
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。(DAC数字模拟转换器)
-
12位(0~2¹²⁻¹)逐次逼近型ADC,1μs转换时间 1MHz,32ADC的最快转换频率
-
输入电压范围0 - 3.3V,转换结果0 - 4095
-
18个输入通道,可测量16个外部(GPIO口)和2个内部信号源(内部温度传感器,内部参考电压 1.2V)
-
规则组(常规使用)和注入组(突发事件)两个转换单元
-
模拟看门狗自动监测输入电压范围(高于阈值/低于时,看门狗会触发中断)
C8T6的资源:ADC1,ADC2,10个外部输入通道
二、逐次逼近型 ADC
2.1 ADC0809芯片
以ADC0809芯片为例
ADC(模数转换器)的作用是把模拟信号(像连续变化的电压)转换成数字信号。以逐次逼近型ADC为例,转换过程大致如下:
- 启动转换:当START信号到来时,启动ADC转换。
- 逐次逼近比较:逐次逼近寄存器(SAR)从最高位开始,依次假设每一位为“1”,并将这个假设的数字量送入DAC(数模转换器)。DAC会把数字量转换成对应的模拟电压。然后,比较器将这个模拟电压与输入的模拟电压进行比较。
- 如果DAC输出的模拟电压小于输入的模拟电压,说明假设的这一位“1”是合理的,就保留这一位为“1”。
- 如果DAC输出的模拟电压大于输入的模拟电压,说明假设的这一位“1”不合理,就将这一位置为“0”。
- 按照这样的方式,从最高位到最低位依次进行判断,直到确定所有位的值。
- 转换结果输出:当所有位都判断完毕后,逐次逼近寄存器中的数字量就是输入模拟电压对应的数字量。此时,EOC(转换结束信号)会发出提示,当OE(输出使能)有效时,8位三态锁存缓冲器就会把转换得到的数字量输出(如D0 - D7)。
2.2 STM32里的ADC
- 有 18 个输入通道(如 ADCx_IN0 - ADCx_IN15 等)。
- 模拟多路开关,可选择最多 4 个注入通道、最多 16 个规则通道。
- 包含注入通道数据寄存器(4×16 位,无需担心数据覆盖)、规则通道数据寄存器(16 位,可配合 DMA)。
- 规则组:可以把它想象成日常的 “常规工作流程”,按照预先设定好的顺序,对多个指定的模拟输入通道依次进行转换。转换后的数据存储在规则通道数据寄存器中,并且可以配合 DMA(直接内存访问)进行数据传输,这样在进行大量数据转换时,不需要 CPU 频繁介入,提高了系统效率,适合连续、大量的常规数据采集场景。
- 注入组:注入组类似于 “突发任务” 或者 “优先任务” 组。当系统有一些紧急或者需要优先处理的模拟信号转换需求时,就可以使用注入组。它可以在规则组转换的过程中插入进来,优先进行转换。转换后的数据存储在注入通道数据寄存器中,这些寄存器通常有多个(比如 4 个 16 位的寄存器),可以存储多组注入转换的数据,而且由于是优先处理的 “突发” 任务,不需要像规则组那样依赖 DMA 进行大规模数据传输,适合小批量、高优先级的转换场景。
- 两者关系:规则通道负责常规、连续的数据采集,而注入通道可中断规则通道的转换过程,优先执行自身的转换任务,完成后再回到规则通道继续之前的转换。这样的设计能灵活管理不同优先级的模拟信号转换,既保障了常规数据的采集,又能及时响应特殊、紧急的转换需求,提升了 ADC 对复杂场景的适应能力。
- PS:规则通道数据寄存器只有一个16位的寄存器,需要配合DMA及时将数据转运,否则会出现数据覆盖丢失的问题。
- 时钟部分(ADCCLK,最大 14MHz,属于 RCC,只能选择 6/8 分频,来源于 RCC)。
- 触发转换部分,涉及多种触发源(如 TIM1、TIM2 等定时器相关触发,以及外部触发等)
- 模拟看门狗等模块,用于监测输入电压范围,可触发中断(至 NVIC 的 ADC 中断)。
三、ADC基本结构
3.1 基本结构图
按照此图来编写程序
3.2 ADC通道和引脚复用关系
3.3 规则组的4种转换模式
- 单次转换 + 非扫描模式
当 ADC 接收到触发信号后,仅对一个预设通道进行一次转换,转换完成后便停止工作。若需要再次转换,必须重新施加触发信号。 - 连续转换 + 非扫描模式
一次触发后,ADC 会对同一个通道进行连续不断的转换,无需再次触发,直到手动停止转换或系统复位。 - 单次转换 + 扫描模式
触发后,ADC 会按预设的通道序列(如通道 1→通道 2→通道 3)依次对多个通道各进行一次转换,完成所有通道的一轮扫描后自动停止。 - 连续转换 + 扫描模式
一次触发后,ADC 会按预设通道序列循环连续扫描(如通道 1→通道 2→通道 3→通道 1→通道 2…),不断重复转换过程,直到手动停止。
3.4 触发控制(规则组的触发源)
3.5 其它有关细节
- 采用数据右对齐
- 转换时间
AD转换的步骤:采样,保持,量化,编码(ADC逐次比较的过程)
STM32 ADC的总转换时间(T_{CONV}) = 采样时间 + 12.5个ADC周期
例如:当ADCCLK = 14MHz,采样时间为1.5个ADC周期
(T_{CONV}) = 1.5 + 12.5 = 14个ADC周期 = 1μs - 校准
ADC有个内置自校准模式,校准可大幅度减小因内部电容器组的变化而造成的精准度误差,建议每次上电后执行一次校准,启动校准前,ADC必须处于关电状态超过至少两个ADC时钟周期(STM32会自动完成) - 硬件电路
四、ADC程序
基本步骤
①开启RCC时钟,包括ADC和GPIO的时钟,ADCCLK
②配置GPIO成模拟输入的模式
③配置多路开关,把左边通道接到右边的规则组里
④配置ADC转换器,用结构体来配置
⑤调用ADC_cmd()函数
4.1 ADC+单通道(单次转换非扫描)
接线图
- AD.h 代码模块
#ifndef __AD_H
#define __AD_Hvoid AD_Init(void);
uint16_t AD_GetValue(void);#endif
- AD.c 代码模块
#include "stm32f10x.h" // Device headervoid AD_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*设置ADC时钟*/RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为模拟输入/*规则组通道配置*/ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0/*ADC初始化*/ADC_InitTypeDef ADC_InitStructure; //定义结构体变量ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换,失能,每转换一次规则组序列后停止ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1/*ADC使能*/ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行/*ADC校准*/ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准while (ADC_GetResetCalibrationStatus(ADC1) == SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1) == SET);
}/*** 函 数:获取AD转换的值* 参 数:无* 返 回 值:AD转换的值,范围:0~4095*/
uint16_t AD_GetValue(void)
{ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
- main.c 代码模块
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"uint16_t ADValue; //定义AD值变量
float Voltage; //定义电压变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化AD_Init(); //AD初始化/*显示静态字符串*/OLED_ShowString(1, 1, "ADValue:");OLED_ShowString(2, 1, "Voltage:0.00V");while (1){ADValue = AD_GetValue(); //获取AD转换的值Voltage = (float)ADValue / 4095 * 3.3; //将AD值线性变换到0~3.3的范围,表示电压OLED_ShowNum(1, 9, ADValue, 4); //显示AD值OLED_ShowNum(2, 9, Voltage, 1); //显示电压值的整数部分OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2); //显示电压值的小数部分Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间}
}
4.2 ADC+多通道
采用扫描模式实现多通道,最好配合DMA防止数据覆盖
本次利用单次转换非扫描模式,只要在每次触发转换之前,手动更改一下列表第一个位置通道即可
接线图
- AD.h 代码模块
#ifndef __AD_H
#define __AD_Hvoid AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);#endif
- AD.c 代码模块
#include "stm32f10x.h" // Device headervoid AD_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*设置ADC时钟*/RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0、PA1、PA2和PA3引脚初始化为模拟输入/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*//*ADC初始化*/ADC_InitTypeDef ADC_InitStructure; //定义结构体变量ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换,失能,每转换一次规则组序列后停止ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1/*ADC使能*/ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行/*ADC校准*/ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准while (ADC_GetResetCalibrationStatus(ADC1) == SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1) == SET);
}/*** 函 数:获取AD转换的值* 参 数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3* 返 回 值:AD转换的值,范围:0~4095*/
uint16_t AD_GetValue(uint8_t ADC_Channel)
{ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5); //在每次转换前,根据函数形参灵活更改规则组的通道1ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"uint16_t AD0, AD1, AD2, AD3; //定义AD值变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化AD_Init(); //AD初始化/*显示静态字符串*/OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");while (1){AD0 = AD_GetValue(ADC_Channel_0); //单次启动ADC,转换通道0AD1 = AD_GetValue(ADC_Channel_1); //单次启动ADC,转换通道1AD2 = AD_GetValue(ADC_Channel_2); //单次启动ADC,转换通道2AD3 = AD_GetValue(ADC_Channel_3); //单次启动ADC,转换通道3OLED_ShowNum(1, 5, AD0, 4); //显示通道0的转换结果AD0OLED_ShowNum(2, 5, AD1, 4); //显示通道1的转换结果AD1OLED_ShowNum(3, 5, AD2, 4); //显示通道2的转换结果AD2OLED_ShowNum(4, 5, AD3, 4); //显示通道3的转换结果AD3Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间}
}