Evan Rust
Published © GPL3+

Mirroring Robot Movements with Packet Radios

Use a couple of packet radio boards to mirror one robot's movements in the other one from up to 500m away.

IntermediateFull instructions provided4 hours1,150

Things used in this project

Hardware components

Adafruit Feather M0 RFM69HCW Packet Radio
×2
SparkFun Full-Bridge Motor Driver Breakout - L298N
SparkFun Full-Bridge Motor Driver Breakout - L298N
×2
DC motor (generic)
×8

Software apps and online services

VS Code
Microsoft VS Code
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Schematic

Code

swarmBot.ino

C/C++
Either define LEADER or FOLLOWER, but not both and not none
#include "pinDefs.h"
#include <queue>
#include <Adafruit_NeoPixel.h>
#include <RHReliableDatagram.h>
#include <RH_RF69.h>

#define MOVEMENT_DELAY 100
const uint8_t motorPins[4] = {MOTOR_IN1, MOTOR_IN2, MOTOR_IN3, MOTOR_IN4};

// only uncomment ONE based on its role!
//#define LEADER
#define FOLLOWER

struct Movement
{
    int16_t leftMotor, rightMotor, movementTime;
};

#ifdef LEADER
#define ADDRESS 1
#define DEST_ADDRESS 2
const Movement movements[] = 
    {{200, 200, 1000}, {50, -50, 2500}, {-100, -100, 750}, 
    {-50, 50, 480}, {200, 200, 1000}, {-150, 200, 600}, {200, -150, 600}, 
    {-100, -100, 1000}, {0, 0, 4000}, {100, 200, 2000}};

#endif
#ifdef FOLLOWER
#define ADDRESS 2
std::queue<Movement> movementHistory;

#endif

Adafruit_NeoPixel pixel(1, RGB_LED_PIN, NEO_GRB + NEO_KHZ800);
RH_RF69 rf69(RFM69_CS, RFM69_INT);
RHReliableDatagram rf69_manager(rf69, ADDRESS);

void setup()
{
    Serial.println(115200);
    radioInit();
    pixel.begin();
    for(uint8_t i = 0; i < 4; i++)
    {
        pinMode(motorPins[i], OUTPUT);
    }
}

uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];
uint8_t data[] = "OK";

void loop()
{
    
    #ifdef LEADER
    uint8_t radioPacket[6];
    Movement movement;
    for(uint8_t movementNum = 0; movementNum < sizeof(movements) / sizeof(Movement); movementNum++)
    {
        movement = movements[movementNum];
        bool success = false;
        while(!success) {
            radioPacket[0] = movement.leftMotor >> 8;
            radioPacket[1] = movement.leftMotor & 0xFF;
            radioPacket[2] = movement.rightMotor >> 8;
            radioPacket[3] = movement.rightMotor & 0xFF;
            radioPacket[4] = movement.movementTime >> 8;
            radioPacket[5] = movement.movementTime & 0xFF;
            Serial.printf("Sending leftMotor=0x%04x\n", (radioPacket[0] << 8) + radioPacket[1]);
            if(rf69_manager.sendtoWait((uint8_t *)radioPacket, 6, DEST_ADDRESS)) {
                uint8_t len = sizeof(buf);
                uint8_t from;
                if(rf69_manager.recvfromAckTimeout(buf, &len, 2000, &from)) {
                    Serial.println(F("received by client"));
                    success = true;
                } else Serial.println("Failed to receive");
            } else Serial.println(F("Failed to send"));
            if(success) {
                runMotors(movement);
            }
        }
    }
    #endif
    #ifdef FOLLOWER
    if(rf69_manager.available()) {
        uint8_t len = sizeof(buf);
        uint8_t from;
        Movement movement;
        if(rf69_manager.recvfromAck(buf, &len, &from)) {
            movement.leftMotor = (buf[0] << 8) + buf[1];
            movement.rightMotor = (buf[2] << 8) + buf[3];
            movement.movementTime = (buf[4] << 8) + buf[5];
            Serial.printf("Packet contents= %d, %d, %d\n", movement.leftMotor, movement.rightMotor, movement.movementTime);
            movementHistory.push(movement);
            if(rf69_manager.sendtoWait(data, sizeof(data), from)) {
                runMotors(movement);
            } else Serial.println(F("Send failed"));
        }
    }
    #endif
}

void radioInit()
{
    pinMode(RFM69_RST, OUTPUT);
    digitalWrite(RFM69_RST, LOW);

    // manual reset
    digitalWrite(RFM69_RST, HIGH);
    delay(10);
    digitalWrite(RFM69_RST, LOW);
    delay(10);

    if (!rf69_manager.init()) {
        Serial.println("RFM69 radio init failed");
        while (1);
    }
    Serial.println("RFM69 radio init OK!");
    if (!rf69.setFrequency(RF69_FREQ)) {
        Serial.println("setFrequency failed");
    }
    rf69.setTxPower(20, true);
}

void runMotors(Movement & toMove)
{
    int16_t m1 = (toMove.rightMotor > 0) ? toMove.rightMotor : 0,
            m2 = (toMove.rightMotor > 0) ? 0 : -toMove.rightMotor,
            m3 = (toMove.leftMotor > 0) ? toMove.leftMotor : 0,
            m4 = (toMove.leftMotor > 0) ? 0 : -toMove.leftMotor;
    int16_t motorVals[4] = {m1, m2, m3, m4};

    for(uint8_t i = 0; i < 4; i++)
        analogWrite(motorPins[i], motorVals[i]);

    delay(toMove.movementTime);

    for(uint8_t i = 0; i < 4; i++)
        analogWrite(motorPins[i], 0);
}

pinDefs.h

C/C++
#include "Arduino.h"

#define MOTOR_IN1 12
#define MOTOR_IN2 11
#define MOTOR_IN3 9
#define MOTOR_IN4 10

#define RGB_LED_PIN A0

#define RF69_FREQ 915.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4

Credits

Evan Rust

Evan Rust

121 projects • 1055 followers
IoT, web, and embedded systems enthusiast. Contact me for product reviews or custom project requests.

Comments