A kit from China with three stepper motors and a linear rail has become a fully controllable robot arm – with custom firmware, inverse kinematics, 3D simulation and a web interface I use to save and run points and paths. Below, the arm runs in the browser to try out.
The same inverse kinematics as in the real software: a target is set, the computer works out the joint angles. Dragging with finger or mouse rotates the view; the arm is controlled with sliders and buttons. No server, no installation – everything runs live.
The kit's original software was a simple remote control. I rebuilt everything – firmware and PC software – so that the arm really does what it is supposed to do.
I enter Cartesian X/Y/Z coordinates in millimetres – the software calculates the joint angles from them and moves along true straight paths, divided into fine segments.
A live 3D view shows at all times how the arm is currently positioned – rotatable, zoomable, rendered entirely in-house without external libraries.
Save, name and return to positions. Waypoints, pauses and gripper actions can be combined into complete sequences and played in a loop.
Custom firmware with limit switch monitoring during every move, software travel limits against the end stops and a real emergency stop at the press of a key.
For the rotary axis I replaced the mechanical limit switch with an optical light barrier – including suitably configured signal logic in the firmware.
I decoded the communication protocol of the original firmware by disassembly – the basis for later replacing it with completely custom firmware.
Deliberately kept lean: runs locally, without a cloud, without heavy dependencies.
The heart is an Arduino Mega 2560 with a RAMPS 1.4 shield – the classic platform for motor control. Three stepper motors drive the joints, a fourth the linear rail. Special feature: on the rotary axis an optical light barrier sits instead of a mechanical limit switch, so that the axis can safely spin through if in doubt.
| Function | Connection (RAMPS) | Pin |
|---|---|---|
| Upper arm joint (X) | Driver slot | STEP/DIR/EN |
| Lower arm joint (Y) | Driver slot | STEP/DIR/EN |
| Rotary axis (Z) | Driver slot | STEP/DIR/EN |
| Linear rail (E) | E0 driver | STEP/DIR/EN |
| Limit switch, upper arm | X-min | D3 |
| Limit switch, lower arm | Y-min | D14 |
| Light barrier, rotary axis | Z-min (S/−/+) | D18 |
| Gripper / suction cup / fan | FET outputs | D10 / D9 / D8 |
| Power supply | Power supply unit | 12 V DC |
I want to specify a point in space – and the arm should set its joints to match by itself. That is inverse kinematics. The arm is a classic two-joint chain on a turntable, which means it can be solved exactly in closed form.
First the turntable rotates so that the target point lies in the plane of the arm. The rotation angle follows directly from the sideways and forward components:
In this plane a two-joint problem remains: reach r and height z give the direct distance d to the target. Using the law of cosines the two arm angles follow:
If d is greater than a + b (too far) or less than |a − b| (too close), the point is unreachable – the software then refuses the move instead of running into an end stop.
Conversely, forward kinematics calculates the actual position back from measured joint angles – so the 3D view always knows where the arm really is. I checked both directions against each other: the back-calculation agrees to within less than a thousandth of a millimetre.
From installation to the first program – in a compact summary.
Install Python 3, once pip install pyserial, then Start_Windows.bat. The browser opens the interface at localhost:8000 – completely local, no cloud.
In the Arduino IDE, install the library AccelStepper and open
RobotArmFW.ino – then select the board “Mega 2560” and upload. The factory firmware is kept as a backup.
Select the port, connect. Use M119 to test the limit switches – for the light barrier, “interrupted = 1” should appear. Only then make small jog steps.
With G28 each axis moves to its limit switch (coarse → back off → fine) and sets the zero point. It can be aborted at any time with the emergency stop (space bar, button or !).
Move each axis a known distance, measure it, adjust “steps per unit”. After that the Cartesian millimetres are true to scale.
Limit switch monitoring during every move, software travel limits against the end stops, unreachable targets are rejected, a real emergency stop halts everything immediately.
| Command | Function |
|---|---|
| G0 / G1 X Y Z E F | Joint/axis move (coordinated, with ramps) |
| G4 S<s> | Pause / dwell time |
| G28 [X Y Z] | Homing (can be aborted) |
| G90 / G91 | Absolute / relative mode |
| G92 X Y Z E | set current position |
| M17 / M18 | motors on / off |
| M42 P<pin> S<0..255> | switch output (gripper/suction cup/fan) |
| M112 or ! | Emergency stop – halts everything immediately |
| M114 | report position |
| M119 | report limit switch states |
The complete guide, including calibration, comes with the download as the
FIRMWARE_ANLEITUNG.md and LIESMICH.md files.
From the 3D simulation – this is how every joint position is shown live.
The three joint angles are calculated from a target point in space – classic two-joint trigonometry plus base rotation.
def ik(x, y, z, geo): # Cartesian -> joint angles (returns: angles or None) a, b = geo["lower_arm"], geo["upper_arm"] base = math.degrees(math.atan2(x, y)) # base rotation r = math.hypot(x, y) - geo["tool_offset_h"] # reach zz = z - geo["base_height"] # height above shoulder d = math.hypot(r, zz) if d > a + b or d < abs(a - b): return None # unreachable q2 = math.acos((a*a + d*d - b*b) / (2*a*d)) shoulder = math.atan2(zz, r) + q2 # lower arm elbow = math.acos((a*a + b*b - d*d) / (2*a*b)) upper = shoulder + elbow - math.pi # upper arm, absolute return {"z": base, "y": deg(shoulder), "x": deg(upper)}
X, Y and Z mean different things in two places here – as a coordinate in space and as a joint name in G-code. This is the most common stumbling block when working with the arm. Clicking a row highlights the axis.
| Letter | As a coordinate (input fields, Cartesian jog) | As a joint (G-code, joint jog, firmware) |
|---|---|---|
X | left / right in mm | Upper arm absolute angle in degrees |
Y | forward / back in mm | Lower arm angle above horizontal in degrees |
Z | Height in mm | Base rotation in degrees |
E | – | Rail in mm |
A G1 Z50 does not move 50 mm upwards – it rotates the base to 50°. To go up in height, use the Cartesian jog or the coordinate input; they send the target position through the inverse kinematics and only then produce the joint angles.
Two points with a straight line between them – obvious for a human, not for an articulated arm. If the joints are simply moved evenly from start to target angles, the tip describes an arc. For a true straight line, the path has to be broken down into small steps and the inverse kinematics calculated for each and every one of them – which is exactly what move_cart(linear=True)
does in the software, with cart_step as the step size.
Firmware, control software and guides – all in one package. Runs on any PC with Python; the firmware goes onto the controller via the Arduino IDE.
⭳ Request ArmControl.zipQuestions about the construction, the decisions behind it or how it could carry over to another case.