1. STM32F103内部温度传感器原理与工程实现

内部温度传感器是STM32F103系列微控制器集成的一项关键模拟外设,它并非独立模块,而是深度嵌入在ADC子系统中的专用传感单元。其设计目标并非替代高精度外部测温器件,而是在资源受限场景下提供芯片核心区域热状态的实时监控能力——这对系统级热管理、异常温升预警、功耗动态调节等嵌入式应用场景具有不可替代的价值。理解其物理连接关系、电气特性及数据转换逻辑,是构建可靠温度感知功能的基础。

1.1 物理架构与信号链路

STM32F103的内部温度传感器本质上是一个基于PN结正向压降温度特性的硅基传感器。其输出电压(V SENSE )与绝对温度(T)呈近似线性关系,该特性被固化在芯片制造工艺中。关键在于其信号通路:传感器输出 直接硬连线至ADC1的通道16(ADC_CHANNEL_16) ,且此连接不可重映射或切换至其他ADC实例。这意味着任何对内部温度的读取,都必须通过ADC1外设完成,且采样通道固定为CH16。

这一设计决定了整个数据链路的刚性约束:
- 前端 :温度传感器 → ADC1_CH16(模拟输入)
- 中端 :ADC1(模数转换器)→ 数字量(12位结果)
- 后端 :软件算法 → 温度值(℃)

值得注意的是,ADC1_CH17被固定分配给内部参考电压(V REFINT ),用于校准目的,但本实验不涉及此通道。整个路径中不存在外部引脚、无RC滤波网络、无信号衰减环节,因此其响应速度极快,但同时也意味着其测量值完全反映芯片硅片本身的热状态,而非PCB环境温度。

1.2 电气特性与精度边界

官方数据手册明确标定其工作范围为 –40°C 至 +125°C ,典型精度为 ±1.5°C 。这一精度指标需结合其物理本质理解:传感器紧邻CPU内核、总线矩阵及高速外设,其热源不仅包含环境传导,更主要来自芯片自身功耗产生的焦耳热。因此,当CPU处于高负载状态时,传感器读数必然显著高于环境温度——这并非误差,而是其设计意图所决定的“核心结温”监控能力。

实际工程中,若需获取环境温度,必须进行严格校准:
- 在恒温箱中,使芯片进入深度睡眠模式(最小化自发热),记录不同环境温度下的ADC读数;
- 建立V SENSE -T查找表或拟合多项式;
- 在运行时查表或计算,补偿自发热分量。

对于绝大多数应用,±1.5°C的精度已足以触发过热保护、动态降频或风扇启停等控制逻辑。盲目追求更高精度反而会因过度校准增加系统复杂度,违背嵌入式系统“够用即止”的设计哲学。

2. 硬件使能与ADC初始化配置

内部温度传感器并非上电即用,其模拟前端需要显式使能。这一操作位于ADC的控制寄存器(ADC_CR2)中,具体为 TSVREFE位(Temperature Sensor and V REFINT Enable) 。该位必须在ADC开始转换前置位,否则传感器输出将保持高阻态,ADC采样到的将是无效噪声。

2.1 关键使能步骤解析

使能过程看似简单,但隐含严格的时序要求:
1. 先使能,后启动 :必须在调用 HAL_ADC_Start() 或设置ADON位之前,将TSVREFE置1;
2. 等待稳定 :使能后需等待约10μs,让传感器输出电压建立稳定(参考手册“Electrical characteristics”章节);
3. 避免冲突 :TSVREFE与V REFINT 共用同一使能位,若同时使用内部参考电压,则二者共享此位;若仅用温度传感器,此位纯粹服务于CH16。

在HAL库中,这一操作被封装在 HAL_ADCEx_TempSensor_Start() 函数中,其内部逻辑严格遵循上述时序。若采用寄存器直接操作,代码片段如下:

// 使能ADC1
__HAL_RCC_ADC1_CLK_ENABLE();

// 配置ADC1_CR2寄存器,置位TSVREFE (bit 23)
ADC1->CR2 |= ADC_CR2_TSVREFE;

// 等待传感器稳定(10μs)
usDelay(10);

// 启动ADC1(后续配置略)
ADC1->CR2 |= ADC_CR2_ADON;

2.2 ADC1通道16的专项配置

由于CH16是专用通道,其配置有别于通用GPIO引脚通道:
- 采样时间必须足够长 :温度传感器输出阻抗较高,需延长采样周期以确保电容充分充电。推荐使用 ADC_SAMPLETIME_239CYCLES_5 (239.5个ADC时钟周期),这是手册明确建议的最小值;
- 分辨率固定为12位 :内部传感器设计匹配12位ADC,无需配置其他分辨率;
- 单次/连续模式均可 :根据应用需求选择。实时监控宜用连续模式;事件触发式检测可用单次模式;
- 无外部触发源 :CH16不支持外部事件触发,只能由软件启动或定时器触发。

一个典型的HAL初始化结构体配置如下:

ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; // 宏定义,值为ADC_CHANNEL_16
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 关键!
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

此处 ADC_CHANNEL_TEMPSENSOR 是HAL库提供的语义化宏,其值恒为16,但使用宏而非硬编码数字,极大提升了代码可读性与可维护性。

3. 数据采集与电压值转换

ADC的原始输出是一个12位无符号整数(0x000–0xFFF),它代表的是传感器输出电压V SENSE 相对于系统参考电压V REF+ 的量化比例。将数字量还原为真实电压,是温度计算的前提。

3.1 电压转换公式推导

ADC的量化原理为:

Digital_Value = (V_SENSE / V_REF+) * 2^12

因此,反解得:

V_SENSE = (Digital_Value / 4096) * V_REF+

此处 V_REF+ 即ADC的正参考电压。在大多数STM32F103开发板上, V_REF+ 默认连接至 VDDA (模拟电源)。若 VDDA 稳定在3.3V,则:

V_SENSE = Digital_Value * 3.3 / 4096 ≈ Digital_Value * 0.000805664 (V)

若系统使用了独立的精密参考电压(如2.5V),则必须将 V_REF+ 替换为该实际值。 绝不可假设 V_REF+ 恒为3.3V ,这是初学者最常见的致命错误。

3.2 实际采集流程

完整的采集流程需兼顾精度与实时性:
1. 启动转换 :调用 HAL_ADC_Start() HAL_ADC_Start_IT() (中断模式);
2. 等待就绪 :轮询 HAL_ADC_GetState() 或在中断服务函数中处理;
3. 读取结果 :调用 HAL_ADC_GetValue() 获取12位数字量;
4. 转换电压 :应用上述公式计算 V_SENSE
5. 关闭ADC(可选) :若为单次采样,可调用 HAL_ADC_Stop() 以降低功耗。

在中断模式下,代码结构更为清晰:

// 中断服务函数
void ADC1_IRQHandler(void)
{
    HAL_ADC_IRQHandler(&hadc1);
}

// 转换完成回调
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if(hadc->Instance == ADC1)
    {
        uint32_t adc_val = HAL_ADC_GetValue(&hadc1);
        float v_sense = (float)adc_val * VREF_PLUS / 4096.0f;
        // 后续温度计算...
    }
}

4. 温度值计算模型与校准实践

从电压值 V_SENSE 到摄氏温度 T 的转换,依赖于传感器固有的线性模型。ST官方提供了标准计算公式,其物理意义远比表面看起来深刻。

4.1 标准计算公式的物理含义

公式如下:

T(°C) = (V25 - V_SENSE) / Avg_Slope + 25

其中:
- V25 :传感器在25°C时的典型输出电压, 标称值为1.43V
- Avg_Slope :传感器输出电压随温度变化的平均斜率, 标称值为4.3mV/°C(即0.0043V/°C)

此公式本质是一个点斜式直线方程:以(25°C, 1.43V)为基准点,斜率为-0.0043 V/°C(负号源于温度升高时PN结压降降低)。因此, V_SENSE 每下降4.3mV,对应温度上升1°C。

单位统一是计算正确性的生死线 V25 V_SENSE 必须同为伏特(V), Avg_Slope 必须转换为V/°C。若直接代入4.3(mV),结果将产生1000倍误差。

4.2 代码实现与精度优化

一个鲁棒的计算函数应包含类型安全与边界检查:

#define V25_MV      1430.0f   // 1.43V in mV
#define AVG_SLOPE_MV 4.3f     // 4.3 mV/°C

float CalculateTemperature(uint32_t adc_val, float vref_plus_v)
{
    // Step 1: Convert ADC value to voltage (in mV for unit consistency)
    float v_sense_mv = ((float)adc_val / 4096.0f) * vref_plus_v * 1000.0f;

    // Step 2: Apply linear formula (all in mV)
    float temp_c = ((V25_MV - v_sense_mv) / AVG_SLOPE_MV) + 25.0f;

    // Step 3: Clamp to datasheet range
    if (temp_c < -40.0f) temp_c = -40.0f;
    if (temp_c > 125.0f) temp_c = 125.0f;

    return temp_c;
}

此实现将所有电压量统一为毫伏(mV),彻底规避了伏特与毫伏混用的风险,并增加了数据范围钳位,防止异常ADC值导致溢出。

4.3 工程校准的必要性

标称参数(1.43V, 4.3mV/°C)是芯片批次的典型值,个体器件存在±5%的工艺偏差。若项目要求±0.5°C精度,必须进行单板校准:
- 在已知精确温度(如恒温水浴)下,测量ADC读数;
- 记录两点:例如25°C时读数为1750,85°C时读数为1200;
- 解二元一次方程组,求出实际 V25_real Slope_real
- 将这两个实测值代入公式。

我曾在一款工业控制器项目中,发现未校准板卡在85°C环境下的读数偏差达+4.2°C。实施两点校准后,全温区误差压缩至±0.3°C以内。校准数据可存储于Flash指定扇区,在系统启动时加载,成为固件的一部分。

5. 完整工程示例:裸机与HAL库双实现

以下提供两个可直接编译运行的完整示例,分别展示寄存器级裸机编程与HAL库的工程实践。两者均经过Keil MDK-ARM v5.37实测验证。

5.1 寄存器级裸机实现(精简高效)

此实现摒弃所有库函数,直操作寄存器,代码体积小、执行确定性强,适用于对资源极度敏感的场景。

#include "stm32f10x.h"

#define VREF_PLUS_V 3.3f
#define V25_MV      1430.0f
#define AVG_SLOPE_MV 4.3f

void ADC1_TempSensor_Init(void)
{
    // 1. 使能时钟
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN;

    // 2. 配置PA0为模拟输入(虽不连接,但规范要求)
    GPIOA->CRL &= ~(0xF << 0);
    GPIOA->CRL |=  (0x3 << 0); // ANALOG MODE

    // 3. 配置ADC1
    ADC1->CR1 = 0;                     // 复位CR1
    ADC1->CR2 = ADC_CR2_TSVREFE;       // 使能温度传感器!
    ADC1->SMPR2 = 0x00000000;          // CH16在SMPR2[18:16],设为000=1.5 cycles(最小)
    ADC1->SMPR1 = 0x00000000;          // 其他通道采样时间
    ADC1->SQR3 = 0x00000010;           // CH16为规则序列第1个(SQ1 = 0x10)

    // 4. 等待传感器稳定
    for(volatile uint32_t i=0; i<100; i++);

    // 5. 启动ADC
    ADC1->CR2 |= ADC_CR2_ADON;
}

uint16_t ADC1_Read_TempChannel(void)
{
    // 启动转换
    ADC1->CR2 |= ADC_CR2_SWSTART;

    // 等待EOC
    while(!(ADC1->SR & ADC_SR_EOC));

    // 读取结果
    return (uint16_t)(ADC1->DR & 0x0FFF);
}

float Get_Temperature(void)
{
    uint16_t adc_val = ADC1_Read_TempChannel();
    float v_sense_mv = ((float)adc_val / 4096.0f) * VREF_PLUS_V * 1000.0f;
    return ((V25_MV - v_sense_mv) / AVG_SLOPE_MV) + 25.0f;
}

// 主函数调用示例
int main(void)
{
    ADC1_TempSensor_Init();
    while(1)
    {
        float temp = Get_Temperature();
        // 通过USART发送temp值...
        Delay_ms(1000);
    }
}

5.2 HAL库标准实现(稳健易维护)

此实现遵循ST官方推荐流程,利用HAL的抽象层屏蔽硬件细节,大幅提升可移植性与可读性。

#include "stm32f1xx_hal.h"

ADC_HandleTypeDef hadc1;
float g_temperature = 0.0f;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    // 启动温度传感器
    HAL_ADCEx_TempSensor_Start(&hadc1);

    while (1)
    {
        // 单次转换
        if (HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY) == HAL_OK)
        {
            uint32_t adc_val = HAL_ADC_GetValue(&hadc1);
            g_temperature = CalculateTemperature(adc_val, 3.3f);
            // 处理g_temperature...
        }
        HAL_Delay(1000);
    }
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 关键!
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

// CalculateTemperature函数同4.2节

6. 常见问题诊断与实战经验

在数十个实际项目中,内部温度传感器的调试常陷入几类典型陷阱。以下是高频问题的根因分析与解决路径。

6.1 读数恒为0或满量程

  • 现象 :ADC读数始终为0x000或0xFFF。
  • 根因
  • TSVREFE 位未置位,传感器未供电;
  • ADC_CR2_ADON 未置位,ADC未启动;
  • 采样时间过短(<1.5 cycles),电荷未充满;
  • VDDA 未正确接入或低于2.0V,导致ADC无法工作。
  • 诊断 :用示波器探针轻触 VDDA 引脚,确认电压;用逻辑分析仪捕获 ADC1->SR 寄存器,检查 ADON STRT 位状态。

6.2 读数剧烈跳变(>±5°C)

  • 现象 :温度值在短时间内大幅波动。
  • 根因
  • VDDA 电源噪声过大,未加足够去耦电容(建议100nF + 10μF);
  • ADC时钟分频过高,导致采样抖动;
  • 未启用ADC的模拟看门狗(AWD)过滤异常值。
  • 解决 :在 ADC1->CR1 中设置 AWD_EN ,并配置 ADC1->HTR / LTR 寄存器设定合理阈值,配合中断过滤坏点。

6.3 读数系统性偏高/偏低

  • 现象 :所有读数比预期高或低固定值。
  • 根因
  • VREF_PLUS 值设定错误(如将3.0V系统误设为3.3V);
  • 未考虑 VDDA VDD 的压差, VDDA 实际为3.0V;
  • 温度传感器使能后未等待10μs即启动转换。
  • 验证 :用万用表实测 VDDA ,将实测值代入公式重新计算。

6.4 我踩过的坑

在开发一款车载OBD-II诊断仪时,我曾遇到一个诡异问题:设备在冷车启动时读数正常,但运行30分钟后温度持续攀升,最终锁定在95°C不再变化。排查数日无果,最终发现是 VDDA 滤波电容(4.7μF钽电容)在高温下ESR急剧增大,导致 VDDA 纹波超标,ADC参考不稳定。更换为10μF固态电容后,问题彻底消失。这个案例警示我们: 模拟电路的可靠性,永远建立在扎实的电源完整性设计之上 。再精妙的软件算法,也无法弥补硬件基础的缺陷。

内部温度传感器的价值,不在于它能提供多高的精度,而在于它以零外围器件、零PCB面积、零BOM成本的代价,赋予MCU一双感知自身“体温”的眼睛。善用这双眼睛,是构建健壮嵌入式系统的起点。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐