本文使用芯片是 STM32F103ZET6
一、ADC基本概念
1. 基本概念
ADC的作用是将模拟信号转换为数字信号,以便微控制器能够理解和处理。在STM32微控制器中,ADC可以实现读取外部传感器的模拟数据的功能,如温度传感器、光敏电阻、压力传感器等。
在STM32系列微控制器中,ADC模块通常包括以下特性和功能:
- 多通道转换: 支持同时转换多个模拟通道的数据。
- 不同分辨率: 可以根据应用需求选择不同的转换精度(分辨率)。
- DMA支持: 可以通过DMA(直接存储器访问)提高转换效率,减少CPU负载。
- 外部触发: 可以通过外部触发信号启动转换过程,实现精确的时间控制。
- 参考电压选择: 可以选择内部或外部参考电压,以适应不同的应用场景。
- 校准: 支持硬件和软件校准,以提高转换精度和准确性。
2. STM32F103ZET6的ADC资源
- 每个STM32F103微控制器通常配备有3个ADC通道,这些通道可以单独使用,也可以双重使用以提高采样率。
- STM32F103的ADC是12位逐次逼近型的模拟数字转换器,具有多达18个复用通道,可测量来自16个外部源和2个内部信号源的信号。
- 可以以单次、连续、扫描或间断执行的方式对这些通道进行A/D转换。
- ADC的结果可以设置为左对齐或右对齐方式存储在16位数据寄存器中,以满足不同应用的需求。
- 除了常规的A/D转换功能外,ADC还允许应用程序检测输入电压是否超出自定义的阈值上限或下限,从而实现对输入信号的快速监测和反应。
3. 结构框图
4. 部分功能讲解
(1)VDDA(Analog Supply Voltage)
ADC模块的模拟电源引脚,通常连接到MCU的模拟电源线路。VDDA提供ADC模块所需的模拟电源电压,确保ADC的正常工作和准确的模拟信号转换。
(2)VSSA(Analog Ground Voltage)
ADC模块的模拟地引脚,通常连接到MCU的模拟地线路。
(3)VREF+(Positive Voltage Reference)
ADC模块的正参考电压引脚,通常连接到外部提供的正参考电压源。VREF+确定了ADC转换过程中的上限电压参考点,通常对应于模拟输入信号的最大量程值。在ADC转换过程中,被测量的模拟信号会与VREF+进行比较,以确定其对应的数字量。
开发板将VREF+和VDDA都连接了MCU供电引脚,测量上限是3.3V。
(4)VREF-(Negative Voltage Reference)
ADC模块的负参考电压引脚,通常连接到外部提供的负参考电压源或地。VREF-确定了ADC转换过程中的下限电压参考点,通常对应于模拟输入信号的最小量程值。
开发板将VREF-连接了接地引脚,这样测量下限是0V。
需要测量的电压值需要调制到VREF-、VREF+范围内进行测量,本文使用开发板测量范围是0~3.3V 。
(5)ADCx_INy
通道 n | ADC1 | ADC2 | ADC3 |
通道0 | PA0 | PA0 | PA0 |
通道1 | PA1 | PA1 | PA1 |
通道2 | PA2 | PA2 | PA2 |
通道 3 | PA3 | PA3 | PA3 |
通道4 | PA4 | PA4 | PF6 |
通道5 | PA5 | PA5 | PF7 |
通道6 | PA6 | PA6 | PF8 |
通道7 | PA7 | PA7 | PF9 |
通道8 | PB0 | PB0 | PF10 |
通道9 | PB1 | PB1 | |
通道10 | PC0 | PC0 | PC0 |
通道11 | PC1 | PC1 | PC1 |
通道12 | PC2 | PC2 | PC2 |
通道13 | PC3 | PC3 | PC3 |
通道14 | PC4 | PC4 | |
通道15 | PC5 | PC5 | |
通道16 | 内部温度传感器 | ||
通道17 | 内部参考电压VREF |
5. 规则通道与注入通道
在STM32微控制器的ADC模块中,分成了两种不同类型的通道:规则通道和注入通道,它们适应于不同的采集场景,功能框图如下图所示:
1. 规则通道(Regular Channels):
规则通道是ADC模块中用于常规数据采样的通道。它们允许用户配置ADC以便按照预定的顺序对这些通道进行连续或单次采样。规则通道通常用于周期性地采集传感器数据或监测模拟信号。一般情况下,规则通道的采样是通过ADC的常规转换触发进行的。
2. 注入通道(Injected Channels):
注入通道是一种高优先级的ADC通道,用于快速响应于外部事件并进行数据采样。注入通道允许用户将某些通道配置为在触发器触发时进行一次或多次采样,而不影响正在进行的规则通道转换。这种功能使得注入通道特别适合于采集突发事件或需要高优先级响应的应用,比如实时监控或故障检测。
6. 分频设置
ADC工作频率一般要求14MHz以下,ADC工作在的APB2总线的72M需要分频,一般分频因子设置为6。
7. 计算采样时间
ADC 要完成输入电压采样需要若干个ADC_CLK周期 , 最小采样周期为1.5个。
=采样时间+12.5个周期Tconv=采样时间+12.5个周期
当ADC_CLK=14MHz时,设置1.5个采样周期 , 则Tconv=1.5+12.5=14个周期=1us。
二、ADC工作流程
1. 使能端口时钟、ADC时钟
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
2. 设置引脚模式为模拟输入
void GPIO_Configuration(void) {
// 初始化 GPIO 变量
GPIO_InitTypeDef GPIO_InitStructure;
// 使能 GPIO 时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 配置引脚模式为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 假设使用 PA0 引脚作为模拟输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; // 模拟输入模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 不使用上拉/下拉
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA
}
3. 设置预分频器分频因子
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
4. 初始化ADC,设置工作模式、规则序列
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
其中结构体介绍:
(1)ADC_TypeDef
ADC_TypeDef定义的 ADC 外设的类型,用于表示具体的 ADC 控制器。
typedef struct
{
__IO uint32_t SR; // ADC 状态寄存器
__IO uint32_t CR1; // ADC 控制寄存器1
__IO uint32_t CR2; // ADC 控制寄存器2
__IO uint32_t SMPR1; // ADC 采样时间寄存器1
__IO uint32_t SMPR2; // ADC 采样时间寄存器2
__IO uint32_t JOFR1; // ADC 规则通道数据偏移寄存器1
__IO uint32_t JOFR2; // ADC 规则通道数据偏移寄存器2
__IO uint32_t JOFR3; // ADC 规则通道数据偏移寄存器3
__IO uint32_t JOFR4; // ADC 规则通道数据偏移寄存器4
__IO uint32_t HTR; // ADC 规则通道高阈值寄存器
__IO uint32_t LTR; // ADC 规则通道低阈值寄存器
__IO uint32_t SQR1; // ADC 规则通道序列1寄存器
__IO uint32_t SQR2; // ADC 规则通道序列2寄存器
__IO uint32_t SQR3; // ADC 规则通道序列3寄存器
__IO uint32_t JSQR; // ADC 注入通道序列寄存器
__IO uint32_t JDR1; // ADC 注入通道数据寄存器1
__IO uint32_t JDR2; // ADC 注入通道数据寄存器2
__IO uint32_t JDR3; // ADC 注入通道数据寄存器3
__IO uint32_t JDR4; // ADC 注入通道数据寄存器4
__IO uint32_t DR; // ADC 规则通道数据寄存器
} ADC_TypeDef;
(2)ADC_InitTypeDef
typedef struct
{
uint32_t ADC_Mode; // ADC 工作模式
FunctionalState ADC_ScanConvMode; // ADC扫描(多通道)或单次(单通道)模式选择
FunctionalState ADC_ContinuousConvMode; // 单次转换或者连续转换选择
uint32_t ADC_ExternalTrigConvEdge; // 外部触发信号
uint32_t ADC_DataAlign; // 转换结果数据对齐方式
uint8_t ADC_NbrOfChannel; // ADC采集通道数
}ADC_InitTypeDef;
5. 使能ADC、校准
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC_Cmd(ADC1, ENABLE);
// 校准
ADC_StartCalibration(ADC1);
// 等待复位校准完成
while(ADC_GetResetCalibrationStatus(ADC1));
// 等待校准结束
while(ADC_GetCalibrationStatus(ADC1));
6. 读取ADC转换值
//设置规则序列通道及采样周期
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,uint8_t Rank,uint8_t ADC_SampleTime);
// 软件触发开启转换
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 获取结果
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// 获取AD转换状态信息
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
三、代码示例
1. 硬件电路
本示例从ADC1_IN1输入模拟信号进行转换(PA1引脚)。
使用开发板原理图如下:
ADC1上使用了一个LM358运放,虽然一些简单情况下可以直接通过MCU端口进行ADC采集, 但使用运放会更好,原因如下:
- 运放可以对信号进行放大;
- 运放的输入阻抗通常很高,避免信号源与ADC输入之间的阻抗不匹配造成信号失真;
- 运放可以配合外部电容、电阴实现滤波,滤除高频噪声或抑制干扰信号;
- 提供过载保护和电压限制功能,保护MCU。
2. adc_utils.c
#include "adc_utils.h"
/**
* @brief ADCx初始化端口
*/
void ADCx_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 模拟输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
// 速度50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 设置分频因子为6
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// ADC1配置
ADC_InitTypeDef ADC_InitStructure;
// 单通道模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
// 扫描模式, 一次转换一个通道
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
// 连续扫描还是单次扫描
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
// 使用软件触发,不使用外部触发
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 通道数
ADC_InitStructure.ADC_NbrOfChannel = 1;
// 完成初始化
ADC_Init(ADC1, &ADC_InitStructure);
// 使能ADC1
ADC_Cmd(ADC1, ENABLE);
// 复位校准
ADC_ResetCalibration(ADC1);
// 等待复位校准完成
while(ADC_GetResetCalibrationStatus(ADC1));
// 开始校准
ADC_StartCalibration(ADC1);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC1));
// 软件触发ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
/**
* @param ch 通道
* @param time 读取次数
* @brief 读取ADC值
*/
u16 Get_Adc(u8 ch, u8 time){
u8 i;
// 规则通道的规则序列进行设定,采样周期最长
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);
u16 value = 0;
for(i = 0; i < time; i++){
// 每次都用软件触发转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 等待转换结束
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// 返回转换结果
value += ADC_GetConversionValue(ADC1);
}
return value / time;
}
3. main.c
#include "gpio_utils.h"
#include "stm32f10x.h"
#include "sys_tick_utils.h"
#include "usart_utils.h"
#include "stdio.h"
#include "adc_utils.h"
#include "led_utils.h"
// 主函数
int main(void)
{
u8 i;
u16 value;
GPIO_Configuration(); // 调用GPIO配置函数
// tick 初始化
sys_tick_init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART3_Init(9600);
printf("starting...");
ADCx_Init();
while (1) // 无限循环
{
i++;
if(i%20==0){
led_flash(0);
}
if(i%50==0){
value = Get_Adc(ADC_Channel_1, 20);
printf("value=%d\r\n", value);
printf("votage=%f\r\n", (float)value*(3.3/4096));
}
delay_ms(10);
}
}
调节开发板上的AD1电位器,可以看到串口输出的电压发生变化 。
本文学习资源来自普中stm32教程
本文代码开源地址:
https://gitee.com/xundh/stm32_arm_learn