
简介STM32F407单片机HAL库版内部温度传感器实验例程源码包面向使用该芯片进行嵌入式开发的学习者与工程师重点演示如何基于HAL库驱动芯片内置温度传感器完成模数转换采集、数据校准与温度换算。压缩包共234个文件以106个C语言源码、120个头文件为主体涵盖模数转换驱动、HAL库外设封装及主程序逻辑另有工程配置文件、启动汇编文件、十六进制烧录文件、文本说明与批处理脚本便于在Keil等开发环境中直接查看运行整个包体仅1.43MB。已有382人浏览学习通过研读该工程源码可以掌握内部温度传感器的初始化流程、HAL库模数转换轮询与中断读取方式及校准公式并可将相关经验迁移至其他STM32系列的温度监控、热保护场景能有效提升基于HAL库的嵌入式项目开发能力。1. 内部温度传感器实验stm32f407 HAL 库例程先想清楚的三件事拿到 stm32f407 的 HAL 库内部温度传感器例程第一反应往往是“测个温度有什么难的”。但把例程烧进去、串口打出来你多半会看到这样一组数据室温 25℃芯片显示 37℃电压换算似乎也对但就是不知道信谁。这是因为 F407 的片内温度传感器测量的是芯片结温而非环境温度而且这款芯片出厂时没有像 L4、L0 那样在系统存储器里烧录温度校准值直接用数据手册典型值 V251.43V、Avg_Slope4.3mV/℃ 去算个体偏差和参考电压偏差会一起进结果。真正有价值的做法是把内部温度传感器当作结温探针和过热保护信号用 HAL 库把 ADC 通道、采样时序、滤波算法串起来并在装机现场做一次简单校准。这个实验也正好是理解 HAL 库 ADC 内部驱动、以及寄存器级能力怎么通过 HAL 库暴露出来的入口。2. stm32f407 内部温度传感器原理与 HAL 库 ADC 驱动模型2.1 TSVREFE 与 ADC1_IN16F407 片内传感器怎么进 ADCSTM32F407 内置的温度传感器位于 ADC1 的输入通道 16也就是常说 ADC1_IN16。它不像外部传感器那样经过某个 GPIO 引脚而是芯片内部直接把感应输出接到 ADC 采样保持电路。值得注意的是ADC2 和 ADC3 没有这条内部通道所以做这个实验时必须用 ADC1这一点在 CubeMX 和 HAL 库配置里非常容易忽略。要让传感器工作还需要把 ADC 控制寄存器 CR2 里的 TSVREFE 位置 1。TSVREFE 是温度传感器与 Vbat 通道的总开关它同时控制着 ADC1_IN16 和 ADC1_IN17 两个内部通道的接入。如果这个位没有置位即使你正确配置了通道读回来的 ADC 结果也是 0 或者一个毫无意义的残值。在 HAL 库中这个位通常在调用HAL_ADC_ConfigChannel时由库内部根据通道号自动处理但前提是你必须把通道配置成ADC_CHANNEL_TEMPSENSOR而不是手动填一个“16”。ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_TEMPSENSOR; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_480CYCLES; sConfig.Offset 0; if (HAL_ADC_ConfigChannel(hadc1, sConfig) ! HAL_OK) { Error_Handler(); }这段代码里Rank表示该通道在规则组序列中的位置单通道下固定为 1 即可SamplingTime建议直接选最大的 480 cycles原因是内部温度传感器等效输出阻抗较高采样时间太短时采样电容充放电不充分读数会抖动甚至出现余晖值Offset是偏移校准字段一般不需要改。配置成功后可以在调试器里直接看ADC1-CR2第 23 位 TSVREFE 应该已经是 1。这也是排查“为什么走了 HAL 库流程还是读不到温度”的第一个观察点。2.2 温度换算公式与 F407 没有出厂校准的困境HAL 库只负责把模拟量变成数字量温度换算公式仍要自己写。STM32F407 数据手册给出的换算关系是T(℃) 25 (V25 - VSENSE) / Avg_Slope典型参数为 V251.43VAvg_Slope4.3mV/℃。V25 表示结温 25℃ 时温度传感器输出电压Avg_Slope 表示输出电压随温度变化的平均斜率。把公式写成这样容易让人误判符号方向更直观的写法是T(℃) 25 - (VSENSE - V25) / 0.0043也就是说VSENSE 越小结温越高。举个例子当 VREF3.3V、ADC 位数为 12 位时ADC 原始值 1775 对应的 VSENSE 大约 1.43V计算出来约 25℃ADC 值掉到 1600 时VSENSE 约 1.289V代入公式大约是 57.8℃。所以看到“ADC 值变小但温度在上升”才是正常现象。比符号更棘手的是精度。STM32F407 的系统存储器里没有出厂温度校准数据不像部分 STM32 系列有 TS_CAL1、TS_CAL2 寄存器可以自动修正个体差异。V25 和 Avg_Slope 的典型值只是同批次统计结果实际每一颗芯片都会有偏差。这带来的后果是同一份例程代码放在不同板子上显示出来的温度可能差 3~5℃。也就是说内部温度传感器更适合做变化趋势检测、结温过温保护和相对温度比较直接当环境温度计用必须做应用前的校准。2.3 HAL 库中打开内部温度通道的正确姿势HAL 库把寄存器操作封装成了HAL_ADC_Init、HAL_ADC_ConfigChannel两步。很多新手只调用了 CubeMX 生成的MX_ADC1_Init()就以为通道已经可用结果发现读取值一直不对。其实MX_ADC1_Init()里只完成了 ADC 工作模式、时钟分频和分辨率的初始化内部温度通道必须再走一次HAL_ADC_ConfigChannel把ADC_CHANNEL_TEMPSENSOR挂到规则组里TSVREFE 才会真正置位。参数数值/要求说明V251.43V典型25℃ 结温时 VSENSE 输出Avg_Slope4.3mV/℃VSENSE 随结温升高近似线性下降通道归属仅 ADC1_IN16ADC2/ADC3 没有内部温度传感器采样时间480 cycles 最稳传感器源阻抗高短采样读数跳变VREF与代码中的 vref 一致F407 只有温度与 Vbat 两个内部 ADC 通道HAL 库还有一个容易误解的地方ADC_CHANNEL_TEMPSENSOR这个枚举对应数值是 16但它同时承担了“内部通道标识”的作用而不是简单等同于通道号。在通道配置函数内部HAL 会先判断通道是不是温度传感器或 Vbat是则自动把 CR2 的 TSVREFE 置位。所以正确的使用顺序是初始化 ADC → 配置温度通道 → 启动转换。如果以后想要把功耗压到最低也可以每次采集完手动清掉ADC1-CR2的 TSVREFE 位但代价是下一次采样要等内部传感器重新稳定。3. 用 CubeMX HAL 库跑通 F407 内部温度传感器最小工程3.1 CubeMX 中 ADC、时钟与串口的配置参数用 CubeMX 生成工程时配置项不需要太多但下面几个参数决定了后面代码能不能稳定工作。以常见的 F407VET6 或 VGT6 开发板为例外部晶振 8MHz系统主频拉到 168MHzAPB2 总线时钟 84MHzADC 时钟由 APB2 分频得到。配置项推荐值原因HSE/PLL8MHz 晶振SYSCLK 168MHzF407 最高主频温度实验没有特殊约束ADC1 ClockPCLK2/4即 21MHz保证 480 cycles 对应较长实际采样时间ADC1 通道IN16 Temperature Sensor内部温度传感器唯一入口Sampling Time480 cycles源阻抗高长采样可以稳定读数Resolution12 bits与温度换算公式中的 4096 对应Continuous ModeDisabled单次转换足够避免一直占用 ADCUSART1115200 8-N-1打印原始 AD、电压与温度时钟分频这里值得多说一句。ADC 时钟在 F407 上最高允许 36MHz很多人直接把 PCLK2/2 得到 42MHz编译器不报错但实际超出规格属于非法配置。选 PCLK2/4 得到 21MHz 后480 cycles 的采样时间约为 22.9µs对内部温度传感器这种高阻源来说是足够宽裕的。Continuous Mode选 Disabled 是为了让代码逻辑更简单每次主动启动、等转换完成、读值、停止不会出现缓冲区数据被不断覆盖的问题。3.2 最小读取代码从原始 AD 值到温度生成工程后主循环代码可以直接这样写。注意这里用snprintf加HAL_UART_Transmit输出日志省去重定向printf的额外配置。#include adc.h #include usart.h #include stdio.h static float adc_to_temp(uint32_t adc_val, float vref) { /* 传感器输出随温度升高而下降注意符号 */ float vsense (float)adc_val / 4096.0f * vref; float temp 25.0f - (vsense - 1.43f) / 0.0043f; return temp; } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_USART1_UART_Init(); uint32_t adc_val; float vsense, temp; char msg[64]; while (1) { HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { adc_val HAL_ADC_GetValue(hadc1); } HAL_ADC_Stop(hadc1); vsense (float)adc_val / 4096.0f * 3.3f; temp adc_to_temp(adc_val, 3.3f); int len snprintf(msg, sizeof(msg), adc%lu vsense%.3fV temp%.2fC\r\n, (unsigned long)adc_val, vsense, temp); HAL_UART_Transmit(huart1, (uint8_t *)msg, len, 100); HAL_Delay(500); } }这段代码把整个读取过程拆成四步HAL_ADC_Start启动一次转换HAL_ADC_PollForConversion在指定超时时间内轮询 EOC 标志HAL_ADC_GetValue取出 12 位转换结果最后HAL_ADC_Stop停止 ADC。注意PollForConversion的第二个参数是超时毫秒数正常转换几十微秒就完成给 10ms 已经非常充裕。adc_to_temp函数里的 3.3f 应该用板子上 VREF 的实际电压替代如果引脚直连 VDDA还要考虑万能表实测值和目标值的差异。3.3 读数异常的排查先看原始值别先调公式内部温度传感器实验失败时大多数人第一反应是去改公式里的 1.43 和 0.0043这是没有意义的。正确做法是先看adc_val原始值落在哪个区间再判断问题出在哪一层。温度在 -40℃ 到 125℃ 范围内时VSENSE 大约在 1.0V 到 1.7V对应 12 位 ADC 原始值大致在 1250 到 2100 之间。如果看到的是 0、4095 或数值长时间冻结问题基本出在通道配置或启动时序上。按下面的顺序排查单步暂停程序在调试器里查看hadc1.Instance-CR2确认 TSVREFE 位是否为 1。检查MX_ADC1_Init之后是否确实调用了HAL_ADC_ConfigChannel并且 Channel 为ADC_CHANNEL_TEMPSENSOR。把代码里的 3.3f 换成测量工具读到的实际 VREF 电压排除参考电压偏差干扰。将采样时间从 480 cycles 改小如果发现读值随之大幅漂移说明当前采样时间不足而不是传感器坏了。提示不要用开发板自带的 3.3V 稳压器输出直接代替 VREF。有些板子 VREF 和 VDDA 之间接了磁珠或二极管实际电压会比 3.3V 低几十毫伏。这个误差乘以 ADC 比例后换算成温度可能造成 1~2℃ 的偏差。4. 从“能读”到“能信”校准、滤波与工程化处理4.1 单点校准与公式偏置矫正如果产品工作环境温度比较固定比如室内设备长期工作在 20℃ 到 40℃ 之间单点校准就能把误差压到可接受范围。做法是让主板在已知室温下断电静置至少 10 分钟让芯片结温与环境温度达到平衡然后上电运行温度读取代码连续采集 100 次取平均值temp_raw。同时用精度不低于 ±0.5℃ 的温度计记录环境温度T_ref两者的差值就是校准偏移量。#define CAL_OFFSET 2.8f /* 由 T_ref - temp_raw 计算得到 */ float temp_out temp_raw CAL_OFFSET;这里偏移量包含了传感器个体偏差、ADC 参考电压偏差以及公式典型值误差是一个合并后的综合修正值。单点校准的局限也很明显它只平移曲线不修正斜率。如果校准点在 30℃实际温度跑到 80℃误差依然会随着温度升高而放大。所以单点校准只适合温度变化范围小的场景。4.2 两点校准自己拟合一阶直线更可靠的方案是自己做一次两点校准把传感器的实际斜率和截距测出来。找一个能稳定维持两个温度点的工具恒温箱最理想没有的话可以用热风枪低温档均匀加热整块板子等串口温度不再变化后再记录。记录时不要只记温度要把对应的 ADC 原始值和换算电压也一起记下下面是一组示意数据。测量点参考温度 T原始 ADVSENSE常温平衡30.2℃17301.393V加热平衡71.5℃14601.176V两个点之间的线性关系可以直接用插值算法计算代码里不再依赖手册典型值。typedef struct { float t; /* 参考温度 ℃ */ float v; /* 对应传感器电压 V */ } cal_pt_t; static const cal_pt_t CAL_A {30.2f, 1.393f}; static const cal_pt_t CAL_B {71.5f, 1.176f}; static float temp_calibrated(float vsense) { float slope (CAL_B.t - CAL_A.t) / (CAL_B.v - CAL_A.v); return CAL_A.t (vsense - CAL_A.v) * slope; }这段代码本质上是用实测斜率替代手册典型斜率。从示例数据算出来传感器实际斜率约为 3.84mV/℃和手册的 4.3mV/℃ 差了约 10%这就是单点校准无法修正的部分。做两点校准时要注意两点跨度尽量超过 20℃并且覆盖目标应用温度区段。如果应用范围是 0℃ 到 60℃校准点取 10℃ 和 55℃ 会比取 30℃ 和 70℃ 更合适。4.3 滑动滤波与一阶低通代码内部温度传感器的瞬时读数往往有 ±1℃ 的波动这是正常的但如果滤波做得太激进又会让温度响应变得很慢。一阶低通滤波是实现简单、效果稳定的折中方案。static float temp_filtered(float temp_raw, float *prev) { float out *prev 0.2f * (temp_raw - *prev); *prev out; return out; }使用时要先把*prev初始化为第一次采集到的原始温度之后每次调用都传入上一次的输出值。滤波系数alpha0.2表示新采样值只贡献 20% 的权重当采样周期为 500ms 时大约经过 2 秒左右能跟上阶跃变化的一半。如果需要消除偶发的尖峰干扰可以在低通前加一个滑动中值窗口取 9 个样本排序后取中间值再把中值送入低通滤波两段配合使用效果更好。 注意 F407 内部温度传感器每次转换结果之间理论上没有相关性不要用过于复杂的滤波算法追求零波动系统稳定后波动 ±0.3℃ 已经算是很好的表现。4.4 工程化注意采样时序、VREF 与功耗传感器刚上电的第一次转换结果经常偏离真实值原因是内部偏置电路还没有稳定。常见做法是在校准完成后先连续读取两三次丢弃前两次结果从第三次开始参与滤波计算或者是在配置通道后加入一个短暂的延时窗口再启动 ADC。这种处理在电池供电设备上更有意义因为每次读取结束都关闭 TSVREFE 来省电下次读取时必须重新等传感器稳定。参考电压是另一个容易被忽视的变量。F407 没有 VREFINT 内部基准通道ADC 转换结果完全取决于 VREF 引脚上的实际电压。如果板子上 VREF 来自一个精度不高的 LDO温漂会直接叠加到温度结果里。这时有两种处理思路一是使用外部高精度基准芯片给 VREF 供电二是在产品出厂校准流程里把参考电压误差一起归入温度偏移量用软件修正。考虑到内部温度传感器本身精度不高建议优先采用第二种降低成本且实现简单。5. 用拷机与对照温度计给内部温度传感器做最终验证5.1 拷机程序与预期温度曲线校准和滤波做完之后需要验证整套逻辑是否真的可信。最实用的方法是让芯片执行重负载浮点运算人为拉高结温观察温度曲线变化。F407 带有 FPUCubeMX 生成的工程默认已经使能 FPU这类代码正好可以作为发热工具。volatile float load 1.0f; while (1) { for (uint32_t i 0; i 500000u; i) { load load * 1.000001f 0.000001f; if (load 1e6f) { load 1.0f; } } temp_calc temp_calibrated(vsense); temp_f temp_filtered(temp_calc, temp_prev); printf(temp%.2fC\r\n, temp_f); HAL_Delay(1000u); }预期曲线应该是上电初期读数接近环境温度进入负载后 30 秒到 2 分钟内逐步上升最终稳定在一个平台。如果读数从一开始就始终不变检查代码是否真的进入了这个 while 循环如果曲线出现锯齿状大跳变问题大概率在采样时间或滤波参数上而不是传感器坏了。5.2 对照测量的三种手段软件读数再平滑也需要一个外部参考来验证绝对值。最方便的是用红外测温枪对准芯片顶部的丝印区域发射率设置为 0.95等待读数稳定后记录壳温。由于内部温度传感器测的是结温壳温通常比结温低 2℃ 到 5℃差多少取决于封装和散热条件。第二个办法是把 DS18B20 用导热硅胶贴在芯片表面等热平衡后与内部温度读数对照两者的趋势和差值会更有参考价值。第三个办法是在上电瞬间记录最低读数刚上电几秒内结温非常接近室温如果这个值比室温高了 5℃ 以上说明单点校准或斜率计算有系统性问题。5.3 校准数据留存与应用代码写法最后一步是把校准参数固化到片内 Flash避免每次上电重新校准。F407 的 HAL 库已经提供了完整的 flash 功能函数用HAL_FLASH_Unlock解锁、HAL_FLASH_Program写入一个 word、HAL_FLASH_Lock锁定配合存储区最后一个 sector 的起始地址即可。写入时注意将两个校准点打包成结构体并带上 magic 字段标记有效性上电初始化时先读取 Flash校验 magic 通过就使用烧录值不通过则回退到手册典型值。typedef struct { uint32_t magic; float t1, v1, t2, v2; } cal_storage_t; /* 写校准参数 */ HAL_FLASH_Unlock(); uint64_t data cal_pack; HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, cal_addr, data); HAL_FLASH_Lock(); /* 上电读取并校验 */ cal_storage_t cal; memcpy(cal, (void *)cal_addr, sizeof(cal)); if (cal.magic ! CAL_MAGIC) { cal default_cal; /* 回退到手册典型值 */ }写入前需要先擦除对应 sector擦除操作同样要先解锁。读回校验通过后再上锁这样下次上电只需一次 memcpy 就能恢复校准参数。整套流程跑通以后内部温度传感器就不再是一个“读着玩”的实验而是一个可以长期稳定输出结温的工程模块之后接 LCD、接串口、接 Modbus 做远程监控都只是数据如何消费的问题。本文还有配套的精品资源点击获取