Wireless robot arm with nRF24L01 on an Arduino Uno
Two Unos and two nRF24L01 radios: joysticks on one board steer a four-servo arm on the other, with a failsafe that holds the arm when the link drops. The whole build, an Arduino Uno and 5 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 Runs in your browser. Free, and no account needed.
The parts retail for about $85. On Mokxi they're free.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Wireless robot arm, the controller: two joysticks on this Uno, sent over
// an nRF24L01 radio to the Uno that drives the arm.
//
// left stick VRX A0 (base), VRY A1 (shoulder)
// right stick VRX A2 (elbow), VRY A3 (claw)
// radio CE on 7, CSN on 8, SCK 13, MOSI 11, MISO 12, VCC on 3.3V
//
// About fifty times a second this board reads the four stick positions
// and sends them as one 8-byte packet. The radio on the arm answers every
// packet in hardware, so radio.write() says whether the packet got there. The
// controller counts the packets that were not answered, which is how a
// real transmitter knows it is losing the link.
//
// Both radios have to agree on the channel, the data rate, the address and
// the payload size. Change one on this side only and every packet is lost.
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(7, 8); // CE on 7, CSN on 8
const byte address[6] = "ARM01";
// What goes over the air: the four sticks, 0 to 1023 each. A struct is
// sent byte for byte, so both boards must declare it the same way.
struct Packet {
int16_t sticks[4];
};
const int STICKS[4] = {A0, A1, A2, A3};
unsigned long sent = 0, lost = 0;
unsigned long lastReport = 0;
void setup() {
Serial.begin(115200);
radio.begin();
radio.setPayloadSize(sizeof(Packet));
radio.setPALevel(RF24_PA_LOW);
radio.openWritingPipe(address);
radio.stopListening();
Serial.println("arm controller: move the sticks");
}
void loop() {
Packet packet;
for (int i = 0; i < 4; i++) packet.sticks[i] = analogRead(STICKS[i]);
bool heard = radio.write(&packet, sizeof(packet));
sent++;
if (!heard) lost++;
// Once a second, how the link is doing.
if (millis() - lastReport >= 1000) {
lastReport = millis();
Serial.print("sent ");
Serial.print(sent);
Serial.print(" lost ");
Serial.println(lost);
}
delay(20);
}
// Wireless robot arm, the arm: this Uno drives a four-servo arm from the
// stick positions the other Uno sends over an nRF24L01 radio.
//
// 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
// radio CE on 7, CSN on 8, SCK 13, MOSI 11, MISO 12, VCC on 3.3V
//
// The sticks are rate controls, as on the wired arm: push a stick and its
// joint keeps moving while you hold it, faster the further you push.
//
// FAILSAFE. A radio link can drop at any moment: out of range, a flat
// battery, the controller switched off. The arm then gets no more packets,
// and the worst thing it could do is keep moving on the last ones it heard.
// So it notes when each packet arrives, and after half a second of silence
// it stops every joint where it is and says so, until the link comes back.
// Every RC receiver has the same idea, called failsafe.
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>
RF24 radio(7, 8); // CE on 7, CSN on 8
const byte address[6] = "ARM01";
// Must match the controller's struct exactly.
struct Packet {
int16_t sticks[4];
};
Servo base, shoulder, elbow, claw;
Servo *joints[4] = {&base, &shoulder, &elbow, &claw};
const int SERVO_PINS[4] = {3, 5, 6, 9};
int angle[4] = {90, 90, 90, 70};
const int LOW_END[4] = {0, 40, 40, 20};
const int HIGH_END[4] = {180, 160, 160, 120};
const int DEAD_BAND = 60; // of 1023, either side of the middle
const unsigned long FAILSAFE_MS = 500;
Packet last = {{512, 512, 512, 512}}; // sticks centered: nothing moves
unsigned long lastHeard = 0;
bool linked = false;
void setup() {
Serial.begin(115200);
for (int i = 0; i < 4; i++) {
joints[i]->attach(SERVO_PINS[i]);
joints[i]->write(angle[i]);
}
radio.begin();
radio.setPayloadSize(sizeof(Packet));
radio.setPALevel(RF24_PA_LOW);
radio.openReadingPipe(1, address);
radio.startListening();
Serial.println("arm: waiting for the controller");
}
void loop() {
while (radio.available()) {
radio.read(&last, sizeof(last));
lastHeard = millis();
if (!linked) {
linked = true;
Serial.println("link up");
}
}
if (linked && millis() - lastHeard > FAILSAFE_MS) {
linked = false;
Serial.println("signal lost: failsafe, every joint holds");
}
if (linked) {
for (int i = 0; i < 4; i++) {
int offset = last.sticks[i] - 512;
if (offset > DEAD_BAND || offset < -DEAD_BAND) {
angle[i] = constrain(angle[i] + offset / 170, LOW_END[i], HIGH_END[i]);
}
}
}
for (int i = 0; i < 4; i++) joints[i]->write(angle[i]);
delay(20);
}
Parts list
9 parts, plus the jumper wires. They retail for about $85; on Mokxi they're free.
- 2 × Arduino Uno R3
- 2 × 2.4 GHz radio, nRF24L01+
- 1 × Robot arm, 4 servos
- 2 × Thumb stick
- 2 × Power, 5 V
How it is wired
29 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Ground: Arduino Uno R3 (1) pin GND; 2.4 GHz radio, nRF24L01+ (1) pin GND
- Arduino Uno R3 (1) pin 13; 2.4 GHz radio, nRF24L01+ (1) pin SCK
- Arduino Uno R3 (1) pin 12; 2.4 GHz radio, nRF24L01+ (1) pin MISO
- Arduino Uno R3 (1) pin 11; 2.4 GHz radio, nRF24L01+ (1) pin MOSI
- Arduino Uno R3 (1) pin 8; 2.4 GHz radio, nRF24L01+ (1) pin CSN
- Arduino Uno R3 (1) pin 7; 2.4 GHz radio, nRF24L01+ (1) pin CE
- Arduino Uno R3 (1) pin 3V3; 2.4 GHz radio, nRF24L01+ (1) pin VCC
- Arduino Uno R3 (1) pin 5V; Thumb stick (1) pin VCC
- Arduino Uno R3 (1) pin A0; Thumb stick (1) pin VRX
- Arduino Uno R3 (1) pin A1; Thumb stick (1) pin VRY
- Arduino Uno R3 (1) pin A2; Thumb stick (2) pin VRX
- Arduino Uno R3 (1) pin A3; Thumb stick (2) pin VRY
- Ground: Arduino Uno R3 (2) pin GND; 2.4 GHz radio, nRF24L01+ (2) pin GND
- Arduino Uno R3 (2) pin 13; 2.4 GHz radio, nRF24L01+ (2) pin SCK
- Arduino Uno R3 (2) pin 12; 2.4 GHz radio, nRF24L01+ (2) pin MISO
- Arduino Uno R3 (2) pin 11; 2.4 GHz radio, nRF24L01+ (2) pin MOSI
- Arduino Uno R3 (2) pin 9; Robot arm, 4 servos pin GRIP
- Arduino Uno R3 (2) pin 8; 2.4 GHz radio, nRF24L01+ (2) pin CSN
- Arduino Uno R3 (2) pin 7; 2.4 GHz radio, nRF24L01+ (2) pin CE
- Arduino Uno R3 (2) pin 6; Robot arm, 4 servos pin ELBOW
- Arduino Uno R3 (2) pin 5; Robot arm, 4 servos pin SHOULDER
- Arduino Uno R3 (2) pin 3; Robot arm, 4 servos pin BASE
- Arduino Uno R3 (2) pin 3V3; 2.4 GHz radio, nRF24L01+ (2) pin VCC
- Ground: Arduino Uno R3 (2) pin GND
- 5 V: Robot arm, 4 servos pin VCC
- Ground: Robot arm, 4 servos pin GND
- Ground: Thumb stick (1) pin GND
- Ground: Thumb stick (2) pin GND
- 5 V: Thumb stick (2) pin VCC
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.