EGT gauge with peak hold and alarm on an Arduino Uno
Exhaust gas temperature from a K-type thermocouple on a MAX6675, in F and C on a 16x2 LCD with a bar graph, a resettable peak, a 1,400 F demo alarm with hysteresis, and SENSOR OPEN when the probe breaks. The whole build, an Arduino Uno and 6 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.
Intermediate About 30 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $39. On Mokxi they're free.
What you'll learn
- Sensors
- Displays
- Lights
- Sound
- Reading a button
- Difficulty: Intermediate
- Time: About 30 minutes
- Board: Arduino Uno R3
- Parts: 8
How it works
Exhaust gas temperature from a K-type thermocouple on a MAX6675, in F and C on a 16x2 LCD with a bar graph, a resettable peak, a 1,400 F demo alarm with hysteresis, and SENSOR OPEN when the probe breaks.
Arduino Uno R3 runs the sketch below. The circuit around it: Thermocouple converter, MAX6675, Pushbutton, Resistor, 220 Ω, LED, red, Buzzer, Character LCD with I2C backpack. pinMode sets CS_PIN as an output. digitalWrite drives CS_PIN LOW. digitalRead reads whether BUTTON_PIN is HIGH or LOW. delay waits 250 milliseconds. Serial.begin opens the serial port at 9600 baud.
Parts list
8 parts, plus the jumper wires. They retail for about $39; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Thermocouple converter, MAX6675
- 1 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 1 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × BuzzerIn Starter Kit, Rocket Flight Computer Kit.
- 1 × Character LCD with I2C backpackIn Claw Machine Kit, Vending Machine Kit, Rhythm Game Kit.
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 pin | Connects to |
|---|---|
| Thermocouple converter, MAX6675 pin SCK | Arduino Uno R3 pin 13 |
| Thermocouple converter, MAX6675 pin SO | Arduino Uno R3 pin 12 |
| Thermocouple converter, MAX6675 pin CS | Arduino Uno R3 pin 10 |
| Buzzer pin 1 | Arduino Uno R3 pin 6 |
| Resistor, 220 Ω pin 1 | Arduino Uno R3 pin 5 |
| Pushbutton pin 1a | Arduino Uno R3 pin 2 |
| Thermocouple converter, MAX6675 pin VCC | Arduino Uno R3 pin 5V |
| Character LCD with I2C backpack pin VCC | Arduino Uno R3 pin 5V |
| Thermocouple converter, MAX6675 pin GND | Arduino Uno R3 pin GND |
| Pushbutton pin 2a | Arduino Uno R3 pin GND |
| LED, red pin C | Arduino Uno R3 pin GND |
| Buzzer pin 2 | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin GND | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin SDA | Arduino Uno R3 pin A4 |
| Character LCD with I2C backpack pin SCL | Arduino Uno R3 pin A5 |
| LED, red pin A | Resistor, 220 Ω pin 2 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// EGT gauge with peak hold and alarm: exhaust gas temperature from a K-type
// thermocouple on a MAX6675, in Fahrenheit and Celsius on a 16x2 I2C LCD,
// with a bar graph, a peak-hold value, a warning and an alarm.
//
// MAX6675 SCK -> 13 (SCK) SO -> 12 (MISO) CS -> 10 VCC -> 5 V
// LCD SDA -> A4 SCL -> A5 VCC -> 5 V
// button pin 2 to ground (the chip's own pull-up): resets the peak
// LED pin 5, through 220 ohms
// buzzer pin 6 (an active 5 V buzzer)
//
// SAFETY. This is a teaching simulation. Never connect a project like this
// to a running car's safety systems (brakes, airbags, steering, ECU wiring,
// anything that can strand or hurt someone). On the bench use a bench supply
// or a 12 V battery with a fuse. In a real vehicle the Uno needs a proper
// automotive-rated front end: a fused feed, a regulator that survives load
// dump, TVS clamps, and a divider or level shift for anything at 12 V; 12 V
// never goes straight onto a 5 V logic pin.
//
// SPI. The MAX6675 talks SPI: pull CS low and it puts 16 bits on SO, one per
// clock, most significant first. It only talks, so MOSI (pin 11) is not
// wired. Raising CS starts a conversion that takes up to 0.22 s, and reading
// sooner just returns the last one, so the sketch reads every 250 ms.
//
// bit 15 always 0
// bits 14 to 3 the temperature, 0 to 1023.75 C, 0.25 C a count
// bit 2 1 when the thermocouple is open (a broken or loose wire)
//
// COLD JUNCTION. A thermocouple measures the difference between its hot tip
// and the cold end where its wires meet the copper of the board. The chip
// measures its own temperature there and adds it back in: that is cold
// junction compensation, and why the reading stays right as the board warms.
// It only works if the chip and its terminals are at the same temperature
// and inside the chip's rating (up to 85 C), so mount the amplifier in the
// cabin, well away from the manifold, and run K-type extension wire to it.
// Ordinary copper wire in between makes new junctions and a wrong reading.
//
// THE PROBE. Use a sheathed EGT probe (a stainless or Inconel sheath and a
// compression fitting in a bung welded to the pipe). A bare bead dies quickly
// in exhaust gas, and a broken piece ahead of a turbo can wreck it.
//
// THE LIMIT IS AN EXAMPLE. There is no universal safe EGT. Pre-turbo on a
// gasoline engine people often quote about 1,500 F (815 C) for sustained
// load, and diesel guidance differs again (and depends on where the probe
// sits). This demo warns at 1,300 F and alarms at 1,400 F (760 C). Use the
// number your engine builder or turbo maker gives you.
//
// The alarm has hysteresis: it trips at 1,400 F but clears only below
// 1,350 F, so a reading that hovers at the line does not chatter the buzzer.
// The warning works the same way, 1,300 F on and below 1,250 F off.
//
// Things to try. Drag the thermocouple slider past 760 C and back down: the
// peak stays until you press the button. Then open the board's properties
// and untick probe: the gauge shows SENSOR OPEN, never a made-up number.
#include <SPI.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
const int CS_PIN = 10;
const int BUTTON_PIN = 2;
const int LED_PIN = 5;
const int BUZZER_PIN = 6;
// Example limits in whole degrees F. Change them for your engine.
const long WARN_F = 1300;
const long ALARM_F = 1400;
const long HYSTERESIS_F = 50;
// The bar graph's full scale.
const long BAR_FULL_F = 1800;
const int BAR_CELLS = 9;
const unsigned long READ_MS = 250;
const unsigned long DEBOUNCE_MS = 30;
LiquidCrystal_I2C lcd(0x27, 16, 2);
// One to four lit columns of a character cell, for the bar graph. A full
// cell is the display's own character 0xFF.
const uint8_t BAR_ROWS[4] = {0x10, 0x18, 0x1C, 0x1E};
enum Level { LEVEL_OK, LEVEL_WARN, LEVEL_ALARM };
Level level = LEVEL_OK;
bool havePeak = false;
long peakQ = 0; // the highest good reading, in quarter degrees C
unsigned long lastRead = 0;
bool beep = false;
bool buttonDown = false;
bool buttonSeen = false;
unsigned long buttonChanged = 0;
// One 16-bit read of the MAX6675.
uint16_t readMax6675() {
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW);
uint16_t v = SPI.transfer(0x00);
v = (v << 8) | SPI.transfer(0x00);
digitalWrite(CS_PIN, HIGH);
SPI.endTransaction();
return v;
}
// Quarter degrees C to whole degrees F, rounded: F = C * 9 / 5 + 32.
long toF(long q) {
long n = q * 9;
return (n >= 0 ? n + 10 : n - 10) / 20 + 32;
}
// Quarter degrees C to whole degrees C, rounded.
long toC(long q) {
return (q >= 0 ? q + 2 : q - 2) / 4;
}
// A number right-aligned in a field, on the LCD or on Serial.
void printPadded(Print &out, long v, int width) {
int digits = 1;
for (long t = v < 0 ? -v : v; t >= 10; t /= 10) digits++;
if (v < 0) digits++;
for (int i = digits; i < width; i++) out.print(' ');
out.print(v);
}
void drawBar(long f) {
long steps = f <= 0 ? 0 : f * (BAR_CELLS * 5) / BAR_FULL_F;
if (steps > BAR_CELLS * 5) steps = BAR_CELLS * 5;
lcd.setCursor(0, 1);
for (int cell = 0; cell < BAR_CELLS; cell++) {
long lit = steps - cell * 5;
if (lit >= 5) lcd.write((uint8_t)0xFF);
else if (lit >= 1) lcd.write((uint8_t)lit);
else lcd.print(' ');
}
}
void drawPeak() {
lcd.setCursor(BAR_CELLS, 1);
lcd.print(" P");
if (havePeak) {
printPadded(lcd, toF(peakQ), 4);
lcd.print('F');
} else {
lcd.print("----F");
}
}
// The button: a press resets the peak to the reading now.
void checkButton() {
bool down = digitalRead(BUTTON_PIN) == LOW;
unsigned long now = millis();
if (down != buttonSeen) {
buttonSeen = down;
buttonChanged = now;
}
if (now - buttonChanged >= DEBOUNCE_MS && down != buttonDown) {
buttonDown = down;
if (down) {
havePeak = false;
Serial.println("Peak reset");
lastRead = now - READ_MS; // read again straight away
}
}
}
void setup() {
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
SPI.begin();
Serial.begin(9600);
lcd.init();
lcd.backlight();
for (int i = 0; i < 4; i++) {
uint8_t rows[8];
for (int r = 0; r < 8; r++) rows[r] = BAR_ROWS[i];
lcd.createChar(i + 1, rows);
}
lcd.print("EGT gauge");
Serial.println("EGT gauge: warn 1300 F, alarm 1400 F (example limits)");
// Let the chip finish its first conversion.
delay(250);
lastRead = millis() - READ_MS;
}
void loop() {
checkButton();
unsigned long now = millis();
if (now - lastRead < READ_MS) return;
lastRead = now;
uint16_t v = readMax6675();
bool open = v & 0x0004;
if (open) {
// The open bit: show it, never a made-up temperature.
level = LEVEL_OK;
beep = !beep;
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, beep ? HIGH : LOW); // a slow blink: the gauge is blind
lcd.setCursor(0, 0);
lcd.print("SENSOR OPEN ");
lcd.setCursor(0, 1);
for (int i = 0; i < BAR_CELLS; i++) lcd.print(' ');
drawPeak();
Serial.print("SENSOR OPEN");
} else {
long q = (v >> 3) & 0x0FFF; // quarter degrees C
long f = toF(q);
if (!havePeak || q > peakQ) {
peakQ = q;
havePeak = true;
}
// Warning and alarm, each with the same hysteresis band below its limit.
if (f >= ALARM_F) level = LEVEL_ALARM;
else if (level == LEVEL_ALARM && f >= ALARM_F - HYSTERESIS_F) level = LEVEL_ALARM;
else if (f >= WARN_F) level = LEVEL_WARN;
else if (level != LEVEL_OK && f >= WARN_F - HYSTERESIS_F) level = LEVEL_WARN;
else level = LEVEL_OK;
beep = level == LEVEL_ALARM ? !beep : false;
digitalWrite(LED_PIN, level == LEVEL_OK ? LOW : HIGH);
digitalWrite(BUZZER_PIN, beep ? HIGH : LOW);
lcd.setCursor(0, 0);
printPadded(lcd, f, 4);
lcd.print("F ");
printPadded(lcd, toC(q), 4);
lcd.print("C ");
lcd.print(level == LEVEL_ALARM ? "HIGH" : level == LEVEL_WARN ? "WARN" : " ");
drawBar(f);
drawPeak();
Serial.print("EGT ");
Serial.print(f);
Serial.print(" F ");
Serial.print(toC(q));
Serial.print(" C");
}
Serial.print(" peak ");
if (havePeak) {
Serial.print(toF(peakQ));
Serial.print(" F");
} else {
Serial.print("--");
}
Serial.print(" ");
Serial.println(open ? "FAULT" : level == LEVEL_ALARM ? "ALARM" : level == LEVEL_WARN ? "WARN" : "OK");
}
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() {
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
SPI.begin();
Serial.begin(9600);
lcd.init();
lcd.backlight();
for (int i = 0; i < 4; i++) {
uint8_t rows[8];
for (int r = 0; r < 8; r++) rows[r] = BAR_ROWS[i];
lcd.createChar(i + 1, rows);
}
lcd.print("EGT gauge");
Serial.println("EGT gauge: warn 1300 F, alarm 1400 F (example limits)");
// Let the chip finish its first conversion.
delay(250);
lastRead = millis() - READ_MS;
}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() {
checkButton();
unsigned long now = millis();
if (now - lastRead < READ_MS) return;
lastRead = now;
uint16_t v = readMax6675();
bool open = v & 0x0004;
if (open) {
// The open bit: show it, never a made-up temperature.
level = LEVEL_OK;
beep = !beep;
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, beep ? HIGH : LOW); // a slow blink: the gauge is blind
lcd.setCursor(0, 0);
lcd.print("SENSOR OPEN ");
lcd.setCursor(0, 1);
for (int i = 0; i < BAR_CELLS; i++) lcd.print(' ');
drawPeak();
Serial.print("SENSOR OPEN");
} else {
long q = (v >> 3) & 0x0FFF; // quarter degrees C
long f = toF(q);
if (!havePeak || q > peakQ) {
peakQ = q;
havePeak = true;
}
// ... 38 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The temperature reads 0 or too low or too high. Thermocouple converter
Check the thermocouple polarity. If the reading falls when heated, swap the two leads. The MAX6675 works with K-type thermocouples only. - Readings repeat or look stuck. Thermocouple converter
A conversion takes about 220 ms, so read no faster than 4 times a second. The pins are SCK, CS and SO only. Keep the chip away from heat, since it measures its own cold junction. - The input reads random HIGH and LOW when the button is not pressed. Pushbutton
A floating pin picks up noise. Add a 10 k pull-down or pull-up resistor, or use INPUT_PULLUP and wire the button between the pin and GND. Then pressed reads LOW. - The button seems always pressed, or never changes. Pushbutton
A 4-leg tactile switch has legs joined in pairs. Place it across the breadboard center gap so the pairs sit on different rows. Wired along one side, the legs are already connected. - The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure. - A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground. - The LED does not light, or it lights once and then dies. LED
LEDs have polarity. The longer leg (anode) goes toward the positive side. Always add a series resistor, about 220 ohms to 1 k ohm on a 5 V pin. - A blue or white LED stays dark on 3.3 V. LED
Blue and white LEDs drop about 3 V, so 3.3 V leaves almost nothing for the resistor. Use a 5 V supply, or a smaller resistor and a red or green LED.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.