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.