1. STM32内部温度传感器原理与工程实现全解析

内部温度传感器是STM32系列微控制器集成的一项关键模拟外设,它并非独立芯片,而是直接嵌入在硅基片内部的精密温敏结构。其核心价值在于为系统提供芯片结温(Junction Temperature)的实时反馈,而非环境温度测量。这一特性决定了它在过热保护、动态频率调节、功耗管理及系统健康状态监控等场景中不可替代。但必须明确:该传感器测量的是MCU自身发热导致的硅片温度变化,其数值会显著高于PCB环境温度,且受封装热阻、工作负载、散热条件影响极大。工程实践中若误将其当作环境温度计使用,将导致严重逻辑错误。本节将从物理原理、硬件接口、数学模型到软件实现,完整还原这一外设的工程落地路径。

1.1 硬件架构与电气特性

STM32各主流系列(F1/F4/F7/H7)均集成了基于带隙基准(Bandgap Reference)原理的内部温度传感器。其本质是一个对温度敏感的PN结二极管,其正向压降(V BE )随温度呈线性变化。该结构被精密设计并校准,确保在-40°C至+125°C宽温域内具备可预测的输出特性。所有系列均通过专用模拟通道接入ADC模块,但具体通道映射、供电控制位及校准参数存在代际差异,这是工程配置的首要依据。

系列 温度测量范围 典型精度 最大采样时间要求 ADC通道映射 上电控制寄存器位
F1 -40°C ~ +125°C ±1°C ≥17.1 μs ADC1_IN16 ADC_CR2.TSVREFE
F4/F7 -40°C ~ +125°C ±1.5°C ≥10.0 μs ADC1_IN16 (F407) / ADC1_IN18 (F429/F7) ADC_CR2.TSVREFE
H7 -40°C ~ +125°C ±1.0°C ≥9.0 μs ADC3_IN18 ADC3_CR.TSEN

关键点解析:
- 精度标注含义 :表中“典型精度”指在25°C标定点附近、标准测试条件下的统计平均误差;“最大误差”是器件在整个温度范围内保证的最差情况偏差。实际应用中,±1°C典型值意味着在多数工况下,读数与真实结温偏差不超过1摄氏度,但极端高低温点可能接近±2°C极限。
- 采样时间强制约束 :此参数非性能优化项,而是硬件时序要求。若配置的ADC采样周期短于表中最小值,传感器输出电压未能稳定至最终值,ADC转换结果将包含显著误差。例如F1系列要求≥17.1μs,若配置为1.5个ADC时钟周期(假设ADCCLK=14MHz,周期≈71ns),则总采样时间仅107ns,远低于要求,必然导致数据失真。
- 上电控制机制 :传感器默认处于断电状态以降低静态功耗。启用前必须通过设置对应控制位(如F1/F4/F7的 ADC_CR2.TSVREFE 或H7的 ADC3_CR.TSEN )为其模拟电路供电。此操作需在ADC使能前完成,且需等待足够时间(通常数微秒)让内部电路建立稳定偏置。

1.2 温度-电压转换模型与校准原理

内部温度传感器的输出并非直接温度数字量,而是与温度呈线性关系的模拟电压(V sense )。ADC采集得到的是该电压对应的数字量(DN),再经由特定数学模型反推温度。各系列模型虽形式相似,但核心校准参数源于芯片出厂时的精密测试,存储于唯一OTP(One-Time Programmable)区域,确保每颗芯片个体特性被准确补偿。

1.2.1 F1系列:单点校准线性模型

F1系列采用经典两点式线性插值简化模型:
$$ T(°C) = \frac{(V_{25} - V_{sense})}{\text{Avg_Slope}} + 25 $$

  • V 25 :25°C时的基准电压,典型值为1.43V。此值存储于芯片数据手册的“Electrical Characteristics”章节,是芯片批次的标称中心值。
  • Avg_Slope :V sense -T曲线的平均斜率,典型值为4.3 mV/°C(即0.0043 V/°C)。该斜率反映了温度每变化1°C,V sense 的电压变化量。
  • V sense :当前实测电压值,由ADC数字量换算得出:
    $$ V_{sense} = \frac{DN \times V_{ref}}{2^{12}} $$
    其中,DN为12位ADC转换结果(0-4095),V ref 为ADC参考电压(通常为3.3V)。

工程意义 :此模型假设V sense -T曲线为完美直线,以25°C点为锚定基准。其优势在于计算简单、资源消耗极低,适用于对精度要求不苛刻的场合。但忽略高阶非线性,全温域内最大残余误差约±2°C。

1.2.2 F4/F7系列:增强单点校准模型

F4/F7系列模型结构相同,但校准参数不同:
$$ T(°C) = \frac{(V_{25} - V_{sense})}{\text{Avg_Slope}} + 25 $$

  • V 25 :典型值为0.76V(F429/F7数据手册确认)。
  • Avg_Slope :典型值为2.5 mV/°C(即0.0025 V/°C)。

关键差异 :V 25 和斜率值的变更,源于F4/F7工艺节点升级及传感器结构优化。0.76V的基准电压显著低于F1的1.43V,这要求ADC参考电压(V ref )必须稳定且精确,否则小电压变化将被放大为更大的温度计算误差。例如,若V ref 存在±1%偏差,F4的温度计算误差将比F1大近一倍。

1.2.3 H7系列:双点校准高精度模型

H7系列摒弃单点线性假设,采用基于两个校准点的分段线性模型,大幅抑制非线性误差:
$$ T(°C) = \left( \frac{TS_CAL2 - TS_CAL1}{110 - 30} \right) \times (DN - TS_CAL1) + 30 $$

  • TS_CAL1 :30°C时的ADC校准数字量,存储于地址 0x1FF1E820 (16位值)。
  • TS_CAL2 :110°C时的ADC校准数字量,存储于地址 0x1FF1E822 (16位值)。
  • DN :当前ADC转换得到的12位数字量(ADC3_IN18通道)。

革命性改进 :此模型不再依赖模拟电压V sense ,而是直接在数字域进行线性插值。校准点 TS_CAL1 和 TS_CAL2 是芯片在严格温控环境下实测获得,并烧录至OTP。它们已隐含了V ref 、ADC增益、偏移及传感器非线性等所有系统级误差。因此,H7模型在-40°C至+125°C全范围内的典型精度可达±1.0°C,远超前代。

工程实践要点 :读取OTP校准值必须使用 HAL_FLASHEx_OEM_GetTemperatureCalibrationData() (HAL库)或直接内存映射访问。由于OTP区域无写保护,首次读取后应缓存至RAM,避免频繁Flash访问。

1.3 数据手册参数定位实战指南

所有校准参数均源自官方芯片数据手册(Datasheet),绝非参考手册(Reference Manual)或固件库文档。以下以STM32F103C8T6为例,详解参数查找路径:

  1. 打开《STM32F103xx Datasheet》 (DS5319),定位至“6. Electrical characteristics”章节。
  2. 查找“Temperature sensor characteristics”子章节 (通常在6.3.15节)。
  3. 精确定位表格 :该表格标题为“Typical temperature sensor characteristics”,包含四行关键参数:
    • V<sub>sense</sub> vs. temperature linearity error : 明确标注最大误差为±2°C,印证前述表格。
    • V<sub>sense</sub> at 25°C : 表格中“Typ.”列为 1.43 V ,即V 25 。
    • Average slope of V<sub>sense</sub> vs. temperature : “Typ.”列为 4.3 mV/°C ,即Avg_Slope。
    • V<sub>sense</sub> startup time : 此为建立时间,与温度计算无关,可忽略。
  4. 交叉验证采样时间 :返回“6.3.10 ADC characteristics”章节,查找“Sampling time for temperature sensor”条目,确认最小要求为17.1μs。

常见误区警示 :
- 混淆Datasheet与RM :参考手册(RM)仅描述寄存器功能,不提供任何器件级电气参数。V 25 和斜率值只存在于Datasheet。
- 忽略“Typ.”与“Max”区别 :设计中应优先采用“Typ.”(典型值)进行计算,因其代表大多数器件的中心性能;“Max”仅用于最坏情况分析(Worst-Case Analysis)。
- 未查证具体型号 :F103C8与F103ZET6的V 25 值完全相同,但F100系列可能不同。务必核对所用芯片的具体型号手册。

2. 基于HAL库的F1系列温度传感器驱动开发

本节以STM32F103C8T6(“Blue Pill”)为核心,基于STM32CubeMX生成的HAL库框架,构建一个高鲁棒性的内部温度传感器驱动。目标是实现:可靠上电、精准采样、温度计算、结果输出。整个流程严格遵循“上电→ADC采集→电压换算→温度计算”的三步铁律。

2.1 CubeMX基础配置

  1. 时钟树配置 :确保 ADCCLK ≤ 14MHz(F1最高允许值)。若系统主频为72MHz,需将APB2预分频器设为 /6 ,使ADCCLK = 72MHz / 6 = 12MHz。
  2. ADC1初始化 :
    • Mode : Independent mode (独立模式)
    • Data Alignment : Right alignment (右对齐,标准)
    • Scan Conversion Mode : Disabled (禁用扫描,因仅采集单通道)
    • Continuous Conversion Mode : Disabled (禁用连续转换,采用单次触发)
    • Discontinuous Conversion Mode : Disabled
    • External Trigger Conversion Source : Software start (软件触发)
    • Conversion Data Register : Regular data register
  3. ADC通道配置(关键!) :
    • 在 Channel Configuration 中,勾选 IN16 (即ADC1_IN16)。
    • Rank : 1 (第一序列)
    • Sampling Time : 239.5 Cycles (选择最大采样时间档位)。此值对应采样时间为:239.5 × (1/12MHz) ≈ 20μs > 17.1μs,完全满足要求。
  4. GPIO配置 :无需额外GPIO,温度传感器为纯内部外设。
  5. 生成代码 :勾选 Generate peripheral initialization as a pair of '.c/.h' files per peripheral ,生成后, main.c 中将包含 MX_ADC1_Init() 函数。

2.2 关键驱动代码实现

HAL库并未自动处理温度传感器的上电控制,此步骤必须由用户代码显式完成。以下是完整的驱动实现:

#include "main.h"
#include "adc.h"

// 定义校准常量(来自F103C8 Datasheet)
#define V25_TYPICAL     1.43f      // V, 25°C时基准电压
#define AVG_SLOPE_TYPICAL 0.0043f  // V/°C, 平均斜率

// 温度传感器驱动结构体
typedef struct {
    ADC_HandleTypeDef *hadc;        // 指向ADC句柄
    uint32_t adc_value;             // 存储ADC原始数字量
    float voltage;                  // 计算出的V_sense电压
    float temperature;              // 最终计算出的温度
} TempSensor_HandleTypeDef;

static TempSensor_HandleTypeDef g_temp_sensor;

/**
  * @brief  初始化温度传感器
  * @param  hadc: ADC句柄指针
  * @retval HAL status
  */
HAL_StatusTypeDef TempSensor_Init(ADC_HandleTypeDef *hadc) {
    if (hadc == NULL) return HAL_ERROR;

    g_temp_sensor.hadc = hadc;

    // 步骤1: 使能温度传感器电源 (关键!)
    // 对应寄存器: ADC_CR2, 位: TSVREFE (Bit 23)
    // HAL库未提供此功能,需直接操作寄存器
    hadc->Instance->CR2 |= ADC_CR2_TSVREFE;

    // 等待传感器上电稳定 (保守等待10us)
    HAL_Delay(1);

    return HAL_OK;
}

/**
  * @brief  执行一次温度采样与计算
  * @retval HAL status
  */
HAL_StatusTypeDef TempSensor_ReadTemperature(float *pTemp) {
    HAL_StatusTypeDef status;
    uint32_t dn;

    if (pTemp == NULL) return HAL_ERROR;

    // 步骤2: 启动ADC转换 (单次模式)
    status = HAL_ADC_Start(g_temp_sensor.hadc);
    if (status != HAL_OK) return status;

    // 等待转换完成
    status = HAL_ADC_PollForConversion(g_temp_sensor.hadc, HAL_MAX_DELAY);
    if (status != HAL_OK) return status;

    // 获取数字量
    dn = HAL_ADC_GetValue(g_temp_sensor.hadc);

    // 步骤3: 数字量 -> 电压 -> 温度 (三步计算)
    // 1. DN -> V_sense (假设Vref = 3.3V)
    g_temp_sensor.voltage = (float)dn * 3.3f / 4095.0f;

    // 2. V_sense -> Temperature (F1公式)
    g_temp_sensor.temperature = ((V25_TYPICAL - g_temp_sensor.voltage) / AVG_SLOPE_TYPICAL) + 25.0f;

    *pTemp = g_temp_sensor.temperature;
    return HAL_OK;
}

/* 使用示例:在main()循环中调用 */
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    // 初始化温度传感器驱动
    if (HAL_OK != TempSensor_Init(&hadc1)) {
        Error_Handler(); // 处理初始化失败
    }

    while (1) {
        float temp;
        if (HAL_OK == TempSensor_ReadTemperature(&temp)) {
            // 将temp发送至串口或LCD显示
            printf("Chip Temp: %.2f°C\r\n", temp);
        }
        HAL_Delay(1000); // 每秒读取一次
    }
}

2.3 核心代码深度解析

  • TempSensor_Init() 中的寄存器操作 : hadc->Instance->CR2 |= ADC_CR2_TSVREFE; 是驱动成败的关键。 ADC_CR2_TSVREFE 是HAL库定义的宏,其值为 0x00800000 (即 1 << 23 )。此行代码直接置位ADC_CR2寄存器的第23位,为温度传感器供电。若遗漏此步,后续所有ADC采集都将返回0或随机值。
  • 采样时间的安全裕度 :CubeMX中选择 239.5 Cycles ,是基于ADCCLK=12MHz计算出的20μs,比17.1μs要求高出约17%。这为PCB走线寄生电容、电源纹波等不确定因素提供了充足缓冲,是工业级设计的必备实践。
  • HAL_ADC_PollForConversion() 的可靠性 :在资源受限的嵌入式系统中,轮询(Polling)比中断(Interrupt)或DMA更简单、更可控。 HAL_MAX_DELAY 确保函数不会因ADC故障而无限期挂起,但实际项目中应加入超时计数器以增强健壮性。
  • 浮点运算的权衡 :示例中使用 float 进行计算,代码简洁易懂。但在裸机或RTOS环境下,若对性能极度敏感,可全部改用整数运算(如将V 25 放大1000倍,斜率放大1000000倍),避免FPU开销。

3. H7系列双点校准模型的高精度实现

H7系列凭借其双点校准模型,为温度测量精度树立了新标杆。本节以STM32H743VI为例,详解如何利用OTP中存储的 TS_CAL1 和 TS_CAL2 实现亚摄氏度级精度。

3.1 OTP校准数据读取与验证

H7的校准数据位于特定OTP地址,读取前需解锁Flash,且必须验证数据有效性:

#include "main.h"
#include "adc.h"

// H7校准数据结构
typedef struct {
    uint16_t ts_cal1; // 30°C校准值 @ 0x1FF1E820
    uint16_t ts_cal2; // 110°C校准值 @ 0x1FF1E822
    uint8_t  valid;   // 校准数据是否有效标志
} H7_TempCalData_t;

static H7_TempCalData_t g_h7_cal_data;

/**
  * @brief  从OTP读取并验证H7温度校准数据
  * @retval HAL status
  */
HAL_StatusTypeDef H7_TempCal_Read(void) {
    // 1. 解锁Flash编程
    HAL_FLASH_Unlock();

    // 2. 读取OTP数据
    g_h7_cal_data.ts_cal1 = *(uint16_t*)0x1FF1E820;
    g_h7_cal_data.ts_cal2 = *(uint16_t*)0x1FF1E822;

    // 3. 验证数据有效性 (关键!)
    // OTP未编程时,值为0xFFFF。若任一值为0xFFFF,校准无效
    if ((g_h7_cal_data.ts_cal1 == 0xFFFF) || (g_h7_cal_data.ts_cal2 == 0xFFFF)) {
        g_h7_cal_data.valid = 0;
        HAL_FLASH_Lock();
        return HAL_ERROR;
    }

    // 4. 简单合理性检查:ts_cal2 应大于 ts_cal1 (因温度升高,V_sense下降,DN增大)
    if (g_h7_cal_data.ts_cal2 <= g_h7_cal_data.ts_cal1) {
        g_h7_cal_data.valid = 0;
        HAL_FLASH_Lock();
        return HAL_ERROR;
    }

    g_h7_cal_data.valid = 1;
    HAL_FLASH_Lock();
    return HAL_OK;
}

为何需要验证?
- OTP区域在芯片出厂时已被烧录,但某些早期批次或特殊定制版本可能存在校准数据未写入的情况,此时地址内容为全1(0xFFFF)。直接使用会导致计算崩溃。
- ts_cal2 > ts_cal1 是物理定律的必然结果:温度从30°C升至110°C,V sense 电压单调下降,而ADC数字量DN与电压成正比,故 ts_cal2 (110°C点)必然大于 ts_cal1 (30°C点)。此检查是防止硬件故障或数据损坏的最后一道防线。

3.2 高精度温度计算函数

/**
  * @brief  使用H7双点校准模型计算温度
  * @param  dn: 当前ADC3_IN18通道采集到的12位数字量
  * @retval 计算出的温度值 (°C),校准无效时返回-273.15f
  */
float H7_TempCal_Calculate(uint16_t dn) {
    if (!g_h7_cal_data.valid) {
        return -273.15f; // 绝对零度,表示错误
    }

    // 公式: T = ((TS_CAL2 - TS_CAL1) / (110 - 30)) * (DN - TS_CAL1) + 30
    // 为提高精度,全程使用float计算
    float slope = (float)(g_h7_cal_data.ts_cal2 - g_h7_cal_data.ts_cal1) / 80.0f;
    float temp = slope * (float)(dn - g_h7_cal_data.ts_cal1) + 30.0f;

    // 边界钳位:确保结果在合理物理范围内
    if (temp < -40.0f) temp = -40.0f;
    if (temp > 125.0f) temp = 125.0f;

    return temp;
}

/* 完整驱动调用示例 */
HAL_StatusTypeDef H7_TempSensor_Read(float *pTemp) {
    HAL_StatusTypeDef status;
    uint32_t dn;

    // 1. 确保ADC3已正确初始化 (需在CubeMX中配置ADC3_IN18)
    // 2. 启动ADC3转换
    status = HAL_ADC_Start(&hadc3);
    if (status != HAL_OK) return status;

    status = HAL_ADC_PollForConversion(&hadc3, HAL_MAX_DELAY);
    if (status != HAL_OK) return status;

    dn = HAL_ADC_GetValue(&hadc3);

    // 3. 执行高精度计算
    *pTemp = H7_TempCal_Calculate((uint16_t)dn);
    return HAL_OK;
}

精度提升的本质 :F1/F4模型的误差主要来源于两点:
1. V ref 不稳定性 :3.3V电源的±5%波动会直接引入同等比例的温度误差。
2. 传感器非线性 :V sense -T曲线并非完美直线,单点校准无法消除。
H7的双点模型将这两个误差源全部封装在校准数据 TS_CAL1 和 TS_CAL2 中。这两个值是在芯片出厂时,用高精度温箱和标准电压源,在30°C和110°C两个点实测得到的ADC读数。因此, H7_TempCal_Calculate() 函数本质上是在执行一个针对该颗芯片个体特性的、经过工厂校准的线性插值,从而实现了“开箱即用”的高精度。

4. 工程实践中的关键问题与解决方案

在真实项目中部署内部温度传感器,远不止于代码编写。以下是我个人在多个量产项目中踩过的坑与总结的解决方案。

4.1 采样噪声与稳定性问题

现象 :温度读数在几摄氏度内剧烈跳变,无法稳定。
根源分析 :
- 电源噪声 :ADC对V ref 和V dd 噪声极其敏感。开关电源(DC-DC)的纹波会直接耦合到ADC结果。
- 数字干扰 :CPU高速运行、DMA传输、USB通信产生的地弹(Ground Bounce)会污染模拟地(AGND)。
- 采样时间不足 :虽满足最小要求,但未留足裕量应对PCB寄生效应。

解决方案 :
1. 硬件层面 :
- 为ADC模块单独铺设模拟电源(V dda )和地(V ssa ),并通过磁珠(Ferrite Bead)与数字电源隔离。
- 在V dda 引脚就近放置100nF陶瓷电容 + 10μF钽电容,形成低阻抗滤波网络。
2. 软件层面 :
- 增加采样次数与滤波 :不依赖单次采样,改为连续采集16次,丢弃最大最小值后取平均。
- 降低采样频率 :将读取间隔从100ms延长至1s。温度变化本身缓慢,高频采样毫无意义,反而增加噪声暴露机会。
- 在低功耗模式下采样 :在 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI) 唤醒后立即采样,此时系统噪声最低。

4.2 结温与环境温度的工程转化

核心认知 :内部传感器测量的是芯片“结温”,而非“环境温度”。二者关系由热阻公式决定:
$$ T_{junction} = T_{ambient} + (P_{dissipated} \times R_{\theta JA}) $$
其中, R_θJA 为结到环境的热阻(单位°C/W), P_dissipated 为芯片功耗。

实用技巧 :
- 建立经验映射表 :在我的一个工业网关项目中,我们固定CPU负载(如运行空循环占满100%),在恒温箱中从-20°C到+70°C标定,记录下每个环境温度点对应的结温读数。最终生成一张查找表(LUT),在产品运行时,先读取结温,再通过LUT查得环境温度。此法简单有效,规避了复杂热阻计算。
- 动态功耗补偿 :对于负载变化大的系统(如视频编解码),可实时监测CPU利用率、外设时钟频率、核心电压等参数,构建一个简单的功耗估算模型,再从结温中减去估算的温升,得到更接近环境的温度。这比单纯依赖LUT更灵活。

4.3 多核系统(H7)的ADC资源竞争

H7为双核(CM7+CM4),ADC3虽为专用,但其时钟、DMA、中断向量均需协调。若CM4核心正在执行高优先级实时任务,而CM7试图启动ADC3转换,可能因总线仲裁延迟导致采样时机不准。

解决方案 :
- 严格划分ADC所有权 :在系统初始化时,由主核(CM7)完成ADC3的所有配置(包括时钟、GPIO、中断),并将ADC3句柄 hadc3 声明为全局 const ,禁止从CM4访问。所有ADC3操作均由CM7统一调度。
- 使用事件驱动而非轮询 :将 HAL_ADC_Start_IT() 与 HAL_ADC_ConvCpltCallback() 结合,在回调中处理数据,释放主循环压力,避免长时间阻塞。

我在调试一个H7双核电机驱动时,曾因CM4意外调用 HAL_ADC_Stop() 导致CM7的ADC3中断失效,电机保护功能瘫痪。自此,我们在所有多核项目中,都强制规定: 模拟外设的控制权必须集中,且只能由一个核心拥有 。这是血的教训换来的铁律。

更多推荐