STM32内部温度传感器与ADC双通道采集实战:从原理到代码优化

在嵌入式开发中,ADC(模数转换器)是最基础也最核心的外设之一。大多数开发者对ADC的基本使用已经驾轻就熟——配置单通道、读取电压值、简单计算后输出结果。但当我们把目光投向STM32内部集成的温度传感器,结合双通道ADC采集技术时,会发现一片全新的应用天地。本文将带你深入探索这一组合的独特价值,从硬件原理到代码优化,提供一套完整的实战方案。

1. STM32内部温度传感器的工作原理与特性

STM32系列微控制器内部集成了一个温度传感器,这个看似简单的功能模块实则蕴含着精妙的设计。与外部温度传感器相比,它不需要额外占用GPIO引脚,也不受外部电路干扰,特别适合对成本敏感且要求可靠性的应用场景。

温度传感器的关键参数:

  • 典型测量范围:-40°C到+125°C(具体范围因型号而异)
  • 精度:±1.5°C(在25°C时),整体精度约±3°C
  • 输出特性:输出电压随温度线性变化,斜率约为2.5mV/°C
  • 校准值:出厂时在30°C和110°C两点校准,校准值存储在芯片特定地址

温度传感器连接在ADC的专用通道上,不同型号STM32的具体通道号可能不同。以STM32F103系列为例:

// STM32F103内部温度传感器通道定义
#define TEMP_SENSOR_ADC_CHANNEL ADC_Channel_16

需要注意的是,内部温度传感器在默认情况下是关闭的,需要通过设置ADC_CCR寄存器的TSVREFE位来启用:

ADC_TempSensorVrefintCmd(ENABLE);  // 启用温度传感器和内部参考电压

2. 双通道ADC采集的硬件架构设计

实现温度传感器与外部电压同步采集的关键在于合理配置ADC的多通道扫描模式。STM32的ADC模块支持多种工作模式,我们需要特别关注以下两种:

  1. 扫描模式:按预定顺序自动切换多个通道进行转换
  2. 连续模式:完成一轮转换后自动开始下一轮转换

双通道采集的硬件连接示例:

信号类型GPIO引脚ADC通道备注
外部电压输入PA1ADC1_IN1需确保输入电压≤VDDA
内部温度传感器-ADC1_IN16内部连接,无需外部电路
参考电压--通常使用内部VDDA作为参考

在时钟配置方面,ADC的输入时钟不能超过14MHz。对于72MHz的系统时钟,推荐的分频设置:

RCC_ADCCLKConfig(RCC_PCLK2_Div6);  // 72MHz/6=12MHz

3. 代码实现与DMA优化

传统的单通道ADC采集代码难以满足双通道应用的需求,我们需要重构整个采集流程。下面是一个完整的实现方案:

3.1 初始化配置

void ADC1_DualChannel_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    ADC_InitTypeDef ADC_InitStruct;
    DMA_InitTypeDef DMA_InitStruct;
    
    // 1. 启用相关外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    // 2. 配置外部电压输入引脚(PA1)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 3. 启用内部温度传感器
    ADC_TempSensorVrefintCmd(ENABLE);
    
    // 4. DMA配置
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)&adc_values;
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStruct.DMA_BufferSize = 2;
    DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStruct);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    
    // 5. ADC基本配置
    ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStruct.ADC_ScanConvMode = ENABLE;       // 启用扫描模式
    ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; // 连续转换
    ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStruct.ADC_NbrOfChannel = 2;            // 两个通道
    ADC_Init(ADC1, &ADC_InitStruct);
    
    // 6. 配置通道及其采样时间
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);  // PA1
    ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_239Cycles5); // 温度传感器
    
    // 7. 启用ADC DMA
    ADC_DMACmd(ADC1, ENABLE);
    
    // 8. 校准并启用ADC
    ADC_Cmd(ADC1, ENABLE);
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
    
    // 9. 开始转换
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

3.2 数据读取与处理

使用DMA后,ADC转换结果会自动存储到指定数组中,我们只需定期读取即可:

// 定义全局变量存储ADC结果
uint16_t adc_values[2];  // index0:外部电压, index1:温度传感器

// 温度计算函数
float Get_Temperature(void) {
    float vsense = (float)adc_values[1] * 3.3f / 4095.0f;
    float temperature = (vsense - 0.76f) / 0.0025f + 25.0f;
    return temperature;
}

// 外部电压读取函数
float Get_ExternalVoltage(void) {
    return (float)adc_values[0] * 3.3f / 4095.0f;
}

4. 性能优化与实用技巧

4.1 采样时间优化

不同信号源需要不同的采样时间:

  • 外部电压:通常信号阻抗较低,可使用较短的采样时间(如55.5周期)
  • 温度传感器:输出阻抗较高,需要更长的采样时间(如239.5周期)

采样时间计算公式:

总转换时间 = (采样周期 + 12.5) × ADC时钟周期

以12MHz ADC时钟为例:

  • 55.5周期 → 5.67μs
  • 239.5周期 → 21μs

4.2 软件滤波算法

针对温度传感器的特性,推荐采用以下滤波组合:

  1. 移动平均滤波:减少随机噪声
#define FILTER_SIZE 8
float temp_history[FILTER_SIZE];
uint8_t index = 0;

float Moving_Average_Filter(float new_value) {
    temp_history[index] = new_value;
    index = (index + 1) % FILTER_SIZE;
    
    float sum = 0;
    for(int i=0; i<FILTER_SIZE; i++) {
        sum += temp_history[i];
    }
    return sum / FILTER_SIZE;
}
  1. 中值滤波:消除偶发干扰脉冲

4.3 温度校准提升精度

虽然STM32内部温度传感器精度有限,但通过两点校准可显著改善:

  1. 在已知温度T1下读取ADC值V1
  2. 在已知温度T2下读取ADC值V2
  3. 计算实际斜率:slope = (T2 - T1)/(V2 - V1)
  4. 应用校准公式:T_actual = T1 + slope × (V_current - V1)

4.4 低功耗优化策略

对于电池供电设备:

void Enter_LowPowerMode(void) {
    // 1. 降低ADC时钟
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);  // 9MHz
    
    // 2. 增加采样时间,允许使用更高阻抗的输入电路
    ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_480Cycles);
    
    // 3. 间歇采样模式
    ADC_ContinuousConvModeCmd(ADC1, DISABLE);
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    ADC_ExternalTrigConfig(ADC1, ADC_ExternalTrigConv_T3_TRGO);
}

5. 创新应用场景

结合双通道采集能力,可以构建多种实用应用:

  1. 温度补偿电压测量系统:

    • 通道1测量外部电压
    • 通道2监测芯片温度
    • 根据温度变化补偿电压测量值
  2. 设备健康监测:

    • 长期记录芯片温度变化
    • 分析温度与工作电压的关联性
    • 提前发现散热问题或电源异常
  3. 环境感知节点:

    • 温度作为环境参数
    • 外部通道连接光敏电阻
    • 实现低成本多参数传感
// 温度补偿示例
float Get_CompensatedVoltage(void) {
    float raw_voltage = Get_ExternalVoltage();
    float temperature = Get_Temperature();
    float compensation = 0.001f * (temperature - 25.0f); // 0.1%/°C补偿
    return raw_voltage * (1.0f + compensation);
}

在实际项目中,我发现内部温度传感器虽然精度有限,但其稳定性和重复性相当出色。通过适当的软件校准和滤波,完全能够满足大多数工业应用的需求。特别是在空间受限或成本敏感的设计中,这种方案比外接温度传感器更具优势。

更多推荐