Iron Man Repulsor Glove PCB (with laser)
This PCB will allow you to build your very own Iron Man repulsor glove, complete with movement based lights and sounds! for more details, ingluding the materials used, and how to assemble the glove, please watch the full length tutorial on my YouTube channel here:
.
#include <WiFi.h>
#include <ESP32Servo.h>
#include <esp_now.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
#include <FastLED.h>
#include <Wire.h>
// ----------------------------- SETTINGS -----------------------------
#define BUTTON_PIN 1
#define NEOPIXEL_PIN 10
#define NUM_PIXELS 8
#define I2C_SDA_PIN 5
#define I2C_SCL_PIN 6
#define LASER_SERVO_PIN 11
#define LASER_LED_PIN 2
// ----------------------------- LED BRIGHTNESS -----------------------
#define BRIGHTNESS_ANIMATED 200
#define BRIGHTNESS_STATIC 150
// ----------------------------- ESP-NOW MAC ADDRESSES ------------------------
uint8_t receiverAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t soundBoardMAC[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t leftRepulsorMAC[6] = { 0x00 ,0x00, 0x00, 0x00, 0x00, 0x00 };
// ----------------------------- OBJECTS --------------------------------------
Adafruit_ADXL345_Unified accel(12345);
CRGB leds[NUM_PIXELS];
Servo laserServo;
// ----------------------------- LASER SERVOS ---------------------------------
int laserClosedPos = 180;
int laserOpenPos = 160;
int laserServoTarget = laserClosedPos;
int laserServoPos = laserClosedPos;
int laserOpenSpeed = 7;
int laserCloseSpeed = 7;
bool detachLaserServoOpen = false;
bool detachLaserServoClosed = true;
unsigned long laserDetachTime = 0;
unsigned long lastLaserUpdate = 0;
const unsigned long laserUpdateInterval = 20;
// ----------------------------- BUTTON VARIABLES -----------------------------
unsigned long buttonPressStartTime = 0;
const unsigned long longPressThreshold = 5000; // 5000ms for animated/static toggle
bool buttonDown = false;
unsigned long lastButtonUpTime = 0;
const unsigned long doubleClickDelay = 500;
int clickCount = 0;
// ----------------------------- REPULSOR STATE -----------------------------
enum FadeState { OFF, FADING_ON, ON, FADING_OFF, ALWAYS_ON, BLAST };
FadeState fadeState = OFF;
unsigned long fadeStartTime = 0;
const unsigned long fadeDuration = 500;
enum LedMode { ANIMATED, STATIC };
LedMode repulsorMode = ANIMATED;
const int filterSize = 20;
float angleBuffer[filterSize] = {0};
int bufferIndex = 0;
const unsigned long angleCheckInterval = 50;
unsigned long lastAngleCheckTime = 0;
unsigned long blastStartTime = 0;
const unsigned long blastDuration = 1500;
const unsigned long blastCooldown = 1000;
bool blastTriggered = false;
unsigned long lastBlastTime = 0;
float lastAccelMag = 0;
// ----------------------------- LASER FLICKER CONTROL -----------------------
unsigned long flickerDelayAfterOpen = 200;
unsigned long flickerTriggerTime = 0;
bool flickerScheduled = false;
bool flickering = false;
int flickerTotalCount = 4;
int flickerInterval = 30;
int remainingFlickers = 0;
int flickerState = 0;
unsigned long flickerStartTime = 0;
// ----------------------------- UTILITIES ------------------------------------
bool isValidMac(const uint8_t *addr) {
for (int i = 0; i < 6; ++i) if (addr[i] != 0x00) return true;
return false;
}
void safeAddPeer(const uint8_t *addr, const char *label) {
if (!isValidMac(addr)) return;
esp_now_peer_info_t p = {};
memcpy(p.peer_addr, addr, 6);
p.channel = 0;
p.encrypt = false;
esp_now_add_peer(&p);
}
void sendWirelessData(uint8_t data, const uint8_t *addr) {
if (!isValidMac(addr)) return;
esp_now_send(addr, &data, sizeof(data));
}
float calculateMovingAverage(float newValue) {
static float sum = 0;
static float readings[filterSize];
sum -= readings[bufferIndex];
readings[bufferIndex] = newValue;
sum += newValue;
bufferIndex = (bufferIndex + 1) % filterSize;
return sum / filterSize;
}
// ----------------------------- LASER CONTROL -------------------------------
void toggleLaser() {
// Reverse direction immediately, even mid-move
if (laserServoPos != laserServoTarget) {
laserServoTarget = (laserServoTarget == laserClosedPos) ? laserOpenPos : laserClosedPos;
} else {
laserServoTarget = (laserServoPos == laserClosedPos) ? laserOpenPos : laserClosedPos;
}
// Schedule flicker if opening
if (laserServoTarget == laserOpenPos) {
flickerScheduled = true;
flickerTriggerTime = millis() + flickerDelayAfterOpen;
digitalWrite(LASER_LED_PIN, LOW);
} else {
flickerScheduled = false;
digitalWrite(LASER_LED_PIN, LOW);
}
// Ensure servo is attached and clear detach
if (!laserServo.attached()) laserServo.attach(LASER_SERVO_PIN);
laserDetachTime = 0;
}
void updateLaserServo() {
unsigned long now = millis();
if (now - lastLaserUpdate < laserUpdateInterval) return;
lastLaserUpdate = now;
if (laserServoPos != laserServoTarget) {
if (!laserServo.attached()) laserServo.attach(LASER_SERVO_PIN);
int step = (laserServoTarget > laserServoPos) ? 1 : -1;
int speed = (laserServoTarget > laserServoPos) ? laserOpenSpeed : laserCloseSpeed;
int moveAmount = step * speed;
if (step > 0) moveAmount = min(moveAmount, laserServoTarget - laserServoPos);
else moveAmount = max(moveAmount, laserServoTarget - laserServoPos);
laserServoPos += moveAmount;
laserServo.write(laserServoPos);
laserDetachTime = 0;
if (laserServoPos == laserServoTarget) {
bool detachFlag = (laserServoPos == laserOpenPos) ? detachLaserServoOpen : detachLaserServoClosed;
if (detachFlag) laserDetachTime = now + 2000;
}
}
if (laserDetachTime && laserServoPos == laserServoTarget && now >= laserDetachTime) {
laserServo.detach();
laserDetachTime = 0;
}
}
// ----------------------------- BUTTON HANDLING ------------------------------
void handleButtonPress() {
int buttonState = digitalRead(BUTTON_PIN);
unsigned long now = millis();
if (buttonState == LOW && !buttonDown) {
buttonDown = true;
buttonPressStartTime = now;
}
if (buttonState == HIGH && buttonDown) {
buttonDown = false;
unsigned long pressDuration = now - buttonPressStartTime;
if (pressDuration >= longPressThreshold) {
if (repulsorMode == STATIC) {
repulsorMode = ANIMATED;
fadeState = OFF;
fadeStartTime = millis();
} else {
repulsorMode = STATIC;
fadeState = ALWAYS_ON;
}
} else {
clickCount++;
if (clickCount == 1) lastButtonUpTime = now;
}
}
if (clickCount == 1 && now - lastButtonUpTime > doubleClickDelay) {
sendWirelessData(101, receiverAddress);
clickCount = 0;
}
if (clickCount == 2) {
toggleLaser();
clickCount = 0;
}
}
// ----------------------------- REPULSOR – ANIMATED MODE -------------------
void handleAccelerometer() {
unsigned long now = millis();
if (now - lastAngleCheckTime < angleCheckInterval) return;
lastAngleCheckTime = now;
sensors_event_t evt;
accel.getEvent(&evt);
float angle = atan2(evt.acceleration.y, evt.acceleration.z) * 180 / PI;
float filtered = calculateMovingAverage(angle);
float accelMag = sqrt(evt.acceleration.x*evt.acceleration.x +
evt.acceleration.y*evt.acceleration.y +
evt.acceleration.z*evt.acceleration.z);
float accelMagChange = accelMag - lastAccelMag;
lastAccelMag = accelMag;
if (!blastTriggered && fadeState == ON && now - lastBlastTime > blastCooldown) {
if (accelMag > 9.0 && accelMagChange < -4.0) {
fadeState = BLAST;
blastTriggered = true;
blastStartTime = now;
lastBlastTime = now;
sendWirelessData(13, soundBoardMAC);
}
}
if (fadeState == BLAST && (now - blastStartTime > blastDuration)) {
blastTriggered = false;
fadeState = ON;
}
if (filtered <= -60 && fadeState == OFF) {
sendWirelessData(12, soundBoardMAC);
fadeState = FADING_ON;
fadeStartTime = now;
} else if (filtered >= -35 && fadeState == ON) {
sendWirelessData(11, soundBoardMAC);
fadeState = FADING_OFF;
fadeStartTime = now;
}
}
// ----------------------------- NEOPIXELS -------------------------------------
void handleNeoPixels() {
unsigned long now = millis();
CRGB baseColor = (repulsorMode == STATIC) ? CRGB(BRIGHTNESS_STATIC,BRIGHTNESS_STATIC,BRIGHTNESS_STATIC)
: CRGB(BRIGHTNESS_ANIMATED,BRIGHTNESS_ANIMATED,BRIGHTNESS_ANIMATED);
switch(fadeState) {
case OFF: fill_solid(leds, NUM_PIXELS, CRGB::Black); break;
case FADING_ON: {
int b = map(now - fadeStartTime, 0, fadeDuration, 0, 255);
CRGB fadedColor = baseColor;
fadedColor.nscale8_video(b);
fill_solid(leds, NUM_PIXELS, fadedColor);
if (now - fadeStartTime >= fadeDuration) fadeState = ON;
break;
}
case ON: fill_solid(leds, NUM_PIXELS, baseColor); break;
case FADING_OFF: {
int b = map(now - fadeStartTime, 0, fadeDuration, 255, 0);
CRGB fadedColor = baseColor;
fadedColor.nscale8_video(b);
fill_solid(leds, NUM_PIXELS, fadedColor);
if (now - fadeStartTime >= fadeDuration) fadeState = OFF;
break;
}
case ALWAYS_ON: fill_solid(leds, NUM_PIXELS, baseColor); break;
case BLAST:
for (int i=0;i<NUM_PIXELS;i++){
int flick = random(100,150);
leds[i] = CRGB(flick,flick,flick);
}
break;
}
FastLED.show();
}
// ----------------------------- LASER FLICKER EXECUTION ----------------------
void executeFlickerEffect() {
unsigned long now = millis();
if (now - flickerStartTime >= flickerInterval) {
flickerState = !flickerState;
digitalWrite(LASER_LED_PIN, flickerState);
if (flickerState == 0) {
remainingFlickers--;
if (remainingFlickers == 0) {
flickering = false;
digitalWrite(LASER_LED_PIN, HIGH);
}
}
flickerStartTime = now;
}
}
// ----------------------------- SETUP ----------------------------------------
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("=== Repulsor Glove + Laser Servo Boot ===");
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LASER_LED_PIN, OUTPUT);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) Serial.println("ESP-NOW init failed");
safeAddPeer(receiverAddress,"Receiver");
safeAddPeer(leftRepulsorMAC,"Left Glove");
safeAddPeer(soundBoardMAC,"Soundboard");
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
if(!accel.begin()) { Serial.println("ADXL345 not detected!"); while(1) delay(1000); }
FastLED.addLeds<WS2812, NEOPIXEL_PIN, GRB>(leds, NUM_PIXELS);
FastLED.clear(); FastLED.show();
laserServoPos = laserClosedPos;
laserServoTarget = laserClosedPos;
laserServo.attach(LASER_SERVO_PIN);
laserServo.write(laserClosedPos);
delay(200);
laserServo.detach();
}
// ----------------------------- LOOP -----------------------------------------
void loop() {
handleButtonPress();
handleAccelerometer(); // ADDED: Repulsor triggers
handleNeoPixels();
updateLaserServo();
if (flickerScheduled && millis() >= flickerTriggerTime) {
flickerScheduled = false;
flickering = true;
remainingFlickers = flickerTotalCount;
flickerStartTime = millis();
}
if (flickering) executeFlickerEffect();
}
Iron Man Repulsor Glove PCB (with laser)
Project images are for reference only. Actual production is based on the manufacturing files on the project page.
Please review the designer's notes (e.g., PCB thickness) and select the appropriate options.
PCBWay is not responsible
for issues caused by unsuitable parameter selections.
For more important ordering information, please refer to
Read More
Raspberry Pi 5 7 Inch Touch Screen IPS 1024x600 HD LCD HDMI-compatible Display for RPI 4B 3B+ OPI 5 AIDA64 PC Secondary Screen(Without Speaker)
BUY NOW- Comments(1)
- Likes(1)
-
Engineer
Jan 03,2026
- 1 USER VOTES
- YOUR VOTE 0.00 0.00
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
10design
-
10usability
-
10creativity
-
10content
More by Plentiful_Props_3D
-
ESP32-S3 breakout board for motorizing Iron Man helmets
this PCB will allow you to create a breakcout board for a WaveShare ESP32-S3 specifically for the pu...
-
Iron Man Motorized helmet breakout board for ESP32 S3 Mini
Please watch this YouTube tutorial covering how to 3D print, assemble, solder the PCB, and upload th...
-
Iron Man Repulsor Glove PCB (with laser)
This PCB will allow you to build your very own Iron Man repulsor glove, complete with movement based...
-
Helldivers 2 Strataem Ball Prop PCB
This PCB will allow you to add lights and sounds to the MakerLab Stratagem Beacon from Helldivers 2!...
-
PP3D ESP32 breakout board for Iron Man helmet voice recognition module
this PCB is abreakout board for a WaveShare ESP32-S3 which will allow you to easily wire up and cont...
-
Iron Man Repulsor Glove PCB
This PCB will allow you to build your very own Iron Man repulsor glove, complete with movement based...
-
Helldivers 2 Sample Container Prop
Check out the drive link to get access to the other PCB gerber files (common sample and RGB array) a...
-
Iron Man Arc Reactor Prop
these PCBs combined with the modified crashworks code will allow you to make an illuminated Arc Reac...
-
Breakout Board for Motorizing Iron Man Cosplay Helmets!
This is a PCB breakout board intended to be used for motorizing Iron Man helmets. With an ATTiny85 U...
-
Programmable Mist Maker - XIAO / QT PY Extension
1150 2 1 -
RadioHAT - Raspberry Pi radio development platform
961 0 2 -
QWIIC-VL53L4CD Time-of-Flight Distance Sensor Module
1170 0 1 -
-
-
-
ARPS-2 – Arduino-Compatible Robot Project Shield for Arduino UNO
3382 0 6 -
-
A Compact Charging Breakout Board For Waveshare ESP32-C3
4001 3 8 -
AI-driven LoRa & LLM-enabled Kiosk & Food Delivery System
4391 2 2







