How Flight_Controller "respond" the receiver programmatically

I've been building Bi-copter. I use Nano_flight_controller, NRF_5v_Pro_mini receiver and NRF_transmitter. At first, I try how receiver and the transmitter and both work as I wish. I followed the instruction in Youtube tutorial (howtomechatronic and electronoobs) and try to understand how the code works programmatically (Sorry, I am newbie in Arduino programming).

I played with map() where I learn it in this awesome site's references about it and it works as I wished.

When I wire and connect all the things in my bicopter using PPM configuration (pin 2), it processes the flight controller codes and disobey the receiver (when I move the joystick in transmitter). It does nothing.

Now I'm sure that the problem is in flight_controller code where I DON'T KNOW how to fix it.

Here's my flight controller I took from

#include <Wire.h>
#include <Servo.h>


Servo right_prop;
Servo left_prop;
Servo R_servo;
Servo L_servo;
/*MPU-6050 gives you 16 bits data so you have to create some 16int constants
 * to store the data for accelerations and gyro*/

int16_t Acc_rawX, Acc_rawY, Acc_rawZ,Gyr_rawX, Gyr_rawY, Gyr_rawZ;
//int ir_pin = A0;
//int ir_value = 0;

float Acceleration_angle[2];
float Gyro_angle[2];
float Total_angle[2];




float elapsedTime, time, timePrev;
int i;
float rad_to_deg = 180/3.141592654;

float PID, pwmLeft, pwmRight, error, previous_error;
float pid_p=0;
float pid_i=0;
float pid_d=0;
/////////////////PID CONSTANTS/////////////////
double kp=2;//3.55
double ki=0;//0.003
double kd=0;//2.05
///////////////////////////////////////////////

double throttle = 1150; //initial value of throttle to the motors
float desired_angle = -13.5; //This is the angle in which we whant the
                         //balance to stay steady

//#define LED_PIN (13)

void setup() {
  Wire.begin(); //begin the wire comunication
  Wire.beginTransmission(0x68);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);
  Serial.begin(115200);
  right_prop.attach(10); 
  left_prop.attach(9);  
  R_servo.attach(11);
  L_servo.attach(3);
  delay(500);
  R_servo.write(10);
  L_servo.write(10);
  delay(1000);
  R_servo.write(45);
  L_servo.write(35);
  time = millis(); //Start counting time in milliseconds
  /*In order to start up the ESCs we have to send a min value
   * of PWM to them before connecting the battery. Otherwise
   * the ESCs won't start up or enter in the configure mode.
   * The min value is 1000us and max is 2000us, REMEMBER!*/
  left_prop.writeMicroseconds(1000); 
  right_prop.writeMicroseconds(1000);
  delay(7000); /*Give some delay, 7s, to have time to connect
                *the propellers and let everything start up*/
}//end of setup void


void loop() {

/////////////////////////////I M U/////////////////////////////////////
    timePrev = time;  // the previous time is stored before the actual time read
    time = millis();  // actual time read
    elapsedTime = (time - timePrev) / 1000; 
    //Serial.print(time/1000);
    //Serial.print("\t");
    
  
  /*The tiemStep is the time that elapsed since the previous loop. 
   * This is the value that we will use in the formulas as "elapsedTime" 
   * in seconds. We work in ms so we haveto divide the value by 1000 
   to obtain seconds*/

    angle_calcs();
    pid_calcs();
  
  left_prop.writeMicroseconds(pwmLeft);
  right_prop.writeMicroseconds(pwmRight);

Serial.print(pwmLeft);Serial.print(" > ");
Serial.print(pwmRight);Serial.print(" > \n");


  //if (time/1000 >= 17) {
  //R_servo.write(75);
  //L_servo.write(55);
  
//}
//Serial.println(ir_value);

/*
ir_value = analogRead(ir_pin); //Read analog input from IR sensor

// Only send pulses to motors if IR sensor indicates that something is not infront of device
if (ir_value <= 50) {
  left_prop.writeMicroseconds(pwmLeft);
  right_prop.writeMicroseconds(pwmRight);
}
// If something is close to sensor send min pulse
else {
  left_prop.writeMicroseconds(1100);
  right_prop.writeMicroseconds(1100);
}
//timer when IR sensor is not in use
/*  
else if ( time/1000 <= 15) {
  left_prop.writeMicroseconds(1300);
  right_prop.writeMicroseconds(1300);  
}
*/
//Serial.print(pwmLeft);
//Serial.print("\t");
//Serial.print(pwmRight);
//Serial.print("\t");
//Serial.println(ir_value);


//Initiate forward tilt of servos for forward movement

//if (time/1000 >= 10) {
//  R_servo.write(73);
//  L_servo.write(60);
  
//}

//Flight timer
if (time/1000 >= 30) {
  left_prop.writeMicroseconds(1000);
  right_prop.writeMicroseconds(1000);
}



    //Serial.print(pwmLeft);
    //Serial.print("\t");
    //Serial.println(pwmRight);
previous_error = error; //Remember to store the previous error.

}//end of loop void

void pid_calcs () {
  /*///////////////////////////P I D///////////////////////////////////*/

/*First calculate the error between the desired angle and 
*the real measured angle*/
error = Total_angle[0] - desired_angle;
    
/*Next the proportional value of the PID is just a proportional constant
*multiplied by the error*/

pid_p = kp*error;

/*The integral part should only act if we are close to the
desired position but we want to fine tune the error. That's
why I've made a if operation for an error between -3 and 3 degree.
To integrate we just sum the previous integral value with the
error multiplied by  the integral constant. This will integrate (increase)
the value each loop till we reach the 0 point*/
if(-3 <error <3)
{
  pid_i = pid_i+(ki*error);  
}

/*The last part is the derivate. The derivate acts upon the speed of the error.
As we know the speed is the amount of error that produced in a certain amount of
time divided by that time. For taht we will use a variable called previous_error.
We substract that value from the actual error and divide all by the elapsed time. 
Finnaly we multiply the result by the derivate constant*/

pid_d = kd*((error - previous_error)/elapsedTime);

/*The final PID values is the sum of each of this 3 parts*/
PID = pid_p + pid_i + pid_d;

/*We know taht the min value of PWM signal is 1000us and the max is 2000. So that
tells us that the PID value can/s oscilate more than -1000 and 1000 because when we
have a value of 2000us the maximum value taht we could sybstract is 1000 and when
we have a value of 1000us for the PWM sihnal, the maximum value that we could add is 1000
to reach the maximum 2000us*/
if(PID < -350)
{
  PID=-350;
}
if(PID > 350)
{
  PID=350;
}

/*Finnaly we calculate the PWM width. We sum the desired throttle and the PID value*/
pwmLeft = throttle + PID;
pwmRight = throttle - PID;


/*Once again we map the PWM values to be sure that we won't pass the min
and max values. Yes, we've already maped the PID values. But for example, for 
throttle value of 1300, if we sum the max PID value we would have 2300us and
that will mess up the ESC.*/
//Right
if(pwmRight < 1000)
{
  pwmRight= 1000;
}
if(pwmRight > 2000)
{
  pwmRight=2000;
}
//Left
if(pwmLeft < 1000)
{
  pwmLeft= 1000;
}
if(pwmLeft > 2000)
{
  pwmLeft=2000;
}

/*Finnaly using the servo function we create the PWM pulses with the calculated
width for each pulse*/
if (time/1000 <= 17) {
left_prop.writeMicroseconds(pwmLeft);
right_prop.writeMicroseconds(pwmRight);
}

}

void angle_calcs() {
    /*Reed the values that the accelerometre gives.
   * We know that the slave adress for this IMU is 0x68 in
   * hexadecimal. For that in the RequestFrom and the 
   * begin functions we have to put this value.*/
   
     Wire.beginTransmission(0x68);
     Wire.write(0x3B); //Ask for the 0x3B register- correspond to AcX
     Wire.endTransmission(false);
     Wire.requestFrom(0x68,6,true); 
   
   /*We have asked for the 0x3B register. The IMU will send a brust of register.
    * The amount of register to read is specify in the requestFrom function.
    * In this case we request 6 registers. Each value of acceleration is made out of
    * two 8bits registers, low values and high values. For that we request the 6 of them  
    * and just make then sum of each pair. For that we shift to the left the high values 
    * register (<<) and make an or (|) operation to add the low values.*/
    
     Acc_rawX=Wire.read()<<8|Wire.read(); //each value needs two registres
     Acc_rawY=Wire.read()<<8|Wire.read();
     Acc_rawZ=Wire.read()<<8|Wire.read();
     //Acc_rawX -= 2199;
     //Acc_rawY -= 2547;
     //Acc_rawZ += 666;

 
    /*///This is the part where you need to calculate the angles using Euler equations///*/
    
    /* - Now, to obtain the values of acceleration in "g" units we first have to divide the raw   
     * values that we have just read by 16384.0 because that is the value that the MPU6050 
     * datasheet gives us.*/
    /* - Next we have to calculate the radian to degree value by dividing 180º by the PI number
    * which is 3.141592654 and store this value in the rad_to_deg variable. In order to not have
    * to calculate this value in each loop we have done that just once before the setup void.
    */

    /* Now we can apply the Euler formula. The atan will calculate the arctangent. The
     *  pow(a,b) will elevate the a value to the b power. And finnaly sqrt function
     *  will calculate the rooth square.*/
     /*---X---*/
     Acceleration_angle[0] = atan((Acc_rawY/16384.0)/sqrt(pow((Acc_rawX/16384.0),2) + pow((Acc_rawZ/16384.0),2)))*rad_to_deg;
     /*---Y---*/
     Acceleration_angle[1] = atan(-1*(Acc_rawX/16384.0)/sqrt(pow((Acc_rawY/16384.0),2) + pow((Acc_rawZ/16384.0),2)))*rad_to_deg;
 
   /*Now we read the Gyro data in the same way as the Acc data. The adress for the
    * gyro data starts at 0x43. We can see this adresses if we look at the register map
    * of the MPU6050. In this case we request just 4 values. W don¡t want the gyro for 
    * the Z axis (YAW).*/
    
   Wire.beginTransmission(0x68);
   Wire.write(0x43); //Gyro data first adress
   Wire.endTransmission(false);
   Wire.requestFrom(0x68,4,true); //Just 4 registers
   
   Gyr_rawX=Wire.read()<<8|Wire.read(); //Once again we shif and sum
   Gyr_rawY=Wire.read()<<8|Wire.read();
   //Gyr_rawX += 142;
   //Serial.print(Gyr_rawX);
   //Gyr_rawY += 18;
   //Serial.print("\t");
   //Serial.println(Gyr_rawY);
   //Serial.println("\t");
   
   
 
   /*Now in order to obtain the gyro data in degrees/seconda we have to divide first
   the raw value by 131 because that's the value that the datasheet gives us*/

   /*---X---*/
   Gyro_angle[0] = (Gyr_rawX/131.0);
   //+ 4.383425926; 
   //Serial.print(Gyro_angle[0]);
   //Serial.print("\t");
   /*---Y---*/
   Gyro_angle[1] = (Gyr_rawY/131.0);
   //+ 0.62037037;
   //Serial.println(Gyro_angle[1]);

   /*Now in order to obtain degrees we have to multiply the degree/seconds
   *value by the elapsedTime.*/
   /*Finnaly we can apply the final filter where we add the acceleration
   *part that afects the angles and ofcourse multiply by 0.98 */

   /*---X axis angle---*/
   Total_angle[0] = 0.98 *(Total_angle[0] + Gyro_angle[0]*elapsedTime) + 0.02*Acceleration_angle[0];
   /*---Y axis angle---*/
   Total_angle[1] = 0.98 *(Total_angle[1] + Gyro_angle[1]*elapsedTime) + 0.02*Acceleration_angle[1];
   
   //Now we have our angles in degree and values from -10º0 to 100º aprox
    //Serial.print(Total_angle[0]);
    //Serial.print("\t");
    //Serial.println(Total_angle[1]);

}

Now it is about 2 months 11 days and It still not work. I really want to know HOW flight controller "response" the receiver in processing what transmitter wants.

UPDATE:
I see that the flight controller use PWM config and my receiver use PPM config. Wait, I'm trying to understand what those are. Some sites say it talks about how Flight_controller "speaks to" receiver.

Hi @jeanloveard

Here's an article on creating a NRF transmitter and receiver for RC aircraft and vehicles:

https://www.thefscreations.info/2018/12/diy-rc-transmitter-and-receiver-for-rc.html

Okay thanks for the reference, Sure I will check it. wait

Okay, Now I turn the receiver into PWM communication. I try to modify the reference you give based on my project like this below (Is this correct?)

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

#define no_ch 7
int pwm[no_ch];

const uint64_t pipeIn = 0xE8E8F0F0E1LL; 

RF24 radio(10, 9); CE and CSN pin 
struct MyData {
byte throttle;
byte yaw;
byte pitch;
byte roll;
byte AUX1;
byte AUX2;
byte throttle2;
};

MyData data;

void getPWMValues(){
  pwm[0] = map(data.throttle, 1150, 1850, 0, 255);
  pwm[1] = map(data.yaw,      1150, 1850, 0, 255);
  pwm[2] = map(data.pitch,    1150, 1850, 0, 255);
  pwm[3] = map(data.roll,     1150, 1850, 0, 255);
  pwm[4] = map(data.AUX1,     1500, 1650, 0, 255);
  pwm[5] = map(data.AUX2,     0, 1, 0, 255);
  pwm[6] = map(data.throttle2, 1000, 2000, 0, 255);  
  }

void setupPWM(){
  pinMode(2,OUTPUT);
  pinMode(4,OUTPUT);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(7,OUTPUT);
  pinMode(8,OUTPUT);
  pinMode(12,OUTPUT);  
}

void resetData(){
data.throttle = 0;
data.yaw = 127;
data.pitch = 127;
data.roll = 127;
data.AUX1 = 0;
data.AUX2 = 0;
data.throttle2 = 0;
}

void setup(){
Serial.begin(9600);
resetData();
setupPWM();
radio.begin();
radio.setAutoAck(false);
radio.setDataRate(RF24_250KBPS);
radio.openReadingPipe(1,pipeIn);
radio.startListening();
}

unsigned long lastRecvTime = 0;
void recvData(){
  while ( radio.available() ) {
  radio.read(&data, sizeof(MyData));
  lastRecvTime = millis(); //here we receive the data
  }
}

void loop(){
recvData();
unsigned long now = millis();
if (now - lastRecvTime > 1000 ){
resetData();
}
getPWMValues();
Serial.print("Throttle: "); Serial.print(data.throttle);  Serial.print(" > ");
Serial.print("Yaw: ");      Serial.print(data.yaw);       Serial.print(" > ");
Serial.print("Pitch: ");    Serial.print(data.pitch);     Serial.print(" > ");
Serial.print("Roll: ");     Serial.print(data.roll);      Serial.print(" > ");
Serial.print("Aux1: ");     Serial.print(data.AUX1);      Serial.print(" > ");
Serial.print("Aux2: ");     Serial.print(data.AUX2);      Serial.print(" > ");
Serial.print("Throttle2: "); Serial.print(data.throttle);  Serial.print("\n");

}

in void setupPWM(){ I choose the PINS (which are not used - and used for output pin in flight_controller = 3 and 11 for servos and 9 & 10 for ECSs).

I was reading about how a drone (bicopter) can "hover" in a fixed value of IMU. Please ignore my "Throttle2" as it is in future experiment, thanks.

I’d like to read that.

In my experience, without a functioning IMU it is impossible to hover or otherwise fly a multirotor r/c aircraft.

Where did you find that?

a7

I was building a self-balancing robot and it was successful. There I found that IMU is how "other robot" stays in balance, right? - And, I'm sure that drone/bicopter needs a "fixed speed" - I guess :smiley:

(note: I want to make a flying vehicle using it. and, it flies about 40CMs over the ground :smiley: ...... in the future (days/months))

Sorry, language issue. I thought you were saying that by using fixed values instead of an IMU, you could at least hover an aircraft.

a7

I see from my serial_COM7 that the transmitter works as I wish. BUT, no movement from the servo and motor. It does nothing. Please help.

How the receiver communicates with the flight_controller? pleaaseee answer me :cry:

Why do you have the sketch stop controlling the prop servos after the sketch has been running for 17 seconds?!?

Okay, wait ... I'm checking it now (thanks so much for reply)

I commented the code you mentioned but it doesn't work. Well, I just ... don't know. Perhaps the writer of the code use the codes you said for IR sensor.

I had uploaded multiwii before with bicopter config but no luck. Perhaps, I don't know how to configure. The left servo looks trembling extremely.

Hi @jeanloveard

The mapping here isn't correct:

RC receivers output pulses in the 1150us to 1850us range, either to drive motor ESCs or servos. On the ESCs a 1150us pulse represents the 0% miniumum throttle, while 1850us is 100% full throttle. On a servo 1150us and 1850us will be endpoints with around 1500us as the midpoint.

These raw channel values shouldn't be mapped to a 0 to 255 value, but instead,
with the possible exception of the throttle, be mapped to degrees/degrees per second to act as the pilot stick "setpoint" values for your roll, pitch and yaw control loop PIDs. Your sensors/complementary filter providing the PID "input", again in degrees/degrees per second.

Sorry for late response. I'm busy to try your suggestion but I'm still confused how the getPWMValues() send the throttle, yaw, etc in PWM signal to flight controller.

In web programming, I always create an "indicator" or "determiner" where other pages can be executed or called or else based on the concept relation.
Honestly, I don't find the "indicator" similar to that.

in Multiwii I can read HOW to config PPM but no clear "execution". Also, I read the PWM terms used there but I don't find any about the indicator (perhaps, in other file/tabs) where flight controller can recognize the command from receiver (which is sent by transmitter).

Please, I just need it to see how both works.

At least I can have an example of 2 or 3 line of codes, as:

  1. flight controller: "the indicator"
  2. receiver: command

Let me be more closer (example of PWM):
(This is in flight controller situation=>) When pin 2 has a signal (sent by receiver) then flight controller must command the ESC to spin the motor 100 times.

NB:Since PPM only need 1 wire to connect (usually, pin 2), this is more complicated for me LOL) :frowning:

From the receiver code, the data.throttle is stored in pwm[0]. But in println, why don't just call the pwm[0]? I think = that will be different between "calling the data.throttle" and "calling the pwm[0] since data.throttle is inside the map(), right? That's why I use random flight controller as in my post to see how both works but no luck. please

Hi @jeanloveard

Are you now receiving raw data on the receiver channels, such that when you move the transmitter sticks, you can see the changing values on the receiving Arduino's (Serial) console?

yes, you're right @MartinL. There's a changing value based on the stick/potentio I push, but no movement in motor. I myself still doubt of my flight controller above if it corresponds to the receiver.

Have you posted a schematic diagram of this system? I am as confused as you are. You don't know what you are doing, that's OK, but why am I confused?

Typically the end-to-end in r/c modern multirotors looks like this

 fingers > joypots (sticks) > TX => (PPM) => RX > flight controller > esc/servo [> motor]

where TX is you transmitter, RX is you receiver and => => is the transmitted signal RF modulated by the PPM signal.

Please provide info about all your components and the wiring (schematic, just draw it by hand and shoot a picture), for both sides.

a7

Here @alto777 ...

A. Components:

  1. 2 motors A2212/13T, 1000KV and 2 ESCs 30A
  2. 2 Servos SG
  3. Nano + mpu6050 (Flight Controller)
  4. Receiver (Pro Mini 5v and NRF24L01 + 100uf
  5. Transmitter (Mega 2560 + 2 joysticks + 1 potentio + 2 toggle_switches + battere 9v + NRF24L01 + Antena + 100uf

B. Flight_Controller


I just made it now. (Sorry for mess wiring)

Should I upload the receiver and transmitter?

By the way, here it is - how my poor bicopter looks like :smiley:

@jeanloveard Is your 9V battery capable of supplying 60A (2 ESCs 30A) to your brushless motors?

Well, I don't know. I just know it from you @MartinL - must I? If it is a must. I will get it more (I see others, yes, using 11.1v - is that right?) well, poor me.

HOW ABOUT, if i use adapter 12v? I'm just scared for big electric current. Is it okay?