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触发

ADC外部触发方式
以下以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】 获取


©️ 版权声明 | 📮 合作咨询 | 🚑 急救资源

原创内容:@二土电子团队

免责声明:本教程仅供学习参考

未经授权禁止任何形式转载/商用
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐