Library for controlling #MeArm from phenoptix (http://www.phenoptix.com/collections/mearm). Solves inverse kinematics of the arm to position gripper in 3D space.
Dependents: wizArm_WIZwiki-W7500ECO wizArm_WIZwiki-W7500ECO MeArm_Lab2_Task1 MeArm_Lab3_Task1 ... more
Diff: MeArm.cpp
- Revision:
- 0:49b8e971fa83
- Child:
- 1:d2b7780ca6b3
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MeArm.cpp Tue Nov 04 20:54:49 2014 +0000 @@ -0,0 +1,164 @@ +/* +@file MeArm.cpp + +@brief Member functions implementations + +*/ +#include "mbed.h" +#include "MeArm.h" + +MeArm::MeArm(PinName basePin, PinName shoulderPin, PinName elbowPin, PinName gripperPin) +{ + // set up pins as required + base = new PwmOut(basePin); + shoulder = new PwmOut(shoulderPin); + elbow = new PwmOut(elbowPin); + gripper = new PwmOut(gripperPin); + // PWM objects have 50 Hz frequency by default so shouldn't need to set servo frequency + // can do it anyway to be sure. All channels share same period so just need to set one of them + base->period(1.0/50.0); // servos are typically 50 Hz + // setup USB serial for debug/comms + serial = new Serial(USBTX,USBRX); + // leds used for debug + leds = new BusOut(LED4,LED3,LED2,LED1); +} + +void MeArm::setTarget(float x,float y,float z) +{ + target.x = x; + target.y = y; + target.z = z; + + // TODO - add checks for valid target +} + +/// TODO - add checks for valid angles +void MeArm::moveBaseServo(float angle) +{ + // arm facing forward is defined as 90 degrees, angle increases counter-clockwise + int pulseWidthMicroSeconds = 575.0 + 985.0*angle/90.0; + serial->printf("Base Pulse = %d us\n",pulseWidthMicroSeconds); + // equation to convert angle to pulsewidth in us + base->pulsewidth_us(pulseWidthMicroSeconds); + // move servo +} + +void MeArm::moveShoulderServo(float angle) +{ + // angle relative to horizontal + int pulseWidthMicroSeconds = 2705.0 - 1097.0*angle/90.0; + serial->printf("Shoulder Pulse = %d us\n",pulseWidthMicroSeconds); + // equation to convert angle to pulsewidth in us + shoulder->pulsewidth_us(pulseWidthMicroSeconds); + // move servo +} + +void MeArm::moveElbowServo(float angle) +{ + // angle relative to horizontal + int pulseWidthMicroSeconds = 1910.0 - 1095.0*angle/90.0; + serial->printf("Elbow Pulse = %d us\n",pulseWidthMicroSeconds); + // equation to convert angle to pulsewidth in us + elbow->pulsewidth_us(pulseWidthMicroSeconds); + // move servo +} + +void MeArm::moveServos() +{ + moveBaseServo(thetaBase); + moveShoulderServo(thetaShoulder); + moveElbowServo(thetaElbow); +} + +// calculate angle in DEGREES +float MeArm::cosineRule(float adj1, float adj2, float opp) +{ + // cosine rule + float tmp = (adj1*adj1 + adj2*adj2 - opp*opp) / (2*adj1*adj2); + //println("tmp = " + tmp); + + // check for valid angle + if ( fabs(tmp) <= 1.0 ) { + return RAD2DEG*acos(tmp); + } else { + error(1); + } + + return -1.0; // will never get here due to infinite loop +} + +void MeArm::error(int code) +{ + // flash error code in infinite loop + while(1) { + leds->write(code); + wait_ms(100); + leds->write(0); + wait_ms(100); + } +} + +void MeArm::solveInverseKinematics() +{ + serial->printf("#################################\n"); + serial->printf("\nx = %f y = %f z = %f\n\n",target.x,target.y,target.z); + + float maxReach = 220.0; // distance when arm outstretched (mm) + + // rudimentary checks if target is within reach - won't cover all possibilities - need to catch more later + if (target.z > 140.0 || target.z < 25.0 || target.y < 0 || target.y > maxReach || target.x < -maxReach || target.x > maxReach ) { + error(2); // hang and flash error code + } + + // distance to target in x-y plane + float rTarget = sqrt(target.x*target.x + target.y*target.y); + serial->printf("Distance to target = %f mm\n",rTarget); + + // if target out of reach + if (rTarget > maxReach ) { + error(3); // hang and flash error code + } + + // distance to wrist position in x-y plane + float rWrist = rTarget - L3; + serial->printf("Distance to wrist = %f mm\n",rWrist); + + // shoulder offset from centre of rotation of base + float rShoulder = L0; + serial->printf("Distance to shoulder = %f mm\n",rShoulder); + + // direct distance from shoulder to wrist + float Dsw = sqrt( (rWrist - rShoulder)*(rWrist - rShoulder) + (target.z-BASE_HEIGHT)*(target.z-BASE_HEIGHT) ); + serial->printf("Direct distance from shoulder to wrist = %f mm\n",Dsw); + + // angle from shoulder to wrist + float thetaSw; + // angle from shoulder to wrist - with check + if (fabs((rWrist - rShoulder)/Dsw) <= 1.0) { + thetaSw = RAD2DEG*acos((rWrist - rShoulder)/Dsw); + } else { + error(4); + } + + // if target is below base, then reflect the angle + if (target.z < BASE_HEIGHT) + thetaSw*=-1.0; + + serial->printf("Angle from shoulder to wrist = %f degree\n" , thetaSw); + + // elbow internal angle + float beta = cosineRule(L1, L2, Dsw); + serial->printf("Elbow internal angle = %f degrees\n",beta); + + // shoulder angle from horizontal + thetaShoulder = thetaSw + cosineRule(Dsw, L1, L2); + serial->printf("Shoulder servo angle = %f degrees\n",thetaShoulder); + + // convert to servo angle + thetaElbow = 180.0 - thetaShoulder - beta; + serial->printf("Elbow servo angle = %f\n",thetaElbow); + + // calculate angle from top view + thetaBase = RAD2DEG*atan2(target.y, target.x); + serial->printf("Base servo angle = %f degrees\n",thetaBase); +}