STM32急救速成——ADC采集:配置、校准、电压换算与进阶技巧
STM32急救速成——ADC采集:配置、校准、电压换算与进阶技巧
文章目录
ADC:从模拟信号到数字信号的桥梁
ADC(Analog-to-Digital Converter,模数转换器)是嵌入式系统中不可或缺的重要外设,它承担着将连续变化的模拟信号转换为数字信号的关键任务。举个例子,当我们想要测量某个电阻两端的电压时,这个电压信号本身是模拟量,而单片机作为数字处理器无法直接读取这类连续变化的信号。这时候,ADC就扮演了翻译官的角色,它能够将模拟电压值转换为对应的数字量,使得单片机能够识别并进行后续处理和分析。
ADC的用途:连接现实与数字世界的关键
ADC的核心作用在于搭建起模拟世界与数字系统之间的桥梁。它能够将诸如电压、温度、压力等连续变化的模拟信号,转换为单片机或其他处理器可以识别和处理的数字信号。这一功能使得ADC广泛应用于各种需要实时监测物理量的场景,例如温度监测系统、电流检测电路、光线感应控制等。无论是工业控制、消费电子还是物联网设备,ADC都扮演着不可或缺的角色,其用途之广,几乎涵盖了所有需要与模拟世界交互的嵌入式应用领域。
STM32F103ZET6的ADC资源详解
STM32F103ZET6内置了3个12位精度的ADC模块,总共提供18个采集通道,其中包括16个外部信号通道和2个内部信号源通道。这些ADC支持多种工作模式,用户可根据需要配置为单次转换、连续转换、扫描模式或间断采样模式。转换结果支持以左对齐或右对齐的方式存储在16位数据寄存器中,便于程序读取和处理。需要注意的是,ADC模块的输入时钟频率不得超过14MHz,该时钟由APB2总线时钟(PCLK2)经过分频后产生,在实际配置时应特别注意分频系数的设置。
1. ADC通道与引脚对应关系
STM32F103ZET6的ADC通道与具体引脚对应关系如下表所示。需要注意的是,多个ADC的同一通道可能复用了同一个GPIO引脚,在实际使用中需根据具体需求进行配置。
| 通道 | ADC1 | ADC2 | ADC3 |
|---|---|---|---|
| 通道0 | PA0 | PA0 | PA0 |
| 通道1 | PA1 | PA1 | PA1 |
| 通道2 | PA2 | PA2 | PA2 |
| 通道3 | PA3 | PA3 | PA3 |
| 通道4 | PA4 | PA4 | PA4 |
| 通道5 | PA5 | PA5 | PA5 |
| 通道6 | PA6 | PA6 | PA6 |
| 通道7 | PA7 | PA7 | PA7 |
| 通道8 | PB0 | PB0 | PB0 |
| 通道9 | PB1 | PB1 | PB1 |
| 通道10 | PC0 | PC0 | PC0 |
| 通道11 | PC1 | PC1 | PC1 |
| 通道12 | PC2 | PC2 | PC2 |
| 通道13 | PC3 | PC3 | PC3 |
| 通道14 | PC4 | PC4 | PC4 |
| 通道15 | PC5 | PC5 | PC5 |
| 通道10 | 内部温度传感器 | ||
| 通道10 | 内部参考电压VREF |
2. ADC时钟配置
ADC输入时钟ADC_CLK由APB2总线时钟分频产生,其最大允许值为14MHz。在库函数开发中,可通过以下函数设置分频系数:
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
可选的分频系数定义如下:
#define RCC_PCLK2_Div2 ((uint32_t)0x00000000)
#define RCC_PCLK2_Div4 ((uint32_t)0x00004000)
#define RCC_PCLK2_Div6 ((uint32_t)0x00008000)
#define RCC_PCLK2_Div8 ((uint32_t)0x0000C000)
由于APB2总线时钟通常为72MHz,而ADC最高工作频率为14MHz,一般推荐选择6分频,使ADC时钟频率为12MHz,在满足性能要求的同时留有一定余量。
3. ADC工作模式
STM32F1系列ADC支持三种主要工作模式:
- 单次转换模式:在该模式下,ADC仅执行一次转换操作。可以通过设置ADC_CR2寄存器的ADON位(仅规则通道)或通过外部触发(规则通道或注入通道)来启动转换,此时CONT位应设置为0。
- 连续转换模式:在此模式下,ADC完成一次转换后会立即启动下一次转换。可通过外部触发或设置ADC_CR2寄存器的ADON位来启动,需要将CONT位置1。
- 扫描模式:该模式下ADC会自动按预置顺序转换一组通道,适用于需要循环采集多路信号的场景。
4. ADC转换时间
ADC的总转换时间由采样时间和固定转换周期组成,计算公式为:总转换时间 = 采样时间 + 12.5个周期。其中采样时间可配置,最短为1.5个周期,因此最短总转换时间为14个时钟周期。
使用软件触发时,可通过以下参数配置采样时间:
#define ADC_SampleTime_1Cycles5 ((uint8_t)0x00)
#define ADC_SampleTime_7Cycles5 ((uint8_t)0x01)
#define ADC_SampleTime_13Cycles5 ((uint8_t)0x02)
#define ADC_SampleTime_28Cycles5 ((uint8_t)0x03)
#define ADC_SampleTime_41Cycles5 ((uint8_t)0x04)
#define ADC_SampleTime_55Cycles5 ((uint8_t)0x05)
#define ADC_SampleTime_71Cycles5 ((uint8_t)0x06)
#define ADC_SampleTime_239Cycles5 ((uint8_t)0x07)
5. ADC校准流程
使能ADC后,必须进行校准以确保转换精度。使用标准库开发时,可通过以下代码完成校准:
ADC_ResetCalibration(ADC1); // 重置指定ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待重置完成
ADC_StartCalibration(ADC1); // 开始ADC校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
6. ADC转换结果与实际电压换算
ADC转换得到的原始值(AD值)为数字量,需通过换算得到实际电压值。STM32的ADC为12位分辨率,因此AD值范围为04095(2^12-1),对应的电压范围通常为03.3V(参考电压VREF+)。
换算公式为:
实际电压值 = (AD值 / 4095) * 3.3
其中,AD值为ADC转换得到的数字量读数,实际电压值的单位为伏特(V)。例如,当AD值为2048时,对应的电压值约为1.65V。
ADC配置步骤详解
要完成STM32 ADC模块的配置和使用,需要按照以下步骤有序进行:
首先,需要使能相关GPIO端口和ADC模块的时钟,并将用于ADC采集的引脚设置为模拟输入模式,这是信号能够正确输入到ADC的前提。
接下来,根据系统时钟频率配置ADC的分频因子,确保ADC时钟不超过14MHz的最大限制,通常选择6分频使时钟工作在12MHz。
然后,初始化ADC参数,包括设置ADC的工作模式(单次、连续或扫描模式)、数据对齐方式、规则通道序列的长度和顺序等关键参数。
完成初始化后,使能ADC模块并进行校准。校准是保证ADC测量精度的关键步骤,包括复位校准和启动校准两个过程,需要等待校准完成。
最后,通过软件触发或外部触发启动AD转换,等待转换完成后即可读取转换结果寄存器中的AD值,进而通过换算得到实际的电压值。
ADC配置与数据采集实战
1. ADC初始化配置详解
下面以ADC1的通道1(PA1引脚)为例,展示完整的ADC初始化流程。配置采用6分频系数、单次转换模式,并使用软件触发方式。
/*
*==============================================================================
*函数名称:ADC1_Init
*函数功能:初始化ADC1模块
*输入参数:无
*返回值:无
*备 注:配置为单次转换模式,软件触发,右对齐数据
*==============================================================================
*/
void ADC1_Init(void)
{
// 定义初始化结构体
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 开启GPIOA和ADC1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
// 配置ADC时钟分频因子为6(72MHz/6=12MHz)
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 配置GPIO引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // ADC1通道1对应PA1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置ADC参数
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; // 转换通道数为1
ADC_Init(ADC1, &ADC_InitStructure); // 初始化ADC1
ADC_Cmd(ADC1, ENABLE); // 使能ADC1
// ADC校准流程
ADC_ResetCalibration(ADC1); // 复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待复位完成
ADC_StartCalibration(ADC1); // 开始校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能软件转换启动
}
2. 软件触发转换的实现
在库函数开发中,配置为软件触发模式后,可通过以下函数启动AD转换:
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState)
3. 读取转换结果与数据处理
库函数提供了直接读取转换结果的API:
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx)
下面提供一个实用的数据采集函数,支持多次采样取平均值,提高测量精度:
/*
*==============================================================================
*函数名称:Get_ADC_Value
*函数功能:读取指定规则通道的AD转换值(多次采样平均)
*输入参数:ch:规则通道ADC_Channel_x;times:采样次数
*返回值:多次采样的平均值
*备 注:采用239.5周期采样时间,提高测量精度
*==============================================================================
*/
u16 Get_ADC_Value(u8 ch, u8 times)
{
u32 temp_val = 0;
u8 t;
// 配置规则通道:ADC1,指定通道,序列1,239.5周期采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);
for(t = 0; t < times; t++)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动软件转换
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换完成
temp_val += ADC_GetConversionValue(ADC1); // 累加转换结果
delay_ms(5); // 适当延时
}
return temp_val / times; // 返回平均值
}
实战项目:电压采集与串口输出
本实战项目将使用ADC1的通道1(PA1引脚)进行电压采集,并将结果通过串口输出到上位机。由于开发板上可能没有现成的分压电阻电路,我们可以直接将PA1引脚连接到3.3V电源来验证ADC采集功能(注意:如需测量外部电压,请确保电压范围在0-3.3V之间,避免损坏ADC模块)。
1. ADC初始化配置
/*
*==============================================================================
*函数名称:ADC1_Init
*函数功能:初始化ADC1模块
*输入参数:无
*返回值:无
*备 注:配置为独立模式、单次转换、软件触发、右对齐
*==============================================================================
*/
void ADC1_Init(void)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 开启GPIOA和ADC1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
// 设置ADC分频因子为6(72MHz/6=12MHz)
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 配置PA1为模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // ADC1通道1对应PA1引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置ADC参数
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; // 1个转换通道
ADC_Init(ADC1, &ADC_InitStructure); // 初始化ADC1
ADC_Cmd(ADC1, ENABLE); // 使能ADC1
// ADC校准流程
ADC_ResetCalibration(ADC1); // 复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待复位完成
ADC_StartCalibration(ADC1); // 开始校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
}
2. 主程序实现
u16 gAdcAdValue = 0; // 存储原始AD转换值
float gAdcVol = 0; // 存储计算后的电压值
int main(void)
{
Med_Mcu_Iint(); // 系统初始化(包含串口配置)
ADC1_Init(); // 初始化ADC1模块
printf("STM32 ADC电压采集实验启动...\r\n");
while(1)
{
gAdcAdValue = Get_ADC_Value(ADC_Channel_1, 10); // 获取10次采样平均值
gAdcVol = (gAdcAdValue / 4095.0f) * 3.3f; // 计算实际电压值
printf("AD值: %4d, 电压: %.2f V\r\n", gAdcAdValue, gAdcVol); // 串口输出结果
delay_ms(500); // 延时500ms,控制输出频率
}
}
拓展应用
1. 定时器触发ADC采集
根据STM32中文参考手册,ADC支持通过定时器触发进行转换(注意:只有PWM的上升沿可以触发AD转换)。可用的触发源包括:
- TIM1_CH1:定时器1通道1的PWM触发
- TIM1_CH2:定时器1通道2的PWM触发
- TIM1_CH3:定时器1通道3的PWM触发
- TIM2_CH2:定时器2通道2的PWM触发
- TIM3_TRGO:定时器3触发
- TIM4_CH4:定时器4通道4的PWM触发

以下以TIM4通道4触发ADC采集为例,展示具体配置方法。
定时器PWM配置(不重映射引脚)
/*
*==============================================================================
*函数名称:TIM4_CH4_PWM_Init
*函数功能:初始化定时器4的PWM通道4
*输入参数:per:自动重装载值;psc:预分频系数
*返回值:无
*备 注:配置PB9为TIM4通道4输出
*==============================================================================
*/
void TIM4_CH4_PWM_Init(u16 per, u16 psc)
{
// 结构体定义
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
// 初始化GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PB9作为TIM4_CH4输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 初始化定时器时基参数
TIM_TimeBaseInitStructure.TIM_Period = per; // 自动装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 分频系数
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
// 初始化PWM参数
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 输出极性低
TIM_OCInitStructure.TIM_Pulse = 500; // 初始脉冲宽度
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
TIM_OC4Init(TIM4, &TIM_OCInitStructure); // 初始化通道4
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable); // 使能预装载寄存器
TIM_ARRPreloadConfig(TIM4, ENABLE); // 使能自动重装载预装载
TIM_Cmd(TIM4, ENABLE); // 使能定时器
}
ADC配置(TIM4触发)
/*
*==============================================================================
*函数名称:ADC1_Init
*函数功能:初始化ADC1,配置为TIM4_CH4触发
*输入参数:无
*返回值:无
*备 注:使用外部触发模式,TIM4通道4作为触发源
*==============================================================================
*/
void ADC1_Init(void)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
// 设置ADC分频因子为6(72MHz/6=12MHz)
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 配置规则通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);
// GPIO配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // ADC1通道1(PA1)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// ADC参数配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 关闭连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4; // TIM4通道4触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个转换通道
ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化
// 使能外部触发
ADC_ExternalTrigConvCmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE); // 开启AD转换器
// ADC校准
ADC_ResetCalibration(ADC1); // 复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待复位完成
ADC_StartCalibration(ADC1); // 开始校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
}
主程序实现
u16 gAdcAdValue = 0; // 存储AD转换原始值
float gAdcVol = 0; // 存储计算后的电压值
int main(void)
{
Med_Mcu_Iint(); // 系统初始化
TIM4_CH4_PWM_Init(1000, 71); // 初始化TIM4通道4 PWM(1kHz频率)
ADC1_Init(); // 初始化ADC1
while(1)
{
gAdcAdValue = ADC_GetConversionValue(ADC1); // 获取转换结果
gAdcVol = (gAdcAdValue / 4095.0f) * 3.3f; // 计算实际电压值
printf("Vol=%.2f V\r\n", gAdcVol); // 串口输出结果
delay_ms(500); // 延时控制输出频率
}
}
配置说明:
- TIM4_CH4_PWM_Init(1000, 71):分频系数为71+1,自动重装载值为1000,产生1kHz方波
- 触发AD转换的频率为1kHz,与PWM占空比无关
- ADC在每个PWM上升沿自动触发一次转换
2. 交流信号采集技术
STM32的ADC输入电压范围为0~3.3V,无法直接采集负电压信号。例如,对于下图所示的交流信号:
信号中位于0V以下的部分无法被直接采集。因此,在实际应用中,需要在交流信号输入ADC引脚之前,添加一个直流偏置电路,将整个交流信号抬升至0V以上,确保ADC能够完整采集波形。
3. 交流信号有效值计算
ADC常用于电流监测应用,通过采集电流互感器线圈两端的电压来监测主线路电流。由于通常处理的是交流信号,需要计算有效值。
常用的有效值计算方法有两种:一种是峰峰值除以√2,另一种是计算均方根(RMS)。推荐使用均方根计算方法,因为峰峰值容易受到噪声干扰而产生误差。
以下是一个计算50Hz交流信号有效值的示例程序。假设信号已添加1.65V直流偏置,ADC以1kHz频率采样(每个周期20个点):
int gAdcAdValue[20]; // 存储一个周期采样结果的数组
int gAdcValidValue = 0; // 存储计算得到的有效值
/*
*==============================================================================
*函数名称:Med_Adc_ValidValueCal
*函数功能:计算交流信号有效值(RMS)
*输入参数:无
*返回值:无
*备 注:假设信号已添加1.65V偏置(对应AD值2048)
*==============================================================================
*/
void Med_Adc_ValidValueCal(void)
{
int tempVar = 0; // 循环变量
long squarSum = 0; // 平方和(使用long防止溢出)
// 计算平方和
for(tempVar = 0; tempVar < 20; tempVar++)
{
// 去除直流偏置(1.65V对应AD值2048)
int acComponent = gAdcAdValue[tempVar] - 2048;
// 累加平方值
squarSum += (long)acComponent * acComponent;
}
// 求平均值
squarSum /= 20;
// 开平方得到均方根(有效值)
gAdcValidValue = sqrt(squarSum);
}
误差校正方法:
在实际应用中,通常需要进行误差校正:
- 分段校正:在不同测量区间使用不同的校正系数,适用于误差呈分段线性的情况
- 比例校正:适用于误差随测量值增大而增大的情况,通过减去或加上一定比例的计算值进行校正
注意事项:
- 确保采样数据类型的范围足够,防止计算过程中溢出
- 实际应用中可能需要添加数字滤波处理以提高测量稳定性
- 上述代码仅供参考,实际应用时需要根据具体硬件特性进行调整
💡毕设特供
我们已经将STM32 ADC的源码工程打包整理好了。大家可以关注文末GZ号——>后台私信【ADC】 获取。
更多推荐




所有评论(0)