Shop OBEX P1 Docs P2 Docs Learn Events
Well it's plywood - Page 3 — Parallax Forums

Well it's plywood

13»

Comments

  • ercoerco Posts: 20,255
    edited 2013-03-13 17:24
    The only "deal of the day" I can post about is fireworks stands in SC/NC and (of course) South of the Border. If you don't know, see http://www.thesouthoftheborder.com/

    Oh, and on I-95, I drove right past the east coast HQ of "Harbor Freight"... drool, drool!
  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-03-21 00:42
    A little more progress on the physical build. I have to add another motor controller and cooling, and then the programming begins.

    [video=youtube_share;PUprJMROBSM]
  • Martin_HMartin_H Posts: 4,051
    edited 2013-03-21 05:52
    Jason, I'm drooling. A robot arm that size will be really capable.
  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-03-21 08:46
    I'll be happy if it's capable of lifting a light tool. :) It's much heavier than I expected, so I may need to rebuild the shoulder to hold two steppers, like the ST robotics one, or at least move the existing steppers back further to balance the weight better. I had to turn up the current to the motor because it was skipping.

    The distance between the shoulder and wrist pivots is 14". My single 88-oz-in stepper will put out 1.75lbs of torque at 14" with the gear reduction I have. The arm already weighs quite a bit, but the weight is slightly counterbalanced, and not that far away, so it may just be okj. Once I put the wrist on I may hit the tipping point, but this is good enough to get started on the programming. :) I have a bunch of pics of the physical build I'll post when I get some time.
  • Martin_HMartin_H Posts: 4,051
    edited 2013-03-21 13:34
    Jason, have you seen this http://thecardboardrobot.com/? What's interesting about his design is how he kept the motor for the elbow in the base. I also liked his tubular construction because it seems to have a good strength to weight ratio.
  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-03-21 14:45
    I've contemplated moving the motor to drive the shoulder onto the base. The way it's built, I could simply move the stepper to the opposite side of the joint - The stepper would sit on the side of the piece that connects the waist to the shoulder, and drive the shoulder from there. I could then move the elbow motor onto the upper arm in the same way. It'd help keep the amount of weight it has to support down, but I'd have to make sure the piece extending from the waist to shoulder was sturdy enough not to have a brace.

    This picture shows where the shoulder stepper is mounted now. If I moved it further away from the pivot I could get more counter-balance action from it. I also want to try just adding some weight. The pulley is currently bolted to the shoulder, so it stays fixed as the arm moves around it. I could reverse it so the pulley was on the other side, and rotated with the arm.
    4-photo 4.JPG


    This picture shows the waist / shoulder. The brace runs up the side, and I'd have to remove that to mount the stepper there.
    4-photo 5.JPG


    These two show how the pivots are done. I use large diameter ball bearings seated into both sides of the wood.
    2-photo 5.JPG


    Then I have these "half spools" with thrust bearings that go on either side. You bolt an arm to one of the spool halves, bolt a pulley to the other one, then bolt the two halves together. There's a 1/2" hole in the middle to run wiring through so I don't have to worry about it getting tangled.
    3-photo 1.jpg


    I've also been contemplating cutting a bunch of holes in the wood to reduce weight. Probably the next iteration I do will have some pockets cut into the wood to remove material and lower the weight, but the stepper motors are the bulk of it. Moving them further back or adding counterweight will likely make the most difference. I want the thing to be relatively beefy (IE sturdy), but cheap to reproduce.

    So far the parts cost is a little over $200.
    968 x 1296 - 396K
    968 x 1296 - 426K
    968 x 1296 - 410K
    1024 x 765 - 106K
  • ercoerco Posts: 20,255
    edited 2013-04-30 13:55
    Looks like a buyers market for robot arm kits these days. Here's a metal (not plywood!) 6 axis kit with servos (no controller, no batteries) for $108, free ship: http://www.ebay.com/itm/New-6-DOF-Manipulator-Aluminum-Robot-Arm-Kits-for-Smart-Car-Base-/160998716216#ht_1497wt_679

    and another, BYO servos, for $62 shipped: http://www.ebay.com/itm/Aluminium-Robot-6-DOF-Arm-Clamp-Claw-Mount-Kit-for-Arduino-Compatible-/171020974053#ht_3910wt_816
  • Martin_HMartin_H Posts: 4,051
    edited 2013-08-07 08:37
    Jason, any further progress on your robot arm? It would be great if you had a construction thread where you detailed things like the sources for your pulleys and the ID of your large bore bearings. Also I've been pondering the wooden half spools which look complicated to fabricate. Wouldn't hollow threaded rod which matched the ID of the bearings work to feed able through?
  • ercoerco Posts: 20,255
    edited 2013-11-24 18:34
    This plywood arm seller has disappeared. Too bad, it was a nice little kit. Maybe $100 was too low for the seller/maker to keep it up. It was a 5-DOF arm. Kit included a controller, NiMH battery & charger, all of questionable value. Neither Martin nor I used the controller.

    On a brighter note, here's a metal 6-DOF robot arm kit from China for $108 shipped for anyone who missed out. No controller or battery, but it includes all servos, 4 are metal gear types. Martin_H? Duane? Amanda (maybe name it Armand!)? Who will answer the call? Christmas is coming!

    http://www.ebay.com/itm/New-6-DOF-Manipulator-Aluminum-Robot-Arm-Kits-for-Smart-Car-Base-/160998716216?pt=LH_DefaultDomain_0&hash=item257c457338

    Looks to be a decent value IMO. The main thing that concerns me is that it uses that heavy metal gripper and a full size servo. That load subtracts from the payload capacity of the arm. BTW, that gripper alone sells (sans servo) for $27 shipped: http://www.ebay.com/itm/Manipulator-Aluminum-Robot-Arm-Robotic-for-Smart-Car-Base-/121145738605?pt=LH_DefaultDomain_0&hash=item1c34d9456d
  • ajwardajward Posts: 1,129
    edited 2013-11-24 22:33
    It's in my watch list. Just haven't yet made the decision to buy. I did get one of these guys tho'.
    Thinking about getting one of the aluminum critters to play with. A bit cheaper here.
    Another find. Gripper with pan/tilt bracket (no servos).

    Amanda


  • Martin_HMartin_H Posts: 4,051
    edited 2013-11-25 06:48
    It's too bad he stopped selling the arm kit. If he wasn't making enough profit he should have unbundled the controller which would have increased his margin. I gave him a pointer to my firmware which works with the Arduino or my libPropelleruino, so he could have just said bring your own controller. He was pretty impressed with the smoothness of control IK affords.

    Anyway, when I finish my scara arm I'm going to work on a three servo delta robot arm. Then I'll have three types of robot arms. After that I don't plan on building another robot arm using servos. Instead the next arm will use surplus NEMA-23 stepper motors I bought off eBay for $30. Given the rate at which I'm completing projects look for it in late 2014.
  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-11-25 20:14
    Martin_H wrote: »
    Jason, any further progress on your robot arm? It would be great if you had a construction thread where you detailed things like the sources for your pulleys and the ID of your large bore bearings. Also I've been pondering the wooden half spools which look complicated to fabricate. Wouldn't hollow threaded rod which matched the ID of the bearings work to feed able through?

    Martin,

    The bearings were from VXB, and the pulleys / belts from SDP-SI. I mocked it all up on paper to figure out rough measurements, and then used their online calculator to get proper offsets for the pulley locations. For the spools, it'd work just as well to have three circular pieces instead of two half spools, and you could make do with far less. I was trying to visually replicate an ST-Robotics R-12, and used the thrust bearings for rigidity, but I don't actually know how necessary they are.

    No further progress at this point - Been playing with the quad some more, and had a crash on Sunday, so I'll be building up another board (again, sigh) over the next little while.

    So many projects, so little time.

    J
  • Martin_HMartin_H Posts: 4,051
    edited 2013-12-29 17:17
    Over in the general discussion forum I posted a video of my arm using a vacuum gripper to pick up and move cards. I also extended the arm's firmware to include gcode support. This allows a PC program (e.g. CNC software) to emit gcode and I can then stream it to the arm using tools (e.g. GRBL Controller http://zapmaker.org/projects/grbl-controller-3-0).

    Here's the gcode parser
    // Arm dimensions( mm )
    #define BASE_HGT 75     
    #define HUMERUS 94       // shoulder-to-elbow "bone" 5.75"
    #define ULNA 78          // elbow-to-wrist "bone" 7.375"
    #define GRIPPER 72.00    // vacuum gripper length
    //#define GRIPPER 150.00   // gripper (incl.heavy duty wrist rotate mechanism) length 3.94"
    
    #define LINE_BUFFER_SIZE 64
    
    // Define Grbl status codes.
    #define STATUS_OK 0
    #define STATUS_BAD_NUMBER_FORMAT 1
    #define STATUS_EXPECTED_COMMAND_LETTER 2
    #define STATUS_UNSUPPORTED_STATEMENT 3
    #define STATUS_ARC_RADIUS_ERROR 4
    #define STATUS_MODAL_GROUP_VIOLATION 5
    #define STATUS_INVALID_STATEMENT 6
    #define STATUS_SETTING_DISABLED 7
    #define STATUS_SETTING_VALUE_NEG 8
    #define STATUS_SETTING_STEP_PULSE_MIN 9
    #define STATUS_SETTING_READ_FAIL 10
    #define STATUS_IDLE_ERROR 11
    #define STATUS_ALARM_LOCK 12
    #define STATUS_OVERFLOW 13
    #define STATUS_VERSION 14
    
    class Parser
    {
      public:
        /*
          constructor used to bind the parser to a robot arm.
          Parameters:
            arm  reference to the robot arm.
         */
        Parser(RobotArm *arm)
        {
          _arm = arm;
        }
    
        /**
         * Prepares the input buffer to receive a new message and tells the serial connected device it is ready for more.
         */
        void reset() {
          iter = 0;              // clear input buffer
          Serial.print(F("> ")); // signal ready to receive input
        }
    
        /**
         * Listen to the serial port for incoming commands and deal with them
         */
        void listen()
        {
          boolean iscomment = false;
    
          // listen for serial commands
          while(Serial.available() > 0) {
            // Read input when it is available.
            char c = Serial.read();
    
            // if end of line reached
            if ((c == '\n') || (c == '\r')) {
              if (iter > 0) {// Line is complete. Then execute!
                buffer[iter] = 0; // Terminate string
                reportMessage(processCommand());
              }
              else {
                // Empty or comment line. Skip block.
                reportMessage(STATUS_OK); // Send status message for syncing purposes.
              }
    
              reset();
            }
            else {
              if (iscomment) {
                // Throw away all comment characters
                if (c == ')') {
                  // End of comment. Resume line.
                  iscomment = false;
                }
              }
              else {
                if (c <= ' ') {
                  // Throw away whitepace and control characters
                  // except control x which gets the version string
                  if (c == 24)
                    reportMessage(STATUS_VERSION);
                }
                else if (c == '/') { 
                  // Block delete not supported. Ignore character.
                }
                else if (c == '(') {
                  // Enable comments flag and ignore all characters until ')' or EOL.
                  iscomment = true;
                }
                else if (iter >= LINE_BUFFER_SIZE-1) {
                  // Report line buffer overflow and reset
                  reportMessage(STATUS_OVERFLOW);
                  reset();
                }
                else if (c >= 'a' && c <= 'z') { // Upcase lowercase
                  buffer[iter++] = c-'a'+'A';
                }
                else {
                  buffer[iter++] = c;
                }
              }
            }
          }
        }
    
        /* returns a status to the serial line
         */
        void reportMessage(int status_code)
        {
          if (status_code == 0)
          { // STATUS_OK
            Serial.print(F("ok\r\n"));
          }
          else if (status_code == STATUS_VERSION)
          {
            Serial.print(F("Grbl v0.8c ['$' for help]\r\n"));
          }
          else
          {
            Serial.print(F("error: "));
            switch (status_code)
            {          
              case STATUS_BAD_NUMBER_FORMAT:
                Serial.print(F("Bad number format")); break;
              case STATUS_EXPECTED_COMMAND_LETTER:
                Serial.print(F("Expected command letter")); break;
              case STATUS_UNSUPPORTED_STATEMENT:
                Serial.print(F("Unsupported statement")); break;
              case STATUS_ARC_RADIUS_ERROR:
                Serial.print(F("Invalid radius")); break;
              case STATUS_MODAL_GROUP_VIOLATION:
                Serial.print(F("Modal group violation")); break;
              case STATUS_INVALID_STATEMENT:
                Serial.print(F("Invalid statement")); break;
              case STATUS_SETTING_DISABLED:
                Serial.print(F("Setting disabled")); break;
              case STATUS_SETTING_VALUE_NEG:
                Serial.print(F("Value < 0.0")); break;
              case STATUS_SETTING_STEP_PULSE_MIN:
                Serial.print(F("Value < 3 usec")); break;
              case STATUS_SETTING_READ_FAIL:
                Serial.print(F("EEPROM read fail. Using defaults")); break;
              case STATUS_IDLE_ERROR:
                Serial.print(F("Busy or queued")); break;
              case STATUS_ALARM_LOCK:
                Serial.print(F("Alarm lock")); break;
              case STATUS_OVERFLOW:
                Serial.print(F("Line overflow")); break;
            }
          }
          Serial.print(F("\r\n"));
        }
    
      private:
        RobotArm * _arm;
        char buffer[LINE_BUFFER_SIZE];
        int iter;  
    
        /** Allows human to enter degrees
         */
        float deg2Rad(float degValue)
        {
          return degValue * PI / 180.0;
        }
    
        /** trasforms radians for display.
         */
        float rad2Deg(float degValue)
        {
          return degValue * 180.0 / PI;
        }
    
        /**
         * Extracts the floating point argument from the desire code.
         * @return the value found.  If nothing is found, val is returned.
         * @input code the character to look for.
         * @input val the return value if code is not found.
         **/
        float getArgument(char code, float val) {
          char* ptr = buffer;
          while (ptr && *ptr && ptr < buffer + iter) {
            if (*ptr == code) {
              return atof(ptr + 1);
            }
            ptr++;
          }
          return val;
        }
    
        /**
         * Read the input buffer and find any recognized commands.  One G or M command per line.
         */
        int processCommand() {
          int cmd = getArgument('G', -1);
          switch(cmd) {
          case  0: // move linear
          case  1: // move linear
              _arm->moveXY( getArgument('X', _arm->getX()), getArgument('Y', _arm->getY()) );
              _arm->moveZ( getArgument('Z', _arm->getZ()) );
    
            break;
          // pause
          case  4:
            delay( getArgument('P', 0) * 1000);
            break;
    
          // home g code parks the arm
          case 28:
            _arm->park();
            break;
    
          // set position
          case 92:
            _arm->setPosition( getArgument('X', _arm->getX()), getArgument('Y', _arm->getY()), getArgument('Z', _arm->getZ()),
                               deg2Rad( getArgument('A', rad2Deg(_arm->getWristAngle())) ) );
            _arm->setWristRotate( deg2Rad( getArgument('C', rad2Deg(_arm->getWristRotate())) ) );
            break;
    
          // don't do anything on an unrecognized code.
          default:
            break;
          }
    
          // Miscellaneous commands used for end effector control.
          cmd = getArgument('M', -1);
          switch (cmd)
          {
            // Motor command is used to enable vacuum system
          case 10:
            _arm->moveGripper(-30);
            break;
    
          // Motor command is used to disable vacuum system
          case 11:
            _arm->moveGripper(110);
            break;
            
          case 114:
            Serial.print(F("X="));
            Serial.print(_arm->getX());
            Serial.print(F(", Y="));
            Serial.print(_arm->getY());
            Serial.print(F(", Z="));
            Serial.print(_arm->getZ());
            Serial.print(F(", A="));
            Serial.print(rad2Deg(_arm->getWristAngle()));
            Serial.print(F(", C="));
            Serial.println(rad2Deg(_arm->getWristRotate()));
            break;
    
          // don't do anything on an unrecognized code.
          default:
            break;
          }
          
          return STATUS_OK;
        }
    };
    

    Here's the up to date RobotArm object:
    #include <math.h>
    #include <Servo.h>
    
    // float to int conversion with rounding
    #define fti(x) ((x) >= 0 ? (int)((x)+0.5) : (int)((x)-0.5))  
    
    // Angular values for common angles
    const float STEP_ANGLE      = PI/360;
    const float RIGHT_ANGLE     = HALF_PI;
    const float STRAIGHT_ANGLE  = PI;
    const float FULL_ROTATION   = 2.0 * PI;
    
    // Defines and manages a single joint of the arm.
    class Joint
    {
      public:
        /*
          setParameters : configures the joint with a number of measure constants used to position the joint.
          Parameters:
            pin       the numeric value of the pin.
            minWidth  the minimum pulse with for this joint.
            maxWidth  the maximum pulse with for this joint.
            center    the pulse width which centers the joint.
            widthPerRadian    the pulse width to radian ratio which is signed to handle inverted servos.
         */
        void setParameters(int pin, int minWidth, int maxWidth, int center, float widthPerRadian)
        {
          _center = center;
          _widthPerRadian = widthPerRadian;
          _servo.attach(pin, minWidth, maxWidth);
        }
    
        /*
          setPosition: Computes the pulse width that matches the desired joint angle
          using the constraints of known angle to pulse values.  Servo center is zero,
          counter clockwise a quarter circle is Pi/2, while clockwise is -Pi/2.
    
          Parameters:
            angle    32-bit floating point radian value
        */
        void setPosition(float angle)
        {
          int pulseWidth = _center + fti(_widthPerRadian * angle);
    
          _servo.writeMicroseconds(pulseWidth);
        }
    
        private:
          int          _center;
          float        _widthPerRadian;
          Servo        _servo;
    };
        
    class RobotArm
    {
      public:
        enum JointIndex { AZIMUTH=0, ALTITUDE=1, ELBOW=3, WRIST=4, ROTATE=5, GRIP=6, COUNT=7 };
    
        /* Constructor used to initialize arm parameters.
           Parameters:
            baseHeight   height of the altitude joint in mm.
            humerous   shoulder-to-elbow "bone" in mm
            ulna       elbow-to-wrist "bone" in mm
            gripper    gripper (incl. wrist rotate mechanism) in mm.
         */
        RobotArm(float baseHeight, float humerous, float ulna, float gripper)
        {
          _baseHeight = baseHeight;
          _humerous = humerous;
          _ulna = ulna;
          _gripper = gripper;
    
          /* pre-calculations */
          _humerousSq = _humerous * _humerous;
          _ulnaSq = _ulna * _ulna;
          
          _rampDelay = 12;
        }
        
        /*
          setters to inject the servo parameters for each joint.
          Parameters
            jointIdx - the enumerated name of the joint.
            pin      - the numeric value of the pin.
            minWidth - the minimum pulse with for this joint.
            maxWidth - the maximum pulse with for this joint.
            center   - pulse width that centers the joint
            widthPerRadian - the pulse width to deflect the servo one radian from center
          */
        void setJointParameters(JointIndex jointIdx, int pin, int minWidth, int maxWidth, int center, float widthPerRadian)
        {
          joints[jointIdx].setParameters( pin, minWidth, maxWidth, center, widthPerRadian);
          if (jointIdx == ALTITUDE)
          {
             joints[jointIdx+1].setParameters( pin-1, minWidth, maxWidth, center, widthPerRadian);
          }
        }
    
        /*
         Park - Move the arm servos to the parked position.  Called during start and stop.
          By parking the arm the servos will start from a known position and minimize startup
          lurch.
        */
        void park()
        {
          setWristRotate( 0.0 );
          setGripper( 80.0 );
          setPosition( 0.0, 150.0, 25.0, 0.0 );
        }
    
        /*
          SetPosition : Arm positioning routine utilizing inverse kinematics.  Since the arm is
            resting on a surface Z can only be positive.  Servo movement contraints prevent y
            from being negative, But X can be a signed value.
            Note: This must be called before and of the move routines to initialize arm state.
          Parameters:
            x   the side to side displacement.
            y   the distance out from the base center.
            z   the height above the table
            wristAngle    wrist ground angle of the gripper in radians.
         */
        void setPosition( float x, float y, float z, float wristAngle )
        {
          // Save the cartesian space coordinates.
          _x = x;
          _y = y;
          _z = z;
    
          // grip angle in radians for use in calculations
          _wristGroundAngleRads = wristAngle;
          
          // Compute base angle using the x,y coordinates
          setAzimuth(atan2( x, y ));
    
          // Compute the vector magnitude and hold in the y coordinate.
          y = sqrt(( x * x ) + ( y * y ));
    
          // Calculate Gripper offsets based on grip angle and length.
          float grip_off_z = sin( _wristGroundAngleRads ) * _gripper;
          float grip_off_y = cos( _wristGroundAngleRads ) * _gripper;
    
          // Wrist position
          float wrist_z = z - grip_off_z - _baseHeight;
          float wrist_y = y - grip_off_y;
    
          // Use Pythagorean theorem to calculate shoulder to wrist distance.
          float s_w = ( wrist_z * wrist_z ) + ( wrist_y * wrist_y );
          float s_w_sqrt = sqrt( s_w );
    
          // s_w angle to ground
          float a1 = atan2( wrist_z, wrist_y );
    
          // s_w angle to humerus, note the need to clamp this value between -1 to 1!
          float q = ((_humerousSq - _ulnaSq) + s_w) / (2 * _humerous * s_w_sqrt);
          if (q > 1)
            q = 1;
          else if (q < -1)
            q = -1;
    
          float a2 = acos(q);
    
          // shoulder angle
          float altitudeRads = a1 + a2;
          setAltitude(altitudeRads);
    
          // elbow angle
          float elb_angle_r = acos(( _humerousSq + _ulnaSq - s_w ) / ( 2 * _humerous * _ulna ));
          float elb_angle_rn = -( STRAIGHT_ANGLE - elb_angle_r );
    
          // wrist angle
          float wri_angle_r = _wristGroundAngleRads - elb_angle_rn - altitudeRads;
    
          // Set the joints
          setElbow(elb_angle_r);
          setWristAngle(wri_angle_r);
        }
      
        // getX returns the current gripper X position.
        float getX()
        {
          return _x;
        }
        
        /*
          moveX : smoothly movesX axis of arm from current position to newX passed in.
          Parameters:
            newX  the new left right displacement
         */
        void moveX(float newX)
        {
          float inc = getInc(getX(), newX) * 0.75;
    
          // Move the arm to the post x offset
          for(float idx = getX(); loopAgain(idx, newX, inc); idx += inc)
          {
            setX(idx);
            delay(_rampDelay);
          }
        }
    
        /*
          SetX : Arm positioning routine utilizing inverse kinematics.  Moves the arm from
          the current X coordinate to the newX passed in.  It maintains all other position
          state.
          Parameters:
            newX   the new left right displacement
         */
        void setX(float newX)
        {
          setPosition(newX, _y, _z, _wristGroundAngleRads);
        }
    
        // getY returns the current gripper Y position.
        float getY()
        {
          return _y;
        }
    
        /*
          moveY : smoothly moves Y axis of arm from current position to newY passed in.
          Parameters:
            newY  the new front to back displacement
         */
        void moveY(float newY)
        {
          float inc = getInc(getY(), newY) * 0.75;
          
          // Move the arm to the post x offset
          for(float idx = getY(); loopAgain(idx, newY, inc); idx += inc)
          {
            setY(idx);
            delay(_rampDelay);
          }
        }
    
        /*
          SetY : Arm positioning routine utilizing inverse kinematics.  Moves the arm from
          the current Y coordinate to the newY passed in.  It maintains all other position
          state.
          Parameters:
            newY   the new forward/back displacement
         */
        void setY(float newY)
        {
          setPosition(_x, newY, _z, _wristGroundAngleRads);
        }
    
        // getZ returns the current gripper Z position.
        float getZ()
        {
          return _z;
        }
    
        /*
          moveZ : smoothly moves Z axis of arm from current position to newZ passed in.
          Parameters:
            newZ  the new up down displacement
         */
        void moveZ(float newZ)
        {
          float inc = getInc(getZ(), newZ) * 0.75;
    
          // Move the arm to the post x offset
          for(float idx = getZ(); loopAgain(idx, newZ, inc); idx += inc)
          {      
            setZ(idx);
            delay(_rampDelay);
          }
        }
    
        /*
          SetZ : Arm positioning routine utilizing inverse kinematics.  Moves the arm from
          the current Z coordinate to the newZ passed in.  It maintains all other position
          state.
          Parameters:
            z   the new height above the table
         */
        void setZ(float newZ)
        {
          setPosition(_x, _y, newZ, _wristGroundAngleRads);
        }
    
        /*
          moveXY : smoothly moves X and Y axis of arm from current position to newX and newY
          passed in. Useful for compound motion in the XY plane.
          Parameters:
            newX  the new left right displacement
            newY  the new front back displacement
         */
        void moveXY(float newX, float newY)
        {
          if (newX == _x)
            moveY(newY);
          else if (newY == _y)
            moveX(newX);
          else
          {
            float deltaX = newX - _x;
            float deltaY = newY - _y;
            
            // compute the slope from delta y / delta x
            float slope = deltaY / deltaX;
    
            float inc = getInc(getX(), newX);
    
            // Move the arm to the post x offset
            for(float idx = getX(); loopAgain(idx, newX, inc); idx += inc)
            {
              newY += slope;
              setXY(idx, newY);
              delay(_rampDelay);
            }
          }
        }
    
        /*
          setXY : moves X and Y axis of arm from current position to newX and newY
          passed in. Useful for compound motion in the XY plane.
          Parameters:
            newX  the new left right displacement
            newY  the new front back displacement
         */
        void setXY(float newX, float newY)
        {
          setPosition(newX, newY, _z, _wristGroundAngleRads);
        }
    
        /*
         setAzimuth: Sets base angle data member, computes the servo pulse width for that angle,
           and sets the associated servo.
         Parameters:
           baseAngle    floating point radian value
        */
        void setAzimuth(float baseAngle)
        {   
          _azimuthAngleRads = baseAngle;
          joints[AZIMUTH].setPosition(_azimuthAngleRads);
        }
    
       /*
         setAltitude: Sets the should angle member, computes the servo pulse width for that angle,
           and sets the associated servo.
         Parameters:
           shoulderAngle   32-bit floating point radian value
        */
        void setAltitude(float altitudeAngleRads)
        {
          // Rotate the coordinate system by a right angle so that a straight angle is
          // a full extension of the joint.
          _altitudeAngleRads = altitudeAngleRads - RIGHT_ANGLE;
    
          joints[ALTITUDE].setPosition(_altitudeAngleRads);
          joints[ALTITUDE+1].setPosition(_altitudeAngleRads);
        }
    
        /*
          SetElbow: Sets elbow angle data member, computes the servo pulse width for that angle,
            and sets the associated servo.
          Parameters:
            elbowAngle   floating point radian value
         */
        void setElbow(float elbowAngle)
        {
          // Rotate the coordinate system by a right angle so that a straight angle is
          // a full extension of the joint.
          _elbowAngleRads = elbowAngle - STRAIGHT_ANGLE;
          joints[ELBOW].setPosition(_elbowAngleRads);
        }
    
        /* returns the wrist ground angle
         */
        float getWristAngle()
        {
          return _wristGroundAngleRads;
        }
    
        /*
          SetWristAngle: Sets the wrist angle member, computes the servo pulse width for that angle,
            and sets the associated servo.
          Parameters:
            wristAngle   32-bit floating point radian value
         */
        void setWristAngle(float wristAngle)
        {
          joints[WRIST].setPosition(wristAngle);
        }
    
        /* getWristRotate: returns current wrist rotation angle.
         */    
        float getWristRotate()
        {
          return _wristRotAngleRads;
        }
    
        /*
          setWristRotate: Sets the gripper angle of rotation member, computes the servo pulse width,
            and sets the associated servo.
          Parameters:
            gripAngle   32-bit floating point radian value
         */
        void setWristRotate(float wristRotateAngle)
        {
          _wristRotAngleRads = wristRotateAngle;
          joints[ROTATE].setPosition(_wristRotAngleRads);
        }
      
        // getGripper returns the current gripper distance
        float getGripper()
        {
          return _fingerAngleRads;
        }
    
        /*
          moveGripper : smoothly moves gripper seperation from current position to newDist passed in.
          Parameters:
            newDist  the new gripper distance
         */
        void moveGripper(float newDist)
        {
          float inc = getInc(getGripper(), newDist);
    
          // Move the arm to the post x offset
          for(float idx = getGripper(); loopAgain(idx, newDist, inc); idx += inc)
          {
            setGripper(idx);
            delay(_rampDelay);
          }
        }
    
        /*
          setGripper: Sets the gripper distance of the fingers.
          Parameters:
            distance   32-bit floating point radian value
         */
        void setGripper(float distance)
        {
          _fingerAngleRads = distance;
          joints[GRIP].setPosition(distance);
        }
    
      private:
        float _baseHeight;
        float _humerous;
        float _ulna;
        float _gripper;
        float _humerousSq;
        float _ulnaSq;
    
        // Coordinate of gripper tip in cartesian space.
        float _x, _y, _z;
        float _wristGroundAngleRads;
    
        // Coordinates in arm revolute polar space.
        float _azimuthAngleRads;
        float _altitudeAngleRads;
        float _elbowAngleRads;
        float _wristRotAngleRads;
        float _fingerAngleRads;
        
        int _rampDelay;
    
        Joint joints[COUNT];
        
        inline float getInc(float start, float finish)
        {
          return start > finish ? -1 : 1;
        }
    
        inline bool loopAgain(float idx, float limit, float inc)
        {
          bool retVal = false;
          if (inc > 0 && idx <= limit)
              retVal = true;
          else if (inc < 0 && idx >= limit)
              retVal = true;
         
          return retVal;     
        }
    };
    

    Finally here's the gcode seen in the video:
    G28
    M11
    G92 X0 Y75 Z40 A-90.00
    G1 X-55
    G1 Z0
    M10
    G1 Z20
    G1 Y140
    G92 C-27
    G1 Z0
    M11
    G1 Z20
    G92 C0
    G1 Y75
    G1 Z0
    M10
    G1 Z20
    G1 Y135
    G92 C-27
    G1 Z0
    M11
    G1 Z20
    G92 C0
    G1 Y75
    G1 Z0
    M10
    G1 Z20
    G1 X0
    G1 Y140
    G92 C-50
    G1 Z0
    M11
    G1 Z20
    G92 C0
    G1 Y75
    G1 X-55
    G1 Z0
    M10
    G1 Z20
    G1 X0
    G1 Y135
    G92 C-50
    G1 Z0
    M11
    G1 Z20
    G92 C0
    G1 Y75
    G1 X-55
    G1 Z0
    M10
    G1 Z20
    G1 X55
    G1 Y140
    G92 C-70
    G1 Z0
    M11
    G1 Z20
    G92 C0
    G1 Y75
    G1 X-55
    G1 Z0
    M10
    G1 Z20
    G1 X55
    G1 Y140
    G92 C-70
    G1 Z0
    M11
    G1 Z30
    G92 C0
    G1 Y75
    G1 X0
    

    And video:
  • ercoerco Posts: 20,255
    edited 2014-01-04 11:26
    Metal arm kit from China on sale for $99.36 now: http://www.ebay.com/itm/160998716216?ssPageName=STRK:MEWAX:IT&_trksid=p3984.m1423.l2649

    Servos (4 metal gear) & hardware included, no battery or controller. Who will answer the call?
  • ercoerco Posts: 20,255
    edited 2014-03-31 10:32
    Stock market is up yet robot arm prices are dropping. Same metal arm kit as above on sale for $97, no battery or controller. I had to get one just to review it. I'd love to recommend the original plywood arm kit which Martin and I have, but that seller has been MIA for a while.

    http://www.ebay.com/itm/New-6-DOF-Manipulator-Aluminum-Robot-Arm-Kits-for-Smart-Car-Base-/160998716216
  • ercoerco Posts: 20,255
    edited 2014-04-08 09:37
    Nice kit! Just got it. All the metal brackets are beautiful black powder coated. Tiny base bracket, it will have to bolted to a board to use. Servos (prolly clones) are included, but no controller.

    Two problems though:

    1) No instructions. Waiting on seller. Bad photos abound online, but I haven't found any assembly instructions online yet. I love a good puzzle, but...
    2) No obvious ways or screws to connect to the plastic servo horns. Other arm sellers' ads say "you may want metal servo horns", which would solve the problem. I ordered these, which should solve the problem (and should have been included in the kit): http://www.ebay.com/itm/281295998759 Works out to 92.4 cents each. Naturally, I need six and they come in 5-packs. Just like hot dogs & buns, nothing ever works out just right.

    Edit: The holes in the plastic servo horns can be easily hand-threaded with a 2-56 tap, or else forego tapping and use some elbow grease to drive 2-56 screws in for an interference fit.
Sign In or Register to comment.