[UPDATES] Bicopter stabilization algorith (PLEASE HELP NEEDED!!!)

Update:

I clean up the code and separate it into functions.

I was able to combine MPU, PID for servos and BT connection.

I am still having a hard time arming and operating brushless motors using analogwrite.

I wasn't able to calculate angles using mpu.getmotion6. I was getting a nan output after doing this math

Angle[0] = 0.98 * (Angle[0] + Gy[0] * 0.010) + 0.02 * Acc[0];
Angle[1] = 0.98 * (Angle[1] + Gy[1] * 0.010) + 0.02 * Acc[1];

Gy[] and Acc[] values were ok, so i do not know was what going on.

But when getting values from mpu using wire it worked.

I attach my new code. Still trying to figure out how to work with analogwrite for brushelss.

Any suggenstion or sample code????

MPU_Servos.ino (5.01 KB)

Hi Marco

The other option is to use hardware PWM rather than analogWrite.

The following code will control your two motors with 400Hz PWM signals, at 11-bit resolution on D9 and D10:

void setup() {
  // Initialise timer 1 for phase and frequency correct PWM
  pinMode(9, OUTPUT);                         // Set digital pin 9 (D9) to an output
  pinMode(10, OUTPUT);                        // Set digital pin 10 (D10) to an output
  TCCR1A = _BV(COM1A1) | _BV(COM1B1);         // Enable the PWM outputs OC1A, and OC1B on digital pins 9, 10
  TCCR1B = _BV(WGM13) | _BV(CS11);            // Set phase and frequency correct PWM and prescaler of 8 on timer 1
  ICR1 = 2500;                                // Set the PWM frequency to 400Hz
  OCR1A = 0;                                  // Set motor throttle to 0 on D9
  OCR1B = 0;                                  // Set motor throttle to 0 on D10
}

void loop() {
  OCR1A = 1000;                               // Set motor throttle to 0 on D9
  OCR1B = 1000;                               // Set motor throttle to 0 on D10
  delay(1000);                                // Wait for 1 second
  OCR1A = 1200;                               // Set motor throttle to low on D9
  OCR1B = 1200;                               // Set motor throttle to low on D10
  delay(1000);                                // Wait for 1 second
}

Just load the OCR1A or the OCR1B registers with the value in microseconds you'd like to send to each ESC. The exact values will depend on your ESC calbration, but are usually somewhere between 1000us (standby) and 2000us (maximum throttle).

Note that in this instance, there's no need to map your values between 0 and 255 like analogWrite().

MartinL:
Hi Marco

The other option is to use hardware PWM rather than analogWrite.

The following code will control your two motors with 400Hz PWM signals, at 11-bit resolution on D9 and D10:

void setup() {

// Initialise timer 1 for phase and frequency correct PWM
  pinMode(9, OUTPUT);                        // Set digital pin 9 (D9) to an output
  pinMode(10, OUTPUT);                        // Set digital pin 10 (D10) to an output
  TCCR1A = _BV(COM1A1) | _BV(COM1B1);        // Enable the PWM outputs OC1A, and OC1B on digital pins 9, 10
  TCCR1B = _BV(WGM13) | _BV(CS11);            // Set phase and frequency correct PWM and prescaler of 8 on timer 1
  ICR1 = 2500;                                // Set the PWM frequency to 400Hz
  OCR1A = 0;                                  // Set motor throttle to 0 on D9
  OCR1B = 0;                                  // Set motor throttle to 0 on D10
}

void loop() {
  OCR1A = 1000;                              // Set motor throttle to 0 on D9
  OCR1B = 1000;                              // Set motor throttle to 0 on D10
  delay(1000);                                // Wait for 1 second
  OCR1A = 1200;                              // Set motor throttle to low on D9
  OCR1B = 1200;                              // Set motor throttle to low on D10
  delay(1000);                                // Wait for 1 second
}



Just load the OCR1A or the OCR1B registers with the value in microseconds you'd like to send to each ESC. The exact values will depend on your ESC calbration, but are usually somewhere between 1000us (standby) and 2000us (maximum throttle). 

Note that in this instance, there's no need to map your values between 0 and 255 like analogWrite().

I am testing what you suggested and the code alone works perfect but when i integrate it with the whole bicopter code the servos on pin 6 and 5 stop working.

If i comment the initServos (); On the setup the motors work. I think that is because servo.h use timer1 but i do not know to much about timers

Code attached pls help,

Looking foward to hearing your comments,
Marco

bicopter.ino (6.22 KB)

Hi Marco,

I think that is because servo.h use timer1...

Yes, you're right. I overlooked the fact that the servo library uses timer1. This is also the reason why analogWrite() on pins 9 and 11 (which also uses timer1) didn't work.

To overcome this it's necessary to use the 8-bit timer2 for your motors and the 16-bit timer1 for your servos. This is because only timer1 is capable of phase and frequency correct PWM that allows you to set the frequency at 50Hz for your servos.

Timer2 on the other hand can only operate in phase correct PWM, which can be set at 490Hz that's suitable for your motor ESCs.

To convert timer1 for servo operation at 50Hz just change the ICR1 register from 2500 to 20000. Load the OCR1A and OCR1B registers with 1500 to centre the servos. Here's the code, it's identical to the previous code except that I've changed the ICR1 register to 20000:

void setup() {
  // Initialise timer 1 for phase and frequency correct PWM at 50Hz on digital pins D9 and D10
  pinMode(9, OUTPUT);                         // Set digital pin 9 (D9) to an output
  pinMode(10, OUTPUT);                        // Set digital pin 10 (D10) to an output
  TCCR1A = _BV(COM1A1) | _BV(COM1B1);         // Enable the PWM outputs OC1A, and OC1B on digital pins 9, 10
  TCCR1B = _BV(WGM13) | _BV(CS11);            // Set phase and frequency correct PWM and prescaler of 8 on timer 1
  ICR1 = 20000;                               // Set the PWM frequency to 50Hz
  OCR1A = 1500;                               // Centre the servo on D9 and D10
  OCR1B = 1500;                              
}

void loop() {
  OCR1A = 1300;                               // Move the servo on D9 and D10
  OCR1B = 1300;                               
  delay(1000);                                // Wait for 1 second
  OCR1A = 1700;                               // Move the servo on D9 and d10
  OCR1B = 1700;                               
  delay(1000);                                // Wait for 1 second
}

For the your motors, either use analogWrite() function on digital pins D3 and D11, or use the following code that sets up timer2 for phase correct PWM at 490Hz on D3 and D11:

// Set up timer 2 to output 490Hz, 8-bit resolution PWM on pins D11 and D3
void setup() {
  pinMode(11, OUTPUT);                                // Set digital pin 11 (D11) to an output (OC2A)
  pinMode(3, OUTPUT);                                 // Set digital pin 3 (D3) to an output (OC2B)
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);    // Enable phase correct PWM outputs OC2A and OC2B on digital pins D11 and D3 
  TCCR2B = _BV(CS22);                                 // Set the prescaler to 64 on timer 2
}

void loop() {  
  OCR2A = 125;         // Set motor ESCs to standby at 1000us pulsewidth
  OCR2B = 125;                              
  delay(2000);         // Wait for 2 seconds
  OCR2A = 150;         // Set motor ESCs to idle up at 1200us pulsewidth
  OCR2B = 150;                              
  delay(2000);         // Wait for 2 seconds
}

Note that for the motors you'll have to map your 0us to 2000us ESC pulses to 0 to 255, as timer2's only 8-bit.

The reason for all this, is the fact that the Arduino Nano's Atmel 328P microcontroller has only one 16-bit timer (timer1) with 2 output channels.

Alternatively, you could consider the Arduino Micro with its Atmel 32U4 microcontroller. This has two 16-bit timers (timers1 & 3) with 4 output channels and would give you higher a 11-bit resolution PWM for your motors, in addition to your servos. (The disadvantage of the Arduino Micro is that it only has a limited number of interrupt pins).

That being said, the Arduino Nano's been successfully used in a lot of multi-rotor projects.

For the your motors, either use analogWrite() function on digital pins D3 and D11, or use the following code that sets up timer2 for phase correct PWM at 490Hz on D3 and D11:

Thanks for the answer i really appreciate it. Right now i am using pin 2 and 3 for the hc-05 since i am using softwarserial

I think i can use any of the digital pins for that right?

So in my case i will use:

  • Pins 9 and 10 -> servos
  • Pins 3 and 11 -> motors
  • Pins 5 and 6 -> hc05

Is this ok?

Thanks,
Marco

Hi Marco,

So in my case i will use:

  • Pins 9 and 10 -> servos
  • Pins 3 and 11 -> motors
  • Pins 5 and 6 -> hc05

Is this ok?

Yes. The software serial library should work just fine with digital pins 5 and 6.

MartinL:
Hi Marco,

Yes. The software serial library should work just fine with digital pins 5 and 6.

Hey good news. The three pieces are working great and without interferences!!!!!!
Motors
Servos
Bluetooth

Now i have to seat down and tune pid values :slight_smile:

I attach my code just in case someone wants to check it out

Martin thanks for the advice

More updates/questions/good news. Coming soon

Cheers

bicopter.ino (7.04 KB)

Does someone has any suggestion on how to tune the pid values that controls the servos?

With the ones that controls the brushless is easier because by holding the bicopter and move it. if it goes back to its position properly is ok

But with servos i do not know how to test if the value are tune properly

I also attach the android app that i developed for this

Looking forward to hearing suggestions

Thanks,
Marco

app-debug.apk.zip (1.13 MB)

Hi Marco,

To find the correct PID values, I'd suggest starting off by initially only using the proportional or P term. You should be able to get airborne using the P term alone. Then when you're happy with results then gradually increase the I term, followed lastly by the D term.

The correct gain values will be determined by the value of your inputs, for example inputs in degrees/s will have a different gain from say inputs in radians/s. Another factor is the size of your aircraft. Usually the larger the aircraft the greater the gain.

I'm using roll, pitch and yaw rate control loops using degrees/s (for both the pilot setpoint and the gyro input). Here are the PID values for for my small 250mm tricopter:

Roll
P: 0.8
I: 0.5
D: 0.0002

Pitch
P: 0.8
I: 0.5
D: 0.0002

Yaw (Servo Controlled)
P: 1.6
I: 0.2
D 0.0

Obviously these values are just an example, as your bicopter is a different configuration (especially the pitch axis, which uses servo rather than motor control), but the priciple is the same. I'd start off using lower values P gain values first then gradually increase them until the aircraft becomes more responsive, before adding I and D.

It still doesn't fly yet but i am close to achieve that.

One of my main obstacles now is how to control the yaw.

I know that i should add the yaw correction on the servos positions but what i can figure out yet is how to calculate the yaw using the mpu6050.

I read some articules that say that you need a digital compass but in some other places say that the mpu6050 can do it.

Any suggestion on this?

Thanks,
Marco

Hi Marco,

read some articules that say that you need a digital compass but in some other places say that the mpu6050 can do it.

You don't require a compass. In either rate or auto-level modes, yaw is controlled by rate (degrees/s) and is measured using only your gyroscope's Z-axis.

The compass is required when you need to know the aircraft's heading and is really only necessary in conjunction with a GPS for automated flight.

I know that i should add the yaw correction on the servos positions but what i can figure out yet is how to calculate the yaw using the mpu6050.

Convert your pilot's yaw rate input to degrees/s and use this as the setpoint of your yaw rate PID. Take the gyroscope Z-axis (also in degrees/s) and make this the input to the yaw rate PID.

For a bicopter, the motor mixing for yaw axis should make the servos move the propellers in opposing directions. As the servos are opposite to each other, you can just add (100% of) the yaw rate PID output directly to your servos' centre signals. Usually the centre signal is around 1500us or thereabouts. If the yaw rate is too weak or too strong then just adjust the yaw rate PID's gain.

MartinL:
Hi Marco,

You don't require a compass. In either rate or auto-level modes, yaw is controlled by rate (degrees/s) and is measured using only your gyroscope's Z-axis.

The compass is required when you need to know the aircraft's heading and is really only necessary in conjunction with a GPS for automated flight.

Convert your pilot's yaw rate input to degrees/s and use this as the setpoint of your yaw rate PID. Take the gyroscope Z-axis (also in degrees/s) and make this the input to the yaw rate PID.

For a bicopter, the motor mixing for yaw axis should make the servos move the propellers in opposing directions. As the servos are opposite to each other, you can just add (100% of) the yaw rate PID output directly to your servos' centre signals. Usually the centre signal is around 1500us or thereabouts. If the yaw rate is too weak or too strong then just adjust the yaw rate PID's gain.

I am working on this but after converting gz to angle i am getting this result

As you will see on the numbers below first i am getting nan (i do not know why) and then after reseting the arduino i start getting something but the number is always decreasing, then i turn it 90 degrees to the right and i get a value that doesn't make sense at least for me.

I attached the test code that i am using.

Any help is welcome

| GyZ = nan
| GyZ = nan
| GyZ = nan
| GyZ = nan
| GyZ = nan
| GyZ = nan
| GyZ = nan
| GyZ = nan
| GyZ = nan
| GyZ = nan

I reset the arduino and it start working

| GyZ = -2.83
| GyZ = -5.28
| GyZ = -5.13
| GyZ = -8.03
| GyZ = -7.60
| GyZ = -8.30
| GyZ = -10.74
| GyZ = -10.34
| GyZ = -12.50

I turn it 90 degrees to the right

| GyZ = -160.43
| GyZ = -253.56
| GyZ = -256.15
| GyZ = -248.79
| GyZ = -244.95
| GyZ = -241.60
| GyZ = -238.36
| GyZ = -234.93
| GyZ = -232.15
| GyZ = -229.19
| GyZ = -226.04
| GyZ = -223.43
| GyZ = -220.55
| GyZ = -217.62
| GyZ = -215.33
| GyZ = -212.67
| GyZ = -210.09
| GyZ = -207.72
| GyZ = -204.91
| GyZ = -202.63
| GyZ = -200.52
| GyZ = -197.89

sketch_apr13a (1).ino (1.45 KB)

Hi Marco,

I am working on this but after converting gz to angle i am getting this result

You don't need to convert the gz to angle. Just divide the raw gz value by 131 (for the gyro set at its default of ±250°/s full scale) to get the yaw rate in degrees/s.

To get the bicopter flying initially you only need the gyroscope measuring degrees/s, the calculations shouldn't have anything to do with angle.

MartinL:
Hi Marco,

You don't need to convert the gz to angle. Just divide the raw gz value by 131 (for the gyro set at its default of ±250°/s full scale) to get the yaw rate in degrees/s.

To get the bicopter flying initially you only need the gyroscope measuring degrees/s, the calculations shouldn't have anything to do with angle.

I finally implement it as you suggested

The yaw control seems to work well. But i am still having problems with the pitch, controlingthe servos.

I attache my code and also a youtube video link so you have a better idea of what i am talking about.

The thing is that the pitch control is not smooth

Any suggestion is welcome. I feel that i am almost there

Thanks,
Marco

bicopter.ino (7.54 KB)

Hi Marco

Pitch works in the same way as yaw, except that the servos need to point the motors in the same direction, (as opposed to yaw that points them in the opposite direction). To do this add (100% of) your pitch rate PID output to the centre point (1500us) of one servo and subtract (100% of) your pitch rate PID output from the other.

In your code you also need plug Gx and Gy into your PID roll and pitch rate control loops rather than Ax and Ay.

Ax and Ay from the complementary filter are required for auto-level, but the first step is to get the bicopter flying in rate mode that only uses the gyroscope, as this is the basis for flight stability.

So, initially you only need 3 PID control loops for roll, pitch and yaw each taking Gx, Gy and Gz as inputs. The pilot PID setpoints for roll, pitch and yaw should also be in degrees/s.

Thanks for the answer you are helping me a lot.

The thing is, and correct me if i am wrong, if i use Gy i am not sure if the bicopter will go back to a 0 degrees position if something alter the pitch same thing with Gx.

I tried it yesterday but as i mentioned when i alter the pitch the bicopter reacts but it doesn't go back to 0 degrees position

I do not know if i am clear,

Thanks,
Marco

Hi Marco,

The thing is, and correct me if i am wrong, if i use Gy i am not sure if the bicopter will go back to a 0 degrees position if something alter the pitch same thing with Gx.

I tried it yesterday but as i mentioned when i alter the pitch the bicopter reacts but it doesn't go back to 0 degrees position

That's right. Nowadays multi-rotor aircraft have a number of different flight modes, this includes rate mode and auto-level.

The most basic mode is rate mode. This uses only the 3-axis gyroscope measuring rotational speed (degrees/s) in the X, Y and Z axes. The pilot setpoint for roll, pitch and yaw is also in rotational speed (degrees/s). This gives a very free flowing, natural style of flight and is the flight mode of choice for most experienced pilots.

As you mentioned, in this mode the flight controller isn't using the accelerometer, as such it has no way of knowing which way is up and therefore how to level the aircraft. Levelling must be maintained by the pilot.

Even though flying in rate mode is initially much more difficult than auto-level, it's necessary to tune and fly the bicopter in this mode before moving on to auto-level. This is because the rate mode PIDs are the basis of stable flight and are also used in auto-level mode.

In auto-level mode you use two outer roll and pitch angle (degrees) PID control loops to control the inner rate (degrees/s) PID control loops. In this mode the pilot setpoint for roll and pitch is an angle, for instance in degrees, between ±45°. The input for these two outer PID loops is the roll and pitch angle, taken from the complementary filter, (that fuses the integral of the gyroscope with the angle generated from the accelerometer to give a reliable angle estimate). The output from these outer PID loops is then used as the input to the roll and pitch rate PID controllers. The inner, nested rate control loops still provide the flight stability, but rather than the pilot driving them directly, as in rate mode, they're now driven by the outer control loops that maintain angle.

In auto-level mode when you let go of the controls the aircraft self levels. This makes it easier to fly, as the flight computer removes the burden of the pilot having to control every aspect of the aircraft's rotational motion (attitude).

The yaw axis remains unchanged from rate mode and is just driven by a single rate PID control loop (gyroscope's Z axis).

MartinL:
In auto-level mode you use two outer roll and pitch angle (degrees) PID control loops to control the inner rate (degrees/s) PID control loops. In this mode the pilot setpoint for roll and pitch is an angle, for instance in degrees, between ±45°. The input for these two outer PID loops is the roll and pitch angle, taken from the complementary filter, (that fuses the integral of the gyroscope with the angle generated from the accelerometer to give a reliable angle estimate). The output from these outer PID loops is then used as the input to the roll and pitch rate PID controllers. The inner, nested rate control loops still provide the flight stability, but rather than the pilot driving them directly, as in rate mode, they're now driven by the outer control loops that maintain angle.

You got exactly to the point of what i am trying to achieve.

Since i have a super basic self-made android app control y want to implement an auto-level mode on my bicopter

i was reading a little bit about this but i still do not get a clear example of how to combine the two pids that i need for pitch and roll.

I do not see how an output in degrees can be the input of a pid that works in degrees/s

Do you have some example that i can work with?

Thanks a lot for these clarifications as i mentions is exactly what i trying to do.

Cheers,
Marco

Hi Marco,

You got exactly to the point of what i am trying to achieve.

Yes, but to set up the flight controller correctly you'll still need to get it flying in rate mode first. Only once you have the correct rate PID gain values that allow the aircraft to fly, can you then add the additional auto-level control loops. The rate PID control loops and their associated P, I and D gain values are critical to the flight control system.

I do not see how an output in degrees can be the input of a pid that works in degrees/s

I struggled with this concept for quite a while, until I realised that the PID output doesn't represent any units in particular. You just adjust the outer PID's gain to scale the output until it matches required bounds for the next stage. The output can be what you want it to be, so you can just take the outer angle PID's output value and plug it straight into the rate PID control loop's setpoint.

For instance, the same principle can be used for automated flight with the GPS replacing the pilot and PID control loops nested like layers of an onion:

GPS Position PIDs -> GPS Speed PIDs -> Angle PIDs -> Rate PIDs -> Motors/Servos

Notice that the sensor input units from one PID has nothing in common with the units of the next. It's just that the gain of each stage is adjusted appropriately until it matches the expected bounds of the next stage.

In this case the effect is to drive the aircraft from where you are, or current GPS latitude/longitude position (PID input), to where you want to be (PID setpoint), your desired latitude/longitude position.

In the same way, auto-level drives the aircraft from you current roll and pitch angle (PID input), to your desired roll and pitch angle (setpoint):

Pilot -> Angle PIDs -> Rate PIDs -> Motors/Servos

Note that in both examples rate PIDs at the end are still providing your basic flight stability.

Do you have some example that i can work with?

The code shows how the roll and pitch PID outputs are plugged directly into the roll and pitch rate PIDs:

float roll = PID(pilotRollAngle - compFilterRollAngle);     // Outer roll angle PID
float pitch = PID(pilotPitchAngle - compFilterPitchAngle);  // Outer pitch angle PID
  
float rollRate = PID(roll - gyroRollRate);                  // Inner roll rate PID
float pitchRate = PID(pitch - gyroPitchRate);               // Inner pitch rate PID
float yawRate = PID(pilotYawRate - gyroYawRate);            // Inner yaw rate PID