Turn signals and hazard flasher on an Arduino Uno
Left, right and hazard buttons flash two 12 V bulbs a side through a relay board at 90 a minute, and an INA219 spots a burned-out bulb by its current and hyper-flashes that side with a warning light, as a real car does. The whole build, an Arduino Uno and 13 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 40 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
- Lights
- Sensors
- Reading a button
- Choosing and reading a resistor
- How an LED lights and why it needs a resistor
- Difficulty: Intermediate
- Time: About 40 minutes
- Board: Arduino Uno R3
- Parts: 16
How it works
Left, right and hazard buttons flash two 12 V bulbs a side through a relay board at 90 a minute, and an INA219 spots a burned-out bulb by its current and hyper-flashes that side with a warning light, as a real car does.
Arduino Uno R3 runs the sketch below. The circuit around it: Pushbutton, Resistor, 330 Ω, LED, red, Relay board, two channels, Current monitor, INA219, Battery: AA, coin cell, LiPo. pinMode sets LEFT_RELAY as an output. digitalWrite drives WARNING_LED LOW. digitalRead reads whether b.pin is HIGH or LOW. delay waits 5 milliseconds. Serial.begin opens the serial port at 9600 baud.
Parts list
16 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
- 3 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 1 × Resistor, 330 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × Relay board, two channels
- 1 × Power
- 1 × Current monitor, INA219
- 1 × Battery: AA, coin cell, LiPoIn Handheld Console Kit, Laser Sword Kit.
- 4 × Filament lamp
- 1 × Slide switchIn Logic Lab Kit, Spaceship Panel 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 |
|---|---|
| Relay board, two channels pin GND | Arduino Uno R3 pin GND |
| Relay board, two channels pin IN1 | Arduino Uno R3 pin 9 |
| Relay board, two channels pin IN2 | Arduino Uno R3 pin 8 |
| Resistor, 330 Ω pin 1 | Arduino Uno R3 pin 7 |
| Pushbutton (3) pin 1a | Arduino Uno R3 pin 4 |
| Pushbutton (2) pin 1a | Arduino Uno R3 pin 3 |
| Pushbutton (1) pin 1a | Arduino Uno R3 pin 2 |
| Current monitor, INA219 pin VCC | Arduino Uno R3 pin 5V |
| Pushbutton (1) pin 2b | Arduino Uno R3 pin GND |
| Pushbutton (2) pin 2b | Arduino Uno R3 pin GND |
| Pushbutton (3) pin 2b | Arduino Uno R3 pin GND |
| LED, red pin C | Arduino Uno R3 pin GND |
| Current monitor, INA219 pin GND | Arduino Uno R3 pin GND |
| Current monitor, INA219 pin SDA | Arduino Uno R3 pin A4 |
| Current monitor, INA219 pin SCL | Arduino Uno R3 pin A5 |
| LED, red pin A | Resistor, 330 Ω pin 2 |
| Relay board, two channels pin VCC | Supply |
| Filament lamp (1) pin 1 | Relay board, two channels pin NO1 |
| Filament lamp (2) pin 1 | Relay board, two channels pin NO1 |
| Relay board, two channels pin COM2 | Relay board, two channels pin COM1 |
| Current monitor, INA219 pin VIN- | Relay board, two channels pin COM1 |
| Filament lamp (3) pin 2 | Relay board, two channels pin NO2 |
| Filament lamp (4) pin 1 | Relay board, two channels pin NO2 |
| Battery: AA, coin cell, LiPo pin V+ | Current monitor, INA219 pin VIN+ |
| Battery: AA, coin cell, LiPo pin V- | Ground |
| Slide switch pin 2 | Filament lamp (1) pin 2 |
| Filament lamp (2) pin 2 | Ground |
| Filament lamp (3) pin 1 | Ground |
| Filament lamp (4) pin 2 | Ground |
| Slide switch pin 1 | Ground |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Turn signals and hazard flasher: an Uno as a car's electronic flasher unit.
// Three buttons pick left, right or hazard; a two-channel relay board switches
// two 12 V bulbs a side from a 12 V lead-acid battery; an INA219 in the feed
// measures the bulbs' current, and a burned-out bulb makes that side
// "hyper-flash" at double rate with the BULB OUT light on, as a real car does.
//
// D2 LEFT button D3 RIGHT button D4 HAZARD button
// (each to GND, using the Uno's internal pull-ups)
// D7 BULB OUT warning LED, through 330 ohms
// D9 relay board IN1: the left bulbs D8 relay board IN2: the right bulbs
// A4 INA219 SDA A5 INA219 SCL
//
// battery + -> INA219 VIN+ ; VIN- -> both relays' COM ; each NO -> its two
// bulbs ; the bulbs' other ends, the battery's minus and the Uno's GND all
// on one ground, the way a car grounds its lamps to the body.
//
// The legal rate (US FMVSS 108) is 60 to 120 flashes a minute; 90 a minute,
// on half the time, is 333 ms on and 333 ms off. A side with a bulb out flashes
// twice as fast, 180 a minute, which is outside the legal range on purpose: it
// is the driver's warning.
//
// The bulb check: each lit bulb draws about 100 mA (the catalog's 12 V 0.1 A
// lamp; a real 1156 or 7440 turn bulb is 21 to 27 W, about 2 A). The sketch
// reads the current near the end of each flash, once the cold filament's
// inrush (about ten times the running current) has died away, and counts the
// bulbs. Fewer than it expects means one is out.
//
// SAFETY: this is a teaching simulation. Never connect a project like this to
// a running car's safety systems or wiring (brakes, airbags, steering, the ECU,
// the lighting a car needs to be legal and seen). On the bench use a 12 V
// battery or supply with a fuse in its lead. A real install needs an
// automotive-rated design: fusing, load-dump and reverse-polarity protection,
// relays and wiring rated for the bulbs' amps, and DOT-legal lamps.
//
// Press Run, then press LEFT, RIGHT or HAZARD. Slide the switch by the front
// left bulb to pull that bulb out of its socket.
#include <Wire.h>
#include <Adafruit_INA219.h>
const int LEFT_BUTTON = 2;
const int RIGHT_BUTTON = 3;
const int HAZARD_BUTTON = 4;
const int WARNING_LED = 7;
const int LEFT_RELAY = 9; // relay board IN1
const int RIGHT_RELAY = 8; // relay board IN2
// 90 flashes a minute, and twice that with a bulb out.
const unsigned long NORMAL_HALF_MS = 333;
const unsigned long FAST_HALF_MS = 167;
// Wait this long into a flash before trusting the current: the relay takes
// about 10 ms to pull in, and a cold filament surges for tens of ms.
const unsigned long SETTLE_MS = 100;
const unsigned long DEBOUNCE_MS = 25;
// One lit 12 V 0.1 A bulb, near enough, at the battery's 12.8 V.
const float BULB_MA = 100.0;
enum Mode { OFF, LEFT, RIGHT, HAZARD };
const char *const MODE_NAMES[] = { "Off", "Left", "Right", "Hazard" };
Adafruit_INA219 ina;
// What the driver has asked for: the stalk (off, left or right) and the hazard
// switch, which overrides the stalk while it is on.
Mode stalk = OFF;
bool hazard = false;
Mode mode = OFF;
bool lampsOn = false;
unsigned long phaseStart = 0;
float lastMilliamps = 0; // the newest settled reading this flash
bool haveReading = false;
bool bulbOut = false;
int shownBulbs = -1; // the bulb count last printed
// A pushbutton bounces for a few ms when pressed: only a level that holds for
// DEBOUNCE_MS counts.
struct Button {
int pin;
int stable;
int last;
unsigned long changed;
};
Button buttons[] = {
{ LEFT_BUTTON, HIGH, HIGH, 0 },
{ RIGHT_BUTTON, HIGH, HIGH, 0 },
{ HAZARD_BUTTON, HIGH, HIGH, 0 },
};
// True once for each clean press.
bool pressed(Button &b) {
int raw = digitalRead(b.pin);
unsigned long now = millis();
if (raw != b.last) {
b.last = raw;
b.changed = now;
}
if (now - b.changed >= DEBOUNCE_MS && raw != b.stable) {
b.stable = raw;
return raw == LOW;
}
return false;
}
// The relay board is active low: LOW on IN pulls the relay in.
void driveLamps(bool on) {
lampsOn = on;
bool left = on && (mode == LEFT || mode == HAZARD);
bool right = on && (mode == RIGHT || mode == HAZARD);
digitalWrite(LEFT_RELAY, left ? LOW : HIGH);
digitalWrite(RIGHT_RELAY, right ? LOW : HIGH);
}
int expectedBulbs() {
if (mode == HAZARD) return 4;
if (mode == LEFT || mode == RIGHT) return 2;
return 0;
}
// Hyper-flash only on a turn: a hazard flasher keeps the legal rate whatever
// the load, as the heavy-duty hazard flashers in older cars did.
unsigned long halfPeriod() {
return (bulbOut && mode != HAZARD) ? FAST_HALF_MS : NORMAL_HALF_MS;
}
void setMode(Mode m) {
if (m == mode) return;
mode = m;
bulbOut = false;
shownBulbs = -1;
digitalWrite(WARNING_LED, LOW);
Serial.println(MODE_NAMES[mode]);
// A flasher starts with the lamps lit, so the first flash is immediate.
phaseStart = millis();
haveReading = false;
driveLamps(mode != OFF);
}
// At the end of each flash: count the bulbs from the current.
void checkBulbs() {
if (!haveReading) return;
int want = expectedBulbs();
int bulbs = (int)(lastMilliamps / BULB_MA + 0.5);
bulbOut = bulbs < want;
digitalWrite(WARNING_LED, bulbOut ? HIGH : LOW);
if (bulbs != shownBulbs) {
shownBulbs = bulbs;
Serial.print(MODE_NAMES[mode]);
Serial.print(": ");
Serial.print((int)(lastMilliamps + 0.5));
Serial.print(" mA, ");
Serial.print(bulbs);
Serial.print(bulbs == 1 ? " bulb" : " bulbs");
if (bulbOut) Serial.print(mode == HAZARD ? ": BULB OUT" : ": BULB OUT, fast flash");
Serial.println();
}
}
void setup() {
// Write the relay pins HIGH before they become outputs, or both relays
// click on for an instant at power-up.
digitalWrite(LEFT_RELAY, HIGH);
digitalWrite(RIGHT_RELAY, HIGH);
pinMode(LEFT_RELAY, OUTPUT);
pinMode(RIGHT_RELAY, OUTPUT);
pinMode(WARNING_LED, OUTPUT);
pinMode(LEFT_BUTTON, INPUT_PULLUP);
pinMode(RIGHT_BUTTON, INPUT_PULLUP);
pinMode(HAZARD_BUTTON, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("Turn signals and hazard flasher");
if (!ina.begin()) Serial.println("No INA219 at 0x40: check SDA and SCL");
ina.setCalibration_32V_1A();
delay(5);
Serial.print("Battery ");
Serial.print(ina.getBusVoltage_V(), 1);
Serial.println(" V");
}
void loop() {
// The state machine's inputs: three debounced buttons.
if (pressed(buttons[0])) stalk = (stalk == LEFT) ? OFF : LEFT;
if (pressed(buttons[1])) stalk = (stalk == RIGHT) ? OFF : RIGHT;
if (pressed(buttons[2])) hazard = !hazard;
setMode(hazard ? HAZARD : stalk);
if (mode == OFF) return;
unsigned long now = millis();
unsigned long inPhase = now - phaseStart;
if (lampsOn && inPhase >= SETTLE_MS) {
lastMilliamps = ina.getCurrent_mA();
haveReading = true;
}
unsigned long half = halfPeriod();
if (inPhase >= half) {
if (lampsOn) checkBulbs();
phaseStart += half; // not "= now", so the rate does not drift
haveReading = false;
driveLamps(!lampsOn);
}
}
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() {
// Write the relay pins HIGH before they become outputs, or both relays
// click on for an instant at power-up.
digitalWrite(LEFT_RELAY, HIGH);
digitalWrite(RIGHT_RELAY, HIGH);
pinMode(LEFT_RELAY, OUTPUT);
pinMode(RIGHT_RELAY, OUTPUT);
pinMode(WARNING_LED, OUTPUT);
pinMode(LEFT_BUTTON, INPUT_PULLUP);
pinMode(RIGHT_BUTTON, INPUT_PULLUP);
pinMode(HAZARD_BUTTON, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("Turn signals and hazard flasher");
if (!ina.begin()) Serial.println("No INA219 at 0x40: check SDA and SCL");
ina.setCalibration_32V_1A();
delay(5);
Serial.print("Battery ");
Serial.print(ina.getBusVoltage_V(), 1);
Serial.println(" V");
}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() {
// The state machine's inputs: three debounced buttons.
if (pressed(buttons[0])) stalk = (stalk == LEFT) ? OFF : LEFT;
if (pressed(buttons[1])) stalk = (stalk == RIGHT) ? OFF : RIGHT;
if (pressed(buttons[2])) hazard = !hazard;
setMode(hazard ? HAZARD : stalk);
if (mode == OFF) return;
unsigned long now = millis();
unsigned long inPhase = now - phaseStart;
if (lampsOn && inPhase >= SETTLE_MS) {
lastMilliamps = ina.getCurrent_mA();
haveReading = true;
}
unsigned long half = halfPeriod();
if (inPhase >= half) {
if (lampsOn) checkBulbs();
phaseStart += half; // not "= now", so the rate does not drift
haveReading = false;
driveLamps(!lampsOn);
}
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- 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. - A relay is on at boot or the logic is inverted. Relay board
These boards are active low, so LOW on IN turns the relay on. Set the pin HIGH before setting it as an output so it does not click at start. - The relay clicks but the load does nothing, or the board resets. Relay board
Use the correct contact: COM with NO for normally off. Power the relay coil with enough current, ideally from a separate 5 V supply, with grounds joined. Mains wiring is dangerous, so be careful.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.