DIY ELECTRONICSTR

← All projects

Waterproof Thermometer (Aquarium / Tea)

Beginner30 min

A thermometer that measures water temperature with a waterproof DS18B20 probe and shows it on a 4-digit display as "24°C". When the temperature leaves the allowed range the buzzer sounds and the display blinks: three fast, high beeps when too hot, a slow, low beep when too cold, so you can tell which by ear alone. It comes set to 23-28 °C for an aquarium; for tea, set the lower limit to 60 °C and it beeps once the tea has cooled enough to drink. It teaches 1-Wire communication and the pull-up resistor, not making the program wait while a reading is taken, and leaving a margin so the alarm does not flicker at the limit. The DallasTemperature and TM1637 libraries are installed from the Library Manager.

Build it step by step in the app

What you need

Wiring steps

  1. 1

    Connect and join the power rails

    Connect and join the power rails

    Connect one of the GND pins on the Arduino's power header to the breadboard's bottom blue (−) rail with a black male-male wire, and the 5V pin on the same header to the bottom red (+) rail with a red wire. The breadboard's top and bottom rails are not connected to each other: at the right end, join the bottom (+) rail to the top (+) rail with a red wire and the bottom (−) rail to the top (−) rail with a black wire. The probe is powered from the bottom rails, the display and the buzzer from the top ones.

    • Arduino GND → the bottom blue (−) rail (black wire)
    • Arduino 5V → the bottom red (+) rail (red wire)
    • the bottom red (+) rail → the top red (+) rail (red wire)
    • the bottom blue (−) rail → the top blue (−) rail (black wire)
  2. 2

    Connect the display

    Connect the display

    Push the female ends of male-female wires onto the display module's four pins; their names are printed on the board: CLK, DIO, VCC, GND. Connect CLK to the Arduino's pin 7 and DIO to pin 6 (DISPLAY_CLK, DISPLAY_DIO). Connect VCC to the top (+) rail and GND to the top (−) rail. Some modules have their pins in a different order, so go by the names printed next to the pins, not by their order.

    • Arduino pin 7 → display CLK (green wire)
    • Arduino pin 6 → display DIO (blue wire)
    • display VCC → the top red (+) rail (red wire)
    • display GND → the top blue (−) rail (black wire)
  3. 3

    Add and wire the buzzer

    Add and wire the buzzer

    Plug the passive buzzer's + leg into column 21 in the top half and its other leg into column 22; the + leg is usually the longer one and the body has a + mark. Connect the Arduino's pin 5 to column 21 with a purple wire (BUZZER_PIN 5). Connect column 22 to the top (−) rail with a short black wire.

    • buzzer + leg → column 21, top half
    • buzzer − leg → column 22, top half
    • Arduino pin 5 → column 21, top half (purple wire)
    • column 22, top half → the top blue (−) rail (black wire)
  4. 4

    Plug in and wire the temperature probe

    Plug in and wire the temperature probe

    Plug the three wires at the end of the probe's cable into row E of the breadboard's bottom half: red (VCC) in column 52, yellow (DATA) in 53 and black (GND) in 54. Wire colours vary by maker; check the probe's label or the seller's page (on some, DATA is white or blue). If the wires are soft, twist their ends firm with your fingers or solder a male pin to each. Connect column 52 to the bottom (+) rail with a short red wire and column 54 to the bottom (−) rail with a short black wire. Connect the Arduino's A0 pin to column 53 with a yellow wire (PROBE_PIN A0): analog pins work as digital pins too. Only the probe and its cable are waterproof; keep the breadboard and the cable's end away from water.

    • temperature probe red wire (VCC) → column 52, bottom half
    • temperature probe yellow wire (DATA) → column 53, bottom half
    • temperature probe black wire (GND) → column 54, bottom half
    • column 52, bottom half → the bottom red (+) rail (red wire)
    • column 54, bottom half → the bottom blue (−) rail (black wire)
    • Arduino A0 → column 53, bottom half (yellow wire)
  5. 5

    Add the 4.7 kΩ pull-up resistor

    Add the 4.7 kΩ pull-up resistor

    Lay the 4.7 kΩ resistor (yellow - violet - red - gold) flat in row C of the bottom half: one end in column 53 (the probe's DATA), the other in column 57. Connect column 57 to the bottom (+) rail with a short red wire, so the resistor sits between the DATA line and 5V. While the 1-Wire line is idle this resistor holds it at 5V; without it the probe is not found at all, or the Serial Monitor reads -127.

    • 4.7 kΩ resistor → columns 53 and 57, bottom half
    • column 57, bottom half → the bottom red (+) rail (red wire)

Arduino sketch

// Install from the Library Manager:
// "DallasTemperature" (Miles Burton;
// it brings OneWire too) and "TM1637"
// (Avishay Orpaz)
#include <OneWire.h>
#include <DallasTemperature.h>
#include <TM1637Display.h>

// The probe's yellow (DATA) wire: analog
// pins work as digital pins too
const int PROBE_PIN = A0;
// The display module's CLK and DIO pins
const int DISPLAY_CLK = 7;
const int DISPLAY_DIO = 6;
const int BUZZER_PIN = 5;

// The allowed range (°C). Aquarium:
// 23-28. To beep once tea has cooled:
// TOO_COLD 60, TOO_HOT 110 (never passed)
const float TOO_COLD = 23.0;
const float TOO_HOT = 28.0;
// true: the alarm is set once it has been
// inside the range (for tea the probe
// starts cold, in the air). Aquarium:
// leave it false
const bool WAIT_UNTIL_IN_RANGE = false;
// To go quiet it must come this far back
// inside: no flicker at the limit
const float MARGIN = 0.5;
// Time between readings (ms)
const unsigned long MEASURE_EVERY = 1000;
// How long a 12-bit reading takes (ms)
const unsigned long CONVERSION_TIME = 750;

// The degree sign and the letter C
const uint8_t SEG_DEGREE = SEG_A | SEG_B | SEG_F | SEG_G;
const uint8_t SEG_LETTER_C = SEG_A | SEG_D | SEG_E | SEG_F;
const uint8_t SEG_DASH = SEG_G;

OneWire oneWire(PROBE_PIN);
DallasTemperature probe(&oneWire);
TM1637Display display(DISPLAY_CLK, DISPLAY_DIO);

// No alarm, too cold or too hot
enum AlarmState { NONE, COLD, HOT };
AlarmState alarmState = NONE;
unsigned long alarmStart = 0;
bool beeping = false;
bool armed = !WAIT_UNTIL_IN_RANGE;

// The last reading; probeOk false if
// there was none
float temperature = 0;
bool probeOk = false;
bool measured = false;
unsigned long requestedAt = 0;
bool waiting = false;

// What the display shows: a temperature,
// BLANK or DASHES; written on a change
const int BLANK = -1000;
const int DASHES = -2000;
int shown = BLANK;

// Shows the temperature: "24°C", "-5°C",
// and from 100 up "100C"
void showTemperature(unsigned long now) {
  if (!measured) return;
  int next = round(temperature);
  if (!probeOk) next = DASHES;
  // Blinks once a second during an alarm
  bool off = (now - alarmStart) % 1000 >= 600;
  if (probeOk && alarmState != NONE && off) next = BLANK;
  if (next == shown) return;
  shown = next;

  if (shown == BLANK) {
    display.clear();
    return;
  }
  if (shown == DASHES) {
    uint8_t dashes[] = { SEG_DASH, SEG_DASH, SEG_DASH, SEG_DASH };
    display.setSegments(dashes);
    return;
  }
  int whole = shown;
  if (whole >= -9 && whole <= 99) {
    display.showNumberDec(whole, false, 2, 0);
    uint8_t unit[] = { SEG_DEGREE, SEG_LETTER_C };
    display.setSegments(unit, 2, 2);
  } else {
    display.showNumberDec(whole, false, 3, 0);
    uint8_t unit[] = { SEG_LETTER_C };
    display.setSegments(unit, 1, 3);
  }
}

// Fast and high when hot, slow and low
// when cold: the sound tells which
void updateBuzzer(unsigned long now) {
  bool beep = false;
  int hz = 0;
  unsigned long t = now - alarmStart;
  if (alarmState == HOT) {
    // 3 short beeps, then 1 s of quiet
    t %= 2000;
    beep = t < 900 && (t / 150) % 2 == 0;
    hz = 2500;
  } else if (alarmState == COLD) {
    // One long beep every 1.5 s
    beep = t % 1500 < 400;
    hz = 700;
  }
  // tone() only when the sound changes
  if (beep != beeping) {
    beeping = beep;
    if (beep) {
      tone(BUZZER_PIN, hz);
    } else {
      noTone(BUZZER_PIN);
    }
  }
}

void checkLimits(unsigned long now) {
  if (!armed) {
    if (temperature < TOO_COLD || temperature > TOO_HOT) return;
    armed = true;
    Serial.println("Inside the range, alarm set.");
  }
  AlarmState next = alarmState;
  if (alarmState == NONE) {
    if (temperature > TOO_HOT) next = HOT;
    if (temperature < TOO_COLD) next = COLD;
  } else if (alarmState == HOT && temperature <= TOO_HOT - MARGIN) {
    next = NONE;
  } else if (alarmState == COLD && temperature >= TOO_COLD + MARGIN) {
    next = NONE;
  }
  if (next == alarmState) return;
  alarmState = next;
  alarmStart = now;
  if (alarmState == HOT) Serial.println("ALARM: too hot!");
  if (alarmState == COLD) Serial.println("ALARM: too cold!");
  if (alarmState == NONE) Serial.println("Back within the range.");
}

void setup() {
  pinMode(BUZZER_PIN, OUTPUT);
  Serial.begin(9600);
  display.setBrightness(5);
  probe.begin();
  // The display and the sound keep going
  // while it measures: read it later
  probe.setWaitForConversion(false);
  Serial.print("Probes found: ");
  Serial.println(probe.getDeviceCount());
  Serial.print("Range: ");
  Serial.print(TOO_COLD, 1);
  Serial.print(" - ");
  Serial.print(TOO_HOT, 1);
  Serial.println(" °C");
}

void loop() {
  unsigned long now = millis();

  if (!waiting && now - requestedAt >= MEASURE_EVERY) {
    probe.requestTemperatures();
    requestedAt = now;
    waiting = true;
  }
  if (waiting && now - requestedAt >= CONVERSION_TIME) {
    waiting = false;
    measured = true;
    float reading = probe.getTempCByIndex(0);
    // -127: no probe, or a broken wire
    probeOk = reading != DEVICE_DISCONNECTED_C;
    if (probeOk) {
      temperature = reading;
      Serial.print("Temperature: ");
      Serial.print(temperature, 1);
      Serial.println(" °C");
      checkLimits(now);
    } else {
      Serial.println("Cannot read the probe: check the wires and the 4.7 kΩ resistor.");
      // No sound for what it cannot read
      alarmState = NONE;
    }
  }

  showTemperature(now);
  updateBuzzer(now);
}

What the Serial Monitor shows

SERIAL MONITOR · 9600 BAUD
Probes found: 1
Range: 23.0 - 28.0 °C
Temperature: 25.4 °C
Temperature: 25.5 °C
Temperature: 28.1 °C
ALARM: too hot!
Temperature: 28.3 °C
Temperature: 27.4 °C
Back within the range.

If a wire is loose

SERIAL MONITOR · 9600 BAUD
Probes found: 0
Range: 23.0 - 28.0 °C
Cannot read the probe: check the wires and the 4.7 kΩ resistor.
Cannot read the probe: check the wires and the 4.7 kΩ resistor.

What you will see

Before uploading the code, install the "DallasTemperature" (Miles Burton; it brings OneWire too) and "TM1637" (Avishay Orpaz) libraries from the Library Manager. Once the code is uploaded, the display shows the temperature within a second, as in "25°C", and updates it every second; the Serial Monitor prints it with a decimal. To test it, hold the probe tight in your palm: once it passes 28 °C the buzzer plays three fast, high beeps and the display blinks. Let go, and the alarm goes quiet when it drops to 27.5 °C. Dip the probe in cold water: below 23 °C you hear a slow, low beep. For tea, set TOO_COLD to 60, TOO_HOT to 110 and WAIT_UNTIL_IN_RANGE to true in the code: the alarm is set once the probe is in the hot tea, and it beeps when the tea drops below 60 °C. If the display shows "----" or the Serial Monitor says "Cannot read the probe", check that the probe's three wires are in the right columns and that the 4.7 kΩ resistor sits in columns 53 and 57. If the display stays dark, check the CLK and DIO wires. The probe's tip is stainless steel, but whether it is food-safe depends on the maker: dip it in tea briefly and wash it afterwards, and do not leave it in a drink for long.

In the app, tick off every wire, match the parts to your inventory and pick up where you left off.

Build it step by step in the app