news 2026/9/17 8:22:15

STM32热敏电阻温度采集报警:ADC到OLED与串口全链路解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32热敏电阻温度采集报警:ADC到OLED与串口全链路解析

简介:这是一套完整的STM32热敏电阻温度监测工程源码,面向嵌入式初学者和开发者,解决从传感器采集到显示、报警、串口输出的全流程问题。工程共231个文件,压缩包约6.33MB,文件类型以c和h源码为主,包含标准外设库驱动、OLED显示驱动、ADC采集配置以及工程编译文件,另有o、crf等中间产物和axf、map等生成文件,便于了解编译与链接结构。目前已有843人学习下载。通过学习这一工程,可以掌握热敏电阻阻值变化经ADC转换为数字温度的算法,学会用SPI或I2C驱动OLED实时显示温度,利用GPIO控制蜂鸣器实现超限报警,并通过UART把温度数据发送到串口调试助手。源码模块清晰、注释明确,适合直接移植到智能家居、工业环境监测或竞赛实训中,是一份能动手复现并扩展的实用资料。

1. STM32 温度采集报警:热敏电阻、OLED、蜂鸣器和串口这条链路从头调

把热敏电阻、OLED、蜂鸣器接到 STM32 上,硬件连线用不了多少时间,真正的复杂度全在数据链路里:热敏电阻不能直接怼 ADC 引脚,要先搭分压;OLED 的 I2C 地址写错一位就是白屏;蜂鸣器在阈值附近反复启停比不响更恼人;printf 不做重定向的话,串口调试助手只会收到空白或乱码。这条链路每一步看起来都有现成例程,但拼在一起时,采样时间、滞回区间、数据帧格式这些参数才是决定项目能不能稳定跑起来的关键。这篇文章顺着数据流把整条链路拆开,从热敏电阻分压采集、B 值换算温度,到 OLED 实时显示、蜂鸣器阈值报警,最后通过 USART 把温度和原始 ADC 值送到串口调试助手,每段都给出 HAL 库代码和参数依据。做毕设或者桌面温度计这类小工具,直接对照着把它变成稳定可交付的代码。

2. 热敏电阻分压与 ADC 换算:从电阻值到浮点温度

2.1 分压电路设计:热敏电阻不能直连 ADC

STM32 的 ADC 只能测量电压,而热敏电阻的物理量是阻值,所以第一步是把电阻变化转换成电压变化,最常见也最稳妥的做法是串联分压:NTC 热敏电阻一端接 3.3V,另一端接到 ADC 引脚,同时引脚再接一个固定电阻到 GND,中间抽头就是 ADC 采样点。NTC 的特点是温度升高阻值下降,于是这个分压点的电压会随温度升高而上升,方向直观。

选型上,网上最常见、例程最多的是 25°C 下标称 10kΩ、B 值为 3950 的 NTC,固定电阻也配 10kΩ。这个组合的好处是 25°C 时 NTC 阻值恰好等于固定电阻,分压点电压处于 3.3V 的中点 1.65V,整个量程内可获得相对均衡的灵敏度。分压公式是 V_adc = 3.3 × R_fixed / (R_ntc + R_fixed),可以用一段 Python 把不同温度下的预期电压先算出来,接线之前心里就有数。

# 用 B 值方程估算各温度下 NTC 阻值和分压电压 import math R0 = 10000.0 # 25°C 标称阻值 B = 3950.0 # 热敏指数,NTC 厂家给出 Rf = 10000.0 # 分压固定电阻 VCC = 3.3 T0 = 298.15 # 25°C 对应的开尔文温度 def r_ntc(t): tk = t + 273.15 return R0 * math.exp(B * (1.0 / tk - 1.0 / T0)) for t in [0, 10, 25, 40, 60, 80]: r = r_ntc(t) v = VCC * Rf / (r + Rf) # NTC 在上、固定电阻在下 print(f"T={t:3d}C R={r:8.1f} V_adc={v:.3f}V")

这段代码里 R0 是 25°C 时的 NTC 阻值,B 是热敏指数,决定了阻值随温度变化的曲线形态。运行后可以看到 0°C 时分压约 0.85V,25°C 时 1.65V,80°C 时约 2.90V,整个范围落在 ADC 量程中段,不会出现低位溢出。把 NTC 和固定电阻的位置对调,电压方向会反转,后续换算公式也要跟着改。

分压电阻的精度直接影响温度准确性,固定电阻建议不要用 5% 的普通色环电阻,至少选 1% 金属膜,有条件直接上 0.1%。B 值的离散度也要关注,同样标 10k 3950 的传感器,实际 B 值可能偏差 ±1%,在 100°C 附近换算误差会被放大到 2°C 以上。

B 值25°C 到 50°C 阻值变化趋势常见应用场景
3380下降到约 4.2kΩ家电、空调温度探头
3950下降到约 3.6kΩ3D 打印、工业模块、毕设最常见
4100下降到约 3.5kΩ汽车电子、高温测点

选传感器时确认好 B 值,代码里的常量要跟实物一致,否则中低温段误差不大,高温段偏差会越来越明显。

2.2 ADC 采样配置:为什么采样时间要拉到 239.5 周期

以 STM32F103C8T6 为例,ADC1 的通道 0 对应 PA0 引脚。CubeMX 里配置很简单:PA0 设为 Analog 模式,ADC1 使能,分辨率 12 位。真正影响读数质量的是 Sampling Time 这个参数,务必选 239.5 Cycles,对应代码里的ADC_SAMPLETIME_239CYCLES_5

原因在 NTC 的高内阻特性:低温时 NTC 阻值可能到 30kΩ 以上,ADC 内部采样电容需要在采样阶段完成充电,充电时间常数由源阻抗和内部电容共同决定。采样时间如果只给 1.5 周期,电容还没充满就进入转换阶段,读到的电压会明显偏低,而且温度越低偏低越严重。239.5 周期在 ADC 时钟 12MHz 下约 20µs,对这个量级的源阻抗已经足够。这一条直接决定采样数据是否可信,是整套代码里最不该省的一步。

// ADC1 通道 0(PA0) 单通道采样配置 void ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; // 单通道模式 hadc1.Init.ContinuousConvMode = DISABLE; // 每次显式触发转换 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐,低 12 位有效 hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 关键:长采样时间 HAL_ADC_ConfigChannel(&hadc1, &sConfig); } uint32_t ADC1_ReadRaw(void) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); // 等待转换完成,超时 10ms return HAL_ADC_GetValue(&hadc1); }

配置里ContinuousConvMode设为 DISABLE 是因为我们想在主循环里按节奏主动采一次,而不是让 ADC 自己连续跑,这样便于控制采样频率。PollForConversion的超时参数给 10ms 足够,正常转换在几十微秒内完成。手动改成了长采样时间后,如果温度读数仍然跳,就要往硬件连接方向排查,而不是继续加采样时间。

2.3 用 B 值公式换算,还是查表插值

ADC 原始值只是 0 到 4095 的整数,要先还原成电压,再反推 NTC 阻值,最后用 B 值公式换算成温度。B 值公式由 NTC 的定义式反解而来:R = R0 × exp(B × (1/T - 1/T0)),其中 T0 是 298.15K,对应 25°C。反解后得到 T = B × T0 / (T0 × ln(R/R0) + B),注意所有温度都要先换算成开尔文,最后再减掉 273.15。

#include <math.h> // 由 NTC 阻值换算温度,单位 °C float R_to_Temp(float r) { const float B = 3950.0f; // NTC 的 B 值 const float R0 = 10000.0f; // 25°C 标称阻值 const float T0 = 298.15f; // 25°C 开尔文 float tk = B * T0 / (T0 * logf(r / R0) + B); return tk - 273.15f; }

logf是自然对数,需要包含 math.h。在 Keil 工程里建议勾选 Target 选项里的 Use MicroLIB,否则浮点 printf 和数学函数的链接体积会明显膨胀,F103 这类芯片的 Flash 很快就吃紧。F103 的 Cortex-M3 内核不带硬件 FPU,浮点运算由软件完成,但一次温度换算只有几十微秒,测温场景每秒采十几次完全没有压力。

对应到完整链路,从 ADC 原始值到温度这样串起来:

float read_temperature(void) { uint32_t raw = ADC1_ReadRaw(); // 0~4095 float v_adc = (float)raw / 4095.0f * 3.3f; // 还原采样点电压 float r_ntc = 10000.0f * (3.3f - v_adc) / v_adc; // 固定电阻在 GND 侧 return R_to_Temp(r_ntc); }

这段代码里3.3f用的是 VDDA 标称值,实际开发板 VDDA 不一定是精确的 3.300V,这也是系统误差的主要来源之一,后面校准时用万用表实测修正。

查表插值是另一条路线:预先算出 0 到 100°C 每 1°C 对应的 ADC 值或电阻值,运行时先二分查找落在哪个区间,再做线性插值。查表法不依赖浮点库,执行时间确定,适合对实时性有要求或者不想引入 log 函数的裸机工程;缺点是换一种 NTC 型号就要重新生成整张表。原型验证阶段用 B 值公式更快,定型后用查表法更稳,两条路线各有适用场景。

换算方式代码量典型精度换传感器成本适用阶段
B 值公式5 行25°C 附近 0.3~0.5°C,高温偏差放大只改 B 值常量快速原型、通用演示
查表插值约 50 行取决于表密度,可做到 0.1°C重新生成表格和烧录批量定型、传感器型号固定

3. 用 HAL 库点亮 OLED、接通蜂鸣器报警

3.1 0.96 寸 I2C OLED:HAL 库驱动代码的最小闭环

0.96 寸 OLED 有 I2C 和 SPI 两个版本。I2C 版只要 SCL、SDA、VCC、GND 四根线,占用两个 IO,适合对刷新率要求不高的温度显示;SPI 版刷新快,但要多接 CS、DC、RESET 三根控制线,连线复杂度上了一个台阶。这款项目显示内容只有一行温度和一两个状态字符,I2C 版本就够用,驱动芯片绝大多数是 SSD1306。

接线按 I2C1 的默认引脚走:SCL 接 PB6,SDA 接 PB7,VCC 接 3.3V,GND 共地。I2C 地址一般是 0x3C,如果模块上 SA0 焊盘被拉高,地址会变成 0x3D,白屏时优先检查这个。HAL 库的HAL_I2C_Mem_Write需要的是 8 位地址格式,也就是把 7 位地址左移一位,代码里写0x3C << 1,这是新手最容易卡住的地方。

// SSD1306 写命令与写数据:Mem_Write 的第一个字节是控制字节 static void Oled_WriteCmd(uint8_t cmd) { HAL_I2C_Mem_Write(&hi2c1, 0x3C << 1, 0x00, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 10); } static void Oled_WriteData(uint8_t dat) { HAL_I2C_Mem_Write(&hi2c1, 0x3C << 1, 0x40, I2C_MEMADD_SIZE_8BIT, &dat, 1, 10); } // 初始化序列:核心是打开 charge pump 并退出睡眠 void Oled_Init(void) { const uint8_t init[] = { 0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40, 0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12, 0x81, 0xCF, 0xD9, 0xF1, 0xDB, 0x40, 0xA4, 0xA6, 0xAF }; for (uint8_t i = 0; i < sizeof(init); i++) Oled_WriteCmd(init[i]); }

初始化序列里,0x8D 0x14是打开内部电荷泵,SSD1306 没有这一步屏幕不会亮;0xA8 0x3F设置驱动路数为 64 行;0x20 0x00选水平寻址模式;0xAF最后打开显示。很多 HAL 库驱动 OLED 代码把这一整段封装成OLED_Init(),出错概率也集中在这里。

显示温度时,OLED 是 128x64 点阵,显存共 1024 字节,每 8 个点纵向组成一个字节。显示字符串就是先把字符字模按列拷贝进显存缓冲区,然后整帧刷到屏幕上。为做查询方便,一般把字模按 6x8 点阵存放,调用时逐字节拷贝:

// 在指定页(每页 8 行像素)显示字符串,随后整帧刷新 void Oled_ShowString(uint8_t page, uint8_t col, char *s) { extern uint8_t frame[1024]; // 显存缓冲区 while (*s) { for (uint8_t i = 0; i < 6; i++) // 从 6x8 字模表取第 col 个字模字节写入对应位置 frame[page * 128 + col * 6 + i] = font6x8[(*s - 32) * 6 + i]; s++; col++; } // 刷新整帧 Oled_WriteCmd(0x21); Oled_WriteCmd(0); Oled_WriteCmd(127); Oled_WriteCmd(0x22); Oled_WriteCmd(0); Oled_WriteCmd(7); for (uint16_t i = 0; i < 1024; i++) { uint8_t b = frame[i]; HAL_I2C_Mem_Write(&hi2c1, 0x3C << 1, 0x40, I2C_MEMADD_SIZE_8BIT, &b, 1, 10); } }

主循环里一行代码完成温度显示,显示刷新频率保持在 10Hz 左右就够了,人眼不会感到迟滞,同时避免 I2C 刷屏占用太多总线时间。

3.2 蜂鸣器驱动电路:有源和无源蜂鸣器的接法差异

蜂鸣器按有无内部振荡源分成有源和无源两类。有源蜂鸣器内部自带振荡电路,通电即响;无源蜂鸣器需要外部给 2 到 4kHz 的方波才会发声。报警场景建议优先无源蜂鸣器,因为它的音调和节奏完全由程序控制,能做出「短促报警」和「持续报警」的区分,有源蜂鸣器只能以固定频率一直响,报警辨识度差一些。

常用 STM32 开发板自带的蜂鸣器模块通常已经集成三极管驱动,但如果外接裸蜂鸣器,就不能直接用 GPIO 去推,STM32 引脚输出电流有限,带不动蜂鸣器工作电流,必须加一级三极管放大。典型接法是用 NPN 三极管 S8050,GPIO 通过 1kΩ 基极限流电阻接到基极,蜂鸣器串联在电源和集电极之间,发射极直接接地。

连接节点去向说明
PB11kΩ → S8050 基极限流保护 GPIO 和三极管
S8050 发射极GND低侧驱动
蜂鸣器正极5V 或 3.3V按蜂鸣器标称电压选择
蜂鸣器负极S8050 集电极三极管导通后形成回路
续流二极管反向并联蜂鸣器两端1N4148,吸收反向电动势

GPIO 输出高电平时三极管导通,蜂鸣器通电发声,这是最常见的低侧驱动电路。无源蜂鸣器是感性负载,断开瞬间会产生反电动势,不加续流二极管容易在集电极产生高压尖峰,时间长了可能击穿三极管。

无源蜂鸣器的报警逻辑可以做成非阻塞翻转方波,用HAL_GetTick()做时间基准,每 200ms 翻转一次电平,形成「响 200ms、停 200ms」的节奏:

// 无源蜂鸣器驱动:按 200ms 节奏断续报警 void Buzzer_Update(uint8_t alarm, uint32_t now) { static uint32_t last_toggle = 0; static uint8_t state = 0; if (!alarm) { HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET); state = 0; return; } if (now - last_toggle >= 200) { // 200ms 翻转一次,形成断续音 last_toggle = now; state ^= 1; HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, state ? GPIO_PIN_SET : GPIO_PIN_RESET); } }

这段函数不给方波频率,给的是报警节奏。直接用HAL_Delay(200)也能实现类似效果,但主循环里还要刷 OLED、发串口,阻塞延时会让屏幕刷新和串口输出都出现卡顿,所以非阻塞方式才是这个场景的正解。

3.3 报警逻辑:滞回阈值与非阻塞鸣叫配合

报警阈值常见的写法是温度超过 40°C 就报警、低于 40°C 就关闭,但这种单阈值逻辑有个明显的坑:温度在 39.8 到 40.2 之间波动时,蜂鸣器会反复开启和关闭,听起来像是接触不良。解决办法是加滞回区间,报警触发点和释放点分开设,本项目用触发 40°C、释放 37°C 的区间做 3°C 滞回。

#define TEMP_ALARM_ON 40.0f // 进入报警阈值 #define TEMP_ALARM_OFF 37.0f // 退出报警阈值,略低于触发点 uint8_t Buzzer_Check(float temp) { static uint8_t alarm = 0; if (temp >= TEMP_ALARM_ON) alarm = 1; // 达到触发点进入报警 if (temp <= TEMP_ALARM_OFF) alarm = 0; // 降到释放点才解除 return alarm; }

static变量保存上一次的报警状态,函数被反复调用时才能记住当前是不是已经处于报警中。滞回带的宽度设置要考虑传感器噪声,一般噪声不超过 1°C,3°C 留足了余量,同时不会让报警反应显得迟钝。这个状态机还能继续扩展,比如把报警状态机从二态改成「正常-预警-报警」三态,OLED 上分别显示不同提示,串口输出里也多一个状态字段,为后续上位机联动留下空间。

4. 串口发送到调试助手:printf 重定向与数据帧设计

4.1 printf 重定向:让 USART1 变成调试助手能读的输出口

CubeMX 里把 USART1 配置为异步模式,波特率 115200,数据位 8,停止位 1,无校验,对应 PA9 的 TX 和 PA10 的 RX。硬件连接上,如果调试助手这边用的是 USB 转 TTL 模块,STM32 的 TX 接模块的 RX,RX 接模块的 TX,同时两边必须共地,否则串口收数据会断断续续。

标准 C 库的 printf 默认输出到 stdout,而 stdout 在裸机工程里没有实现,所以要把输出重定向到 USART1。HAL 库里只需要重写 fputc 这一个函数:

#include <stdio.h> int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100); return ch; }

fputc收到一个字符就通过 HAL_UART_Transmit 发一个字符,超时时间给 100ms 足够。重定向完成之后,printf 的所有输出都会走 USART1,串口调试助手直接就能收到文字。要注意 Keil 工程里必须勾选 Target 选项中的 Use MicroLIB,否则完整的 C 标准库 printf 会把浮点格式化函数的体积带大,在 F103 上容易遇到 Flash 不足,而且 MicroLIB 的 printf 才支持%.1f这类浮点格式。

如果不打算引入 printf,也可以直接用 HAL_UART_Transmit 发送格式化后的字符串,效果一样:

char buf[32]; int n = sprintf(buf, "temp=%.1f\r\n", temp); HAL_UART_Transmit(&huart1, (uint8_t *)buf, n, 100);

这种方式不依赖标准输出重定向,逻辑更加裸露直观。另外,如果用的是 ST-Link 自带的虚拟串口,Windows 驱动没有正确安装时设备管理器里会出现黄色感叹号,串口助手也打不开对应端口,先解决驱动再查程序。

4.2 数据帧格式选择:文本、逗号分隔和 JSON 各有什么适用场景

数据到了串口调试助手这一步,格式决定了后面连接的方便程度。只是人眼观察,纯文本就够;以后要接上位机或者服务端,就需要结构化一些的帧格式。

格式示例优点适用场景
纯文本T=25.6C ALARM=0人眼直接读,排错直观临时调试
逗号分隔25.6,2048,0短小,Excel 或脚本易分割简单记录、采集
JSON{"temp":25.6,"raw":2048,"alarm":0}自描述,上位机解析不依赖字段位置对接上位机、服务器

人眼观察用纯文本最直接,SSCOM、XCOM 这类常用串口调试助手直接以 ASCII 显示,字段一清二楚。逗号分隔格式最省带宽,适合长时间记录后导入表格工具处理。JSON 帧虽然长一点,但每个字段自带语义,上位机用现成的 JSON 库解析即可,以后增加风速、湿度等字段也不需要改协议。

推荐在主循环里按固定周期发送 JSON 帧,字段包含温度、原始 ADC 值和报警状态,一次把传感器信息带全:

printf("{\"temp\":%.1f,\"raw\":%lu,\"alarm\":%u}\r\n", temp, raw, alarm);

注意字符串里的双引号要用反斜杠转义,帧尾必须是\r\n,串口调试助手靠回车换行来区分一帧的结束,不加的话所有数据会粘连在一行里,后续做数据回放时很难拆分。发送频率控制在 5 到 10Hz 即可,115200 波特率下每秒发送的数据量远低于信道容量,频繁发送只会增加上位机解析压力。

4.3 边界检测与完整主循环:别让传感器故障伪装成温度

ADC 满量程值 4095 或者接近 0 的值,按温度公式算出来会是极端温度,但实际对应的是接线故障。回顾分压结构:NTC 开路时抽头被固定电阻拉到 GND,ADC 读到接近 0;固定电阻短路时抽头接近 VCC,ADC 读到接近 4095。正常测温范围内 ADC 值不会走到这两个极端,因此可以做传感器健康检查,放在温度换算之前。

// 传感器异常判断:0 表示异常,1 表示正常 uint8_t temp_sensor_check(uint32_t raw) { if (raw < 100 || raw > 4050) return 0; return 1; }

阈值留了 100 和 4050 的余量,避免把低温高压的场景误判为故障。异常时就不该继续走温度换算流程,OLED 显示错误提示,串口输出传感器错误,让调试的人立刻知道是接线而不是温度。

完整主循环把这一整套逻辑串起来,OLED 显示和蜂鸣器报警各司其职:

while (1) { uint32_t raw = ADC1_ReadRaw(); if (!temp_sensor_check(raw)) { Oled_ShowString(0, 0, "SENSOR ERR"); printf("sensor_error\r\n"); } else { float temp = read_temperature(); char line[16]; sprintf(line, "T=%.1fC", temp); Oled_ShowString(0, 0, line); uint8_t alarm = Buzzer_Check(temp); Buzzer_Update(alarm, HAL_GetTick()); printf("{\"temp\":%.1f,\"raw\":%lu,\"alarm\":%u}\r\n", temp, raw, alarm); } HAL_Delay(100); }

主循环里HAL_Delay(100)控制整体节奏为 10Hz,每次循环采集一次 ADC、更新一次 OLED、发送一次串口帧。温度采集不属于高速场景,10Hz 足够让串口助手里看到平滑变化曲线,还能给 I2C 刷屏和 printf 留出充足时间。

5. 稳定输出温度:滤波、冰水校准与故障巡检

5.1 滑动平均窗口取多大才合适

ADC 单次采样的随机噪声通常有几到十几个 LSB,直接送进温度公式会让 OLED 上的小数位来回跳动。常见做法是滑动平均,连续采 8 次取平均,窗口更新方式比简单累加平均更省内存。窗口太大会让温度响应变慢,报警触发也随之延迟,8 次在 10Hz 采样下引入约 800ms 延迟,用在阈值判断上完全能接受。

#define WINDOW_SIZE 8 uint32_t adc_buf[WINDOW_SIZE]; uint8_t adc_idx = 0; uint32_t adc_sum = 0; // 每次采集后把新值滑入窗口 adc_sum -= adc_buf[adc_idx]; // 移除最旧的值 adc_buf[adc_idx] = raw; // 写入新值 adc_sum += adc_buf[adc_idx]; // 累加 adc_idx = (adc_idx + 1) % WINDOW_SIZE; raw = adc_sum / WINDOW_SIZE; // 平滑后的 raw

把这段逻辑放在temp_sensor_check之前还是之后有讲究,推荐先做异常判断再做平滑,否则一次故障采样的极端值会把窗口内的平均值拉偏。注意这里用的是整数运算,避免浮点除法引入额外的 CPU 开销。ADC 值平滑之后再做温度换算,高压侧和低压侧的噪声都会被压低,串口助手里看到的曲线就干净很多。

5.2 冰水校准和电源基准修正

整条链路跑通后,第一件要做的事是验证温度准不准,冰水混合物是最便宜的标准温度源。冰块加少量水,在标准大气压下稳定在 0°C 附近,把 NTC 探头完全浸入,等 30 秒热平衡后,看串口调试助手的读数。如果显示 1 到 2°C 的偏差,优先检查两个源头。

第一个是固定电阻精度,10kΩ 的 5% 电阻实际阻值可能落在 9.5 到 10.5kΩ 之间,直接导致分压曲线系统性偏移。把固定电阻换成 0.1% 金属膜电阻,这类偏差基本消失。第二个是 VDDA 实际电压,代码里3.3f是标称值,开发板上的 3.3V 经过 LDO 后通常有 1% 到 2% 的误差。用万用表实测 VDDA 对 GND 的电压,例如实际为 3.28V,把换算代码里的常量替换成实测值,误差能显著收敛。

再用一个高温点验证曲线形态,手心温度约 33°C 到 35°C,握紧 NTC 等读数稳定,观察从 0°C 到体温区间内的测量线性度。如果中间段偏差仍然超过 0.5°C,多半是 B 值不匹配,需要更换 NTC 型号或者调整 B 值常量。串口帧里的 raw 字段在调试期间不要省掉,它能帮助区分是传感器问题还是换算问题。标完冰水 0°C 和手心温度两个点,把 3.3f 常量按实测电压替换,这条链路的误差通常就能收进 ±0.5°C,OLED 上的温度值和串口助手里的曲线就都能交付了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 8:21:56

S7-1215C视觉分拣:TCP通讯、FIFO队列与九点标定实战

简介&#xff1a;围绕西门子S7-1215C PLC与信捷视觉系统构建工业机器人分拣系统的期刊论文PDF&#xff0c;面向自动化、机电一体化专业技术人员以及职业院校技能大赛选手与指导教师&#xff0c;重点解决分拣机器人软硬件集成、通讯调试与故障排查等实践问题。资源为1个pdf文件&…

作者头像 李华
网站建设 2026/9/17 8:21:44

Spring事务在微信红包退款中的三大陷阱与解决方案

1. 微信红包退款失败背后的Spring事务陷阱那天接到阿强的电话&#xff0c;他刚从腾讯微信支付部门的面试出来&#xff0c;声音里透着不服气。"Fox哥&#xff0c;你说这面试官是不是故意刁难人&#xff1f;我就说用Transactional保证退款事务&#xff0c;他居然说这么写上线…

作者头像 李华
网站建设 2026/9/17 8:21:42

WLAN基础概念与VLAN Pool配置实战:AP/AC/转发模式全解析

1. WLAN基础概念&#xff1a;先弄明白无线网络里的三个角色和一条隧道很多人第一次看到WLAN这三个字母&#xff0c;觉得它就是Wi-Fi&#xff0c;做项目的时候也不当回事。但等你在现场遇到一台AC带几百个AP、几千个终端上网的场景&#xff0c;就会发现WLAN背后那些概念——AP、…

作者头像 李华
网站建设 2026/9/17 8:20:11

Edge AI 场景下 PCIe 与 USB 2.0 的 I/O 桥接方案解析

在工控圈子里泡久了你会发现&#xff0c;聊到 Edge AI&#xff0c;大家条件反射全是模型、算力、推理框架&#xff0c;很少有人把注意力放在 I/O 这一层。但真正把设备送进产线的人心里都清楚&#xff0c;一个边缘盒子能不能稳定干活&#xff0c;很多时候卡在接口而不是芯片。我…

作者头像 李华
网站建设 2026/9/17 8:19:18

PyTorch 分布式通信拓扑优化:NCCL 环状 Ring 与树状 Tree 算法物理机理

PyTorch 分布式通信拓扑优化&#xff1a;NCCL 环状 Ring 与树状 Tree 算法物理机理在多机多卡大规模分布式训练&#xff08;如 64 卡、256 卡、1024 卡集群&#xff09;中&#xff0c;底层的 AllReduce 梯度通信算法 决定了整个算力集群的线性扩展效率。 当 PyTorch DDP 或 Dee…

作者头像 李华
网站建设 2026/9/17 8:19:17

基于压缩感知的图像加密压缩混合算法实践

1. 项目背景与核心价值在数字图像处理领域&#xff0c;数据安全与传输效率始终是一对需要平衡的矛盾体。传统做法往往将压缩和加密作为两个独立环节处理&#xff0c;这不仅增加了计算开销&#xff0c;还可能因分步操作导致安全隐患。我们团队研发的这种混合算法&#xff0c;正是…

作者头像 李华