Motor starter on an Arduino Uno
START and a fail-safe STOP, a relay and a small DC motor, and an ACS712 that feeds an inverse-time overload trip with a manual RESET. A teaching model at low voltage, not a motor starter for real equipment. 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.
Advanced About 40 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $36. On Mokxi they're free.
What you'll learn
- Motors
- Sensors
- Reading a button
- Choosing and reading a resistor
- How an LED lights and why it needs a resistor
- Difficulty: Advanced
- Time: About 40 minutes
- Board: Arduino Uno R3
- Parts: 14
How it works
START and a fail-safe STOP, a relay and a small DC motor, and an ACS712 that feeds an inverse-time overload trip with a manual RESET. A teaching model at low voltage, not a motor starter for real equipment.
Arduino Uno R3 runs the sketch below. The circuit around it: Pushbutton, Resistor, 220 Ω, LED, green, LED, red, Limit switch, lever microswitch, Current sensor, ACS712. pinMode sets START_PIN as an input with its pull-up on. digitalWrite drives RELAY LOW. digitalRead reads whether b.pin is HIGH or LOW. analogRead measures the voltage on CURRENT as a number. delay waits 2 milliseconds.
Parts list
14 parts, plus the jumper wires and a bench instrument. The parts retail for about $36; on Mokxi they're free, and so are the instruments.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 2 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 2 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, greenIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × Limit switch, lever microswitchIn Claw Machine Kit, Vending Machine Kit.
- 1 × Current sensor, ACS712
- 1 × Relay moduleIn Halloween Prop Kit.
- 1 × DC motor
- 1 × Bench power supplyIn Pinball Table Kit.
- 1 × Slide switchIn Logic Lab Kit, Spaceship Panel Kit.
- 1 × Resistor, 2 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop 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 module pin IN | Arduino Uno R3 pin 8 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 6 |
| Resistor, 220 Ω (1) pin 1 | Arduino Uno R3 pin 5 |
| Pushbutton (2) pin 1a | Arduino Uno R3 pin 4 |
| Limit switch, lever microswitch pin NC | Arduino Uno R3 pin 3 |
| Pushbutton (1) pin 1a | Arduino Uno R3 pin 2 |
| Current sensor, ACS712 pin VCC | Arduino Uno R3 pin 5V |
| Relay module pin VCC | Arduino Uno R3 pin 5V |
| Pushbutton (1) pin 2a | Arduino Uno R3 pin GND |
| Pushbutton (2) pin 2a | Arduino Uno R3 pin GND |
| LED, green pin C | Arduino Uno R3 pin GND |
| LED, red pin C | Arduino Uno R3 pin GND |
| Limit switch, lever microswitch pin COM | Arduino Uno R3 pin GND |
| Current sensor, ACS712 pin GND | Arduino Uno R3 pin GND |
| Relay module pin GND | Arduino Uno R3 pin GND |
| DC motor pin 2 | Arduino Uno R3 pin GND |
| Bench power supply pin V- | Arduino Uno R3 pin GND |
| Resistor, 2 Ω pin 2 | Arduino Uno R3 pin GND |
| Current sensor, ACS712 pin OUT | Arduino Uno R3 pin A0 |
| LED, green pin A | Resistor, 220 Ω (1) pin 2 |
| LED, red pin A | Resistor, 220 Ω (2) pin 2 |
| Bench power supply pin V+ | Current sensor, ACS712 pin IP+ |
| Relay module pin COM | Current sensor, ACS712 pin IP- |
| DC motor pin 1 | Relay module pin NO |
| Slide switch pin 2 | Relay module pin NO |
| Resistor, 2 Ω pin 1 | Slide switch pin 3 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Motor starter: START and STOP buttons, a relay, a small DC motor and an
// ACS712 current sensor that trips on overload.
//
// A TEACHING MODEL, NOT A REAL STARTER. This is a low voltage stand-in (a 6 V
// motor on a bench supply) for the control logic of a motor starter. Never
// use it to switch a motor on mains or three-phase power, and never in place
// of a listed contactor and overload relay.
//
// pin 2 START button, normally open, other leg to GND
// pin 3 STOP button, wired normally closed, other leg to GND
// pin 4 RESET button, normally open, other leg to GND
// pin 5 green RUN LED, through 220 ohms
// pin 6 red TRIP LED, through 220 ohms
// pin 8 the relay module's IN (its VCC on 5 V, GND on GND)
// A0 the ACS712's OUT (its VCC on 5 V, GND on GND)
//
// The motor's own loop is the bench supply, the ACS712's copper, the relay's
// COM and NO contacts and the motor. It never touches the Arduino's circuit.
//
// To try an overload, slide the JAM switch: it puts a 2 ohm load across the
// motor, about 3 A at 6 V, as a locked rotor would. Turn the supply down and
// the same switch gives a milder overload.
//
// STOP IS FAIL-SAFE. The STOP button is wired through its normally closed
// contact to GND, so the pin reads LOW while the button is at rest and HIGH
// when it is pressed. A broken wire reads HIGH too (the chip's pull-up wins),
// and HIGH means stop. So a cut STOP wire stops the motor and keeps it from
// starting, instead of leaving a STOP button that does nothing. STOP works in
// every state and beats START.
//
// SEAL-IN. START is a press, not a switch. One press turns the relay on, and
// the sketch holds it on after the button is let go, as a real starter's
// contactor holds itself in. STOP or a trip lets it go.
//
// OVERLOAD, INVERSE TIME. A heat accumulator stands in for the overload
// relay's heater. Every 50 ms, while the motor runs over the setpoint, heat
// rises by (I^2 - Iset^2) / Iset^2 times RATE; under the setpoint it falls by
// COOL. At 100% the starter trips. The bigger the overload the faster the
// heat builds (current squared), so a locked or jammed motor trips in a few
// seconds and a mild overload takes much longer. The surge as the motor
// spins up is short, so it never fills the accumulator.
//
// AFTER A TRIP the relay stays off. START is ignored and a start request is
// not remembered. The starter comes back only when RESET is pressed after the
// heat has cooled below RESET_HEAT. A RESET pressed while still hot is
// refused and not remembered either. After RESET the motor stays off until a
// new press of START: there is no automatic restart, so a motor never starts
// by itself.
//
// Serial (115200): `STATE run amps 0.8 heat 12%` every half second, and
// event lines: START, STOP, TRIP overload, RESET ok, RESET refused.
const int START_PIN = 2;
const int STOP_PIN = 3;
const int RESET_PIN = 4;
const int RUN_LED = 5;
const int TRIP_LED = 6;
const int RELAY = 8;
const int CURRENT = A0;
// ACS712 5 A board: 185 mV an amp, 5 V and 1023 steps, so 26.42 mA a step.
const long MA_PER_STEP_X100 = 2642;
// Overload model. Setpoint is the motor's full-load current.
const long SETPOINT_MA = 400;
const long RATE_HUNDREDTHS = 100; // 1 % of heat a second for each unit of (I/Iset)^2 - 1
const long COOL_HUNDREDTHS = 300; // 3 % a second under the setpoint, or off
const long TRIP_HEAT = 10000; // 100.00 %
const long RESET_HEAT = 3000; // RESET is accepted below 30.00 %
const unsigned long TICK_MS = 50;
enum State { OFF, RUN, TRIPPED };
State state = OFF;
struct Button {
int pin;
bool stable; // the debounced level, true = HIGH
bool raw;
unsigned long since;
};
Button startBtn = {START_PIN, true, true, 0};
Button stopBtn = {STOP_PIN, false, false, 0}; // NC: LOW at rest
Button resetBtn = {RESET_PIN, true, true, 0};
int zeroSteps = 512;
long heat = 0; // hundredths of a percent
long amps_mA = 0;
unsigned long lastTick = 0;
unsigned long lastReport = 0;
// Returns true on the loop the debounced level changes.
bool update(Button &b) {
bool level = digitalRead(b.pin) == HIGH;
if (level != b.raw) {
b.raw = level;
b.since = millis();
}
if (b.raw != b.stable && millis() - b.since >= 20) {
b.stable = b.raw;
return true;
}
return false;
}
// The current in the motor's lead, in mA: the mean of 16 readings.
long readMilliamps() {
long sum = 0;
for (int i = 0; i < 16; i++) sum += analogRead(CURRENT);
long steps = sum / 16 - zeroSteps;
if (steps < 0) steps = -steps;
return steps * MA_PER_STEP_X100 / 100;
}
void printTenths(long mA) {
long tenths = (mA + 50) / 100;
Serial.print(tenths / 10);
Serial.print('.');
Serial.print(tenths % 10);
}
void report(const char *name) {
Serial.print("STATE ");
Serial.print(name);
Serial.print(" amps ");
printTenths(amps_mA);
Serial.print(" heat ");
Serial.print(heat / 100);
Serial.println('%');
}
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: motor starter (low voltage teaching model)");
pinMode(START_PIN, INPUT_PULLUP);
pinMode(STOP_PIN, INPUT_PULLUP);
pinMode(RESET_PIN, INPUT_PULLUP);
pinMode(RUN_LED, OUTPUT);
pinMode(TRIP_LED, OUTPUT);
pinMode(RELAY, OUTPUT);
digitalWrite(RELAY, LOW);
// The motor is off, so the sensor reads its zero.
long sum = 0;
for (int i = 0; i < 64; i++) {
sum += analogRead(CURRENT);
delay(2);
}
zeroSteps = (int)(sum / 64);
startBtn.stable = startBtn.raw = digitalRead(START_PIN) == HIGH;
stopBtn.stable = stopBtn.raw = digitalRead(STOP_PIN) == HIGH;
resetBtn.stable = resetBtn.raw = digitalRead(RESET_PIN) == HIGH;
report("off");
}
void loop() {
// A press is a change to LOW. A held button makes one press, not many.
bool startPress = update(startBtn) && !startBtn.stable;
bool resetPress = update(resetBtn) && !resetBtn.stable;
update(stopBtn);
bool stopOpen = stopBtn.stable; // HIGH: pressed, or the wire is broken
// STOP works in every state and beats everything else.
if (stopOpen && state == RUN) {
state = OFF;
Serial.println("STOP");
}
// A start request lasts one pass of the loop. It is never kept for later.
if (startPress) {
if (state == OFF && !stopOpen) {
state = RUN;
Serial.println("START");
} else if (state == TRIPPED) {
Serial.println("START ignored, tripped");
} else if (stopOpen) {
Serial.println("START ignored, STOP open");
}
}
if (resetPress && state == TRIPPED) {
if (heat < RESET_HEAT) {
state = OFF;
Serial.println("RESET ok");
} else {
Serial.print("RESET refused, heat ");
Serial.print(heat / 100);
Serial.println('%');
}
}
digitalWrite(RELAY, state == RUN ? HIGH : LOW);
if (millis() - lastTick >= TICK_MS) {
lastTick = millis();
amps_mA = state == RUN ? readMilliamps() : 0;
long q = amps_mA * amps_mA / 1000;
long s = SETPOINT_MA * SETPOINT_MA / 1000;
if (state == RUN && q > s) {
heat += (q - s) * RATE_HUNDREDTHS * (long)TICK_MS / (s * 1000L);
} else {
heat -= COOL_HUNDREDTHS * (long)TICK_MS / 1000L;
}
if (heat < 0) heat = 0;
if (heat >= TRIP_HEAT) {
heat = TRIP_HEAT;
if (state == RUN) {
state = TRIPPED;
digitalWrite(RELAY, LOW);
Serial.println("TRIP overload");
}
}
}
digitalWrite(RUN_LED, state == RUN ? HIGH : LOW);
if (state == TRIPPED) {
// Fast blink while hot, steady once cool enough to reset.
bool on = heat < RESET_HEAT ? true : (millis() / 125) % 2;
digitalWrite(TRIP_LED, on ? HIGH : LOW);
} else {
digitalWrite(TRIP_LED, LOW);
}
if (millis() - lastReport >= 500) {
lastReport = millis();
report(state == RUN ? "run" : state == TRIPPED ? "trip" : "off");
}
}
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);
Serial.println("Mokxi Arduino Uno: motor starter (low voltage teaching model)");
pinMode(START_PIN, INPUT_PULLUP);
pinMode(STOP_PIN, INPUT_PULLUP);
pinMode(RESET_PIN, INPUT_PULLUP);
pinMode(RUN_LED, OUTPUT);
pinMode(TRIP_LED, OUTPUT);
pinMode(RELAY, OUTPUT);
digitalWrite(RELAY, LOW);
// The motor is off, so the sensor reads its zero.
long sum = 0;
for (int i = 0; i < 64; i++) {
sum += analogRead(CURRENT);
delay(2);
}
zeroSteps = (int)(sum / 64);
startBtn.stable = startBtn.raw = digitalRead(START_PIN) == HIGH;
stopBtn.stable = stopBtn.raw = digitalRead(STOP_PIN) == HIGH;
resetBtn.stable = resetBtn.raw = digitalRead(RESET_PIN) == HIGH;
report("off");
}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() {
// A press is a change to LOW. A held button makes one press, not many.
bool startPress = update(startBtn) && !startBtn.stable;
bool resetPress = update(resetBtn) && !resetBtn.stable;
update(stopBtn);
bool stopOpen = stopBtn.stable; // HIGH: pressed, or the wire is broken
// STOP works in every state and beats everything else.
if (stopOpen && state == RUN) {
state = OFF;
Serial.println("STOP");
}
// A start request lasts one pass of the loop. It is never kept for later.
if (startPress) {
if (state == OFF && !stopOpen) {
state = RUN;
Serial.println("START");
} else if (state == TRIPPED) {
Serial.println("START ignored, tripped");
} else if (stopOpen) {
Serial.println("START ignored, STOP open");
}
}
if (resetPress && state == TRIPPED) {
if (heat < RESET_HEAT) {
state = OFF;
// ... 45 more lines in the editorTroubleshooting
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. - The input reads the wrong level, or floats when released. Limit switch
Use COM and one contact. NC is closed at rest, NO closes when pressed. Add a pull-up or INPUT_PULLUP so the pin never floats. - Readings chatter when the lever moves. Limit switch
Contacts bounce for a few milliseconds. Debounce in software with a 10 to 20 ms delay, or add a small capacitor across the switch.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.