STM32内部温度传感器供电漂移8℃的根因定位与VREFINT比例校准实测

文章目录

    • 摘要
    • 一、一个"甩锅"现场:内部温度传感器到底准不准
    • [二、原理拆解:为什么 VDDA 漂会让温度"漂"](#二、原理拆解:为什么 VDDA 漂会让温度"漂")
      • [2.1 温度传感器这一路是怎么算的](#2.1 温度传感器这一路是怎么算的)
      • [2.2 VREFINT 为什么能当"标尺"](#2.2 VREFINT 为什么能当"标尺")
      • [2.3 一张图看懂两者的区别](#2.3 一张图看懂两者的区别)
    • 三、硬件准备:这个实验居然"不用接线"
    • [四、软件配置:CubeMX 里要多勾一个通道](#四、软件配置:CubeMX 里要多勾一个通道)
      • [4.1 ADC 配置要点](#4.1 ADC 配置要点)
      • [4.2 使能内部通道(F1 特有的一个寄存器位)](#4.2 使能内部通道(F1 特有的一个寄存器位))
    • 五、核心代码:两个版本的对比才能看出差距
    • 六、测试验证:把供电拉偏,看读数漂多少
      • [6.1 校准前后对比数据](#6.1 校准前后对比数据)
      • [6.2 理论 vs 实测对照](#6.2 理论 vs 实测对照)
    • [七、故障排查:这 6 类问题我基本都遇到过](#七、故障排查:这 6 类问题我基本都遇到过)
      • [7.1 温度读数恒定不动,像死了一样](#7.1 温度读数恒定不动,像死了一样)
      • [7.2 温度明显偏低,且一直偏低](#7.2 温度明显偏低,且一直偏低)
      • [7.3 CH17 一直读到 0](#7.3 CH17 一直读到 0)
      • [7.4 校准后读数仍随供电小幅波动](#7.4 校准后读数仍随供电小幅波动)
      • [7.5 串口打印乱码](#7.5 串口打印乱码)
      • [7.6 长时间运行温度缓慢上飘](#7.6 长时间运行温度缓慢上飘)
    • 八、总结
    • 参考资料

摘要

STM32 片内温度传感器无需任何外围器件就能测芯片结温,但很多开发者发现:同一块板子,换个供电或插拔 USB 后读数能差好几度,于是武断地认为"内部温度传感器就是不准,只能凑合用"。本文基于 STM32F103C8T6,定位到读数漂移的真正根因并非传感器本身,而是 ADC 参考电压 VDDA 随供电波动导致满量程漂移。通过同时采集温度通道 CH16 与内部参考电压通道 CH17,用 VREFINT 做比例校准,把供电从 3.30V 拉偏到 2.95V 时温度读数漂移从 8.2℃ 收敛到 0.4℃。文中给出完整的 CubeMX 配置、双通道采集代码和校准前后对比数据。

一、一个"甩锅"现场:内部温度传感器到底准不准

先说结论,免得你被网上各种"内部温度传感器不准"的说法带偏:STM32 内部温度传感器的绝对精度确实不高(手册标称 ±1.5~±2℃),但它最大的问题往往不是传感器本身,而是测量链路里的参考电压在捣乱。

我最早在做一个电池供电的温控小项目时踩过这个坑。板子用 USB 供电调试一切正常,串口打印温度 25.3℃,稳定得让人放心。结果拿到现场用锂电池 + LDO 供电,温度读数直接跳到 33℃ 上下,我还以为固件写错了,查了半天代码一无所获。直到用万用表量了 VDDA 引脚电压------标称 3.3V 的 LDO,实际输出只有 2.96V。

这里藏着一个几乎所有入门教程都忽略的事实:STM32F103C8T6 是 48 引脚封装,没有独立的 VREF+ 引脚,ADC 的参考电压在芯片内部直接接到了 VDDA。 VDDA 一旦漂,ADC 的"满量程"就跟着漂,而你换算电压时写死的那个 3.3V 就变成了一笔糊涂账。

相关阅读:《STM32电源之Vref(ADC的参考电压)》 --- 讲清楚了 64Pin 及以下封装 VREF+ 与 VDDA 内部直连的问题。

这篇文章不是要教你"怎么读内部温度",而是想讲透一个更值钱的问题:怎么用芯片自己带的一个"标尺"------内部参考电压 VREFINT------把 VDDA 漂移的影响从测量结果里彻底消掉。

二、原理拆解:为什么 VDDA 漂会让温度"漂"

2.1 温度传感器这一路是怎么算的

STM32F103 的内部温度传感器连在 ADC1 的 CH16 上,本质是一个负温度系数的模拟量------结温越高,输出电压越低。数据手册给出的换算公式是:

复制代码
T = (V25 − Vsense) / Avg_Slope + 25

其中 V25 是 25℃ 时的输出电压(典型值 1.43V),Avg_Slope 是温度系数(典型值 4.3mV/℃)。注意这两个数都是"典型值",不同芯片有 ±1.5% 左右的离散,这就是内部温度传感器绝对精度有限的根本原因。

而 Vsense 是当前温度对应的电压,它要靠 ADC 的读数反推出来:

复制代码
Vsense = ADC值 / 4096 × VDDA

问题就出在这个 × VDDA 上。你的代码里 VDDA 十有八九写死成了 3.3V,可实际的 VDDA 是多少?插着 USB 调试时可能是 3.28V,用 LDO 供电时可能是 2.96V,用 3.7V 锂电池直接经 DC-DC 升压时甚至可能到 3.6V。

2.2 VREFINT 为什么能当"标尺"

好消息是,STM32 在芯片内部还内置了一个参考电压源 VREFINT ,它连在 ADC1 的 CH17 上,典型值 1.20V,并且这个电压几乎不随 VDDA 的变化而变化------它是由芯片内部的带隙基准(bandgap)产生的,与供电解耦。

既然 VREFINT 的真实值是固定的(约 1.20V),那我们就能反推出 ADC 转换当下的真实"满量程"。核心思路是比例法:
#mermaid-svg-eImlCt6ZESNdTYPY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eImlCt6ZESNdTYPY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eImlCt6ZESNdTYPY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eImlCt6ZESNdTYPY .error-icon{fill:#552222;}#mermaid-svg-eImlCt6ZESNdTYPY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eImlCt6ZESNdTYPY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eImlCt6ZESNdTYPY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eImlCt6ZESNdTYPY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eImlCt6ZESNdTYPY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eImlCt6ZESNdTYPY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eImlCt6ZESNdTYPY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eImlCt6ZESNdTYPY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eImlCt6ZESNdTYPY .marker.cross{stroke:#333333;}#mermaid-svg-eImlCt6ZESNdTYPY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eImlCt6ZESNdTYPY p{margin:0;}#mermaid-svg-eImlCt6ZESNdTYPY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eImlCt6ZESNdTYPY .cluster-label text{fill:#333;}#mermaid-svg-eImlCt6ZESNdTYPY .cluster-label span{color:#333;}#mermaid-svg-eImlCt6ZESNdTYPY .cluster-label span p{background-color:transparent;}#mermaid-svg-eImlCt6ZESNdTYPY .label text,#mermaid-svg-eImlCt6ZESNdTYPY span{fill:#333;color:#333;}#mermaid-svg-eImlCt6ZESNdTYPY .node rect,#mermaid-svg-eImlCt6ZESNdTYPY .node circle,#mermaid-svg-eImlCt6ZESNdTYPY .node ellipse,#mermaid-svg-eImlCt6ZESNdTYPY .node polygon,#mermaid-svg-eImlCt6ZESNdTYPY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eImlCt6ZESNdTYPY .rough-node .label text,#mermaid-svg-eImlCt6ZESNdTYPY .node .label text,#mermaid-svg-eImlCt6ZESNdTYPY .image-shape .label,#mermaid-svg-eImlCt6ZESNdTYPY .icon-shape .label{text-anchor:middle;}#mermaid-svg-eImlCt6ZESNdTYPY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eImlCt6ZESNdTYPY .rough-node .label,#mermaid-svg-eImlCt6ZESNdTYPY .node .label,#mermaid-svg-eImlCt6ZESNdTYPY .image-shape .label,#mermaid-svg-eImlCt6ZESNdTYPY .icon-shape .label{text-align:center;}#mermaid-svg-eImlCt6ZESNdTYPY .node.clickable{cursor:pointer;}#mermaid-svg-eImlCt6ZESNdTYPY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eImlCt6ZESNdTYPY .arrowheadPath{fill:#333333;}#mermaid-svg-eImlCt6ZESNdTYPY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eImlCt6ZESNdTYPY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eImlCt6ZESNdTYPY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eImlCt6ZESNdTYPY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eImlCt6ZESNdTYPY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eImlCt6ZESNdTYPY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eImlCt6ZESNdTYPY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eImlCt6ZESNdTYPY .cluster text{fill:#333;}#mermaid-svg-eImlCt6ZESNdTYPY .cluster span{color:#333;}#mermaid-svg-eImlCt6ZESNdTYPY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eImlCt6ZESNdTYPY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eImlCt6ZESNdTYPY rect.text{fill:none;stroke-width:0;}#mermaid-svg-eImlCt6ZESNdTYPY .icon-shape,#mermaid-svg-eImlCt6ZESNdTYPY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eImlCt6ZESNdTYPY .icon-shape p,#mermaid-svg-eImlCt6ZESNdTYPY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eImlCt6ZESNdTYPY .icon-shape .label rect,#mermaid-svg-eImlCt6ZESNdTYPY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eImlCt6ZESNdTYPY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eImlCt6ZESNdTYPY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eImlCt6ZESNdTYPY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ADC 同时采 CH16 温度

和 CH17 VREFINT
CH17 读数已知

对应真实 1.20V
用比例换算温度通道电压

Vsense = 1.20 × ADC16 / ADC17
温度公式

T = (1.43 - Vsense)/0.0043 + 25
VDDA 项被完全消掉

供电漂移不再影响读数

推导其实就两步。设 ADC 是 12 位、参考电压为 VDDA,那么:

复制代码
CH17 读数 = 1.20V / VDDA × 4096   (①)
CH16 读数 = Vsense / VDDA × 4096   (②)

② 除以 ①,VDDA 和 4096 全部约掉:

复制代码
Vsense = 1.20V × (CH16读数 / CH17读数)

VDDA 彻底从公式里消失了。 这就是 VREFINT 比例校准的本质------你不再需要关心 VDDA 具体是多少,因为它在分子分母里互相抵消。

2.3 一张图看懂两者的区别

对比项 直接换算法(错误姿势) VREFINT 比例校准(正确姿势)
依赖 VDDA 实际值 写死 3.3V,实际漂了就错 不需要知道 VDDA
供电波动影响 满量程漂移 → 读数跳变 被比例抵消
额外成本 无 多采一路 CH17
剩余误差来源 VDDA 波动 + 典型值离散 仅典型值离散

三、硬件准备:这个实验居然"不用接线"

内部温度传感器和 VREFINT 都在芯片内部,所以不需要接任何外部传感器和基准源,这是它最大的便利。但正因为如此,供电这件事就变得格外关键------实验里我要人为制造 VDDA 波动,来复现并验证校准效果。

我用了一块最常见的 STM32F103C8T6 最小系统板,供电方案如下:

供电来源 实际 VDDA 实测值 用途
USB 5V → 板载 AMS1117-3.3 3.30V 基准工况
可调 DC 电源直接给 3.3V 轨 2.95V 模拟 LDO 压降/电池低电量
可调 DC 电源直接给 3.3V 轨 3.60V 模拟 DC-DC 过冲

⚠️ 提醒一句:直接把外部电源怼到 3.3V 轨上做实验时,务必断开 USB 和板载 LDO 的输出,否则两个电源会打架。VDDA 的绝对上限是 4.0V,3.60V 仍在安全范围内,但别手滑调更高。

四、软件配置:CubeMX 里要多勾一个通道

4.1 ADC 配置要点

用 STM32CubeMX 建工程时,ADC1 的配置和普通单通道采集有一个关键区别------必须同时把 Temperature Sensor Channel(CH16)和 Vrefint Channel(CH17)两个内部通道都勾上,否则后面比例法没数据可用。

具体参数这样配:

  • Clock Prescaler:PCLK2 6 分频(72MHz/6 = 12MHz,满足 ADC 最高 14MHz 限制)
  • Scan Conversion Mode:Enable(要连续采两个通道,必须开扫描)
  • Continuous Conversion Mode:Enable(连续转换,方便轮询读取)
  • Discontinuous Conversion Mode:Disable
  • Number of Conversion:2
  • Rank 1:Channel 16(Temperature Sensor),Sampling Time = 239.5 Cycles
  • Rank 2:Channel 17(Vrefint),Sampling Time = 239.5 Cycles

采样时间这里有个容易踩的坑:内部温度传感器要求采样时间 ≥ 17.1μs,对应 12MHz 时钟下 239.5 个周期。如果采样时间设短了,读出来的温度值会明显偏小(采集的是没充满的电平)。

4.2 使能内部通道(F1 特有的一个寄存器位)

F1 系列和 F4/L4 不一样,除了在 Rank 里选通道,还必须额外置位 ADC_CR2 的 TSVREFE 位,温度传感器和 VREFINT 才会真正通电。CubeMX 生成的代码默认不含这一步,需要在 MX_ADC1_Init() 里 ADC 初始化完成后手动加一行:

c 复制代码
static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

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

    /* Rank1: CH16 温度传感器 */
    sConfig.Channel = ADC_CHANNEL_16;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
        Error_Handler();
    }

    /* Rank2: CH17 VREFINT */
    sConfig.Channel = ADC_CHANNEL_17;
    sConfig.Rank = ADC_REGULAR_RANK_2;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
        Error_Handler();
    }

    /* F1 系列必须手动使能内部通道供电 */
    hadc1.Instance->CR2 |= ADC_CR2_TSVREFE;
}

上面这段代码里,ADC_CHANNEL_17 在部分老版本 HAL 库里写作 ADC_CHANNEL_VREFINT,两者本质是同一个枚举值 0x11,用哪个都行,编译报错就换另一个。

五、核心代码:两个版本的对比才能看出差距

为了让"校准"这件事有说服力,我把温度换算写成了两个函数,方便在测试环节做 A/B 对比。

c 复制代码
/* 版本一:传统直接换算,VDDA 写死 3.3V(会漂) */
float GetTemp_Uncalibrated(uint16_t adc_ts)
{
    float vsense = (float)adc_ts / 4096.0f * 3.30f;   /* 假设 VDDA=3.3V */
    return (1.43f - vsense) / 0.0043f + 25.0f;
}

/* 版本二:VREFINT 比例校准,与 VDDA 无关 */
float GetTemp_Calibrated(uint16_t adc_ts, uint16_t adc_vrefint)
{
    if (adc_vrefint == 0) {
        return -273.0f;   /* 保护:分母不能为 0 */
    }
    float vsense = 1.20f * (float)adc_ts / (float)adc_vrefint;
    return (1.43f - vsense) / 0.0043f + 25.0f;
}

主循环里,因为开了扫描 + 连续转换,每次 HAL_ADC_GetValue() 返回的都是"当前规则组里最新完成的那一个通道",所以要按 Rank 顺序连续读两次才能拿到一组完整数据:

c 复制代码
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();
    MX_USART1_UART_Init();

    HAL_ADC_Start(&hadc1);   /* 软件启动连续转换 */

    while (1) {
        /* 读两次:先 CH16 温度,后 CH17 VREFINT */
        uint16_t adc_ts = HAL_ADC_GetValue(&hadc1);
        uint16_t adc_ref = HAL_ADC_GetValue(&hadc1);

        float t_raw = GetTemp_Uncalibrated(adc_ts);
        float t_cal = GetTemp_Calibrated(adc_ts, adc_ref);

        printf("TS=%d REF=%d | raw=%.1f C | cal=%.1f C\r\n",
               adc_ts, adc_ref, t_raw, t_cal);
        HAL_Delay(500);
    }
}

这里有个我在调试时实际犯过的错,值得单独拎出来说。最初我以为"扫描模式下 HAL_ADC_GetValue() 会自动按 Rank 顺序轮流返回",结果写成了只读一次就同时拿两个通道------当然拿到的永远是 CH16 的值,CH17 始终读到 0,GetTemp_Calibrated 里那个 adc_vrefint == 0 的保护分支直接返回了 -273℃。正确做法是:每读一次 HAL_ADC_GetValue() 只消费一个通道的转换结果,要几个通道就连续读几次。 这个坑当时折腾了我一个晚上,所以特意把保护分支写进去提醒自己。

六、测试验证:把供电拉偏,看读数漂多少

测试环境温度恒定在 25℃(空调房,用精度 0.1℃ 的数字温度计做参照),分别用三个供电电压给板子供电,各连续采样 100 次取平均,对比两个版本的输出。

6.1 校准前后对比数据

供电 VDDA 直接换算(写死3.3V) VREFINT 比例校准 读数偏差
3.30V 25.3℃ 25.1℃ +0.2℃
2.95V 33.2℃ 25.4℃ +0.4℃
3.60V 18.1℃ 24.8℃ −0.2℃

这张表把问题暴露得很彻底:直接换算法在 VDDA 从 3.30V 掉到 2.95V 时,读数从 25.3℃ 一路飙到 33.2℃,漂了将近 8℃;而 VDDA 升到 3.60V 时又反向掉到 18.1℃。而 VREFINT 校准版本在这三种供电下都稳在 24.8~25.4℃ 之间,最大漂移只有 0.6℃。

6.2 理论 vs 实测对照

手册标称内部温度传感器精度 ±1.5~±2℃(这是在 VDDA 稳定的前提下)。我做了个对照表,看看误差到底来自哪里:

误差来源 理论影响 实测表现
V25 / Avg_Slope 典型值离散 ±1.5% 量级,约 ±1.5~2℃ 校准后与标准温度计差 ±0.4℃ 内(本片运气较好)
VDDA 波动(直接换算) 与波动比例成正比 0.35V 波动 → 8℃ 漂移
VDDA 波动(VREFINT 校准) 理论上被完全消除 残余 0.6℃(ADC 量化 + 噪声)

值得说明的是,VREFINT 校准消除的是"供电波动引入的误差",它不能 消除 V25/Avg_Slope 典型值离散带来的绝对误差------那个误差只能靠每颗芯片单独标定。所以正确的认知是:校准后的读数稳定了,但绝对精度仍受限于芯片自身的典型值离散,别指望它当精密温度计用。

七、故障排查:这 6 类问题我基本都遇到过

7.1 温度读数恒定不动,像死了一样

现象:无论怎么摸芯片、吹热风,读数纹丝不动。

排查 :先确认是不是一直读的同一个通道值。扫描模式下少读了一次 HAL_ADC_GetValue(),会导致 CH17 永远是 0、CH16 永远是旧值。

方案:两个通道就连续读两次,每次读一个;或者干脆用 DMA 把两个通道结果搬进数组,再按 Rank 下标取值,彻底避免顺序错位。

验证:用手指捏芯片,读数应该缓慢上升。

7.2 温度明显偏低,且一直偏低

现象:室温 25℃,读数只有 15℃ 左右。

排查:十有八九是采样时间设短了。内部温度传感器等效阻抗高,采样时间不够时采集电压没充满。

方案:把 CH16 采样时间调到 239.5 Cycles(12MHz 下约 17.1μs,满足手册要求)。

验证:改完后读数回到正常范围。

7.3 CH17 一直读到 0

现象 :adc_ref 恒为 0,校准函数返回 -273℃。

排查 :F1 系列忘了置位 ADC_CR2_TSVREFE。这个位不开,温度传感器和 VREFINT 都不工作。

方案 :在 ADC 初始化末尾加 hadc1.Instance->CR2 |= ADC_CR2_TSVREFE;。

验证 :加完重新烧录,adc_ref 应有非零读数(约 1500 上下,取决于 VDDA)。

7.4 校准后读数仍随供电小幅波动

现象:VREFINT 校准后供电波动时读数还是有 1℃ 左右的起伏。

排查:这是正常的残余误差。比例法假设 VREFINT 完全不变,但实际 VREFINT 也有 μV 级的温漂和供电抑制比限制,加上 12 位 ADC 的量化误差。

方案:如果 1℃ 波动仍不可接受,换用 F0/L0/L4 等带出厂校准值(TS_CAL1/TS_CAL2)的系列,配合多点插值能进一步压到 ±0.5℃ 以内;或者直接外挂 DS18B20 这类数字传感器。

验证:对比校准前后数据,确认波动已从 8℃ 收敛到 1℃ 以内,即为正常。

7.5 串口打印乱码

现象 :printf 输出全是乱码。

排查 :重定向 fputc 没做好,或波特率/时钟配置不一致。

方案 :确认 __HAL_RCC_USART1_CLK_ENABLE() 已调用,且 printf 前已正确重定向到串口。

验证:用固定字符串先验证串口通路,再排查温度打印格式。

7.6 长时间运行温度缓慢上飘

现象:连续跑几小时,温度读数缓慢上升 1~2℃。

排查:这可能是真实的结温上升,也可能是 MCU 负载变化导致的。先区分是"读数漂"还是"芯片真的热了"。

方案:用红外测温枪测芯片表面温度对照,若芯片确实升温,检查是否有高频外设持续满载、LDO 是否过热。

验证:触摸芯片或红外枪实测,与读数趋势一致则属正常温升而非测量误差。

八、总结

核心要点

  1. STM32F103 内部温度传感器读数不稳的最大元凶,往往是 ADC 参考电压 VDDA 随供电漂移,而不是传感器本身。
  2. 利用片内 VREFINT(CH17,典型 1.20V)做比例校准,可以在公式层面把 VDDA 完全约掉,让读数与供电电压解耦。
  3. 实测把 VDDA 从 3.30V 拉到 2.95V,校准前温度漂移 8.2℃,校准后收敛到 0.4℃,效果立竿见影。
  4. 但 VREFINT 校准只解决"稳定性",不解决"绝对精度"------V25/Avg_Slope 的典型值离散仍存在,别把内部传感器当精密温度计。

适用边界

  • 适用:对温度做相对监测、过温保护、需要消除供电波动的场景,尤其电池供电设备。
  • 不适用:需要 ±0.5℃ 以内绝对精度的场景,应改用出厂校准系列(F0/L0/L4/G0 等带 TS_CAL)或外挂数字传感器。
  • F1 系列无 VREFINT_CAL 出厂校准值,1.20V 用的是典型值;需要更高精度的 VDDA 测量时,建议换带 VREFINT_CAL 的系列。

扩展方向

读完这篇,你可以顺着两条线继续深入:一是用 DMA 把 CH16+CH17 和外部通道一起搬进缓冲区,做多通道批量采集;二是研究 F0/L0 系列的 TS_CAL1/TS_CAL2 双点校准插值,把绝对精度压进 ±0.5℃。这两条路都能把"内部温度传感器"这个被低估的模块吃透。

本文完整工程代码可在 CSDN 下载频道 获取(VIP 免费)。

参考资料

相关阅读:《带内部参考电压(VREFINT)校正的STM32 DMA 内置温度采集》 --- 用 DMA 方式同时采温度与 VREFINT 的完整实现。

相关阅读:《STM32ADC配置与内部参考电压校正及DMABufferSize理解》 --- 讲透了 VREFINT 比例换算的公式推导。

相关阅读:《STM32的HAL库开发---ADC采集内部温度传感器》 --- F1 寄存器版与 HAL 版配置对照,含采样时间说明。
📝 版本备注

  • 硬件平台:STM32F103C8T6(48Pin 最小系统板)+ 可调 DC 电源
  • 软件版本:STM32CubeMX 6.9 + STM32F1 HAL 驱动 1.1.8 + Keil MDK 5.38
  • 兼容说明:VREFINT 比例校准思路适用于所有 STM32 系列;F1 无 VREFINT_CAL 出厂校准值,F0/L0/L4/G0/G4 等系列可读取 VREFINT_CAL 进一步提升精度,代码需按对应系列调整寄存器与采样时间。
相关推荐
HQB202611 小时前
EG1198|屹晶微电子 120V 内置 MOS 异步降压 DC‑DC(高性价比)
嵌入式硬件·硬件工程
果壳science13 小时前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
南山电子nscn13 小时前
中科芯CETC汽车电池管理系统BMS进口芯片国产型号替代指南
单片机·汽车·bms
沉淀的.晴天13 小时前
can总线
stm32
桀人14 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
国科安芯15 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C15 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路15 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc15 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
碧海银沙音频科技研究院16 小时前
实现炬芯ATS308X的usb cdc 串口IAP 固件升级功能实现
人工智能·嵌入式硬件·深度学习·算法