Arc reactor and faceplate on an Arduino Uno
An arc reactor style chest light on a breathing 16 LED ring, and a servo faceplate that glides open on a touch or a button while the eyes fade out. The whole build, an Arduino Uno and 9 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.
Build it as the Arc Reactor Kit: the parts in numbered bags and a booklet that checks every step. Retails for about $44, free on Mokxi.
What you'll learn
- Fading a light smoothly with PWM
- Making a glow look like breathing
- Moving a servo slowly instead of snapping it
- Two inputs for one action: button and touch
- Timing with millis() instead of delay()
- Difficulty: Intermediate
- Time: About 40 minutes
- Board: Arduino Uno R3
- Parts: 11
How it works
A 16 LED ring on pin 6 glows blue-white and slowly swells and fades, about 33 times a second. Two white LEDs on pins 5 and 3 are the helmet's eyes. A hobby servo on pin 9 is the faceplate.
Press the button on pin 2 or touch the TTP223 pad on pin 4. The eyes fade to dark first, then the servo glides the plate from 10 to 110 degrees, two degrees every 15 ms.
Trigger it again and the plate glides shut. Once it is closed, the eyes come back with a quick 400 ms flicker, like a system booting.
Parts list
11 parts, plus the jumper wires. They retail for about $38; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × Addressable RGB LEDsIn Claw Machine Kit, Halloween Prop Kit, Christmas Lights Kit.
- 1 × Touch sensor, TTP223In Desk Buddy Kit, Arc Reactor Kit.
- 1 × Hobby servoIn Halloween Prop Kit, Christmas Lights Kit, Starter Projects Kit.
- 2 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 2 × LED, whiteIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × Resistor, 330 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab 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 |
|---|---|
| Hobby servo pin PWM | Arduino Uno R3 pin 9 |
| Resistor, 330 Ω pin 1 | Arduino Uno R3 pin 6 |
| Resistor, 220 Ω (1) pin 1 | Arduino Uno R3 pin 5 |
| Touch sensor, TTP223 pin SIG | Arduino Uno R3 pin 4 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 3 |
| Pushbutton pin 1a | Arduino Uno R3 pin 2 |
| Addressable RGB LEDs pin VDD | Arduino Uno R3 pin 5V |
| Touch sensor, TTP223 pin VCC | Arduino Uno R3 pin 5V |
| Hobby servo pin VCC | Arduino Uno R3 pin 5V |
| Addressable RGB LEDs pin VSS | Arduino Uno R3 pin GND |
| Touch sensor, TTP223 pin GND | Arduino Uno R3 pin GND |
| Hobby servo pin GND | Arduino Uno R3 pin GND |
| LED, white (1) pin C | Arduino Uno R3 pin GND |
| LED, white (2) pin C | Arduino Uno R3 pin GND |
| Pushbutton pin 2a | Arduino Uno R3 pin GND |
| Resistor, 330 Ω pin 2 | Addressable RGB LEDs pin DIN |
| LED, white (1) pin A | Resistor, 220 Ω (1) pin 2 |
| LED, white (2) pin A | Resistor, 220 Ω (2) pin 2 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Arc reactor: an arc reactor style chest light and a helmet faceplate.
//
// pin 6 the reactor: a 16 LED WS2812 ring, through 330 ohm on the data line
// pin 9 the faceplate servo's signal
// pin 2 the gauntlet button, other side to GND (internal pull-up)
// pin 4 the TTP223 touch pad on the helmet (HIGH while touched)
// pin 5 the left eye, through 220 ohm
// pin 3 the right eye, through 220 ohm
//
// Touch the pad or press the button to open the faceplate; do it again to
// close it. The eyes fade out before the plate lifts and flicker back on
// after it closes, and the reactor breathes the whole time.
//
// # Breathing
//
// A steady light looks like a lamp. A light that slowly swells and fades looks
// alive. The sketch walks a phase from 0 to 255 and back, and turns it into a
// brightness between GLOW_LOW and full with a gentle curve, so the light
// lingers at the top and bottom the way breathing does.
//
// # Moving a servo gently
//
// servo.write(110) would slam the plate open as fast as the motor can go. The
// sketch moves it two degrees every 15 ms instead, so it glides, and keeps
// the reactor breathing in between steps.
#include <Adafruit_NeoPixel.h>
#include <Servo.h>
const int RING_PIN = 6;
const int SERVO_PIN = 9;
const int BUTTON = 2;
const int TOUCH = 4;
const int EYE_LEFT = 5;
const int EYE_RIGHT = 3;
const int LEDS = 16;
const int CLOSED = 10; // faceplate angles, in degrees
const int OPEN = 110;
const int GLOW_LOW = 50; // the dimmest the reactor breathes down to
Adafruit_NeoPixel ring(LEDS, RING_PIN, NEO_GRB + NEO_KHZ800);
Servo plate;
int angle = CLOSED;
int target = CLOSED;
int eyes = 0; // eye brightness now, 0 to 255
int eyesTarget = 255;
int phase = 0;
int phaseStep = 3;
bool lastButton = false;
bool lastTouch = false;
unsigned long buttonAt = 0;
unsigned long frameAt = 0;
unsigned long servoAt = 0;
unsigned long flickerUntil = 0;
// The reactor's color at `level`: a cool blue-white, the classic glow.
void glow(int level) {
ring.fill(ring.Color(level * 40 / 255, level * 180 / 255, level));
ring.show();
}
void setEyes(int level) {
analogWrite(EYE_LEFT, level);
analogWrite(EYE_RIGHT, level);
}
void toggle(const char *why) {
if (target == CLOSED) {
target = OPEN;
eyesTarget = 0;
Serial.print("faceplate open (");
} else {
target = CLOSED;
Serial.print("faceplate closed (");
}
Serial.print(why);
Serial.println(")");
}
void setup() {
pinMode(BUTTON, INPUT_PULLUP);
pinMode(TOUCH, INPUT);
pinMode(EYE_LEFT, OUTPUT);
pinMode(EYE_RIGHT, OUTPUT);
Serial.begin(115200);
plate.attach(SERVO_PIN);
plate.write(CLOSED);
ring.begin();
ring.setBrightness(200);
// Power up: one LED at a time around the ring, then the whole core.
for (int i = 0; i < LEDS; i++) {
ring.setPixelColor(i, ring.Color(30, 140, 200));
ring.show();
delay(40);
}
Serial.println("Arc reactor online");
}
void loop() {
unsigned long now = millis();
// Inputs: the button on a falling edge, debounced; the touch pad on a
// rising edge (the TTP223 does its own debouncing).
bool button = digitalRead(BUTTON) == LOW;
if (button && !lastButton && now - buttonAt > 200) {
buttonAt = now;
toggle("button");
}
lastButton = button;
bool touch = digitalRead(TOUCH) == HIGH;
if (touch && !lastTouch) toggle("touch");
lastTouch = touch;
// The faceplate: wait for the eyes to go dark before opening, then glide.
if (now - servoAt >= 15) {
servoAt = now;
bool eyesReady = target == CLOSED || eyes == 0;
if (angle != target && eyesReady) {
angle = angle < target ? min(angle + 2, target) : max(angle - 2, target);
plate.write(angle);
if (angle == CLOSED) {
// Closed: the eyes come back with a flicker, like a system booting.
eyesTarget = 255;
flickerUntil = now + 400;
}
}
plate.refresh();
}
// Thirty-three frames a second for the ring and the eyes.
if (now - frameAt < 30) return;
frameAt = now;
phase += phaseStep;
if (phase >= 255 || phase <= 0) {
phaseStep = -phaseStep;
phase = constrain(phase, 0, 255);
}
// A soft curve: square the phase so the glow lingers near the bottom.
long curve = (long)phase * phase / 255;
glow(GLOW_LOW + curve * (255 - GLOW_LOW) / 255);
if (eyes < eyesTarget) eyes = min(255, eyes + 25);
if (eyes > eyesTarget) eyes = max(0, eyes - 25);
if (now < flickerUntil) {
setEyes(random(2) ? eyes : 0);
} else {
setEyes(eyes);
}
}
Code walkthrough
The key functions of the sketch, and what each one does.
glow sketch.ino
Paints the whole ring one color. The blue-white mix comes from scaling red, green and blue by the same level, so a lower level is the same color, just dimmer. It calls ring.show() to send the color to the LEDs.
// The reactor's color at `level`: a cool blue-white, the classic glow.
void glow(int level) {
ring.fill(ring.Color(level * 40 / 255, level * 180 / 255, level));
ring.show();
}setEyes sketch.ino
Writes one brightness to both eye pins with analogWrite. Pins 5 and 3 both support PWM, so a value from 0 to 255 gives smooth fades.
void setEyes(int level) {
analogWrite(EYE_LEFT, level);
analogWrite(EYE_RIGHT, level);
}toggle sketch.ino
Flips the plate's goal between CLOSED and OPEN. It does not move anything itself. When opening, it also sets the eye goal to 0, so the eyes fade before the plate lifts. It prints which input asked.
void toggle(const char *why) {
if (target == CLOSED) {
target = OPEN;
eyesTarget = 0;
Serial.print("faceplate open (");
} else {
target = CLOSED;
Serial.print("faceplate closed (");
}
Serial.print(why);
Serial.println(")");
}loop sketch.ino
Checks the button and the touch pad, then every 15 ms nudges the servo two degrees toward its goal. Every 30 ms it advances the breathing phase, redraws the ring and steps the eyes by 25. Nothing waits, so all of it keeps moving.
void loop() {
unsigned long now = millis();
// Inputs: the button on a falling edge, debounced; the touch pad on a
// rising edge (the TTP223 does its own debouncing).
bool button = digitalRead(BUTTON) == LOW;
if (button && !lastButton && now - buttonAt > 200) {
buttonAt = now;
toggle("button");
}
lastButton = button;
bool touch = digitalRead(TOUCH) == HIGH;
if (touch && !lastTouch) toggle("touch");
lastTouch = touch;
// The faceplate: wait for the eyes to go dark before opening, then glide.
if (now - servoAt >= 15) {
servoAt = now;
bool eyesReady = target == CLOSED || eyes == 0;
if (angle != target && eyesReady) {
angle = angle < target ? min(angle + 2, target) : max(angle - 2, target);
plate.write(angle);
if (angle == CLOSED) {
// Closed: the eyes come back with a flicker, like a system booting.
eyesTarget = 255;
flickerUntil = now + 400;
}
}
// ... 24 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The faceplate never opens, or opens before the eyes go dark. This project
The plate waits untileyesreaches 0. Check the eye LEDs are on pins 5 and 3 through 220 ohm resistors, and that the servo signal is on pin 9. - The ring stays dark or shows random colors. This project
Pin 6 goes through the 330 ohm resistor to DIN. The ring also needs 5V on VDD and ground on VSS. - The button toggles twice or not at all. This project
Wire the button between pin 2 and GND. The sketch uses the internal pull-up, so no outside resistor is needed. - The first LED flickers, shows wrong colors, or the whole strip glitches. Addressable RGB LEDs
Join the grounds of the strip and the controller. Add a 300 to 500 ohm resistor in series with the data line. Put a 1000 uF capacitor across the strip's 5 V and GND. - The strip works at low brightness but dims, turns yellow, or resets when many LEDs go white. Addressable RGB LEDs
Each LED can draw about 60 mA at full white. Use a 5 V supply sized for the count, feed power in at several points, and lower the brightness in code. - It never triggers, or triggers by itself. Touch sensor
Power it with 2.0 V to 5.5 V and share ground. Keep your hand and the wires away from the pad while it powers up, since it sets its baseline at startup. Avoid long wires. - The output is HIGH when you expect LOW. Touch sensor
Pads on the board are jumpers for mode. The default is momentary and active high. Soldering the jumpers can change it to toggle or active low. Check your board and its mode. - The servo jitters, twitches or resets the board when it moves. Hobby servo
Power it from its own 5 V supply, not the Arduino 5 V pin, and join the grounds. A hobby servo can pull several hundred milliamps. Add a 100 uF to 470 uF capacitor across its supply.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.