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 进一步提升精度,代码需按对应系列调整寄存器与采样时间。
相关推荐
我是一棵无人问荆的小草17 小时前
stm32f103单片机的NVIC分组2介绍
stm32·单片机·嵌入式硬件
紫幽17 小时前
从 0 用 Vue 做屏并生成 LVGL 单片机代码(完整源码备忘)
前端·单片机
智者知已应修善业18 小时前
【51单片机数码管逐位显示1-8余位不亮】2024-10-23
c语言·经验分享·笔记·嵌入式硬件·51单片机
Be for thing19 小时前
【嵌入式成长3】STC89C51外部中断|中断原理、寄存器、中断服务函数
单片机·嵌入式硬件·学习
科芯创展20 小时前
1A,5.5VIN,同步升降压恒流芯片,XZ3442
单片机
恒锐丰-罗生21 小时前
率能半导体 SS6840 单通道 H 桥电机驱动芯片深度解析
驱动开发·嵌入式硬件·硬件工程
9稳1 天前
基于PLC的小车运料装卸控制系统设计
开发语言·网络·数据库·单片机·嵌入式硬件
拾知_H1 天前
STM32+FreeRTOS基础配置
stm32·嵌入式硬件·freertos
智购科技自动贩卖机1 天前
自动售货机硬件主控方案选型实战:单片机、树莓派、ESP32怎么选?
人工智能·单片机·嵌入式硬件·物联网·网络协议·yolo·架构