Bare bones head tracking shoulder cannon circuitry.

PredatoRay31

New Member
I like to do everything on a cheap budget with things I can find in bulk or find easily. I tried for years to get this right and think I have a pretty stable version of a Shoulder cannon that tracks head movement in both Y and Z axis, and for cheap as I think I can get it with COTS items (Common Off The Shelf).
Here's the video:

It's not your typical caster cannon, but I feel the shoulder cannon is like the Jedi knight's light saber. It is built individually....and I liked the look of the 80/20 Aluminum bracketing.

I used an Arduino Uno, 2ea - 5vdc servos, an MPU6050 Arduino module (6 axis), a project box from Amazon, RJ45 (Ethernet) connectors with panel mounts, a panel mounted audio jack, a few things I had around my shop, and a 2000 maH rechargeable battery bank (the ones used for charging your phones...I will upgrade to 4000+ maH for more hours of use). I'll put the code I used in my next comment.
 
Here is the Arduino Code: FYI....you MUST install both the "I2CDEV" and "MPU6050" libraries into the Arduino IDE. Ensure you do the Zero Sketch from the MPU6050 examples first and input those zeroing numbers (look for gyro offset comment) into the Arduino code below. Each MPU6050 will be slightly different.

It is set up to scan the servos when walking around. Pressing the fire laser button will lock the servos on the last known position, make an LED increase in intensity over a short duration, fire the laser pointer for 0.5s, then dim the LED down fast to off. I did this to simulate it charging up, firing, then powering down. It will only fire once per button push. You could code in a small recoil of the Y Axis servo if you wanted when fired. If you have any questions...please ask. the servos will automatically go to whatever position the MPU sensor is at once the firing function is complete. I can try to add any additional functions that could make it even more awesome. Just need some time to code it in and make sure it works.

Code:
/*
   Predator Shoulder Ca
   nnon v2
   by: Raymond Willis 8/1/17
   r.willis_pi@yahoo.com

   I edited serveral different pre-existing programs to
   create this one. Too many to list all sources. If you
   see your code, thank you.
 
   This program uses 2 servos, 1 MPU6050 3 AXIS Module, 1 LED,
   and 1 Laser Diode to have the servo mounted shoulder cannon
   track your head position like in the Predator movies and
   when a button is pressed the LED birghtens, fires the laser
   diode for 0.5s, and then dims the LED (simulate a build up
   of energy, firing, and then energy dissipation).
 
   The I2CDev and MPU6050 libraries are required for this program.

   The Servos follow the Y and Z axis of the MPU6050. The
   module should be placed at the top-center of your head
   or mask/helmet. When walking around, the Z axis will drift.
   So at certain degrees for an extended period I assume the user
   is walking around and go into scan mode, then when not walking
   around I reset the MPU to re-zero the Z Axis.

   I commented as much as I can to help anyone else use this.
*/

#include <Servo.h>
#include <string.h>
#include <stdio.h>
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"

Servo myservoY; // Roll
Servo myservoX; // Pitch
Servo myservoZ; // Yaw
int countA = 0;
int countB = 0;
int posY;
int posZ;
int rstZ;
int buttonState = LOW;
int oneFlash = 1; // variable to flash the LED Laser once per each button press.
const int buttonPin = 7; // Mom switch pin, fires laser diode
const int ledPin = 3;  // LED
const int laserPin = 4; // Laser Diode

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif
MPU6050 mpu;

#define OUTPUT_READABLE_YAWPITCHROLL    //Comment or uncomment to output data

#define LED_PIN 13
bool blinkState = false;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

// INTERRUPT DETECTION ROUTINE
volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
  mpuInterrupt = true;
}

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(laserPin, OUTPUT);
  pinMode(buttonPin, INPUT);

  // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  TWBR = 24;
// 400kHz I2C clock (200kHz if CPU is 8MHz)
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  Fastwire::setup(400, true);
#endif

  myservoY.attach(9); // Attach Y servo to pin 9
  //myservoX.attach(10);// Attach X servo to pin 10, not used
  myservoZ.attach(11); // Attach Z servo to pin 11


  Serial.begin(115200);
  while (!Serial); // wait for enumeration, others continue immediately

  // initialize device
  Serial.println(F("Initializing I2C devices..."));
  mpu.initialize();

  // verify connection
  Serial.println(F("Testing device connections..."));
  Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));

  // load and configure the DMP
  Serial.println(F("Initializing DMP..."));
  devStatus = mpu.dmpInitialize();

  // supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(2); //was 49
  mpu.setYGyroOffset(-29); //was -26
  mpu.setZGyroOffset(36);  // was 13
  mpu.setZAccelOffset(923); // 1688 factory default for my test chip 1459

  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    // turn on the DMP, now that it's ready
    Serial.println(F("Enabling DMP..."));
    mpu.setDMPEnabled(true);

    // enable Arduino interrupt detection
    Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
    attachInterrupt(0, dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();

    // set our DMP Ready flag so the main loop() function knows it's okay to use it
    Serial.println(F("DMP ready! Waiting for first interrupt..."));
    dmpReady = true;

    // get expected DMP packet size for later comparison
    packetSize = mpu.dmpGetFIFOPacketSize();
  } else {
    // ERROR!
    // 1 = initial memory load failed
    // 2 = DMP configuration updates failed
    // (if it's going to break, usually the code will be 1)
    Serial.print(F("DMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
  }

  // configure LED for output
  pinMode(LED_PIN, OUTPUT);
}

// Main Program Loop
void loop() {
  // if programming failed, don't try to do anything
  if (!dmpReady) return;

  // wait for MPU interrupt or extra packet(s) available
  while (!mpuInterrupt && fifoCount < packetSize) {
    // other program behavior stuff here if oyu want

  }

  // reset interrupt flag and get INT_STATUS byte
  mpuInterrupt = false;
  mpuIntStatus = mpu.getIntStatus();

  // get current FIFO count
  fifoCount = mpu.getFIFOCount();

  // check for overflow (this should never happen unless our code is too inefficient)
  if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
    // reset so we can continue cleanly
    mpu.resetFIFO();
    Serial.println(F("FIFO overflow!"));

    // otherwise, check for DMP data ready interrupt (this should happen frequently)
  } else if (mpuIntStatus & 0x02) {
    // wait for correct available data length, should be a VERY short wait
    while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();

    // read a packet from FIFO
    mpu.getFIFOBytes(fifoBuffer, packetSize);

    // track FIFO count here in case there is > 1 packet available
    // (this lets us immediately read more without waiting for an interrupt)
    fifoCount -= packetSize;

#ifdef OUTPUT_READABLE_YAWPITCHROLL
    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
    Serial.print("ypr\t");
    Serial.print(ypr[0] * 180 / M_PI);
    myservoZ.write(int(ypr[0] * 180 / M_PI) + 90); // Rotation around Z
    rstZ = (ypr[0] * 180 / M_PI); // Z axis into a variable to test
    Serial.print("\t");
    Serial.print(ypr[1] * 180 / M_PI);
    myservoY.write(int(ypr[1] * 180 / M_PI) + 90); // Rotation around Y
    Serial.print("\t");
    Serial.println(ypr[2] * 180 / M_PI);
    myservoX.write(int(ypr[2] * 180 / M_PI) + 90); // Rotation around X
#endif

    /* If the Z axis servo is max left or right for so many seconds,
       you are probably walking around and we need to re-orient the
       Z Axis gyro's earth reference point to maintain correct orientation
       for tracking head movement.
    */
    if (rstZ > 100 || rstZ < (-100)) { // test if Z Axis is at max L or R
      countA++; // count each time too far L or R
      if (countA >= 3) { //if max L or R, 3 consecutive times..reset MPU
        resetMode();
      }
    }

    if (rstZ < 99 && rstZ > (-99)) { // when outside L or R max
      countA = 0; // reset count to 0, you are likely stationary
    }

    buttonState = digitalRead(buttonPin); //read the firing button state
    if (buttonState == HIGH) { // if the button reads High, the button is pressed
      posY = (ypr[1] * 180 / M_PI); //read Y position
      posZ = (ypr[0] * 180 / M_PI); //read Z position
      myservoY.write(posY + 90); //stop moving and stay at last know positions
      myservoZ.write(posZ + 90);
      laserFire(); //perform the laser firing function
    }

    else if (buttonState == LOW) {
      // If Im not holding the button, turn everything off:
      analogWrite(ledPin, 0);
      digitalWrite(laserPin, LOW);
      oneFlash = 1;
    }
  }
  Serial.print("Reset Count: ");
  Serial.println(countA);
  Serial.print("Laser Fire Button: ");
  Serial.println(buttonState);
}

void resetMode() { // reset function
  Serial.println("You must be walking around so let's reset Z Axis reference point!");
  countA = 0;
  mpu.reset();        // reset the Gyros to reset Z axis reference point
  setup();            // re-initialize everything for new gyro data
}

void scanMode() { // scan mode function
  Serial.println("We are in Auto Scan Mode.");
  posY = 90;                                      // set Y servo to 90 degrees
  for (posZ = 45; posZ <= 115; posZ += 1)         // start scanning Z servo L to R
  {
    myservoZ.write(posZ);
    delay(60);
  }
  for (posZ = 115; posZ >= 45; posZ -= 1)
  {
    myservoZ.write(posZ);
    delay(60);
  }
}

void laserFire() { //Led & laser diode firing function
  if (oneFlash >= 1) {

    for (int fadeValue = 10 ; fadeValue <= 255; fadeValue += 5) {
      delay(10);
      analogWrite(ledPin, fadeValue); // LED glows bright, charging up
    }
    analogWrite(ledPin, 255); // after ramp up, keep on
     //recreate mini recoil
    digitalWrite(laserPin, HIGH); //energize laser diode
    delay (500); // Laser Pointer ON time in millisecs after LED ramp up is done.
    digitalWrite(laserPin, LOW); //laser diode off
    for (int fadeValue = 255 ; fadeValue >= 0; fadeValue -= 10) {
      delay(5);
      analogWrite(ledPin, fadeValue); // LED dims down.
    }
    analogWrite(ledPin, 0); //led off
    oneFlash = 0; //set button count to ensure it only fires once per button press
  }
}
 
Last edited:
What a great work, my friend, that is incredible.I am learning to program with arduino to also mount the computer bomb
 
I cannot express enough how incorporating Arduino Uno, Nano, or any other pre-built microcontroller is cheap and easy. Arduino IDE is free and provides a huge list of examples to get you going in no time.... and an quick Google search can yield even more examples and pre-built libraries to code from. You can get Arduino Nanos or Minis for like 5ea for $15 on Amazon (I even bought the Arduino rip offs and they are great for prop building). Seriously...an Arduino Nano, a few switches, flat LEDs, and you can code some cool wrist gauntlets in no time.
I've been toying with the idea of using Arduino to power a linear actuator to have real in/out gauntlet blades, but even the small linear actuators (or solenoids) are over $100. I would think a movement of 6" or more would be enough for the effect. I'll keep searching.
I have made several things with Arduino and it inspires me to make more. I created a wizard staff that lights up depending on how to tap it on the ground and has a secret reed switch to activate a LED sparkle mode. I have another idea (which I have already made schematics for) in which a real temperature sensor will make the 3 LED lights on Vader's chest box vary in pattern and pattern speed based on actual outside temperature.
 
What a great work, my friend, that is incredible.I am learning to program with arduino to also mount the computer bomb
An Arduino Nano, some switches, and flat LEDS on a breadboard. Just randomize the LED sequencing using random function and a switch case for ON/OFF (HIGH/LOW) state.

Random Function:
Arduino - Random
 
Last edited:
Estaba pensando en usar una matriz led 8x8 para cada pantalla y activarla individualmente

Sorry, I don't understand Spanish very well... I wish I did.

I think I got you are using an LED 8x8 matrix for the display. Me personally I would just use the flat LEDs in the pattern used for gauntlets. Many people in the forums have them in the right pattern. From my understanding, in the movie they did not just use the old LED 7 segment displays...they made their own display pattern in LEDs not manufactured normally like this:
Amazon.com: 10 pcs Plastic Metal Common Anode 1 Bit 7 Segment Red LED Display Digital Tube: Home Audio & Theater

Maybe a 16 segmented display would be close:
0.39" (9.9mm) Single Digit Alpha-Numeric Blue LED Display, Black Face, Common Anode (Pack of 3) - - Amazon.com
 
An example of the number 9 using the language yautja

20170816_023730.jpg
 

As long as it uses the MPU6050, any add on module should work.
But, there is still the issue of mapping rotation around the z axis to properly rotate the servo when turning your head. Reason is it defines a center point when first powered up. You have to redefine a center point after walking around. You can't track while walking around. What I did in the code was knowing I can only rotate my head, in degrees, so far with a mask on. When I go over those points, I assume I must be walking around because when you do the z servo will begin to turn thinking you are rotating on the z centerline but you are actually walking away from it, so I tell the servos to default to make the cannon go straight ahead and check that reading every loop. When it gets back under the limits, I must be stationary so reset the center point and start tracking head movement again. When stationary, this works great.
I may be updating the code. The nice thing about the MPU 6050 is it has 2 accelerometers also incorporated. What I may do is read those to see of I'm walking around to default the servos....not using the limits of my head rotation.
 
holy crap.....great work that is amazing you guys on here are incredible with the practical work effects you create...bravo!
 
Wow. I hope one day to achieve something like this but I am new to electronics and programming but am eager to learn. Hopefully your guides will help me achieve my goals or a working wrist computer and plasma caster
 
This thread is more than 3 years old.

Your message may be considered spam for the following reasons:

  1. This thread hasn't been active in some time. A new post in this thread might not contribute constructively to this discussion after so long.
If you wish to reply despite these issues, check the box below before replying.
Be aware that malicious compliance may result in more severe penalties.
Back
Top