实战课题
第 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 课的老规矩:先让每个零件单独出个现象。
#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 值公式、两点线性拟合),我们走最省事的那条:两点法。
做法只有三步:
- 看 DHT11 显示多少度(比如 25 度),同时记下热敏的读数(比如 1800)
- 捏住热敏,等它稳住,再看 DHT11 多少度(比如 33 度),记下热敏的读数(比如 1300)
- 在这两个点之间,用一条直线硬连起来
// ★ 下面这两个数是我编的,你必须换成自己测出来的!
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 那一列。 热敏那个数的作用不是"报温度",是"反应快"——这一点下一节就要用上。
六、动手第三步:让两个数同时上屏幕
#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 的规格是
0~50°C ±2°C——它报 26 度,真实温度可能在 24 到 28 之间任何地方。差个一两度是它的正常表现,不是故障。(第 16 课那段"别拿它跟家里的温度计比对",说的就是这个。)热敏那边更粗:元件个体差异 + 分压电阻误差 + ADC 偏差,三层误差叠一起,能准到正负两三度就算你运气好。
所以这两个数差三四度,是正常的。 你要看的不是"哪个准",是"它俩一会儿差多少、什么时候开始一起动"。
七、动手第四步:分工——谁报警,谁报数
现在到了这一课最关键的设计。两个传感器,报警该听谁的?
先想一个问题:DHT11 要两秒才更新一次。这两秒里,板子是"瞎"的。
如果屋里温度突然窜上去,报警晚两秒——对"报警"这件事来说,两秒挺要命的。
所以分工这样定:
| 负责什么 | 为什么 | |
|---|---|---|
| DHT11 | 报数——屏幕上那个准确的温度 | 它准,规格写明白 |
| 热敏 | 报警——超过你画的那条线就响 | 它快,每一圈都能读 |
这不是我瞎定的,是第 12 课那句话的直接应用——当时我说:"热敏的价值是事件,不是数值。它适合回答'变热了没有'。"
现在你就是在用它回答这个问题。
先标定:那条线画在哪儿
别抄我的数。 按第 11、12 课那套流程:
- 先空跑一两分钟,看热敏读数平时在什么范围飘
- 用手捏住玻璃珠,看它能掉到多少(或者升到多少,看你的方向)
- 在这两个数中间取一个,偏向"变热"那一侧一点,留点余量
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 的 + 和 − 插反了。
为什么这么容易插反? 因为这块板子不印 VCC 和 GND,它印的是 + 和 −——两个小符号,一左一右紧挨着,插线的时候手一抖就换了位置。 而它偏偏不会烧(第 16 课说过:接反了不会立刻烧,但读数一定是错的),所以什么味道、什么烟都没有,只有一行 nan 在那儿等着你。
从现象上你永远猜不到是插反了——nan 的原因太多了,"线插反"只是其中一个。但电压档一量,正负号是不会骗人的。
这个坑是我故意留的,可它也完全是真实情况——丝印不统一,是硬件这行最常见的陷阱(第 4 课那句"丝印是厂家随便印的,功能是芯片定死的",今天又应验了一次)。
这里给你留一条更通用的经验:以后任何模块"完全没反应",第一步永远是量它的电源针。 而量电源针的时候,要红笔点正、黑笔点负——如果你习惯性地"黑笔搭 GND、红笔到处点",那你就只能看到"0V 和 3.3V 换了个位置",得多想一步才能反应过来。两支表笔按极性去点,正负号会直接告诉你答案。
自测
答案与解析
热敏是模拟器件——你什么时候 analogRead,它就什么时候把当前电压交出来,所以你每圈都能看到它在变。DHT11 是数字器件,得走一套单总线协议、内部还要做转换,手册给的采样间隔是至少 1 秒(第 16 课讲的),代码里我们干脆写 2 秒。这不是故障,是它的工作方式。
答案与解析
DHT11 的规格是 0~50°C ±2°C(第 16 课),报 26.5 真实值可能在 24.5 到 28.5 之间。热敏更粗——元件差异、分压电阻误差、ADC 偏差三层叠加,能准到正负两三度就算运气好(第 12 课)。这两个数差几度,不但正常,而且是你这一课要观察的现象本身。 想对齐它们,等于用一个不准的尺子去改另一把不准的尺子。
答案与解析
报警这件事的关键是"及时",不是"精确"。 DHT11 要等转换、要等采样间隔,屋里温度突然窜上去,它可能两秒后才反应过来;热敏是模拟器件,loop 转一圈就能读一次。这正是第 12 课那句话的应用——热敏的价值是"事件"(变热了没有),不是"数值"(现在几度)。让快的去报警,让准的去报数,这叫分工。
nan。你会怎么用万用表把问题定位出来?答题要点
答题要点应该包含:① 先分类——nan 是"读不到/催太急/线不对",跟"数字不太准"是两码事(后者是精度,不是故障);② 量供电:黑表笔搭板子的 GND,红表笔依次点 DHT11 的 +、−、OUT,正常应该是 +=3.3V、−=0V、OUT≈3.3V;③ 如果量出来 + 是 0V、− 是 3.3V,就是这两根插反了;④ 再用红笔点 +、黑笔点 −,读数是负的(比如 -3.3)就更加确认——电流方向反了;⑤ 供电正常的话,断电用通断档量 OUT 到 GPIO4 通不通,排除信号线插错孔。根因就是 DHT11 的丝印是 + / − 而不是 VCC / GND,两根针挨着,容易插反,而且接反不会烧、什么现象都没有,只有一行 nan。
拓展DHT11 为什么非得"慢"?有没有又准又快的温度传感器?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"SHT30 SHT31 精度 响应时间"、"铂电阻 PT100 原理"、"热电偶 响应速度"。想一想:一个传感器"快"和"准"为什么常常不能兼得?如果让你在"每秒测十次但差 3 度"和"每两秒测一次但差 0.2 度"里挑一个,你会怎么选?
拓展我们的两点线性拟合只用了两个点,那"查表法"到底是怎么做的,为什么它更准?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"分段线性插值 查表法 传感器标定"、"NTC B 值公式 计算"。想一想:如果我每隔 2 度就测一个点,然后每两个点之间用直线连,那这条"折线"是不是就非常接近真实曲线了?代价是什么?
本节你要动手做的事
- [ ] 接好四个模块:热敏
AO→GPIO0、DHT11OUT→GPIO4、蜂鸣器→GPIO5、OLED→GPIO8/GPIO9,全部共地 - [ ] 两个传感器先各自跑通,串口里能看到两列数每两秒刷一次
- [ ] 捏住热敏,盯着串口看那十几秒——亲眼看"DHT11 一动不动"和"热敏已经动了"
- [ ] 标定热敏:用 DHT11 当参照,记下"25 度时读数多少""33 度时读数多少"
- [ ] 把两点法算出来的温度写到屏幕上,和 DHT11 那行摆在一起
- [ ] 标定报警阈值,让蜂鸣器"热了就响"——阈值用你自己测出来的
- [ ] 做纸皮表盘:开屏幕的窗、开两个探头的窗(格栅必须露在空气里)、画圆弧刻度、画出那条红色的报警线
- [ ] 如果 DHT11 读不出来,按第九节那三步量一遍。你量到
+和−的电压了吗?
这个课题看起来在做一台温度计,其实在做一件更基础的事:给你两个信息源,让你决定信谁。
这件事你以后会一直遇到。 一个传感器说 A,另一个说 B,它们都不完美——工程师干的活,很多时候不是"找到那个对的",而是"搞清楚每个的脾气,然后安排它们各自干最擅长的那一段"。
你今天给热敏和 DHT11 分了一次工,这就是这件事最小的一次练习。
说句实在的:这一课你真正花时间的,多半不是写代码,是标定——捏着那颗玻璃珠等它稳、拿笔在纸上记两个数、翻来覆去试那条阈值该画哪儿。
这部分确实枯燥,但它就是硬件工程的日常。 现实中的产品,参数都不是从教程上抄来的,是一次次测出来、调出来的。你愿意花这个时间,你就跟"照着视频抄一遍"的人不是一回事了。
下一课我们做第 34 课:联网天气站——这节课你的温度计只知道自己桌上几度,下一课它要知道全世界的天气。
而且下一课有点不一样:前面所有作品的板子都只在桌上自娱自乐,从下一课起,它读的是真实世界的数据。