Servo controlled by NRF24L01 and joystick

Hello all,
So I'm mostly through a project getting animatronic eyes to work with an R/C controller..

The 2 arduinos communicate (can send text back and forth on serial monitor), and with the original 1 board assembly, the servos and joystick play well with each other. However, when I try and send the data I do get a response.. but it's just basically noise movement in the servo vs taking input from the joystick.

Using an Uno for the transmitter, and a Nano with PCA9685 16 Channel 12-bit PWM Servo motor Driver, and both are using NRF24L01 to communicate

//  Nilheim Mechatronics Simplified Eye Mechanism Code
//  Make sure you have the Adafruit servo driver library installed >>>>> https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library
//  X-axis joystick pin: A1
//  Y-axis joystick pin: A0
//  Trim potentiometer pin: A2
//  Button pin: 2

 
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  140 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  520 // this is the 'maximum' pulse length count (out of 4096)

// our servo # counter
uint8_t servonum = 0;

int xval;
int yval;

int lexpulse;
//int rexpulse;

int leypulse;
int leypulseM;
//int reypulse;



//int trimval;

//const int analogInPin = A0;
//int sensorValue = 0;
//int outputValue = 0;
//int switchval = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("8 channel Servo test!");
  pinMode(analogInPin, INPUT);
  pinMode(2, INPUT);
 
  pwm.begin();
  
  pwm.setPWMFreq(50);  // Analog servos run at ~60 Hz updates

  delay(10);
}

// you can use this function if you'd like to set the pulse length in seconds
// e.g. setServoPulse(0, 0.001) is a ~1 millisecond pulse width. its not precise!
void setServoPulse(uint8_t n, double pulse) {
  double pulselength;
  
  pulselength = 1000000;   // 1,000,000 us per second
  pulselength /= 50;   // 60 Hz
  Serial.print(pulselength); Serial.println(" us per period"); 
  pulselength /= 4096;  // 12 bits of resolution
  Serial.print(pulselength); Serial.println(" us per bit"); 
  pulse *= 1000000;  // convert to us
  pulse /= pulselength;
  Serial.println(pulse);

}

void loop() {

  xval = analogRead(A0);
    lexpulse = map(xval, 0,1023, 220, 300);
    //rexpulse = lexpulse;

    switchval = digitalRead(2);
    
    
  yval = analogRead(A1);
    leypulse = map(yval, 0,1023, 350, 450);
    leypulseM = map(yval, 0,1023, 450, 350);
// reypulse = map(yval, 0,1023, 400, 280);

 // trimval = analogRead(A2);
 //   trimval=map(trimval, 320, 580, -40, 40);
   
 
    
      pwm.setPWM(0, 0, lexpulse);
      pwm.setPWM(1, 0, lexpulse);
      pwm.setPWM(2, 0, leypulse);
      pwm.setPWM(3, 0, leypulseM);




          Serial.println(trimval);
      
  delay(5);

}

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  140 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  520 // this is the 'maximum' pulse length count (out of 4096)

// our servo # counter
uint8_t servonum = 0;

int xval;
int yval;

int lexpulse;
//int rexpulse;

int leypulse;
int leypulseM;
//int reypulse;


#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN

const byte address[6] = "SNIT1";

void setup() {
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();


  Serial.begin(9600);
  Serial.println("8 channel Servo test!");
//  pinMode(analogInPin, INPUT);
  pinMode(2, INPUT);
 
  pwm.begin();
  
  pwm.setPWMFreq(50);  // Analog servos run at ~60 Hz updates

  delay(10);
}

void loop () {
  delay(5);
  radio.stopListening();
    xval = analogRead(A0);
    lexpulse = map(xval, 0,1023, 220, 300);
    //rexpulse = lexpulse;

//    switchval = digitalRead(2);
    
    
  yval = analogRead(A1);
    leypulse = map(yval, 0,1023, 350, 450);
    leypulseM = map(yval, 0,1023, 450, 350);
    radio.write(&leypulse, sizeof(leypulse));
    radio.write(&leypulseM, sizeof(leypulseM));
    radio.write(&lexpulse, sizeof(lexpulse));
   
  }
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  140 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  520 // this is the 'maximum' pulse length count (out of 4096)

// our servo # counter
uint8_t servonum = 0;

int xval;
int yval;

int lexpulse;
//int rexpulse;

int leypulse;
int leypulseM;
//int reypulse;


#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN

const byte address[6] = "SNIT1";

void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
  radio.startListening();


  Serial.begin(9600);
  Serial.println("8 channel Servo test!");
  //pinMode(analogInPin, INPUT);
  //pinMode(2, INPUT);
 
  pwm.begin();
  
  pwm.setPWMFreq(50);  // Analog servos run at ~60 Hz updates

  delay(10);
}

void loop () {
  delay(5);
  radio.startListening();
  if (radio.available()) {
    while(radio.available()){
      int lexpulse=0;
      int leypulse=0;
      int leypulseM=0;
      radio.read(&lexpulse, sizeof(lexpulse));
      radio.read(&leypulseM, sizeof(leypulseM));
      radio.read(&leypulse, sizeof(leypulse));
      pwm.setPWM(0, 0, lexpulse);
      pwm.setPWM(1, 0, lexpulse);
      pwm.setPWM(2, 0, leypulse);
      pwm.setPWM(3, 0, leypulseM);
      
    }
  }
}

Thanks so much for the help community... I feel like I'm close but not quite there yet!!!

Your topic has been moved to a more suitable location on the forum. Installation and Troubleshooting is not for problems with (nor for advice on) your project.

Use a struct, or an array to send a single packet,
carrying all information in one packet.

Your strategy (if there is a packet read three) is nonsensical, the while is superfluous.

There is no TX sketch.

There is no room for delay in a communication program.

Here are example programs using structs (per @Whandall ) to send and receive joystick data.

Sender:


#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>


const byte CE_PIN = 9;
const byte CSN_PIN = 10;

const byte slaveAddress[5] = {'R', 'x', 'A', 'A', 'A'};


RF24 radio(CE_PIN, CSN_PIN); // Create a Radio

struct JoyValues
{
  int joy1x;
  int joy1y;
  int joy2x;
  int joy2y;
}joyValues;

unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 1000; // send once per second

const byte joy1xPin = A0;
const byte joy1yPin = A1;
const byte joy2xPin = A2;
const byte joy2yPin = A3;

void setup()
{

   Serial.begin(115200);
   Serial.println("SimpleTx Starting");
      
   radio.begin();
   radio.setChannel(76);  //76 library default
   //RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH and RF24_PA_MAX
   radio.setPALevel(RF24_PA_HIGH);
   radio.setDataRate( RF24_250KBPS );
   radio.setRetries(3, 5); // delay, count
   radio.openWritingPipe(slaveAddress);
  radio.stopListening(); // added, thanks @Whandall
}

void loop()
{
   currentMillis = millis();
   if (currentMillis - prevMillis >= txIntervalMillis)
   {
      send();
      Serial.print("JOY 1 X = ");
      Serial.print( joyValues.joy1x);
      Serial.print("  JOY 1 Y = ");
      Serial.print( joyValues.joy1y);
      Serial.print("  JOY 2 X = ");
      Serial.print( joyValues.joy2x);
      Serial.print("  JOY 2 Y = ");
      Serial.println( joyValues.joy2y);
      prevMillis = millis();
   }
}

//====================

void send()
{
   joyValues.joy1x = analogRead(joy1xPin);
   joyValues.joy1y = analogRead(joy1yPin);
   joyValues.joy2x = analogRead(joy2xPin);
   joyValues.joy2y = analogRead(joy2yPin);
   radio.write( &joyValues, sizeof(joyValues) );
}

Receiver:



// SimpleRx - the slave or the receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

const byte CE_PIN = 9;
const byte CSN_PIN = 10;

const byte thisSlaveAddress[5] = {'R', 'x', 'A', 'A', 'A'};

RF24 radio(CE_PIN, CSN_PIN);

struct JoyValues
{
  int joy1x;
  int joy1y;
  int joy2x;
  int joy2y;
}joyValues;

bool newData = false;

//===========

void setup()
{

   Serial.begin(115200);

   Serial.println("SimpleRx Starting");

   radio.begin();
   radio.setChannel(76);  //76 library default
   //RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH and RF24_PA_MAX
   radio.setPALevel(RF24_PA_HIGH);
   radio.setDataRate( RF24_250KBPS );
   radio.openReadingPipe(1, thisSlaveAddress);
   radio.startListening();
}

//=============

void loop()
{
   getData();
   showData();
}

//==============

void getData()
{
   if ( radio.available() )
   {
      radio.read( &joyValues, sizeof(joyValues) );
      newData = true;
   }
}

void showData()
{
   if (newData == true)
   {
      Serial.print("Data received >> ");
       Serial.print("JOY 1 X = ");
      Serial.print( joyValues.joy1x);
      Serial.print("  JOY 1 Y = ");
      Serial.print( joyValues.joy1y);
      Serial.print("  JOY 2 X = ");
      Serial.print( joyValues.joy2x);
      Serial.print("  JOY 2 Y = ");
      Serial.println( joyValues.joy2y);
      newData = false;
   }
}

EDIT: Added radio.stopListening(); to the sender program. Thanks @Whandall,

1 Like

You should add

  radio.stopListening();

to TX setup(), newer versions of the library require that (IIRC ).

Yes, I should and have. Posted code fixed. I just tested the code and it does work fine with the new version, but I am still ashamed that I forgot to include that line. I figured that Robin2 taught me better.

No big deal, just an oversight.

Checking the result of the write can be handy too. :wink:

I think the getData/showData functions just add optical noise and a global variable,
but that's the way Robin2 designed his examples.

I understand most-ish of that heh.. Makes sense to combine the 3 values into an array,
remove the while, no delays

My strategy being nonsensical and the TX sketch I don't really understand.. I get that the Transmitter is TF and the receiver is RF, but beyond starting the radio and stopping it from listening, what else does it need there to 'TX'?

With my strategy being nonsensical do you mean in it's current structure it won't work? I'm basically just trying to take the sketch to control the servos that I know already works and just get it to send over R/C...

Thanks!

Thanks for the example.. I guess in this sketch it isn't actually moving servos but writing serial text in lieu as an example? Would I be able to replace that with the pwm.setPWM commands for the controller once I include all the libraries and setup?

When I wrote that, there were only two sketches in your OP, one had no NRF object,
one tried to receive, so I could not comment on your TX sketch, it was just missing.
I rarely reread the whole thread.

Checking for one packet and then reading three, is nonsensical.

I send packets in addition to that.

1 Like

Yes, the point of the example is just to illustrate the use of a struct to make up the payload packet.

1 Like

sooo I've ran into to a bit of a syntax/structure issue.. Tried to initiate the struct as shown, but it's saying "'joyValue' was not declared in this scope" n.. I know with int's you need to set a default start value, but how do you address a value to the struct? Shouldn't it just pull that from the int's contained within? based upon my code as it stands am I close??

Thanks for your help and patience for this nooob!

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  140 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  520 // this is the 'maximum' pulse length count (out of 4096)

// our servo # counter
uint8_t servonum = 0;

int xval;
int yval;

struct JoyValue 
{ int lexpulse;
int leypulse;
int leypulseM;
};

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN

const byte address[6] = "SNIT1";



void sendRad(){

      xval = analogRead(A0);
    lexpulse = map(xval, 0,1023, 220, 300);
    //rexpulse = lexpulse;
      
  yval = analogRead(A1);
    leypulse = map(yval, 0,1023, 350, 450);
    leypulseM = map(yval, 0,1023, 450, 350);

    radio.write(&joyValue, sizeof(joyValue));}

    

void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
 
  pwm.begin();
  
  pwm.setPWMFreq(50);  // Analog servos run at ~60 Hz updates

  delay(10);
}

void loop () {
  
  radio.stopListening();

    sendRad();
 
  }


#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  140 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  520 // this is the 'maximum' pulse length count (out of 4096)

// our servo # counter
uint8_t servonum = 0;

int xval;
int yval;

struct JoyValue 
{ int lexpulse;
int leypulse;
int leypulseM;
};


bool newData = false;

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN

const byte address[6] = "SNIT1";

void getData()
{  if (radio.available()) {
      radio.read(&JoyValue, sizeof(JoyValue));
      newData= true;}}

void showData()
{if (newData==true)
{      pwm.setPWM(0, 0, lexpulse);
      pwm.setPWM(1, 0, lexpulse);
      pwm.setPWM(2, 0, leypulse);
      pwm.setPWM(3, 0, leypulseM);
      newData=false;}}


      

void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
  radio.startListening();

  //pinMode(analogInPin, INPUT);
  //pinMode(2, INPUT);
 
  pwm.begin();
  
  pwm.setPWMFreq(50);  // Analog servos run at ~60 Hz updates

  delay(10);
}

void loop () {
  
  getData();
  showData();
      
    }
  
}






JoyValue joystickValue =
{
  1,2,3,4
};

And in functions

joystickValue.lexpulse = 345;
1 Like

You declare the struct JoyValue, but never create an instance of the struct. Create an instance of JoyValue and initialize it as shown by @sterretje.

1 Like

So does the ```

JoyValue joystickValue = 1234 go in place of the existing struct? or in addition to it?

JoyValue joystickValue =
{ int lexpulse;
int leypulse;
int leypulseM;
};   
?

and then in functions you assign a start value to each of the int's in struct by

joystickValue.lexpulse= 345; 

(or in this case if the range of motion is 220, 300- then 260 would be 'zero degrees')

Hehe I definitely need to read deeper into my tutorial book, but I think it was published before structs came along

Also, this is a (not very well paid) project.. I'd gladly throw someone a few bucks to spend 10 minutes walking me through it to get the code to function.. I want to learn but am also coming up against a deadline :wink:

The pain of having to be a fabrication/electronics jack-of-all-trades... anyone want to trade 3D printing or CAD skills for programming :wink:

The first example that I gave declares and initialises a variable, e.g. as a global variable.

The second example shows how you can access a field in the struct; that needs to be in a function.

1 Like

We don't have power at the moment so no working PC :cry:

Will happily try to guide you with a bit of examples if you can wait a couple of hours.

1 Like

I would really appreciate that, honestly any time in the next 48 hours... I just need to get this done. I understand in concept what you are saying, it's just a matter of actually writing that bit of code in the way it needs to be done.

And I will throw you a 'advisory fee' .. only fair if you help me in such an active way :slight_smile:

Here is the sender fixed (I think). It does compile, but I cannot test it. This should show what you need to do as far as creating and using the instance of the struct.

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  140 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  520 // this is the 'maximum' pulse length count (out of 4096)

// our servo # counter
uint8_t servonum = 0;

int xval;
int yval;

struct JoyValue
{
   int lexpulse;
   int leypulse;
   int leypulseM;
};

JoyValue joystickValue;  // create an instance of JoyValue

RF24 radio(7, 8); // CE, CSN

const byte address[6] = "SNIT1";

void sendRad()
{
   xval = analogRead(A0);
   joystickValue.lexpulse = map(xval, 0, 1023, 220, 300);  //use a struct variable
   //rexpulse = lexpulse;

   yval = analogRead(A1);
   joystickValue.leypulse = map(yval, 0, 1023, 350, 450);
   joystickValue.leypulseM = map(yval, 0, 1023, 450, 350);

   radio.write(&joystickValue, sizeof(joystickValue));
}



void setup()
{
   Serial.begin(9600);
   radio.begin();
   radio.openWritingPipe(address);
   radio.setPALevel(RF24_PA_MIN);
   radio.stopListening();

   pwm.begin();

   pwm.setPWMFreq(50);  // Analog servos run at ~60 Hz updates

   delay(10);
}

void loop ()
{

   radio.stopListening();

   sendRad();
}