一支防水 DS18B20 只靠 Arduino 的一支脚就能读出水温:红线接 5 V、黑线接 GND、黄线接 D2,再用一颗 4.7 kΩ 电阻,从那条黄线拉到 5 V。这颗电阻不是可有可无的讲究:在 1-Wire 总线上,它是唯一能把信号线拉高的东西,所以少了它,探头根本不会应答 —— 扫描总线时,一支都找不到。
用 Arduino 监测鱼缸水温,需要哪些零件?
DS18B20 防水探头是一颗直径大约 6 mm 的不锈钢金属胶囊,后面接着一条一米长的黑色线,末端是三根裸露的多股线 —— 红、黄、黑。那一米就是这个作品的边界:它够从水里拉到鱼缸旁边一处干燥的地方,再远就不行了。清单上其余的东西,只是给它一块主板、一个显示屏,还有那一颗电阻。
材料清单 —— DS18B20 鱼缸水温监测器(OLED 显示)
选配 —— 第二支探头,以及一条 USB 线(如果你还缺)
第二支 DS18B20 和第一支共用同一支脚、同一颗上拉电阻 —— 想同时看进水口和出水口,或者一块板管两个缸,要的就是这个。USB A 转 B 线是 Uno 那个方形大插座用的线,抽屉里没有这种形状的话就加一条。
接头的形状决定这些零件到底接不接得上。0.96 吋白色 OLED 出厂就焊好了四针公排针,丝印是 GND、VCC、SCL、SDA —— 地在最前面,请对着板子读,别凭印象,因为它的邻居就是 5 V。这排针插进400 孔面包板,再由公对公杜邦线把每一行接到 Uno 的母座上。探头则完全没有接头:三根裸线直接插进面包板的孔里。
DS18B20 为什么非要一颗 4.7 kΩ 上拉电阻不可?
DS18B20 的数据脚是开漏结构:探头内部的 DQ 接着一颗晶体管,它只能把信号线往下拉到地,没有任何东西能把线往上推。Arduino 的引脚在说话的时候也是同一副样子。所以在 1-Wire 总线上,每一个成员都造得出低电平,却没有一个造得出高电平 —— 那颗拉到 5 V 的电阻,就是这条线唯一升得上去的机制。少了它,线就只是浮空:没有空闲高电平,没有复位脉冲,也就没有回应。
DS18B20 的数据手册把这件事变成了一个死线。要发出一个 1,主机先把线拉低,再放开,而线必须在时隙开始后 15 µs 之内回到有效的高电平。电阻和总线电容组成一条 RC 充电曲线,所以这条死线其实是一份电阻预算。一米探头线加上一行面包板,量级大约是 100 pF:4.7 kΩ × 100 pF 是 0.47 µs 的时间常数,线大约一微秒就恢复了 —— 比那道窗口早了十五倍。换成 10 kΩ 则翻倍到大约两微秒,这也正是为什么「一支探头、一米线」用 10 kΩ 确实跑得起来。可是一旦探头和线长加上去,电容就跟着爬升;到了 1.5 nF 附近,10 kΩ 那条曲线在 15 µs 死线到期时才走了三分之二,位就开始掉,而 4.7 kΩ 仍然留着余量。
所以采购上的答案是:整条总线只要一颗 4.7 kΩ、1/4 W 的电阻 —— 一颗,不是一支探头配一颗,因为几支探头是并在同一条线上的。它没有极性,正反随便插。如果你是从混装包里翻,4.7 kΩ 在蓝色五色环金属膜电阻上是黄-紫-黑-棕(470 × 10),在比较老的四色环碳膜电阻上是黄-紫-红。两颗 10 kΩ 并在同一对孔位上,等于 5 kΩ。

DS18B20 要怎么接到 Arduino Uno?
DS18B20 的三根线是细多股线,末端镀了锡而且散开,面包板的孔却是为硬针脚做的。每一根线在靠近板子之前,先用手指捻紧 —— 一根散出去的铜丝搭到隔壁那一行,就是把 5 V 送上了数据线。家里有烙铁的话,在捻好的线头上点一滴锡,是更牢靠的做法。
| 线 | 接到 | 为什么 |
|---|---|---|
| 探头红线 | Uno 5V |
VDD —— 正常供电,芯片就不必去数据线上抢电流 |
| 探头黑线 | Uno GND |
总线的地参考 |
| 探头黄线 | Uno D2 |
DQ,也就是 1-Wire 数据线 —— 只要在代码里改掉,除了 0 和 1 以外任何数字脚都行,那两支要留给串口监视器 |
| 4.7 kΩ 电阻 | 黄线那一行 → 5V |
上拉电阻。不管接几支探头,整条总线只要一颗 |
OLED GND |
Uno GND |
排针上的第一支 —— 插之前先看丝印 |
OLED VCC |
Uno 5V |
显示屏供电,和探头共用同一条电源轨 |
OLED SCL |
Uno A5 |
I²C 时钟 —— 在 Uno 上是固定脚位,不能随便换 |
OLED SDA |
Uno A4 |
I²C 数据 |
先给面包板送电:一条杜邦线从 Uno 的 5V 母座接到面包板的红线轨,一条从 GND 接到蓝线轨。上面那张表里有三行都要 5 V,而 Uno 只有一个 5V 母座 —— 所以所有东西都留在那两条杜邦线所在的半块板上。接下来,一列五个孔就是整条总线:黄线、4.7 kΩ 电阻的一只脚,还有通往 D2 的那条杜邦线,全部落在这一列上,而电阻的另一只脚够得到 5 V 轨。
密封的只有那颗钢制胶囊和它的线,Uno、面包板和显示屏都不防水,所以请把它们放在水面以上的干燥架子上,只让线从缸沿垂下去。胶囊要吊在开阔的水域里,避开加热棒的出水口:贴着加热棒放,量到的是加热棒,不是这缸水。不锈钢在海水里也会起锈斑 —— 清洗海水缸的时候,顺手检查一下胶囊。这里所有东西都是 5 V 直流;鱼缸加热棒是市电电器,这块面包板上没有任何东西应该被接去控制它。

DS18B20 的 Arduino 代码要用哪个库 —— OneWire 还是 DallasTemperature?
DS18B20 需要两个库,各做各的活。Paul Stoffregen 的 OneWire 用软件模拟出总线要求的微秒级时序;Miles Burton 的 DallasTemperature 架在它上面,把那些时序变成看得懂的指令。两个都从库管理器装,另外再装 Adafruit SSD1306 和 GFX 这一对 —— 我们那篇 DHT11 空气温湿度监测器用的也是它们。
/*
DS18B20 防水探头 -> 0.96" OLED 水温监测器
Arduino Uno + OneWire + DallasTemperature + Adafruit SSD1306
黄色 DATA 线和 5V 之间要接一颗 4.7k 电阻。不管这支脚上挂了
几支探头,整条总线都只要这一颗。
*/
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const uint8_t ONE_WIRE_PIN = 2; // 探头的黄线接在这里
const uint8_t MAX_PROBES = 2; // 要加更多探头就把这个数字调大
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
OneWire oneWire(ONE_WIRE_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress probe[MAX_PROBES];
uint8_t found = 0;
void printRom(const uint8_t *addr) {
for (uint8_t i = 0; i < 8; i++) {
if (addr[i] < 0x10) Serial.print('0');
Serial.print(addr[i], HEX);
}
}
void scanBus() {
sensors.begin(); // 从头重跑一次 1-Wire 搜索
uint8_t onBus = sensors.getDeviceCount();
found = (onBus < MAX_PROBES) ? onBus : MAX_PROBES;
Serial.print(F("Probes on the bus: "));
Serial.println(onBus);
for (uint8_t i = 0; i < found; i++) {
if (sensors.getAddress(probe[i], i)) {
Serial.print(F(" index "));
Serial.print(i);
Serial.print(F(" ROM "));
printRom(probe[i]); // 把它抄下来,在那条线上贴个标签
Serial.println();
sensors.setResolution(probe[i], 12); // 0.0625 C 一档,转换 750 ms
}
}
}
void setup() {
Serial.begin(9600);
// 多数 0.96" 模块的地址是 0x3C,少数被设成 0x3D。
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("OLED not answering - check SDA/SCL and 5V"));
for (;;) { }
}
oled.setTextColor(SSD1306_WHITE);
scanBus();
// 这里印出 "YES",表示有探头把 VDD 接到了 GND,正在从数据线
// 上偷电 —— 读数卡在 85.00 通常就是这个原因。
Serial.print(F("Parasite power: "));
Serial.println(sensors.isParasitePowerMode() ? F("YES") : F("no"));
}
void loop() {
if (found == 0) { // 少了上拉电阻,长的是这个样子,不是 -127
oled.clearDisplay();
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.print(F("NO PROBE FOUND"));
oled.setCursor(0, 16);
oled.print(F("CHECK 4.7k -> 5V"));
oled.setCursor(0, 28);
oled.print(F("AND YELLOW ON D2"));
oled.display();
scanBus(); // 一直找下去,所以线接好了就会自己恢复
delay(1000);
return;
}
sensors.requestTemperatures(); // 一直等到探头说它转换完了(最长 750 ms)
oled.clearDisplay();
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.print(F("WATER TEMP"));
for (uint8_t i = 0; i < found; i++) {
float c = sensors.getTempC(probe[i]);
oled.setTextSize(2);
oled.setCursor(0, 18 + i * 24);
if (c <= DEVICE_DISCONNECTED_C) { // -127:没有人应答
oled.print(F("--.- C"));
Serial.println(F("-127 : no reply - check the 4.7k and the wiring"));
} else {
oled.print(c, 1);
oled.print(F(" C"));
Serial.print(i);
Serial.print(F(" : "));
Serial.println(c, 2);
if (c > 84.9 && c < 85.1) { // 上电后暂存器的默认值
Serial.println(F(" 85.00 : read before conversion, or starved supply"));
}
}
}
oled.display();
delay(1000);
}
这份代码在 Uno 上编译出来占 19,620 字节的 Flash —— 32 KB 的 60% —— 静态 RAM 用掉 595 字节;接着显示库在 begin() 执行时还会再要一块 1,024 字节的帧缓冲,所以在你自己的变量出场之前,Uno 那 2 KB 里已经有大约 1.6 KB 名花有主。这也是为什么这里每一条固定字符串都包在 F() 里:不包的话,它们会被复制到剩下的那点空间里,而不是留在 Flash。requestTemperatures() 会一直阻塞到探头回报转换完成 —— DallasTemperature 库是去轮询总线,而不是固定睡一段时间,所以这段等待是被数据手册里 12 位下 750 ms 的上限「封顶」,而不是每次都等满;改成 9 位可以把上限压到 93.75 ms,代价是步进变粗到 0.5 °C。
如果你已经在跑我们那台 TDS 水质检测仪,这个读数还有第二份工作。电导率大约每上升 1 °C 就变化 2%,而那个作品是按 25 °C 假设的,所以水温偏低时它读出来会偏小。给它一支独立脚上的 DS18B20,就能把一个真实数字交给那份代码 —— 同一块 Uno、多一支探头,假设就变成了测量。
几支 DS18B20 要怎么共用 Arduino 的同一支脚?
每一支 DS18B20 出厂时都被激光刻上了一组 64 位 ROM 码:8 位家族码(这颗零件是 28h)、48 位属于这一颗晶片的序列号,再加上覆盖前七个字节的 8 位 CRC。没有两支探头的号码一样,所以主机可以在同一条线上一次点名一支。它是靠一位一位地走遍总线来找到它们的 —— 遇到两支探头答案不同的位,就先选一条分支、把那个地址走完,再回头处理另一条 —— 上面那份代码里的 getAddress() 读出来的,就是这些地址。
实际做法是:两根黄线插进面包板上同一列,两根红线接红轨,两根黑线接蓝轨,那颗 4.7 kΩ 电阻原地不动 —— 再加第二颗只会把上拉阻值减半,一点好处也没有。把串口监视器调到 9600 波特再跑这份代码,每一组 ROM 码都会在启动时打印一次。想知道哪支是哪支,就用手捧着其中一支泡进温水,看哪个索引在动,然后在那条线上缠一小片胶带做记号。
几支探头怎么落在同一条电源轨上,看一遍比讲一遍清楚:
DS18B20 出现 −127 和 85.00,是什么意思?
DS18B20 有两个读数其实是消息,不是温度 —— 另外还有一个消息,压根就不会变成读数。
| 读数 | 它真正的意思 | 该往哪里查 |
|---|---|---|
Probes on the bus: 0 |
搜索跑完了,没有任何东西应答 —— 从来没有设备把线拉低来报到 | 先查上拉电阻:没有它,线就无法空闲在高电平,复位脉冲自然得不到回应。接着查黄线那一列,再查代码里声明的引脚号 |
−127.00 |
DEVICE_DISCONNECTED_C —— 主机发了复位脉冲,没有设备应答 |
某支启动时找得到、后来不见了的探头:线头松了、某根铜丝时接时断,或者线被缸盖压住了 |
85.00 |
温度寄存器上电后的默认值,还没被写过 | 在转换完成之前就去读了,或者供电在转换途中掉压 |
| 数字合理但不动 | 一个真实数值,只是永远不更新 | requestTemperatures() 写在了循环外面,所以读的永远是同一份暂存器 |
那个 +85 °C 是温度寄存器本身的上电复位值,所以一颗已经上电、但转换还没做完的芯片,会把它交给任何一次读取。它同时也指向供电不足,而这就牵出寄生供电:DS18B20 可以把 VDD 接到地,在信号线空闲为高时从线上取电。我们这支探头有三根线,所以把红线和 5 V 给 VDD:这就是正常供电,也是这里用的模式。寄生模式反过来要求主机在整个转换期间把线死死钳在高电平,因为 4.7 kΩ 的电阻供不出芯片转换时需要的约 1.5 mA。代码里如果看到这种状态就会打印 Parasite power: YES,意思是那根红线没做到它该做的事。
我们从真实客户那里常看到的错误
买了探头,忘了电阻。有位客户来问我们:“For the resistor: do you have a 4.7k or 10k 1/4W resistor available? I need about 10 pcs for my DS18B20 pull-up.” 阻值他记对了,数量则买得很大方:一条总线只要一颗。探头出货就是探头本身 —— 没有转接板,也没有电阻。
以为入门套件里已经有了。另一位客户问一套零件里包不包含 “220 Ohm, 1K Ohm, 4.7K Ohm and 10K Ohm”。手上已经有的,请照上面那组色环去读,别靠猜;如果是要买而不是要翻,就直接买这个阻值 —— 一颗 4.7 kΩ 1/4 W,整条总线就搞定了。
以为显示屏会自己亮。探头明明在跑、OLED 却一片空白,几乎都是 I²C 地址的问题:多数 0.96 吋模块的地址是 0x3C,少数是 0x3D,而 File → Examples → Wire → i2c_scanner 会把 A4/A5 上真正存在的地址印出来,于是你是知道答案之后再去改 oled.begin() 里那一个数字,而不是靠试。这个习惯,我们那篇 LCD1602 I²C 教程也一样在教。
常见问题
DS18B20 真的一定要接电阻吗?
要 —— 数据线和 5 V 之间接一颗 4.7 kΩ 1/4 W 电阻,整条总线一颗就够。探头和 Arduino 都只能把线拉低,所以这颗电阻是唯一能把线抬起来的元件;没有它,总线永远空闲不到高电平,扫描时一支探头也找不到。
我的 DS18B20 为什么读到 −127?
−127 是库里的 DEVICE_DISCONNECTED_C 哨兵值:复位脉冲没有得到回应。如果探头从头到尾就没被找到,代码在启动时会报设备数为零,那问题是上拉电阻没接、电阻插错了列,或者引脚选错。真正的 −127 表示它启动时应答过、后来才停 —— 通常是线头松了,或者某根铜丝只是勉强碰着。
可以用 10 kΩ 电阻代替 4.7 kΩ 吗?
如果只有一支探头、用的是标配那一米线,可以:面对 15 µs 的死线,线大约两微秒就回到有效高电平。但探头和线长一多,电容上去,这点余量就缩水,所以该守住的值还是 4.7 kΩ。两颗 10 kΩ 并联等于 5 kΩ。
一支 Arduino 引脚可以接几支 DS18B20?
够任何一套鱼缸用了。每一支都带着独一无二的 64 位 ROM 地址,所以它们共用一支脚、共用一颗上拉电阻。真正的上限是电学上的,而不是库里写死的数字:线材总电容累积到一定程度,上升沿就会被磨钝。
DS18B20 用来量鱼缸,精度够吗?
绰绰有余。它的标称精度是 −10 °C 到 +85 °C 之间 ±0.5 °C,12 位下的分辨率是 0.0625 °C 一档,所以加热棒出问题,会在鱼察觉之前很久就先显示出来。
最后更新:2026 年 8 月。有问题?WhatsApp 联系我们。



Temperature Sensor DS18B20 Temperature Measurement Probe WaterProof 18B20
0.96‘ OLED Display Module Blue White Screen I2C IIC Serial 128X64 LCD Display Board - 0.96' OLED WHITE
Arduino Uno Compatible SMD UNO R3 with Type B Cable - ATMEGA328P with CH340G-Microcontroller Project
MB102 Breadboard 170 400 830 Holes Breadboard Donut Board Arduino Prototype Multi Color - BREADBOARD (400 HOLES)
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-M 20CM
400 pcs 1/4W Resistor Pack Resistor Kit 20 Common Value with 20 each
Data Cable Type-A Type-C MicroUSB Type-B 0.5m 1m 30cm 0.3m 100cm Data Transfer Upload Code - TYPE-A TO TYPE-B (1.0M)