Nrf24 and vibrating servos (not a power related problem)

hello everybody, I'm working on the rc plane project from the famous(?) website ''How To Mechatronics''. Here is the link: https://howtomechatronics.com/projects/arduino-rc-airplane-diy/ .

My problem is that the servo motors vibrate! I know it's a common problem, but this time I'm sure it's not power-related.

First of all, the code works as the data is exchanged correctly between transmitter and receiver (I checked on the serial monitor and the receiver prints exactly the joysticks' readings). Anyway, when I plug the servo motors to the arduino of the receiving circuit they start vibrating! Meanwhile the nrf24 still receives the right data and prints it on the serial monitor.

I'm sure it's not a problem related to power, because the servos are powered by attaching them to a distinct power source: a third arduino, connected via USB to my laptop. I know arduino's 5V aren't trustworthy, but hey! I did some tests! Using a new code, to directly read the joysticks and move the servos using only the transmitter-side arduino (servos were still powered with the separated, third arduino, so only the signal wires were connected to the transmitter's digital pins). In this scenario the arduino was able to read the analog signals of the joystick and move flawlessly the servos (no command to the transmitter nrf24 was given in this test, just analog-reading and servo-writing).

Assured that the power was enough to run the servos, and that an arduino was capable of managing both reading and writing all the data, I tried to go back to the transmitter/receiver configuration. Now the servos had been plugged to the receiver, while powered still from the third arduino. The only difference I recognize is that the receiving arduino had now to get data from the nrf24 instead of the joysticks. But as I said, from the serial monitor I saw the nrf24 receiving the correct data, both with/without servos. It had just to command the servos. Yet they vibrated. To be precise, I recognized sometimes a spasmodic attempt to follow the instructions given. So they seemed responding, but only for limited time and in a very dirty and fleble way

The only remaining suspect was at software-level, because I've seen some timer related problems between servo library and the nrf. Or worse, some weird electrical noise inside the wirings??

Here are the codes for transmitting and receiving. They are slightly adapted, but work correctly.
TRANSMITTER:

//RC TRANSMISSION OF TWO JOYSTICKS//

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

void setup() {

  Serial.begin(9600);
  
  Mirf.cePin = 9;   //Set the CE pin to D9 4
  Mirf.csnPin = 10; //Set the Csn pin to D10
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init(); //initialization nRF24L01

  //Set the receiving identifier "Sen01" (included from the original manifacturer code, but obsolete)
  //Mirf.setRADDR((byte *)"Sen01");

  //Set the number of bytes sent and received at a time, here we send 4 integers,
  // so a packet of 8 bytes, 2 bytes for each integer
  Mirf.payload = sizeof(unsigned int) * 4;

  //Sending channel, can fill 0~128, send and receive must be consistent.
  Mirf.channel = 3;

  Mirf.config();

  Serial.println("I'm Sender...");
}


//defining the variables that will hold the analog readings from the joysticks
//JOYSTICK1
unsigned int j1PotX = 0;
unsigned int j1PotY = 0;
//JOYSTICK2 
unsigned int j2PotX = 0;
unsigned int j2PotY = 0;



void loop() {

  //reading the joysticks and mapping them into values from 0 to 255 (will be useful 
  //at the receiving side, for the motors and servos)
  j1PotX = map(analogRead(A1), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into value from 0 to 255
  j1PotY = map(analogRead(A0), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into value from 0 to 255
  j2PotX = map(analogRead(A2), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into value from 0 to 255
  j2PotY = map(analogRead(A3), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into value from 0 to 255

//(the step above is somehow contradictory, we actually don't need anymore two bytes for each integer, because, after this mapping,
// our integers look like this: "{ [0] [0] [0] [0] [0] [0] [0] [0] | [8bits representing a 0/255 number] }", so we could save space
//and send only the righten-byte of each. I didn't try to change this part because the rc transmission code was originally intended
//for full integers, and I didn't have enough confidence to mess up things, in particular because transmission/reception data
//packets must ABSOLUTELY match. I had a working code, I tried to stick to it) (the analog joysticks reading code was taken from 
//another example, and it did this mapping, again I'll stick to it).

  //Since nRF24L01 can only send Mirf.payload data byte by byte, exchanging byte arrays,
  //all the data to be transferred must be split into bytes.
  //that's why 4 integers will occupy an array of 8 bytes, let's create them this space:
  
  byte data[Mirf.payload]; //here we will store cell by cell all the halves of each integer

  //now we have to fill the data cells. each int must be splitted into two halves, then we store each half in a cell. every two cells
  //we will find a variable. the splitting procedure is the following: take a 16bit string containing the binary representation of
  //the integer, to isolate the first 8 bits (from the right, so the lowest bits) use "& 0xFF", that means to make a logic operation
  //that compares the 16bit with a 8bit string, taking only the bits from the 16 that match in position with the 8, so the first ones 
  //on the right. This will be stored in the first cell. For the remaing half, the 8bits on the left, just use the ">>8" that means
  //shifting to the right each bit by 8 steps, the result is that the first 8 bits on the right will be eliminated, shifted into the 
  //void, while the bits on the left will occupy their original place, thus making the 16bit an 8bit, with only the bits from the left 
  //half. They will be stored into the seconf cell. This process is repeated 4 times, creating 8 cells
  
  data[0] = j1PotX & 0xFF;  //low eight bits to data[0],
  data[1] = j1PotX >> 8;   //high eight bits to data[1]。

  data[2] = j1PotY & 0xFF;  //low eight bits to data[2],
  data[3] = j1PotY >> 8;   //high eight bits to data[3]。

  data[4] = j2PotX & 0xFF;  //low eight bits to data[4],
  data[5] = j2PotX >> 8;   //high eight bits to data[5]。

  data[6] = j2PotY & 0xFF;  //low eight bits to data[6],
  data[7] = j2PotY >> 8;   //high eight bits to data[7]。

  //Settings to send data
  Mirf.setTADDR((byte *)"Rec01");
  Mirf.send(data);

  //checking the data array that we created
  Serial.println(data[0] + data[1] ); //this two cells define j1PotX
  Serial.println(data[2] + data[3] ); //this two cells define j1PotY
  Serial.println(data[4] + data[5] ); //this two cells define j2PotX
  Serial.println(data[6] + data[7] ); //this two cells define j2PotY


  //The next step can only be performed after the while loop function transmission is completed.
  while (Mirf.isSending()) {}
  //delay(50);
}

RECEIVER:

//RC RECEIVER OF TWO JOYSTICKS, WITH RELATIVE
//PLANE SERVOS AND MOTOR CONTROL
#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>
#include <Servo.h>

#define led 2

Servo throttle;  // create servo object to control the ESC
Servo rudderServo;
Servo elevatorServo;
Servo aileron1Servo;
Servo aileron2Servo;

unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

int throttleValue, rudderValue, elevatorValue, aileron1Value, aileron2Value;

// Adjusting the servos responsiveness, tune it now since we don't have any other analogic
//input. Ideally we should add a potentiometer.
int travelAdjust = 1;

//Define a variable adata to store the final result.
unsigned int j1PotX = 0;
unsigned int j1PotY = 0;
unsigned int j2PotX = 0;
unsigned int j2PotY = 0;

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

  //setup of the nRF24
  Mirf.cePin = 9;     //Set CE Pin to D9
  Mirf.csnPin = 10;  //Set CE Pin to D10
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init(); //initialization nRF24L01

  //Set the receiving identifier "Rev01"
  Mirf.setRADDR((byte *)"Rec01");

  //Set the number of bytes sent and received at a time. we expect data packets of 8bytes,
  //that represent in total four integers (each uses 16bits).
  Mirf.payload = sizeof(unsigned int) * 4 ;

  // Sending channel, can fill 0~128, send and receive must be consistent.
  Mirf.channel = 3;
  Mirf.config();

//  Serial.println("I'm Receiver...");

  //attaching the servo objects to their pins:
  throttle.attach(3);
  rudderServo.attach(4);
  elevatorServo.attach(5);
  aileron1Servo.attach(6);
  aileron2Servo.attach(7);
}


void loop() {

  // Check whether we keep receving data, or we have a connection between the two modules
  currentTime = millis();

  // If current time is more then 5 second since we have recived the last data, that means
  //we have lost connection If connection is lost, reset the data. It prevents
  //unwanted behavior, for example if a drone has a throttle up, if we lose connection
  //it can keep flying away if we dont reset the function
  if ( currentTime - lastReceiveTime > 5000 ) {
    resetData();
  }


  //Define a scratchpad array with a size of Mirf.payload.
  byte data[Mirf.payload];

  if (Mirf.dataReady()) { //Waiting the prepared receive data.
  
    Mirf.getData(data);  //Receive data to data array.

    //now we need to build back from each couple of cells in the array their corresponding
    //integer value, this means that we start a cast operation: "(unsigned int)" means that we
    //will convert the following elements into an unsiged int of 16bits. So we call the second
    //cell, and ask it to move to the left by 8bits, thus filling the leften part of the integer,
    //(the highest, because in binary we write numbers from right to left). then we merge using
    // "|" with the first cell of the couple, that being 8bits long, merged with a 16bit long,
    //will consequently occupy the first 8bits to the right. This will result in all the 16bits
    //filled in the order: integer = { [][][][][][][][] | [][][][][][][][] } == { data[1] | data[0] }
    //we then repeat for each integer.

    j1PotX = (unsigned int)((data[1] << 8) | data[0]);
    j1PotY = (unsigned int)((data[3] << 8) | data[2]);
    j2PotX = (unsigned int)((data[5] << 8) | data[4]);
    j2PotY = (unsigned int)((data[7] << 8) | data[6]);

    //serial monitor check
    Serial.println(j1PotX);
    Serial.println(j1PotY);
    Serial.println(j2PotX);
    Serial.println(j2PotY);

    //Can also output double-byte data.
    //Serial.write(data[1]);
    //Serial.write(data[0]);
  }
  else {
    Serial.print("No data packet");
  }


  ////////////////////////////////ACTUATION:///////////////////////////////////////////////////

  // Controlling throttle - brushless motor with ESC
  //throttleValue = constrain(j1PotY, 80, 255); // If Joysticks stays in middle, we only need values the upper values from 130 to 255
  throttleValue = j1PotY; //in this case we have the full range of motion (we aren't using ps3-like joystick)
  throttleValue = map(throttleValue, 0, 255, 1000, 2000);
  throttle.writeMicroseconds(throttleValue);

  // Elevator control
  elevatorValue = map(j2PotY, 0, 255, (85 - travelAdjust), (35 + travelAdjust));
  elevatorServo.write(elevatorValue);

  // Ailerons control
  aileron1Value = map(j2PotX, 0, 255, (10 + travelAdjust), (80 - travelAdjust));
  aileron1Servo.write(aileron1Value);
  aileron2Servo.write(aileron1Value);

  // Rudder trimming function (tune the number before the "+")
  if (j1PotX > 127) {
    rudderValue = 1 + (j1PotX - 127);
  }
  if (j1PotX < 127) {
    rudderValue = 1 - (127 - j1PotX);
  }
  // Rudder control
  rudderValue = map(rudderValue, 0, 255, (10 + travelAdjust), (90 - travelAdjust));
  rudderServo.write(rudderValue);

  // Monitor the battery voltage
  int sensorValue = analogRead(A3);
  float voltage = sensorValue * (5.00 / 1023.00) * 3; // Convert the reading values from 5v to suitable 12V i
  // If voltage is below 11V turn on the LED
  if (voltage < 11) {
    digitalWrite(led, HIGH);

  }
  else {
    digitalWrite(led, LOW);
  }

}


void resetData() {
    j1PotX = 0;
    j1PotY = 0;
    j2PotX = 0;
    j2PotY = 0;
}

If you need also schematics /pictures of connections tell me. But I think that they aren't crucial since all the elements taken by themselves worked and satisfied the tests. It's the receiving circuit that doesn't want to write the received data to the servos, and makes them vibrate.

Lots of text. I hook on to Your thinking that the receiver might house the error.
There are several Serial.prints in loop() of the receiver. What do they print?

Add a Serial.print of rudderValue!

I use a lot of Serial.println commands here and there whenever I need to check that a certain variable is behaving well. Sometimes I comment them out, others I delete them when I'm done.
The problem persists independently from the number of serial prints (I say this because I tried to remove them all).

Thanks to your comment I had this suspect: I was equating the "int" rudderValue (as well as the other commands), with the joystick readings, that are "unsigned int" data type (this was the result of mixing together different codes from internet). So I tried to add "unsigned-" to the declaration of all the "int" variables, to make everything coherent. Indeed now the circuit seems to work!!!!

But I knew that unsigned int and int are basically the same, that's why I didn't bother to change that when I was merging the two codes together. Is it posssible that this solved my problem??

UPDATE: the problem showed up again, even if less accentuated. In particular now the servos responded very well, but flickered anytime the connection was lost (it happens for few istants every now and then). I solved everything by extending the "if" function:

making it include all the rest of the void loop.

That may depend on whether the value of any of your signed ints ever exceed 32767 and went negative

the signed ints were defined in the receiving stage, so they didn't change by themselves. They were imposed to be equal to the incoming unsigned ints from the transmitter. And the unsigned ints ranged from 0 to 255 because they were mapped in this range before being transmitted.

Hi,

Just a warning, the map() function output values can extend outside those defined in the map() function, if the input values go outside the input values included in the map() function.

So the map() function does not limit its output, just re-maps it from the input.

Tom... :grinning: :coffee: :+1: :australia:

nice to know this! ahahah. I'm a newbie in the programming environment. I proceeded to add a constrain command on each received variable

Right, there's another function constrain() that can be useful in this circumstance.

Without combing through the word salad, I only see

 but hey! I did some tests!

How hard would it be for you to hook up a real power supply with the correct voltage and very plenty enough current? if only to keep others from continuing to pester you about it...

I lost count of how many times it is indeed a power supply issue. If you have no real tools for measuring, no casual tests will convince me to get on your airplane when it is ready to fly!

a7

Availability of components and materials is not something that can be taken for granted from anyone. I don't have a third battery, otherwise I would have used it. Please, don't be reductive, cutting it short to power issues. Even because the tests succeeded to show that servos are absorbing very little power. I understand it may be not enough for your personal advice, but since you are not obliged, you can also abstain from answering

OK, understood. I hope the cause is located :soon:.

I only suggest a common approach, which is to eliminate, to the extent possible, all sources of error one by one.

Which you are doing.

a7

It IS a "power supply problem".

Never use a USB port to power a motor or servo. Doing so may destroy the computer with the USB port.

Use a separate 5-6V power supply, capable of 1 Ampere per small servo, 2.5 A per servo for larger ones, and don't forget to connect the grounds. A 4xAA battery pack will power 1 or 2 small servos.

Correct me if I'm wrong. You are telling me that attaching an arduino UNO to my laptop, then connecting the 5V and the GND pins on a breadbord to power some servos (three in this case), may destroy my computer? Sheesh in that case I was lucky that my hypothesis was correct. Anyway even if here the power was not the problem, I will avoid the USB port next time

Yes. And ALL USB ports are limited in the amount of current they can supply, which is usually less than the current a single servo requires.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.