实战课题

第 33 课:桌面温度计

两个测温度的器件摆在一起——一个快而不准,一个准而不快

我是吴老师。这个课题最值钱的地方,不是做出一台温度计,是让你亲眼看见一件事:

同一件事,可以有两个完全不同的答案。

第 12 课你玩过热敏,第 16 课你玩过 DHT11。当时它们隔了四节课,你大概没往一块儿想过。今天我把它们并排插在同一块板子上,让两个数同时显示在同一块屏幕上。

然后你会看到:

  • 你用手一捂,热敏那个数几秒钟就动了,DHT11 那个数还在那儿发呆
  • 等十几秒,DHT11 才慢慢跟上,而且它跟热敏算出来的度数能差好几度

那一刻你会明白:这两个东西不是"一个好的一个坏的",是"干不同活的两个"。

做完之后你手上是这样的东西:一块纸皮表盘立在桌上,中间嵌着屏幕,屏幕上同时挂着两个温度,表盘上画着刻度,还有一条红色的线标着"到这儿就报警"。真超过那条线,蜂鸣器就"嘀嘀嘀"。

这是你第一个"长期摆在桌上、一直有用"的作品。

一、先说清楚:这个课题的三个环节

完整闭环:传感器感知、主控判断、执行器动作,三个环节缺一不可

环节 这个课题里是谁
传感器(看) 热敏(读 GPIO0 的模拟值)+ DHT11(读 GPIO4 的数字值)
主控(想) 热敏说"变热了"没有?DHT11 说现在几度?
执行器(做) OLED 同时显示两个温度,蜂鸣器报警

这次特别的地方在于:传感器有两个,而且它们是"重复"的。 前面所有课题,传感器都只负责一件事。这个课题要你学会给两个传感器"分工"。

分工怎么分,就是这一课的核心。

二、材料清单

套件里的:ESP32-C3 主控 + 拓展板、热敏模块、DHT11 温湿度模块、OLED 显示屏、有源蜂鸣器、杜邦线若干。

身边要准备的:纸皮、美工刀、剪刀、铅笔、直尺和一根线(画圆弧用,下面讲)、热熔胶枪 + 胶棒。

安全那两条,我不重复第三遍了,你自己记着:美工刀往外推、手别在刀的前进路线上;热熔胶刚挤出来一百多度,别用手指去按。

三、接线:两个传感器,一个都不能认错

接之前先断电。

模块 针脚 接到
热敏 VCC / GND / AO 3.3V / GND / GPIO0
DHT11 + / / OUT 3.3V / GND / GPIO4
蜂鸣器 VCC / GND / I/O 3.3V / GND / GPIO5
OLED GND / VDD / SCK / SDA GND / 3.3V / GPIO9 / GPIO8

三个提醒:

第一,热敏只接 AO,不接 DO DO 那个门槛是靠板上小电位器拧的,拧完你自己都记不住定在哪(第 12 课讲的)。我们读 AO,判断写在代码里。

第二,热敏和 DHT11 别拿混。 两个都是小板子,认法第 12 课讲过:看顶上的元件——热敏是一颗玻璃珠,DHT11 是带格栅的白色方块

第三,DHT11 的丝印是 + OUT ,不是 VCC/GND 这块板子上印的跟你前面见过的所有模块都不一样——+ 就是正极, 就是负极,第 16 课专门讲过。认丝印,别按习惯插。

四、动手第一步:两个传感器先各跑各的

别急着合起来。 第 8 课的老规矩:先让每个零件单独出个现象。

C++
#include "DHT.h"

DHT dht(4, DHT11);          // 数据脚 GPIO4,型号 DHT11

void setup() {
  Serial.begin(115200);
  dht.begin();
}

void loop() {
  int ntc = analogRead(0);            // 热敏:0~4095 的原始数
  float t = dht.readTemperature();    // DHT11:直接给摄氏度

  Serial.print("热敏读数 ");
  Serial.print(ntc);
  Serial.print("    DHT11 ");
  Serial.print(t);
  Serial.println(" °C");

  delay(2000);                        // DHT11 要求的:别读太快
}

烧进去,打开串口。你应该看到两列数每两秒刷一次。

现在做第 12 课那个动作:用手指捏住热敏那颗玻璃珠,别松手,盯住串口。

你会看到一件很有意思的事:

  • 热敏那列数字,几秒之内就开始动了
  • DHT11 那列,纹丝不动地待了十几秒,才慢吞吞地往上爬

这就是"快"和"慢"的第一次同框。 别急着往下走,先把这个现象看够。

为什么 DHT11 这么慢? 热量得从你的手指传进那颗玻璃珠,珠子自己温度升上来,电阻才变——这个过程本来就慢。DHT11 更慢,因为它内部还要把感受到的东西转换成数字,手册上要求的采样间隔是至少 1 秒(第 16 课讲的协议那套东西)。这不是坏了,是它的工作方式。

五、动手第二步:把热敏的读数变成"大概几度"

到这儿热敏给你的还是 0~4095 的原始数——"热敏读数 1520",这话你没法跟人说。

要变成度数,就得换算。第 12 课讲过三条路(查表、B 值公式、两点线性拟合),我们走最省事的那条:两点法。

做法只有三步:

  1. 看 DHT11 显示多少度(比如 25 度),同时记下热敏的读数(比如 1800)
  2. 捏住热敏,等它稳住,再看 DHT11 多少度(比如 33 度),记下热敏的读数(比如 1300)
  3. 在这两个点之间,用一条直线硬连起来
C++
// ★ 下面这两个数是我编的,你必须换成自己测出来的!
int NTC_AT_25 = 1800;       // 25 度左右时,热敏的读数
int NTC_AT_33 = 1300;       // 33 度左右时,热敏的读数

float ntcTemp(int v) {
  return 25.0 + (NTC_AT_25 - v) * (33.0 - 25.0) / (NTC_AT_25 - NTC_AT_33);
}

注意那个减号的顺序——它假设"温度越高、读数越小"。你的模块是不是这个方向,得自己跑一遍确认(第 12 课那句话:写反了程序照样跑,只是永远不对,这种错误最难查)。

热敏电阻的阻值随温度变化是一条曲线,不是直线,所以换算比光敏复杂

看一眼第 12 课那张曲线你就明白为什么这个方法粗了:真实的电阻-温度关系是条弯的,你拿一条直线去连两个点,中间那段还好,两头就越跑越偏。

说清楚:这个方法算出来的度数,粗得很。 两个点之间用直线硬连,出了这段范围就越跑越离谱。它能告诉你"大概二十六七度",但别拿它当依据。

要准的数,看 DHT11 那一列。 热敏那个数的作用不是"报温度",是"反应快"——这一点下一节就要用上。

六、动手第三步:让两个数同时上屏幕

C++
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 oled(128, 64, &Wire, -1);

void drawScreen(float t, float nt) {
  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);

  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.print("DHT11  ");          // 准的那个,写上面
  oled.setTextSize(2);
  oled.setCursor(0, 12);
  oled.print(t, 1);               // 保留一位小数
  oled.print("C");

  oled.setTextSize(1);
  oled.setCursor(0, 38);
  oled.print("NTC    ");          // 快的那个,写下面
  oled.setTextSize(2);
  oled.setCursor(0, 48);
  oled.print(nt, 1);
  oled.print("C");

  oled.display();
}

loop 里调它:drawScreen(t, ntcTemp(ntc));

现象:屏幕上下两行温度。

现在捏住热敏,盯着屏幕看。 上面那行(DHT11)半天不动,下面那行(NTC)几秒就开始爬——而且它爬得比 DHT11 高。

松手,看着它们慢慢回落。上面那行会比下面那行更晚回到原位。

你看到的,就是这两个器件的性格。

热敏和 DHT11 对比:热敏反应快但算不准,只能回答变热了没有;DHT11 直接给出摄氏温度但反应慢、读取要等一两秒

有件事我要专门说:屏幕上这两个数不一样,不是你算错了。

DHT11 的规格是 0~50°C ±2°C——它报 26 度,真实温度可能在 24 到 28 之间任何地方。差个一两度是它的正常表现,不是故障。(第 16 课那段"别拿它跟家里的温度计比对",说的就是这个。)

热敏那边更粗:元件个体差异 + 分压电阻误差 + ADC 偏差,三层误差叠一起,能准到正负两三度就算你运气好。

所以这两个数差三四度,是正常的。 你要看的不是"哪个准",是"它俩一会儿差多少、什么时候开始一起动"。

七、动手第四步:分工——谁报警,谁报数

现在到了这一课最关键的设计。两个传感器,报警该听谁的?

先想一个问题:DHT11 要两秒才更新一次。这两秒里,板子是"瞎"的。

如果屋里温度突然窜上去,报警晚两秒——对"报警"这件事来说,两秒挺要命的。

所以分工这样定:

负责什么 为什么
DHT11 报数——屏幕上那个准确的温度 它准,规格写明白
热敏 报警——超过你画的那条线就响 它快,每一圈都能读

这不是我瞎定的,是第 12 课那句话的直接应用——当时我说:"热敏的价值是事件,不是数值。它适合回答'变热了没有'。"

现在你就是在用它回答这个问题。

先标定:那条线画在哪儿

别抄我的数。 按第 11、12 课那套流程:

  1. 先空跑一两分钟,看热敏读数平时在什么范围飘
  2. 用手捏住玻璃珠,看它能掉到多少(或者升到多少,看你的方向)
  3. 在这两个数中间取一个,偏向"变热"那一侧一点,留点余量
C++
int NTC_ALARM = 1500;       // ★ 换成你自己标定出来的数

void loop() {
  int v = analogRead(0);
  float nt = ntcTemp(v);
  float t  = dht.readTemperature();

  drawScreen(t, nt);

  // 热敏说"变热了"——立刻响,不等 DHT11
  if (v < NTC_ALARM) {              // 方向反了就改成 >
    digitalWrite(5, HIGH);
  } else {
    digitalWrite(5, LOW);
  }

  delay(2000);                       // DHT11 要求的间隔
}

注意那个 delay(2000) 带来的一个后果:报警也是两秒才查一次。 这就有点可惜了——明明热敏是"快"的那个。

想让它真的快起来,就得把 delay(2000) 拆掉,改成用 millis() 判断"距上次读 DHT11 够两秒了吗",中间那段时间只读热敏、只查报警

这就是第 32 课那个 millis() 的用法,一模一样。你已经有这个工具了,试着改改看。 改不出来也没关系——这属于"锦上添花",不改也能交作业。

八、组装:纸皮表盘

电路齐了,现在把它变成桌上能立住的一台"仪表"。

目标:一块竖着的表盘,屏幕嵌在中间,周围画着刻度,背后有个撑脚。

要剪的纸皮:面板(约 12 cm × 10 cm,开 OLED 的窗、开两个探头的窗)、背板(同样大小)、撑脚(一条 10 cm × 3 cm 的纸皮,折成 L 形),外加几条纸皮条做加固。

① 开屏幕的窗。 跟第 32 课一样——OLED 背面朝上,沿着玻璃屏的边缘描一圈,不是描整块板。垫上废纸皮,美工刀慢慢割。

② 给两个传感器开窗,这一步千万别偷懒。

在面板上另找两个位置(离屏幕远一点,别挤在一起),各开一个小方窗,让热敏的玻璃珠DHT11 的白色格栅能露在纸皮外面。

为什么必须露出来? 因为这两个东西测的是"空气的温度"。

  • 热敏要接触空气,才能感受到温度变化
  • DHT11 那个格栅更要露出来——它测湿度靠的就是让空气从格栅进去。捂在纸皮里面,它读出来的湿度就是那一小格死空气的湿度,完全没意义。

③ 画刻度——这一段是这个作品的"脸"。

在面板上、屏幕周围画一条半圆弧。不用买圆规:

拿一根线,一头用图钉按在圆心(屏幕下方的中间位置),另一头绑一支铅笔,把线拉直了转一圈,就是一个正圆。 想画大点就放长线。

在圆弧上每隔一段画一道短线,标上 0 10 20 30 40

然后做一件让刻度"活起来"的事:把你标定出来的报警阈值(比如 30 度)那一道线,用红笔画粗、画长,旁边写上"到这儿报警"。

这样刻度就不是装饰了——它告诉你屏幕上那个数字落在哪儿、离报警还有多远。

④ 把模块粘上去。 热敏和 DHT11 从背面对准窗口,热熔胶打在板子边缘——格栅旁边千万别打胶,一格栅被胶糊住,湿度就废了。

⑤ 屏幕固定。 热熔胶点在 OLED 板子四个角上,别碰玻璃。

⑥ 装撑脚。 撑脚纸皮折成 L 形,短的那边粘在背板下方,长的那边朝后撑住桌子。先干着试一下角度,屏幕仰起来 60 度左右(比第 32 课的游戏机更立一点,因为你是平视看它的),看着舒服了再粘。

⑦ 面板和背板合起来。 用一条纸皮当"合页"粘在顶部,做成能翻开的。留个后路——以后要改线,翻开就行,不用撕。

⑧ 蜂鸣器的声音别堵死。 蜂鸣器顶上有个小发声孔,粘的时候让它朝外、别被纸皮闷住,不然声音会闷得像隔着被子。

九、排查:DHT11 一直读不出来

这是这个课题里我给你留的坑。

做完之后,你会遇到这么个现象:

屏幕上 NTC 那行数字正常在动,热敏一点问题没有。 DHT11 那行要么是 nan,要么是 0.0,要么干脆不更新。

你换了一次 delay 的数值、把库重装了一遍、把代码对着教程看了一遍——还是不行。

别急。按第 5 课那套顺序来:电 → 信号 → 代码。这次我们用电表把 DHT11 的三根针挨个量一遍。

第 0 步:先看现象分个类。 串口里是 nan 还是 0.0

  • nan 多半是"读不到/催太急/线不对"——往下查
  • 一个好端端的数,只是不准——那不是故障,是它的精度(上一节讲过)

第 1 步:量供电。 直流电压档,黑表笔搭板子的 GND(记住是搭板子的地,不是搭 DHT11 的针),红表笔依次点 DHT11 的三个针

红表笔点哪个针 正常的读数
+ 3.3V 左右
0V
OUT 3.3V 左右(空闲时线被拉着高)

现在看你的表。如果你量到的是下面这样:

红表笔点哪个针 你量到的
+ 0V
3.3V

那就是它了——+ 这两根线插反了。

第 2 步:再确认一次。 把红表笔点在 + 上、黑表笔点在 上——如果读数是负的(数字前面带个减号,比如 -3.3),那就更加确定了:电流的方向是反的。

第 3 步:顺手把 OUT 也量了。 如果供电正常(+=3.3V、=0V),那就用通断档(先断电)量一下 OUT 到板子上 GPIO4 那个孔通不通——排除"信号线插错孔"(第 32 课那个坑)。

根因

DHT11 的 + 插反了。

为什么这么容易插反? 因为这块板子不印 VCCGND,它印的是 +——两个小符号,一左一右紧挨着,插线的时候手一抖就换了位置。 而它偏偏不会烧(第 16 课说过:接反了不会立刻烧,但读数一定是错的),所以什么味道、什么烟都没有,只有一行 nan 在那儿等着你。

从现象上你永远猜不到是插反了——nan 的原因太多了,"线插反"只是其中一个。但电压档一量,正负号是不会骗人的。

这个坑是我故意留的,可它也完全是真实情况——丝印不统一,是硬件这行最常见的陷阱(第 4 课那句"丝印是厂家随便印的,功能是芯片定死的",今天又应验了一次)。

这里给你留一条更通用的经验:以后任何模块"完全没反应",第一步永远是量它的电源针。 而量电源针的时候,要红笔点正、黑笔点负——如果你习惯性地"黑笔搭 GND、红笔到处点",那你就只能看到"0V 和 3.3V 换了个位置",得多想一步才能反应过来。两支表笔按极性去点,正负号会直接告诉你答案。

自测

单选题用手指捏住热敏的玻璃珠,为什么 DHT11 的反应会慢那么多?
单选题屏幕上 DHT11 报 26.5°C、热敏算出来 29.1°C,你该怎么办?
单选题这个课题里,为什么让热敏负责报警、而不是让更准的 DHT11 负责?
问答题DHT11 读出来一直是 nan。你会怎么用万用表把问题定位出来?
拓展DHT11 为什么非得"慢"?有没有又准又快的温度传感器?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"SHT30 SHT31 精度 响应时间"、"铂电阻 PT100 原理"、"热电偶 响应速度"。想一想:一个传感器"快"和"准"为什么常常不能兼得?如果让你在"每秒测十次但差 3 度"和"每两秒测一次但差 0.2 度"里挑一个,你会怎么选?

拓展我们的两点线性拟合只用了两个点,那"查表法"到底是怎么做的,为什么它更准?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"分段线性插值 查表法 传感器标定"、"NTC B 值公式 计算"。想一想:如果我每隔 2 度就测一个点,然后每两个点之间用直线连,那这条"折线"是不是就非常接近真实曲线了?代价是什么?

本节你要动手做的事

  • [ ] 接好四个模块:热敏 AOGPIO0、DHT11 OUTGPIO4、蜂鸣器→GPIO5、OLED→GPIO8/GPIO9全部共地
  • [ ] 两个传感器先各自跑通,串口里能看到两列数每两秒刷一次
  • [ ] 捏住热敏,盯着串口看那十几秒——亲眼看"DHT11 一动不动"和"热敏已经动了"
  • [ ] 标定热敏:用 DHT11 当参照,记下"25 度时读数多少""33 度时读数多少"
  • [ ] 把两点法算出来的温度写到屏幕上,和 DHT11 那行摆在一起
  • [ ] 标定报警阈值,让蜂鸣器"热了就响"——阈值用你自己测出来的
  • [ ] 做纸皮表盘:开屏幕的窗、开两个探头的窗(格栅必须露在空气里)、画圆弧刻度、画出那条红色的报警线
  • [ ] 如果 DHT11 读不出来,按第九节那三步量一遍。你量到 + 的电压了吗?

这个课题看起来在做一台温度计,其实在做一件更基础的事:给你两个信息源,让你决定信谁。

这件事你以后会一直遇到。 一个传感器说 A,另一个说 B,它们都不完美——工程师干的活,很多时候不是"找到那个对的",而是"搞清楚每个的脾气,然后安排它们各自干最擅长的那一段"。

你今天给热敏和 DHT11 分了一次工,这就是这件事最小的一次练习。

说句实在的:这一课你真正花时间的,多半不是写代码,是标定——捏着那颗玻璃珠等它稳、拿笔在纸上记两个数、翻来覆去试那条阈值该画哪儿。

这部分确实枯燥,但它就是硬件工程的日常。 现实中的产品,参数都不是从教程上抄来的,是一次次测出来、调出来的。你愿意花这个时间,你就跟"照着视频抄一遍"的人不是一回事了。

下一课我们做第 34 课:联网天气站——这节课你的温度计只知道自己桌上几度,下一课它要知道全世界的天气。

而且下一课有点不一样:前面所有作品的板子都只在桌上自娱自乐,从下一课起,它读的是真实世界的数据。