Skip to content

Week 10 - Machine Week: Connor, Alana, Kabir, Angelina

Here is the machine week group assignment for Connor Cruz, Alana Duffy, Kabir Nawaz, and Angelina Yang.

Assignment

  • design a machine that includes mechanism+actuation+automation+application

  • build the mechanical parts and operate it manually

  • document the group project and your individual contribution

Download Design Files

You can access all of our files here and here

Machine Presentation

Video

Slide

Sketching / Planning

Our idea for the machine was to create an arcade-style claw machine. This would be done with a servo controlling a claw in a box made of acrylic and wood, along with a gantry to move the claw in the X and Y axes. The claw is to pick up things from inside and move them toward the output, and it can be controlled with a joystick or buttons at the front of the machine. This was our sketch: Image

Materials

Component Quantity
Creality 42-34 Stepper motor 3
Servo motor 1
Pulleys 8
Arduino Uno 2
CNC motor shield 1
Guide rods/linear axis 3
Belt 2
Arcade push button 1
16x2 I2C LCD 1
Joystick 1
1/4” acrylic sheets 4
1/2” plywood 1
Neopixel strip 1
350mm Stainless Steel MGN12 Linear Motion Rail Guide 3

Stepper Motors

Programing

We intended to use stepper motors to move the 3 axes of the claw machine.

To configure them, we first needed to connect a CNC Motor Shield to an Arduino Uno and obtain 12V a power source for it. Since we were planning on using 4 motors, we also obtained 4 motor drivers for the shield.

We configured the potentiometers on the drivers to drop roughly 0.86-0.87 V and ran test code on the Arduino.This code was intended to do a full revolution (since the motor has 200 steps), but the motor moved very slowly and didn’t fully revolve.

We tried to troubleshoot every component, and we eventually figured out that it was a problem with the CNC Shield itself. We replaced it, and a single motor worked well.

We then proceeded to get the other two motors to move with a similar process.

Programming in final code

For our machine code we used the Arduino AccelStepper library rather than manually controlling the motor stepper drivers using digitalWrite, as it had an easier API and could track the position. We googled the Arduino CNC Shield pinout to find the correct pins to insert into the code, and we had a MultiStepper group so that the x and y axes could automatically return home in the claw depositing process. For moving the motors we looked up the CoreXY kinematics to get the correct equations for the motor power based on the desired speeds. Here was our code for the x and y and z axes:

namespace xyaxis
{
  // pins
  constexpr int stepA = 2; // built in to cnc shield
  constexpr int dirA = 5; // built in to cnc shield
  constexpr int stepB = 3; // built in to cnc shield
  constexpr int dirB = 6; // built in to cnc shield

  constexpr float maxMotorSpeed = 500;
  constexpr float maxMotorAccel = 1500;
  constexpr int motorHome = 0;

  AccelStepper stepperA(AccelStepper::DRIVER, stepA, dirA);
  AccelStepper stepperB(AccelStepper::DRIVER, stepB, dirB);

  MultiStepper group;

  void setup()
  {
    // note: acceleration isn't actually used since we are using setSpeed
    // except when we call home()
    stepperA.setAcceleration(maxMotorAccel);
    stepperA.setMaxSpeed(maxMotorSpeed);
    stepperB.setAcceleration(maxMotorAccel);
    stepperB.setMaxSpeed(maxMotorSpeed);

    group.addStepper(stepperA);
    group.addStepper(stepperB);
  }

  void home()
  {
    long positions[2];
    positions[0] = motorHome;
    positions[1] = motorHome;
    group.moveTo(positions);
    group.runSpeedToPosition();
  }

  // speedX -maxMotorSpeed to maxMotorSpeed
  // speedY -maxMotorSpeed to maxMotorSpeed
  // note that: Y+ is away from front, X+ is towards right
  void setSpeeds(const float speedX, const float speedY)
  {
    float speedA = speedX + speedY;
    float speedB = speedX - speedY;
    // note: max power = maxMotorSpeed
    // we need to constrain each to max power
    // since they could have gone greater (maxMotorSpeed + maxMotorSpeed = 2*maxMotorSpeed, etc)
    // and we want to keep the same ratio between speedA and speedB
    float speedAProportion = speedA / maxMotorSpeed;
    float speedBProportion = speedB / maxMotorSpeed;
    speedAProportion = abs(speedAProportion);
    speedBProportion = abs(speedBProportion);

    const float dividend = max(max(speedAProportion, speedBProportion), 1);
    // divide the things
    speedA /= dividend;
    speedB /= dividend;

    // flip signs to make it go the correct direction
    stepperA.setSpeed(-speedA);
    stepperB.setSpeed(-speedB);

    stepperA.runSpeed();
    stepperB.runSpeed();
  }
}

namespace zaxis
{
  constexpr int stepZ = 4; // built in to cnc shield
  constexpr int dirZ = 7; // built in to cnc shield

  constexpr float maxMotorAccel = 1600;
  constexpr float maxMotorSpeed = 800;

  constexpr int motorHome = 0;
  constexpr int motorDown = -6000; // TODO find/update the value

  AccelStepper stepperZ(AccelStepper::DRIVER, stepZ, dirZ);

  void setup()
  {
    stepperZ.setAcceleration(maxMotorAccel);
    stepperZ.setMaxSpeed(maxMotorSpeed);
  }

  // note: these methods block!!

  void home() { stepperZ.runToNewPosition(motorHome); }
  void down() { stepperZ.runToNewPosition(motorDown); }
}

Testing

In the video below you can see the z-axis working with our 3D printed part that will hold the claw. There was an amount of z-axis wobble which we noticed:

Gantry

Iteration 1: Designing a 3D-Printed Gantry

For our claw machine’s gantry and xy movement, we decided to implement the CoreXY structure. This would allow us to control the Z-movement of the claw without having to put two motors on the claw (which would exert a lot of pressure on it and potentially break the frame). We referenced the CoreXY website throughout the process of designing the gantry. CoreXY uses two main stepper motors, alongside pulleys and belts to move in the x/y direction seamlessly. Here is a simple diagram that explains the pulley connections:

We ultimately split the machine into five pieces connected with t-slots and belts. We heavily based our design on the above diagram.

We first created a piece to hold the motors. The motors also needed to be displaced from each other since there would be a point where the belts in our design would cross over. Note that this piece was split into 3 separate pieces due to limitations on 3D printer size.

We then created a piece to cross the pulleys, which would be connected to the motor piece by two t-slots on the sides. We also had to keep in mind that for the actual pulleys, they needed to be positioned 0.4 in above the base since this piece was also split into 3 pieces. For these, we used nuts to displace the pulley. Here is a picture of two of them:

Here is the gantry without the middle assembled:

We then created two pieces to attach to the t-slots connecting the motor and pulley pieces. These two pieces would connect with another t-slot, and the claw would be placed on in to allow for movement on the x-axis. The two pieces would also move concurrently on their attached t-slots for y-axis movement. We also placed two pulleys on this to allow for the belts to easily attach to the claw piece.

Here is a picture of the assembled gantry without the piece that would slide and hold the claw and a video of movement:

As shown by the video, movement was straight, but we did not actually have any way to fasten the belts (which had not been attached yet), and the machine was very difficult to keep square.

Claw Holding Piece

For the final primary piece of the gantry, we made the piece that would slide between the two afformentioned pieces. This piece would be the one that actually moved on the x-axis t-slot. This would also be used for the z-axis portion of the claw machine, so we decided to use another stepper motor and a lead screw to attach the claw onto. We designed this simple connector piece to latch onto the gantry plate and the motor. we projected the OnShape models for the gantry plate and stepper motor onto my sketch to figure out the size of the holes.

Once we finished designing, we saved the file as a .STL, sliced it in PrusaSlicer and downloaded it onto the USB.

Assembling

For installation, we used m3 screws of various lengths. The holes weren’t fully aligned, so we used a drill to modify the size. Here is what the connector piece looks like fully attached:

We soon realized that we needed a separate component that could hold the servo still while the lead screw spun. As a solution, Kabir designed a rod with an opening that could attach to the other side and hold it in place. Here is what the z-axis looks like now!

Iteration 2

Although we were able to assemble the 3D printed gantry, the gantry itself proved to have numerous issues with stability and assembly (more information under the Problems Encountered section).

Instead of proceeding with the 3D printed gantry, we decided to go with a different approach with CoreXY, following this video, taken from David, Richard, and Evan’s machine group documentation. This approach involves linear rail guides with MGN12H guides and a slightly different belt orientation. To begin, we began by downloading this folder, which contains all of the necessary 3D-printed parts. We printed the motor bases, the idle supports, and the x-carriage mounts.

To assemble the gantry, we followed the video’s instructions, using M3 screws for the motor/idle supports and 20 mm M5 screws for the pulleys. In certain areas, however, we ended up having to use a drill and creating a bit more space in the filament for the screw to slide into.

Here is one axis assembled:

Here is the xy of the gantry assembled:

Instead of using the original project’s magnet mount file, which attaches onto the x-axis guide, Angelina designed a z-axis mount to hold the third motor and a bracket to clip the belt in place.

It worked pretty well in keeping the belt secured. Here is what it looked like assembled:

Issue with Fitting Gantry

Since we had designed the wooden claw machine frame around the first gantry iteration, the second iteration was not able to fit in the frame. The linear rails that we used were too large to fit in the frame along with the 3D printed pieces, so we needed to find some way to cut the rails down.

During this step, we had some trouble, since our lab currently does not have any machines that can easily cut thick metal. With the assistance of Garrett Nelson, we decided to use a diamond bit on a dremel to cut the rails to the length that we wanted, and then use a grinding stone bit to chamfer the edges and make the side rails approximately equal in length. This process took a fair bit of time, but once the first rail was cut, the others followed easily.

Here is a picture of one of the cuts on the rails and a video of the grinding stone being used:

Z-axis changes

Since the hexagon rod we had initially printed to keep the leadscrew nut from rotating kept falling off, we changed it to use another linear rail instead of a hexagon to make it more reliable:

The Claw

Designing

For the claw, we decided to 3D print a holder for the servo, along with 3D printing the actual claw parts. To start off, we used calipers to measure the SG-90 servo we planned to use in the claw, and designed a 3D model of it in Fusion 360.

The top of the servo contains a servo spline, so we decided to model it such that we could subtract the servo spline model from the claw model, resulting in the claw attaching to the servo. This was a trial-and-error process with a lot of small test prints. Eventually we found that an outer radius of 5.2 mm and an inner radius of 4.3 mm worked best for producing parts that fit onto the servo. We used the lab microscope to aid us in this effort.

This is an image of one of our test designs viewed on a microscope:

Image

For the actual claw parts, we started off with the Fusion 360 built-in add-in for creating a Spur Gear, and then combined each spur gear with a part for the claw to get it to be able to hold things. The spur gears worked so that while the claw part connected to the servo moved clockwise, the claw part connected to the servo holder moved counterclockwise, resulting in the claw opening and closing correctly.

Image

We designed the servo case based on the modeled servo, adding a part where it could be screwed onto the Z-axis (on the left of this following image). We also designed a piece to fit on top to keep the servo in place and prevent it from falling out of the case. The servo case also had a part where a ball bearing was intended to go between it and a servo case. On that part (on the right of this following image), we added a 3D printed part to keep it in place.

Image

Assembling

This was how the entire claw assembly looked at the end. The extrusions near the servo wire hole here were supposed to be an electronics case, but we ended up designing the servo wire to come out directly.

Image

Assembling pt. 2

We decided to reprint the entire claw assembly with a few changes: We removed the electronics compartment on the servo holder since it was unused. Also, we added a rough texture at the ends of the claw parts through creating a cylindrical modifier on each part and enabling Fuzzy Skin at the ends of the claw.

Here’s how the fuzzy skin looked in the slicer:

Image

This is what the end of the part looked like after printing:

Image

In addition, we replaced the part at the end of one of the claws which used a ball bearing to hold the claw, to a clip that just went on top of the axle and prevented the claw from falling off. Both claws were also the same color for more consistency. This is how the claw looked afterward:

Image

Testing

We then tested the claw to make sure that it could hold light objects and grab them. You can see in the following pictures the claw holding some bubble wrap:

Programming

For our programming, we used the Adafruit_TiCoServo library, since we were keeping everything on a single Arduino Uno microcontroller and the default Arduino Servo library can have issues with NeoPixels, while the TiCoServo library uses hardware timers to drive servos.

namespace claw
{
  // PWM port, must be a specific one to support TiCoServo
  constexpr int port = 9; // aka X+, X-
  // Max and min pulse width in microseconds
  // Though we don't actually need to put these explicitly
  constexpr int minUs = 544;
  constexpr int maxUs = 2400;

  // Different positions for the claw
  constexpr int pushyDeg = 84;
  constexpr int closeDeg = 78;
  constexpr int openDeg = 60;

  Adafruit_TiCoServo myservo;

  void set(const int pos) { myservo.write(pos); }

  void pushy() { set(pushyDeg); }
  void closed() { set(closeDeg); }
  void open() { set(openDeg); }

  void setup()
  {
    myservo.attach(port, minUs, maxUs);
    open();
  }
}

Claw Machine Frame

For the exterior of the claw machine, we decided to follow the conventional “arcade look” of a claw machine. To do this, we created three main sides with openings for the acrylic panels, a playing area (including profiles for the button, joystick, and LCD), and a chute for the items to fall into. The original dimensions were intended to be 24” x 24” x 30”, but it ended up being too big for the ShopBot bed, so we scaled it down to 18” x 15” x 22.5”.

Designing in Fusion360

We started out by sketching the sides of the claw machine, the top and bottom, and the playing area, before adding slots and tabs accordingly. We had measured the wood beforehand and added a .005” offset to the thickness (.47” -> .475”), as suggested by Mr. Budzichowski and Mr. Dubick.

Here is the scaled down version of the sketch.

Here are the parameters we established initially:

After We finished designing the individual components, we extruded the sketches by .475” and used the align tool to assemble them.

We saved the file as a .dxf and imported it into Aspire to begin generating the toolpaths.

Laser Cut Test

We laser cut the fusion360 file first to make sure that our design worked before CNC-ing.

CAM and Toolpaths in Aspire

We had to generate a bunch of toolpaths for this design, as there were several profiles and pockets of various depths. we first configured the job setup:

After checking if all vectors were closed, we used the built-in dogbone feature and added them to the design accordingly (value = tool diameter/2):

INSERT IMAGE OF VECTOR IN ASPIRE

I then created the following toolpaths, adding tabs for profiles:

1/4” 1 flute straight bit

  • .375” pocket for playing area (this ensures that theres enough room for the joystick and LCD)

  • .47” pocket for plywood slots

1/8” 1 flute straight bit

  • .47” pocket for the acrylic slots on the top side

3/8” 2 flute compression bit

  • profile for outline (outside)

  • profile for windows on the sides, chute, and plug-hole (inside)

  • .25” pocket for the acrylic windows to slide into and the neopixel channel

Milling

In terms of milling, we followed the our lab’s ShopBot procedures. We first ran an aircut at a 2” z-offset, before homing and running the job again.

The only issue during this process was that the tabs were too shallow and didn’t hold up during the pocket toolpaths. Thankfully, we noticed this issue early on, and with the help of Garrett Nelson, we used the brad nail gun to fasten the wood down again. Later on, we simply used a chisel and a mallot to remove the sides from the sacrifical board.

Assembling

After sanding down the edges with 80-100 grit sandpaper, we were able to easily assemble the sides of the claw machine.

For easy access, we decided to just assemble the sides and playing area. Here’s what it looks like:

Acrylic

Next, we needed to install acrylic panels for the sides and back of the claw machine. Because we had created the rectangle pocket during the CNC job, designing the panels was super quick and easy.

Designing

We designed 2 ~11” x 13.5” pieces of 1/4” acrylic, and a ~13.3” x 12” piece in Fusion360.

We also added four circles to fit m3 screws.

Laser Cutting

We then used the laser cutter to cut the acrylic according to our Fusion360 design. The first time we ran the job, it didn’t cut through entirely, so we had to run the job again.

Installation

Aligning the panels to the window, we drilled holes through the sides of the claw machine and installed m3 screws/nuts. Here is what the front and back look like after installting the acrylic in the claw machine frame:

Neopixels

Programming

For the top of the machine, we also wanted to include a set of neopixels to serve as a decorative piece for the claw machine. We first soldered and then tested a strand of neopixels using the following code, which was taken from Dariyah Strachan’s documentation:

Wires on neopixel strip:

Breadboard wiring:

For testing the NeoPixels we used this example sketch for testing:

// A basic everyday NeoPixel strip test program.

// NEOPIXEL BEST PRACTICES for most reliable operation:
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
//   connect GROUND (-) first, then +, then data.
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
//   a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
// (Skipping these may work OK on your workbench but can fail in the field)

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN    D3

// How many NeoPixels are attached to the Arduino?
#define LED_COUNT 30

// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)


// setup() function -- runs once at startup --------------------------------

void setup() {
  // These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
  // Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
  // END of Trinket-specific code.

  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
}


// loop() function -- runs repeatedly as long as board is on ---------------

void loop() {
  // Fill along the length of the strip in various colors...
  colorWipe(strip.Color(255,   0,   0), 50); // Red
  colorWipe(strip.Color(  0, 255,   0), 50); // Green
  colorWipe(strip.Color(  0,   0, 255), 50); // Blue

  // Do a theater marquee effect in various colors...
  theaterChase(strip.Color(127, 127, 127), 50); // White, half brightness
  theaterChase(strip.Color(127,   0,   0), 50); // Red, half brightness
  theaterChase(strip.Color(  0,   0, 127), 50); // Blue, half brightness

  rainbow(10);             // Flowing rainbow cycle along the whole strip
  theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant
}


// Some functions of our own for creating animated effects -----------------

// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
  for(int a=0; a<10; a++) {  // Repeat 10 times...
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      strip.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in steps of 3...
      for(int c=b; c<strip.numPixels(); c += 3) {
        strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      strip.show(); // Update strip with new contents
      delay(wait);  // Pause for a moment
    }
  }
}

// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(int wait) {
  // Hue of first pixel runs 5 complete loops through the color wheel.
  // Color wheel has a range of 65536 but it's OK if we roll over, so
  // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
  // means we'll make 5*65536/256 = 1280 passes through this loop:
  for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
    // strip.rainbow() can take a single argument (first pixel hue) or
    // optionally a few extras: number of rainbow repetitions (default 1),
    // saturation and value (brightness) (both 0-255, similar to the
    // ColorHSV() function, default 255), and a true/false flag for whether
    // to apply gamma correction to provide 'truer' colors (default true).
    strip.rainbow(firstPixelHue);
    // Above line is equivalent to:
    // strip.rainbow(firstPixelHue, 1, 255, 255, true);
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
  }
}

// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
  int firstPixelHue = 0;     // First pixel starts at red (hue 0)
  for(int a=0; a<30; a++) {  // Repeat 30 times...
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      strip.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in increments of 3...
      for(int c=b; c<strip.numPixels(); c += 3) {
        // hue of pixel 'c' is offset by an amount to make one full
        // revolution of the color wheel (range 65536) along the length
        // of the strip (strip.numPixels() steps):
        int      hue   = firstPixelHue + c * 65536L / strip.numPixels();
        uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
        strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      strip.show();                // Update strip with new contents
      delay(wait);                 // Pause for a moment
      firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
    }
  }
}

After seeing that it worked as we wanted with the breadboard, we created a board in KiCad.

Schematic:

PCB:

Soldered:

We then uploaded the code to the board with the neopixel strip attached and it worked!

Programming again

We eventually decided that it would be simpler to run the NeoPixels off of the Arduino Uno that we were using, so that we only had to deal with a single microcontroller and so that the neopixels could change their color based on the current state of the claw machine. The code in our final machine code still used the Adafruit_NeoPixel library, and had multiple different patterns.

namespace neopixel
{
  constexpr int numpixels = 52; // wow this is a lot of neopixels!
  constexpr int pin = 12; // SpnEn
  Adafruit_NeoPixel pixels(numpixels, pin, NEO_GRB + NEO_KHZ800);

  void setup()
  {
    //pixels.setBrightness(128);
    pixels.begin();
  }

  void patternBlueSnakes()
  {
    pixels.clear();
    const unsigned long offset = millis() / 80;
    for (int i = 0; i < numpixels; i++)
    {
      const unsigned long thing = ((i + offset) / 7) & 1;
      if (thing) { pixels.setPixelColor(i, Adafruit_NeoPixel::Color(0, 0, 160)); }
    }
    pixels.show();
  }

  void patternRedSnakes()
  {
    pixels.clear();
    const unsigned long int offset = millis() / 80;
    for (int i = 0; i < numpixels; i++)
    {
      const unsigned long int thing = ((i + offset) / 7) & 1;
      if (thing) { pixels.setPixelColor(i, Adafruit_NeoPixel::Color(160, 0, 0)); }
    }
    pixels.show();
  }

  void patternSweepyGreen()
  {
    pixels.clear();
    for (int i = 0; i < numpixels; i++) { pixels.setPixelColor(i, Adafruit_NeoPixel::Color(0, (i * 17) % 250 + 5, 0)); }
    pixels.show();
  }

  void clear()
  {
    pixels.clear();
    pixels.show();
  }
}

Installation

Seeing that it worked, we readjusted the soldering and wiring to fit within the channel on the top piece.

Push Button

Programming

Programming the push button was mostly simple. We found the pinout at https://learn.adafruit.com/assets/42961 and soldered jumper wires which we connected to the appropriate locations. We ran into an issue where in our initial implementation of the code, th push button was returning the opposite value of what it should have been; we eventually realized that since the button was pulled up when it was not pressed, the digital read returned true when it was not pressed, meaning that we would need to invert the value returned from digitalRead to get whether it was pressed or not. Our eventual code for the button was:

namespace button
{
  constexpr int buttonLed1 = 11; // z+/z-
  constexpr int buttonSw1 = 10; // y+/y-

  void setup()
  {
    pinMode(buttonLed1, OUTPUT);
    pinMode(buttonSw1, INPUT_PULLUP);
  }

  void setLed(const int val) { digitalWrite(buttonLed1, val); }

  void setLedAnalog(const int val) { analogWrite(buttonLed1, val); }

  void ledOn() { setLed(HIGH); }
  void ledOff() { setLed(LOW); }

  bool readSwitch()
  {
    // we need to invert it because the button connects to ground when it is pressed
    // and we are using INPUT_PULLUP so it is HIGH when not pressed
    return !digitalRead(buttonSw1);
  }
}

Installation

Here’s an image of the push button installed in the claw machine frame. We did not have to use any screws or 3D holders as it was cut perfectly to size when we CNC-ed.

Testing

Here’s a picture of the button lit up and working in the claw machine frame:

LCD

Programming

For programming the LCD, we noticed that the LCD had pins labeled SCL and SDA, suggesting that it communicated over I2C. After connecting the pins to the Arduino correctly and searching around for a bit, we found the LiquidCrystal_I2C.h library and added it to the sketch.

We ran into an issue when testing where the LCD did not light up. We looked for issues in the code for a bit until we noticed that it was displaying the correct characters when we shined a phone flashlight at it. The problem here turned out to be that the LCD backlight wasn’t working for some reason, so we switched the LCD out with a newer one and got it to work.

We also added custom characters by referencing the example sketch to create byte arrays which represented each pixel as a bit. Then, we made various different functions to display different statuses on the LCD screen.

Code for LCD:

namespace lcd
{
  // uses I2C port
  LiquidCrystal_I2C lcd(0x27, 16, 2);

  byte claw[8] = {
    B00100,
    B00100,
    B00100,
    B11111,
    B10001,
    B10001,
    B11011,
  };
  byte joystick[8] = {
    B11111,
    B01110,
    B01110,
    B00100,
    B11111,
    B11111,
    B11111,
  };

  byte button[8] = {
    B00000,
    B01110,
    B01110,
    B11111,
    B11111,
    B11111,
    B11111,
  };

  void displayStart()
  {
    lcd.clear();
    lcd.setCursor(1, 0);
    lcd.print("\7 Claw Machine");
    lcd.setCursor(2, 1);
    lcd.print("\5 Start");
  }

  void displayStarting()
  {
    lcd.clear();
    lcd.setCursor(1, 0);
    lcd.print("\7 Claw Machine");
    lcd.setCursor(2, 1);
    lcd.print("Starting!");
  }

  void displayMain()
  {
    lcd.clear();
    lcd.setCursor(1, 0);
    lcd.print("\7 Claw Machine");
    lcd.setCursor(0, 1);
    lcd.print("\6Move  \5Use Claw");
  }

  void displayMoving()
  {
    lcd.clear();
    lcd.setCursor(1, 0);
    lcd.print("\7 Claw Machine");
    lcd.setCursor(0, 1);
    lcd.print("\7 Picking up.. \7");
  }

  void setup()
  {
    lcd.init();
    lcd.createChar(5, button);
    lcd.createChar(6, joystick);
    lcd.createChar(7, claw);
    lcd.backlight();
  }
}

Installation

Here’s the LCD installed in the claw machine frame. We used M3 screws with bolts to hold it in place.

Joystick

Programming

For programming the joystick, the joystick module had two pins for the X and Y values (VRx and VRy) and a digital pin for the switch (SW). At first, we tried testing the joystick by connecting it to a XIAO RP2040, using analogRead to read the position pins and printing them to the console. We observed that the analog input was changing when we moved the joystick, but the position which was read started erratically changing whenever the joystick was pushed too far to the edge.

Eventually, after taking a look with a multimeter, we realized that the joystick analog pins had signals ranging from 0-5V, but the RP2040 IO pins operated at 3.3V, which was a problem because it caused issues when the RP2040 ADC was trying to read outside the intended voltage range. Since we were already going to use an Arduino Uno for the CNC shield, we tested the joystick input using analogRead on an Arduino, which has a 5V voltage for the IO pins, and it worked correctly.

For our machine code, we needed to subtract 512 from the values read by the joystick to get the range of joystick values read from 0 to 1023, to -512 to 511. We also put in code to set the value which was read to 0 if its absolute value was less than 90, because we observed that the joystick would often read as slightly offset from the origin position even if it was not being pressed, due to joystick drift.

Code for joystick:

namespace joystick
{
  constexpr int portX = 0; // aka A0, Abort
  constexpr int portY = 1; // aka A1, Hold
  constexpr int portSW = 13; // aka D13, SpnDir

  void setup()
  {
    // note: analog read resolution 10 bit is default on arduinos
    // so values range from 0-1023 from analogRead
    pinMode(portX, INPUT);
    pinMode(portY, INPUT);
    pinMode(portSW, INPUT_PULLUP);
  }

  // returns joystick X -512 (left)to 511 (right)
  int getX()
  {
    const int val = analogRead(portX) - 512;
    if (abs(val) < 90) return 0; // deadband to prevent drift
    return -val;
  }

  // returns joystick Y -512 (down) to 511 (up)
  int getY()
  {
    const int val = analogRead(portY) - 512;
    if (abs(val) < 90) return 0; // deadband to prevent drift
    return val;
  }

  // returns whether the switch on the joystick is pressed
  // this is currently unused
  bool getSwitch() { return !digitalRead(portSW); }
}

3D Printed Holder

We 3D printed a holder for the joystick to be attached to the claw machine frame in.

Joystick Holder:

Joystick in Holder:

Installation

We then used M3 screws and bolts to secure the holder and joystick in the claw machine frame.

Putting It All Together

Programming

For programming the claw machine, we wrote a loop and a few functions to combine the various subsystems:

void setup()
{
  claw::setup();
  joystick::setup();
  button::setup();
  xyaxis::setup();
  zaxis::setup();
  neopixel::setup();
  button::setup();
  lcd::setup();
}

void grabTheThing()
{
  zaxis::down();
  delay(500);
  claw::closed();
  delay(1000);
  claw::pushy();
  delay(1000);
  zaxis::home();
  delay(500);
  xyaxis::home();
  delay(500);
  claw::open();
  delay(500);
}

void runningJoystickLoop()
{
  float joystickX = joystick::getX();
  float joystickY = joystick::getY();
  constexpr float scale = xyaxis::maxMotorSpeed / 512.0; // scale from the joystick to the motor speeds
  joystickX *= scale;
  joystickY *= scale;
  xyaxis::setSpeeds(joystickX, joystickY);
}

void loop()
{
  // pre-starting state
  lcd::displayStart();
  while (!button::readSwitch())
  {
    button::setLedAnalog((millis() / 4) % 255); // blink leds
    neopixel::patternRedSnakes();
  }
  neopixel::clear();
  button::ledOn();
  lcd::displayStarting();
  delay(1500);
  // moving joystick state
  lcd::displayMain();
  while (!button::readSwitch())
  {
    runningJoystickLoop(); // run the loop
    neopixel::patternBlueSnakes();
  }
  // moving claw state
  button::ledOff();
  lcd::displayMoving();
  neopixel::clear();
  delay(1500);
  neopixel::patternSweepyGreen();
  grabTheThing();
  neopixel::clear();
  delay(1500);
  neopixel::clear();
}

The final version of the code will be available in our files download.

Wiring and Diagram

We had a lot of different components that needed to be connected to the Arduino Uno. Since the CNC shield goes directly on top of the Arduino and takes up all the pins, we found a diagram online (source) which lists the available pins that can be connected to through the CNC shield: for example, pin D12 on the arduino is labeled ‘SpnEn’ on the CNC shield. The pinout also lists the necessary pins for controlling the motor drivers:

Keeping this in mind, we made sure that we had enough pins for all the electronics within the claw machine. Here is a diagram showing all the pins we used on the Arduino:

Assembling

Here you can see the system integration with all of the parts documented here brought together into our final product :)

Problems Encountered

Most of the issues that we had were with the first gantry model.

Here were the primary issues we encountered:

  • When the outer frame of the gantry was assembled, the screws were not affixed using heat inserts. This caused the problem that although opposite sides were parallel, they were not entirely square.

    • This caused inconsistencies with measuring the straightness of the two 3D printed parts holding the middle, and it caused part of the problems in the next section.
  • We struggled to install the x-axis linear rail onto gantry, which mainly consisted of 3D printed components.

    • When we used the heat inserts, the filament couldn’t fully support it, resulting in the insert falling through.

    • Our next attempt included using adhesives such as hot glue and super glue. While this kept the linear rail stable when moving along the y-axis, it wasn’t sturdy to support the z-axis.

    • Lastly, we tried to fasten the rails onto the gantry using screws and bolt underneath, though this failed to keep the rail still.

  • In the gantry, we did not have any way to affix the belts to the claw piece or tension the belts. When attempting to connect the belts to the pieces, the adhesives we tried ripped off due to it not fastening enough.

  • The screws holding the pulleys in the gantry were initially not displaced from each other, which they needed to be for our particular CoreXY design. To fix this problem, we simply needed to flip the pulleys over. We also added nuts to the bottom of them to further displace them from the 3D printed pieces, which caused them not to collide.

  • Since two of the sides of the gantry were dependent on interlinked 3D printed pieces, this caused some error in terms of making the machine square. It would have been slightly better if they were made up of one 3D printed piece, but the 3D printers in our lab were not big enough to handle that. It also would have been better to use more rails or some other metal beam to link those sides.

  • We also had nothing for the claw to grab. To solve this as fast as possible with such limited time, Alana made and decorated hand balloon people!

  • When we first installed the shelves to hold the gantry, they were too high up and prevented the top from going on the claw machine frame. To solve this, we unscrewed and removed the shelves, mixed wood glue and wood shavings to make a paste, filled the screw holes with the paste, let it dry, sanded down the excess paste, and then re-installed the shelves lower down on the sides.

Shelves too high up:

Removed shelves:

Wood glue and shavings paste:

Dried and sanded down paste:

Shelves moved lower down:

Possible Improvements

Improvements that could be done for the claw machine in the future primarily surround it having a more polished, finished appearance. This includes painting the wood, having vinyl sticker labels for buttons, hiding electronics and wires, and plan the placement of the shelves more accurately so that the gantry does not stick out as much and the front acrylic can be put in. Furthermore, a vinyl shoot for the prizes to come out of and better fitting tabs in the claw machine frame would make the overall machine work better and more smoothly.

Work Distribution

People Description
Alana, Angelina, Connor, Kabir Sketching/Planning
Angelina Bill of Materials
Alana, Angelina Slideshow
Connor Video
Connor Programming Stepper Motors
Connor Designing the Gantry
Kabir Designing the Claw
Kabir Assembling the Claw
Kabir Leadscrew Guide Assembly
Angelina Designing Claw Machine Frame
Angelina Claw Machine Frame CAM-ing
Alana, Angelina Claw Machine Frame Assembling
Angelina Acrylic Designing/Laser Cutting
Alana, Angelina Acrylic Installation
Alana Neopixel Assembly/Programming
Alana Neopixel Wiring
Angelina Neopixel Installation
Alana Push Button installation
Kabir Push Button Programming
Alana LCD Installation
Kabir LCD Programming
Alana Joystick Installation
Alana Joystick Holder design/3D printing
Kabir Joystick Programming
Kabir Controls Programming
Alana Cable Management designing/3D printing
Alana, Angelina, Connor, Kabir Cable Management
Alana, Angelina, Connor, Kabir System Integration
Alana, Angelina, Connor Gantry installation
Alana Gantry corner holders
Angelina, Connor, Alana Gantry wood shelving

Also, thanks for all your help Garrett!!!


Last update: July 8, 2024