Robot arm inverse kinematics on an Arduino Uno
Two knobs set where the claw should be in millimeters, and the Uno solves the shoulder and elbow angles with the law of cosines, the way real arms are steered. The whole build, an Arduino Uno and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Advanced About 30 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $56. On Mokxi they're free.
What you'll learn
- Inverse kinematics: from a target position to joint angles
- The law of cosines on a two-link arm
- Reach limits and servo limits
- Mapping knob readings to millimeters with
map - Returning success or failure from a function
- Difficulty: Advanced
- Time: About 30 minutes
- Board: Arduino Uno R3
- Parts: 6
How it works
Two potentiometers choose a target point for the claw, measured from the shoulder. The knob on A0 sets x, how far forward, from 20 to 150 mm. The knob on A1 sets y, how high, from -60 to 120 mm.
The sketch then runs inverse kinematics: given where the claw should be, find the shoulder and elbow angles. The upper arm (72 mm) and forearm (84 mm) make a triangle with the line to the target, and the law of cosines gives the angles.
If the target is too far away, too close, or needs a servo past its limits of 40 to 160 degrees, the arm holds still and the Serial Monitor says so.
Parts list
6 parts, plus the jumper wires. They retail for about $56; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Robot arm, 4 servos
- 1 × Half breadboard
- 2 × PotentiometerIn Halloween Prop Kit, Starter Projects Kit.
- 1 × Power, 5 V
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Arduino Uno R3 pin GND | Ground |
| Robot arm, 4 servos pin GRIP | Arduino Uno R3 pin 9 |
| Robot arm, 4 servos pin ELBOW | Arduino Uno R3 pin 6 |
| Robot arm, 4 servos pin SHOULDER | Arduino Uno R3 pin 5 |
| Robot arm, 4 servos pin BASE | Arduino Uno R3 pin 3 |
| Potentiometer (1) pin 3 | Arduino Uno R3 pin 5V |
| Potentiometer (2) pin 3 | Arduino Uno R3 pin 5V |
| Potentiometer (1) pin 1 | Arduino Uno R3 pin GND |
| Potentiometer (2) pin 1 | Arduino Uno R3 pin GND |
| Potentiometer (1) pin 2 | Arduino Uno R3 pin A0 |
| Potentiometer (2) pin 2 | Arduino Uno R3 pin A1 |
| Robot arm, 4 servos pin VCC | 5 V |
| Robot arm, 4 servos pin GND | Ground |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Robot arm inverse kinematics: say where the claw should be, in
// millimeters, and the sketch works out the shoulder and elbow angles.
//
// arm BASE pin 3, SHOULDER pin 5, ELBOW pin 6, GRIP pin 9
// VCC and GND from a separate 5 V supply, GND joined to the Uno's
// knobs target x (how far forward) on A0, target y (how high) on A1
//
// The arm is two links seen from the side: the upper arm, L1 long, from the
// shoulder to the elbow, and the forearm, L2 long, from the elbow to the
// claw. Measured from the shoulder, x is forward and y is up.
//
// FORWARD KINEMATICS is the easy way round: given the two link angles,
// where is the claw? With both angles measured up from level,
//
// elbow = (L1 cos(upper), L1 sin(upper))
// claw = elbow + (L2 cos(fore), L2 sin(fore))
//
// INVERSE KINEMATICS is the way round a robot needs: given the claw's
// (x, y), which angles? The shoulder, the elbow and the claw make a
// triangle with sides L1, L2 and d, the straight line from shoulder to
// claw. The law of cosines gives the angle at the shoulder between d and
// the upper arm:
//
// d = sqrt(x*x + y*y)
// a = acos((L1*L1 + d*d - L2*L2) / (2 * L1 * d))
// upper = atan2(y, x) + a (the elbow above the line, not below)
//
// That puts the elbow at (L1 cos(upper), L1 sin(upper)), and the forearm
// simply points from there to the target. If d is longer than L1 + L2 the
// claw cannot reach, and acos would be handed a number past 1.
//
// THIS ARM'S SERVOS. On the desktop arm the shoulder servo leans the upper
// arm: 90 is upright and 180 leans fully forward, so the servo angle is
// 180 minus the upper arm's angle above level. The elbow servo sets the
// forearm through a side linkage: 90 is level and 180 points straight up,
// so the servo angle is 90 plus the forearm's angle above level.
#include <Servo.h>
#include <math.h>
Servo base, shoulder, elbow, claw;
// The two link lengths in millimeters, shoulder to elbow and elbow to claw.
// Measure your own arm and put its numbers here.
const float L1 = 72.0;
const float L2 = 84.0;
// The servos stop short of their ends so the arm cannot fold into itself.
const int LOW_END = 40;
const int HIGH_END = 160;
const float DEGREES = 180.0 / M_PI;
// Solve for a target. Returns false, and leaves the angles alone, when the
// target is out of reach or would need a servo past its limits.
bool solve(float x, float y, int &shoulderAngle, int &elbowAngle) {
float d = sqrt(x * x + y * y);
if (d > L1 + L2 || d < fabs(L1 - L2) || d < 1) return false;
// The law of cosines: the angle at the shoulder between the line to the
// target and the upper arm.
float a = acos((L1 * L1 + d * d - L2 * L2) / (2 * L1 * d));
float upper = atan2(y, x) + a;
// Where that puts the elbow, and the forearm's angle from there.
float elbowX = L1 * cos(upper);
float elbowY = L1 * sin(upper);
float fore = atan2(y - elbowY, x - elbowX);
// Link angles to this arm's servo angles.
int s = (int)lround(180 - upper * DEGREES);
int e = (int)lround(90 + fore * DEGREES);
if (s < LOW_END || s > HIGH_END || e < LOW_END || e > HIGH_END) return false;
shoulderAngle = s;
elbowAngle = e;
return true;
}
int lastX = -1000, lastY = -1000;
void setup() {
base.attach(3);
shoulder.attach(5);
elbow.attach(6);
claw.attach(9);
base.write(90);
claw.write(60);
Serial.begin(115200);
Serial.println("inverse kinematics arm: turn the knobs to move the target");
}
void loop() {
// The knobs give the target: x from 20 to 150 mm, y from -60 to 120 mm.
int x = map(analogRead(A0), 0, 1023, 20, 150);
int y = map(analogRead(A1), 0, 1023, -60, 120);
int s, e;
bool reached = solve(x, y, s, e);
if (reached) {
shoulder.write(s);
elbow.write(e);
}
// Report each new target once.
if (x != lastX || y != lastY) {
lastX = x;
lastY = y;
Serial.print("target x ");
Serial.print(x);
Serial.print(" mm y ");
Serial.print(y);
Serial.print(" mm -> ");
if (reached) {
Serial.print("shoulder ");
Serial.print(s);
Serial.print(" elbow ");
Serial.println(e);
} else {
Serial.println("out of reach or past a joint limit, holding");
}
}
delay(20);
}
Code walkthrough
The key functions of the sketch, and what each one does.
solve sketch.ino
Finds the distance d from shoulder to target. If d is longer than the two links together, or shorter than their difference, it returns false. Otherwise the law of cosines gives the shoulder angle, the elbow position follows, and the forearm points at the target. Then it converts link angles to this arm's servo angles and rejects anything outside 40 to 160.
// Solve for a target. Returns false, and leaves the angles alone, when the
// target is out of reach or would need a servo past its limits.
bool solve(float x, float y, int &shoulderAngle, int &elbowAngle) {
float d = sqrt(x * x + y * y);
if (d > L1 + L2 || d < fabs(L1 - L2) || d < 1) return false;
// The law of cosines: the angle at the shoulder between the line to the
// target and the upper arm.
float a = acos((L1 * L1 + d * d - L2 * L2) / (2 * L1 * d));
float upper = atan2(y, x) + a;
// Where that puts the elbow, and the forearm's angle from there.
float elbowX = L1 * cos(upper);
float elbowY = L1 * sin(upper);
float fore = atan2(y - elbowY, x - elbowX);
// Link angles to this arm's servo angles.
int s = (int)lround(180 - upper * DEGREES);
int e = (int)lround(90 + fore * DEGREES);
if (s < LOW_END || s > HIGH_END || e < LOW_END || e > HIGH_END) return false;
shoulderAngle = s;
elbowAngle = e;
return true;
}setup sketch.ino
Attaches the four servos: base on pin 3, shoulder on 5, elbow on 6 and claw on 9. It parks the base at 90 and the claw at 60, since only the shoulder and elbow are solved. It also starts the Serial Monitor at 115200.
void setup() {
base.attach(3);
shoulder.attach(5);
elbow.attach(6);
claw.attach(9);
base.write(90);
claw.write(60);
Serial.begin(115200);
Serial.println("inverse kinematics arm: turn the knobs to move the target");
}loop sketch.ino
Reads both knobs, turns them into x and y in millimeters, and calls solve. If it succeeds, the shoulder and elbow servos get the new angles. If not, they stay where they were. A line prints only when the target changes.
void loop() {
// The knobs give the target: x from 20 to 150 mm, y from -60 to 120 mm.
int x = map(analogRead(A0), 0, 1023, 20, 150);
int y = map(analogRead(A1), 0, 1023, -60, 120);
int s, e;
bool reached = solve(x, y, s, e);
if (reached) {
shoulder.write(s);
elbow.write(e);
}
// Report each new target once.
if (x != lastX || y != lastY) {
lastX = x;
lastY = y;
Serial.print("target x ");
Serial.print(x);
Serial.print(" mm y ");
Serial.print(y);
Serial.print(" mm -> ");
if (reached) {
Serial.print("shoulder ");
Serial.print(s);
Serial.print(" elbow ");
Serial.println(e);
} else {
Serial.println("out of reach or past a joint limit, holding");
// ... 4 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The arm never moves and the monitor says out of reach This project
The target is outside what 72 mm and 84 mm links can reach. Turn the knobs toward the middle of their range. - The arm jerks or the Uno resets This project
Power the servos from the separate 5 V supply, and join its ground to the Uno's ground. - The claw misses the target on a different arm This project
Measure your own arm and changeL1andL2to match. - The arm jerks, resets the board or moves weakly when several joints move at once. Robot arm
Four SG90 servos can pull an amp or more together. Power them from a separate 5 V supply with a 470 uF or larger capacitor. Join its ground to the board's ground. - A joint hits its end stop and buzzes, or the claw crushes. Robot arm
Limit each servo to a safe angle range in code and move gradually in small steps. Find the open and closed angle of the claw by testing, and never command beyond them. - The reading jumps around or only covers part of the range. Potentiometer
Wire the two outer legs to 5 V and ground and the middle leg (the wiper) to an analog pin. Using the wrong two legs gives a dead or partial range. - Turning the knob does nothing, or the value is stuck at 0 or 1023. Potentiometer
Check that the wiper goes to an analog input (A0 to A5 on an Uno), not a digital pin. Make sure both ends are connected, since a floating end gives noise. - Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.