Servo twitch - Adafruit Feather M0 RFM69HCW & RadioHead

I'm using two Adafruit Feather M0 RFM69HCW as TX and RX nodes to transfer various data. The basic set-up, using a struct containing different data-types, works.

However, already on the TX node, when I serial plot the values read from the pin to which a potentiometer's wiper is connected, the values often jump, and thus a servo connected to the RX node, does not rotate smoothly.

I suspect it has something to do with the handshaking between TX and RX, but what is the cause and how can I prevent these often large jumps in values from occurring?

Should I not use RHReliableDatagram.h and instead send in a "simpler" or "brute force" kind of way with another RadioHead class that prevents the apparent "hickups" on the TX side?

Thanks a lot for some suggestions!

TX code sufficient to show the issue

#include <SPI.h>
#include <RH_RF69.h>
#include <RHReliableDatagram.h>

#define RF69_FREQ 915.0

#define DEST_ADDRESS   1
#define MY_ADDRESS     2

#if defined(ADAFRUIT_FEATHER_M0)
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
#endif

RH_RF69 rf69(RFM69_CS, RFM69_INT);

RHReliableDatagram rf69_manager(rf69, MY_ADDRESS);

const byte pinPotentiometer = A1;

struct nodeData // Struct to contain the data to be transmitted
{
  bool momentaryswitch;
  int potentiometer;
  float sensor;
  unsigned long counter;
} nodeTX; // The struct's variable list name

uint8_t bufTX[RH_RF69_MAX_MESSAGE_LEN];

void setup()
{
  Serial.begin(115200);

  pinMode(RFM69_RST, OUTPUT);

  digitalWrite(RFM69_RST, LOW);

  if (!rf69_manager.init())
  {
    Serial.println("RFM69 radio init failed");
    while (1);
  }

  if (!rf69.setFrequency(RF69_FREQ))
  {
    Serial.println("setFrequency failed");
  }

  rf69.setTxPower(20, true);

  uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
                  };
  rf69.setEncryptionKey(key);

  nodeTX.momentaryswitch = 1; // Some dummy data to transmit
  nodeTX.potentiometer = 512;
  nodeTX.sensor = 21.36;
  nodeTX.counter = 0;
}

void loop()
{
  readPotentiometer ();

  if (rf69_manager.sendtoWait((uint8_t *)&nodeTX, sizeof(nodeTX), DEST_ADDRESS))
  {
    uint8_t len = sizeof(bufTX);
    uint8_t from;
    if (rf69_manager.recvfromAckTimeout(bufTX, &len, 1000, &from))
    {
      bufTX[len] = 0;
      Serial.println((char*)bufTX);
    }
    else
    {
      Serial.println("No reply RX -> TX");
    }
  }
  else
  {
    Serial.println("TX -> RX failed (no ack)");
  }
}

void readMomentarySwitch ()
{}

void readPotentiometer ()
{
  nodeTX.potentiometer = map(analogRead(pinPotentiometer), 0, 1023, 10, 170);
  Serial.println(nodeTX.potentiometer);
}

void readSensor ()
{}

void incrementCounter ()
{}

Image showing the value jumps already on TX before being transmitted

Progress.

I replaced the reliable datagram TX/RX approach with a barebones raw approach, and now the values read from the potentiometer pin on TX are no longer jumping, and on RX, too. I would have liked to keep the node addressing feature, but... ok.

Now the next problem presents itself. RadioHead is incompatible with Servo.h and ServoTimer2 does not compile on the Adafruit Feather M0 RFM69HCW : (

I tried to drive the servo "manually", see RX code below. With values between 10 and 100, the servo on RX follows the potentiometer rotation on TX near instantly, without any jitter or twitching. With values from 100 to 170, however, it twitches a lot the closer the value gets to 170. The servo has its own power supply, its GND connected to the Feather's GND.

Any ideas what I should do?

TX

#include <SPI.h>
#include <RH_RF69.h>

#define RF69_FREQ 915.0

#if defined(ADAFRUIT_FEATHER_M0)
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
#endif

RH_RF69 rf69(RFM69_CS, RFM69_INT);

struct nodeData // Struct to contain the data to be transmitted
{
  bool momentaryswitch;
  int potentiometer;
  float sensor;
  unsigned long counter;
} nodeTX; // The struct's variable list name

const byte pinPotentiometer = A1;

void setup()
{
  Serial.begin(115200);

  pinMode(RFM69_RST, OUTPUT);

  digitalWrite(RFM69_RST, LOW);

  if (!rf69.init())
  {
    Serial.println("RFM69 radio init failed");
    while (1);
  }

  if (!rf69.setFrequency(RF69_FREQ))
  {
    Serial.println("setFrequency failed");
  }

  rf69.setTxPower(20, true);

  uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
                  };
  rf69.setEncryptionKey(key);
}

void loop()
{
  readPotentiometer ();
  
  rf69.send((uint8_t *)&nodeTX, sizeof(nodeTX));
  rf69.waitPacketSent();
}

void readMomentarySwitch ()
{}

void readPotentiometer ()
{
  nodeTX.potentiometer = map(analogRead(pinPotentiometer), 0, 1023, 10, 170);
  Serial.println(nodeTX.potentiometer);
}

void readSensor ()
{}

void incrementCounter ()
{}

RX

#include <SPI.h>
#include <RH_RF69.h>

#define RF69_FREQ 915.0

#if defined(ADAFRUIT_FEATHER_M0)
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
#endif

RH_RF69 rf69(RFM69_CS, RFM69_INT);

struct nodeData // Struct to contain the data to be transmitted
{
  bool momentaryswitch;
  int potentiometer;
  float sensor;
  unsigned long counter;
} nodeTX; // The struct's variable list name

const byte pinServo = 11;

void setup()
{
  Serial.begin(115200);

  pinMode(pinServo, OUTPUT);

  pinMode(RFM69_RST, OUTPUT);

  digitalWrite(RFM69_RST, LOW);

  if (!rf69.init())
  {
    Serial.println("RFM69 radio init failed");
    while (1);
  }

  if (!rf69.setFrequency(RF69_FREQ))
  {
    Serial.println("setFrequency failed");
  }

  rf69.setTxPower(20, true);

  uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
                  };
  rf69.setEncryptionKey(key);
}

void loop()
{
  if (rf69.available())
  {
    uint8_t len = sizeof(nodeTX);
    if (rf69.recv((uint8_t *)&nodeTX, &len))
    {
      rotateServo(pinServo, nodeTX.potentiometer);
      Serial.println(nodeTX.potentiometer);
    }
  }
}

void rotateServo(int pin, int angle)
{
  // Map angle from TX onto servo microseconds range
  int pulseDelay = map(angle, 10, 170, 600, 2100);
  // Enable servo
  digitalWrite(pin, HIGH);
  // Angle in microseconds
  delayMicroseconds(pulseDelay);
  // Disable servo
  digitalWrite(pin, LOW);
  // 50Hz PWM = period of 20ms
  delay(20);
}

More progress.

When I insert

delay(20);

in TX' loop(), matching the 50Hz PWM delay of 20ms in RX' rotateServo(int pin, int angle), jitter and twitch stop. It seems that the TX to RX data transfer was too fast. If so, I seem to have a reliable way to drive a servo while using the RadioHead library.

I'd still like to learn if there's a better way to do this. After all, there's the 20 millisecond servo code induced blocking on RX that may negatively affect other code yet to come.