Built-in project · Arduino Uno R3

Delta-T monitor on an Arduino Uno

Two DS18B20s on one wire read supply and return air: the drop across the coil is judged only while the cooling call runs and has settled, and a drain-pan float cuts the call through a relay and raises an alarm (a low-voltage teaching model). The whole build, an Arduino Uno and 19 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Advanced About 55 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $40. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Sensors
  • School
  • Choosing and reading a resistor
  • How an LED lights and why it needs a resistor
  • Difficulty: Advanced
  • Time: About 55 minutes
  • Board: Arduino Uno R3
  • Parts: 21

How it works

Two DS18B20s on one wire read supply and return air: the drop across the coil is judged only while the cooling call runs and has settled, and a drain-pan float cuts the call through a relay and raises an alarm (a low-voltage teaching model).

Arduino Uno R3 runs the sketch below. The circuit around it: Temperature sensor, DS18B20, Float switch, ZP4510, Relay module, Slide switch, Resistor, 10k Ω, Resistor, 220 Ω. pinMode sets PIN_FLOAT as an input. digitalRead reads whether PIN_RUNNING is HIGH or LOW. Serial.begin opens the serial port at 115200 baud. millis reads the time since power-up, so the sketch can time things without stopping.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Resistor, 220 Ω (6) pin 1Arduino Uno R3 pin 9
Resistor, 220 Ω (5) pin 1Arduino Uno R3 pin 8
Resistor, 220 Ω (4) pin 1Arduino Uno R3 pin 7
Resistor, 220 Ω (3) pin 1Arduino Uno R3 pin 6
Relay module pin NCArduino Uno R3 pin 4
Resistor, 10k Ω (2) pin 2Arduino Uno R3 pin 4
Resistor, 220 Ω (1) pin 1Arduino Uno R3 pin 4
Float switch, ZP4510 pin 2Arduino Uno R3 pin 3
Relay module pin INArduino Uno R3 pin 3
Resistor, 10k Ω (1) pin 2Arduino Uno R3 pin 3
Temperature sensor, DS18B20 (1) pin DQArduino Uno R3 pin 2
Temperature sensor, DS18B20 (2) pin DQArduino Uno R3 pin 2
Temperature sensor, DS18B20 (1) pin VDDArduino Uno R3 pin 5V
Temperature sensor, DS18B20 (2) pin VDDArduino Uno R3 pin 5V
Float switch, ZP4510 pin 1Arduino Uno R3 pin 5V
Relay module pin VCCArduino Uno R3 pin 5V
Slide switch pin 3Arduino Uno R3 pin 5V
Temperature sensor, DS18B20 (1) pin GNDArduino Uno R3 pin GND
Temperature sensor, DS18B20 (2) pin GNDArduino Uno R3 pin GND
Relay module pin GNDArduino Uno R3 pin GND
Resistor, 10k Ω (1) pin 1Arduino Uno R3 pin GND
Resistor, 10k Ω (2) pin 1Arduino Uno R3 pin GND
LED, white pin CArduino Uno R3 pin GND
LED, yellow (1) pin CArduino Uno R3 pin GND
LED, green pin CArduino Uno R3 pin GND
LED, blue pin CArduino Uno R3 pin GND
LED, yellow (2) pin CArduino Uno R3 pin GND
LED, red pin CArduino Uno R3 pin GND
Resistor, 220 Ω (2) pin 1Relay module pin NO
Slide switch pin 2Relay module pin COM
LED, white pin AResistor, 220 Ω (1) pin 2
LED, yellow (1) pin AResistor, 220 Ω (2) pin 2
LED, green pin AResistor, 220 Ω (3) pin 2
LED, blue pin AResistor, 220 Ω (4) pin 2
LED, yellow (2) pin AResistor, 220 Ω (5) pin 2
LED, red pin AResistor, 220 Ω (6) pin 2

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Delta-T monitor: how much cooler is the air leaving an air conditioner's
// coil than the air coming back to it, and is the condensate drain overflowing?
//
// THIS IS A TEACHING MODEL. The call, the relay and the contactor LED run at
// 5 V; a real system switches 24 VAC between R and Y, and a real condensate
// float switch is wired straight into that 24 V line. Never connect this to a
// real air conditioner or a building.
//
//   pin 2    both DS18B20 thermometers (one-wire bus, one 4.7 kohm pull-up)
//   pin 3    float sense: HIGH when the float is up (water high)
//   pin 4    running: HIGH when the contactor is energized
//   pin 6    OK    green LED   delta-T inside 14 to 22 F
//   pin 7    LOW   blue LED    delta-T under 14 F
//   pin 8    HIGH  yellow LED  delta-T over 22 F
//   pin 9    ALARM red LED     condensate overflow, latched
//
// DELTA-T
//   Delta-T is the return air temperature minus the supply air temperature,
//   in degrees F. Across a cooling coil about 20 F is typical, and 14 to 22 F
//   is a common rule of thumb for "healthy". It is a hint, not a diagnosis:
//   it moves with the humidity and with the outdoor temperature.
//     Under 14 F   low refrigerant, too much airflow, or a sensor in the
//                  wrong place (not in the airstream, or too far from the coil).
//     Over 22 F    too little airflow: a dirty filter, a closed register, a
//                  weak blower or a dirty coil.
//   The two sensors are found by their ROM codes (printed at start-up). Paste
//   your own pair into SUPPLY_ROM and RETURN_ROM; they are labelled in the
//   print-out the way you would label the probes with tape.
//
// WHEN IT JUDGES
//   Only while the contactor is running (pin 4 high for 0.5 s), and only after
//   the SETTLE time, so that the coil and the ducts have reached their steady
//   temperatures. Real coils want a few minutes, so SETTLE is 10 minutes real,
//   scaled by 20 here to 30 s. Idle or settling, the verdict LEDs stay dark.
//   Min, max and average delta-T cover the judged samples of the current run
//   and start again with each new run.
//
// CONDENSATE FLOAT SWITCH
//   A float in the drain pan closes when the water rises. In the hardware path
//   it drives a relay whose normally closed contact is in series with the Y
//   call, so rising water opens the call to the contactor with no help from
//   the Uno, the way a real float switch opens the R and Y side. The same
//   float signal also goes to pin 3, and the sketch raises the alarm.
//   Latch rule: the alarm sets after the float has been up for 0.5 s and stays
//   set until the float has been down for CLEAR time (3 minutes real, 9 s
//   here). The relay is not latched: it lets the call back the moment the
//   float drops, as a real float switch does. So the alarm reminds you that
//   water got high, while the call restarts and settles again by itself.

#include <OneWire.h>
#include <DallasTemperature.h>

const int PIN_BUS = 2;
const int PIN_FLOAT = 3;
const int PIN_RUNNING = 4;
const int PIN_OK = 6;
const int PIN_LOW = 7;
const int PIN_HIGH = 8;
const int PIN_ALARM = 9;

const unsigned long SCALE = 20;
const unsigned long SETTLE_MS = 600000UL / SCALE;
const unsigned long CLEAR_MS = 180000UL / SCALE;
const unsigned long DEBOUNCE_MS = 500;

// Tenths of a degree F.
const int LOW_BELOW = 140;
const int HIGH_ABOVE = 220;

// Replace these with the codes your own sensors print at start-up.
DeviceAddress SUPPLY_ROM = {0x28, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29};
DeviceAddress RETURN_ROM = {0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70};

OneWire oneWire(PIN_BUS);
DallasTemperature sensors(&oneWire);

enum Verdict { NONE, LOW_DT, OK_DT, HIGH_DT };
const char *const VERDICT_NAME[] = {"none", "LOW", "OK", "HIGH"};

bool converting = false;
unsigned long requestedAt = 0;
bool sensorsOk = false;
int supplyF = 0, returnF = 0, deltaF = 0;   // tenths

bool running = false;
unsigned long runningSince = 0;
bool runRaw = false;
unsigned long runRawAt = 0;
bool settled = false;
Verdict verdict = NONE;
int dtMin = 0, dtMax = 0;
long dtSum = 0;
unsigned int dtCount = 0;

bool alarm = false;
unsigned int trips = 0;
bool floatRaw = false;
unsigned long floatRawAt = 0;
unsigned long floatLowSince = 0;
unsigned long lastStatus = 0;

void printTenths(int t) {
  if (t < 0) {
    Serial.print('-');
    t = -t;
  }
  Serial.print(t / 10);
  Serial.print('.');
  Serial.print(t % 10);
}

void printRom(const uint8_t *a) {
  for (int i = 0; i < 8; i++) {
    if (a[i] < 16) Serial.print('0');
    Serial.print(a[i], HEX);
  }
}

int toTenthsF(long raw) {
  // raw is 1/128 C; F = C * 1.8 + 32, in tenths
  return (int)((raw * 18 + (raw < 0 ? -64 : 64)) / 128) + 320;
}

void readTemperatures() {
  if (!converting) {
    sensors.requestTemperatures();
    requestedAt = millis();
    converting = true;
    return;
  }
  if (millis() - requestedAt < 800) return;
  converting = false;
  long s = sensors.getTemp(SUPPLY_ROM);
  long r = sensors.getTemp(RETURN_ROM);
  bool good = s != DEVICE_DISCONNECTED_RAW && r != DEVICE_DISCONNECTED_RAW;
  if (!good && sensorsOk) Serial.println("FAULT: a thermometer is not answering, no verdicts");
  sensorsOk = good;
  if (!good) return;
  supplyF = toTenthsF(s);
  returnF = toTenthsF(r);
  deltaF = returnF - supplyF;
  // A new sample counts toward the statistics only while judging.
  if (running && settled) {
    if (dtCount == 0 || deltaF < dtMin) dtMin = deltaF;
    if (dtCount == 0 || deltaF > dtMax) dtMax = deltaF;
    dtSum += deltaF;
    dtCount++;
  }
}

void printStats() {
  if (dtCount == 0) {
    Serial.println("no judged samples");
    return;
  }
  Serial.print("min ");
  printTenths(dtMin);
  Serial.print("F max ");
  printTenths(dtMax);
  Serial.print("F avg ");
  printTenths((int)(dtSum / (long)dtCount));
  Serial.print("F over ");
  Serial.print(dtCount);
  Serial.println(" samples");
}

void updateRunning() {
  bool raw = digitalRead(PIN_RUNNING) == HIGH;
  if (raw != runRaw) {
    runRaw = raw;
    runRawAt = millis();
  }
  if (raw != running && millis() - runRawAt >= DEBOUNCE_MS) {
    running = raw;
    settled = false;
    verdict = NONE;
    if (running) {
      runningSince = millis();
      dtCount = 0;
      Serial.println("RUN start: settling before judging");
    } else {
      Serial.print("RUN stop: ");
      printStats();
    }
  }
  if (running && !settled && millis() - runningSince >= SETTLE_MS) {
    settled = true;
    Serial.println("SETTLED: judging delta-T");
  }
}

void judge() {
  Verdict next = NONE;
  if (running && settled && sensorsOk) {
    next = deltaF < LOW_BELOW ? LOW_DT : (deltaF > HIGH_ABOVE ? HIGH_DT : OK_DT);
  }
  if (next != verdict) {
    verdict = next;
    if (verdict != NONE) {
      Serial.print("VERDICT ");
      Serial.print(VERDICT_NAME[verdict]);
      Serial.print(" dT ");
      printTenths(deltaF);
      Serial.print("F");
      if (verdict == LOW_DT) Serial.print(": check refrigerant, airflow and sensor placement");
      if (verdict == HIGH_DT) Serial.print(": check filter, registers, blower and coil");
      Serial.println();
    }
  }
  digitalWrite(PIN_OK, verdict == OK_DT);
  digitalWrite(PIN_LOW, verdict == LOW_DT);
  digitalWrite(PIN_HIGH, verdict == HIGH_DT);
}

void updateFloat() {
  unsigned long now = millis();
  bool raw = digitalRead(PIN_FLOAT) == HIGH;
  if (raw != floatRaw) {
    floatRaw = raw;
    floatRawAt = now;
  }
  if (raw) floatLowSince = now;
  if (!alarm && raw && now - floatRawAt >= DEBOUNCE_MS) {
    alarm = true;
    trips++;
    Serial.print("ALARM: condensate overflow, the float relay has cut the Y call (trip ");
    Serial.print(trips);
    Serial.println(")");
  }
  if (alarm && !raw && now - floatLowSince >= CLEAR_MS) {
    alarm = false;
    Serial.println("ALARM cleared: the float has been down long enough");
  }
  digitalWrite(PIN_ALARM, alarm);
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_FLOAT, INPUT);
  pinMode(PIN_RUNNING, INPUT);
  pinMode(PIN_OK, OUTPUT);
  pinMode(PIN_LOW, OUTPUT);
  pinMode(PIN_HIGH, OUTPUT);
  pinMode(PIN_ALARM, OUTPUT);
  Serial.println("Mokxi Arduino Uno: delta-T monitor (teaching model, 20x faster timers)");
  sensors.begin();
  sensors.setWaitForConversion(false);
  int found = sensors.getDeviceCount();
  Serial.print(found);
  Serial.println(" thermometers on the bus");
  DeviceAddress a;
  for (int i = 0; i < found; i++) {
    if (!sensors.getAddress(a, i)) continue;
    Serial.print("ROM ");
    printRom(a);
    if (memcmp(a, SUPPLY_ROM, 8) == 0) Serial.print("  SUPPLY");
    if (memcmp(a, RETURN_ROM, 8) == 0) Serial.print("  RETURN");
    Serial.println();
  }
}

void loop() {
  readTemperatures();
  updateRunning();
  updateFloat();
  judge();

  if (millis() - lastStatus >= 1000) {
    lastStatus = millis();
    Serial.print("S ");
    printTenths(supplyF);
    Serial.print("F R ");
    printTenths(returnF);
    Serial.print("F dT ");
    printTenths(deltaF);
    Serial.print("F ");
    if (!running) Serial.print("idle");
    else if (!settled) Serial.print("settling");
    else Serial.print(VERDICT_NAME[verdict]);
    if (alarm) Serial.print(" ALARM");
    if (dtCount > 0) {
      Serial.print(" min ");
      printTenths(dtMin);
      Serial.print(" max ");
      printTenths(dtMax);
      Serial.print(" avg ");
      printTenths((int)(dtSum / (long)dtCount));
    }
    Serial.println();
  }
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Runs once when the board starts. It gets the pins and libraries ready.

void setup() {
  Serial.begin(115200);
  pinMode(PIN_FLOAT, INPUT);
  pinMode(PIN_RUNNING, INPUT);
  pinMode(PIN_OK, OUTPUT);
  pinMode(PIN_LOW, OUTPUT);
  pinMode(PIN_HIGH, OUTPUT);
  pinMode(PIN_ALARM, OUTPUT);
  Serial.println("Mokxi Arduino Uno: delta-T monitor (teaching model, 20x faster timers)");
  sensors.begin();
  sensors.setWaitForConversion(false);
  int found = sensors.getDeviceCount();
  Serial.print(found);
  Serial.println(" thermometers on the bus");
  DeviceAddress a;
  for (int i = 0; i < found; i++) {
    if (!sensors.getAddress(a, i)) continue;
    Serial.print("ROM ");
    printRom(a);
    if (memcmp(a, SUPPLY_ROM, 8) == 0) Serial.print("  SUPPLY");
    if (memcmp(a, RETURN_ROM, 8) == 0) Serial.print("  RETURN");
    Serial.println();
  }
}

loop sketch.ino

Runs over and over for as long as the board has power. This is where the project does its work.

void loop() {
  readTemperatures();
  updateRunning();
  updateFloat();
  judge();

  if (millis() - lastStatus >= 1000) {
    lastStatus = millis();
    Serial.print("S ");
    printTenths(supplyF);
    Serial.print("F R ");
    printTenths(returnF);
    Serial.print("F dT ");
    printTenths(deltaF);
    Serial.print("F ");
    if (!running) Serial.print("idle");
    else if (!settled) Serial.print("settling");
    else Serial.print(VERDICT_NAME[verdict]);
    if (alarm) Serial.print(" ALARM");
    if (dtCount > 0) {
      Serial.print(" min ");
      printTenths(dtMin);
      Serial.print(" max ");
      printTenths(dtMax);
      Serial.print(" avg ");
      printTenths((int)(dtSum / (long)dtCount));
    }
    Serial.println();
  // ... 2 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The sensor reads -127 C or no device is found. Temperature sensor
    Put a 4.7 k resistor between the data line and VCC. Check the pinout, since swapping VCC and GND can overheat the sensor. Then confirm the data pin matches your code.
  • It always shows 85 C. Temperature sensor
    85 C is the power on value, meaning the conversion has not finished or the supply is weak. Wait for the conversion (up to 750 ms at 12 bit) before reading.
  • The switch acts opposite to what you expected: closed when dry, open when wet. Float switch
    The float can be mounted either way up. Turn the float over on the stem to change whether the reed closes or opens when the water rises. Test with a meter.
  • The input reads random values or the pump chatters at the water level. Float switch
    Wire it between the pin and ground with INPUT_PULLUP. Waves make the reed flicker, so add a delay or a short debounce time and keep the float in calm water.
  • The relay does not click, or the board resets when it switches. Relay module
    A relay coil needs more current than a pin can give. Use a module with a transistor driver, and power VCC from 5 V. Share ground between the module and the Arduino.
  • The relay works backwards: on when you expect off. Relay module
    Many modules are active LOW: the input must go LOW to turn the relay on. Set the pin HIGH at startup so the load does not switch on during boot.
  • The input reads random values in one position. Slide switch
    A switch only connects or disconnects. Add a pull-up or pull-down resistor, or use INPUT_PULLUP, so the pin has a defined level when the switch is open.
  • The load never turns on when the switch is flipped. Slide switch
    Use the center (common) leg and one outer leg. The unused outer leg goes nowhere. Check that the switch is in series with the load.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.