I’m currently building a drone from scratch. The frame is an F450, powered by four A2122 930KV motors and 30A Simonk ESCs. My main flight controller is an ESP32-S3, paired with an MPU6050 (providing gyroscope and accelerometer data) and a logic level shifter to convert the 3.3V PWM signals to 5V.
The issue I’m facing is that during testing on a tuning rig (similar to IMG1 ), the drone consistently falls on one of the motor's sides. (Like 45 degrees on Pitch and 45 degrees on Roll Axis)
For control, I’m using a standard cascaded PID system:
- Outer PID: input = desired angle, output = desired angular rate
- Inner PID: input = desired angular rate, output = PWM signal
- Yaw axis: uses only the inner PID (since I don’t have a magnetic compass, I can’t calculate the yaw angle).
Currently, both the Roll and Pitch axes use the same cascaded PID values:
- Outer PID: P = 1.2, I = 0, D = 0.00008(Pitch and Roll)
- Inner PID: P = 2.3, I = 0, D = 0.00002(Pitch and Roll)
- Yaw PID: P = 0.8, I = 0, D = 0(inner PID only, input = angular rate, output = PWM).
More Information
- MPU6050: Already calibrated both Gyro and Accelerometer, and using a complementary filter
- PWM 1000-2000us calibrate every time the drone is connected to the battery
- MPU6050 250Hz(Ideal Condition), Drone Motor(500Hz), whole system including PID 250-300Hz(Rough Estimation)
- Starting level (0° Roll and Pitch), the drone slowly tilts toward one motor. The controller sometimes overcorrects, causing instability, or fails to provide sufficient PWM, letting the drone continue tipping toward that motor’s side.
At this point, I don't know if it's my hardware problem or my software problem. I have been tuning the PID system for a month now, and I'm starting to lose hope. Please help Thanks
Main.ino (Relevant Part)
void setup()
{
Serial.begin(115200);
delay(2000);
Serial.println("Program started...");
MyController.InitDrone(TopLeft,TopRight,BottomLeft,BottomRight,SDA_PIN,SCL_PIN);///Init
MyController.InitPID(OuterPID,InnerPID,YawPID);//Init
delay(100);
WifiManager.bootServer();
}
void loop()
{
if(WifiManager.waitForClient())//Tuning PID via Wifi
{
while(WifiManager.isClientConnected())
{
MyController.ApplyThrust(1500);//PWM
MyController.AutoBalance(0,0,0);///Angle Pitch Roll Yaw
MyController.SubmitPWM();
delay(1);
/// MyController.PrintAll4Motors();
if(WifiManager.ReceiveModifyPID(UpdatedOuterPID,UpdatedInnerPID,UpdateYawPID))///Updating the PID
{
MyController.EnableProtection = false;
OuterPID.Kp = UpdatedOuterPID.P;
OuterPID.Ki = UpdatedOuterPID.I;
OuterPID.Kd = UpdatedOuterPID.D;
InnerPID.Kp = UpdatedInnerPID.P;
InnerPID.Ki = UpdatedInnerPID.I;
InnerPID.Kd = UpdatedInnerPID.D;
YawPID.Kp = UpdateYawPID.P;
YawPID.Ki = UpdateYawPID.I;
YawPID.Kd = UpdateYawPID.D;
MyController.InitPID(OuterPID,InnerPID,YawPID);
}
}
MyController.ShutDownMotors();
}
MyController.ShutDownMotors();
}
PID + Drone Controller Part
#include "Drone_Control_System.h" //Header File
PID_System::PID_System()
: Kp(0), Ki(0), Kd(0),
prevError(0), integral(0),
OutMin(-1000), OutMax(1000),
FeedBackValue(0), Input(0)
{
lastTime = millis();
}
void PID_System::SetPID(float P, float I, float D) {
Kp = P;
Ki = I;
Kd = D;
integral=0;
lastTime = millis();
}
void PID_System::SetOutputLimit(float Min, float Max) {
OutMin = Min;
OutMax = Max;
}
void PID_System::EntryPoint(float In) {
Input = In;
}
void PID_System::Feedback(float Detected) {
FeedBackValue = Detected;
}
float PID_System::PID_Output() {
unsigned long now = millis();
float timeChange = (float)(now - lastTime) / 1000.0f;
if (timeChange <= 0) timeChange = 0.00001f;
float error = Input - FeedBackValue;
integral += error * timeChange;
float derivative = (error - prevError) / timeChange;
float output = (Kp * error) + (Ki * integral) + (Kd * derivative);
if (output > OutMax) output = OutMax;
else if (output < OutMin) output = OutMin;
prevError = error;
lastTime = now;
return output;
}
// ================= Drone Controller ==================
void Drone_Controller::InitDrone(uint16_t TopLeftPin,uint16_t TopRightPin, uint16_t BottomLeftPin, uint16_t BottomRightPin,uint16_t SDA_Pin, uint16_t SCL_Pin)
{
if(Debug) Serial.println("Start Calibrating MPU6050 DO NOT MOVE IT");
InternalGyro.InitGyro(3, 8); // custom SDA/SCL pins
InternalGyro.SetUpdateInterval(4); // 4ms
if(Debug) Serial.println("Finished Calibrating MPU6050");
if(EnableFlight)
{
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
delay(1000);
TopLeft_ESC.attach(TopLeftPin,MIN_PULSE,MAX_PULSE);
delay(1000);
TopRight_ESC.attach(TopRightPin,MIN_PULSE,MAX_PULSE);
delay(1000);
BottomLeft_ESC.attach(BottomLeftPin,MIN_PULSE,MAX_PULSE);
delay(1000);
BottomRight_ESC.attach(BottomRightPin,MIN_PULSE,MAX_PULSE);
delay(1000);
TopLeft_ESC.setPeriodHertz(MotorHZ);//500HZ
delay(100);
TopRight_ESC.setPeriodHertz(MotorHZ);
delay(100);
BottomLeft_ESC.setPeriodHertz(MotorHZ);
delay(100);
BottomRight_ESC.setPeriodHertz(MotorHZ);
delay(100);
//Calibrating ESC
TopLeft_ESC.writeMicroseconds(MAX_PULSE);//2000us
TopRight_ESC.writeMicroseconds(MAX_PULSE);//2000
BottomLeft_ESC.writeMicroseconds(MAX_PULSE);//2000us
BottomRight_ESC.writeMicroseconds(MAX_PULSE);//2000us
delay(5000);
TopLeft_ESC.writeMicroseconds(MIN_PULSE);//1000us
TopRight_ESC.writeMicroseconds(MIN_PULSE);///1000
BottomLeft_ESC.writeMicroseconds(MIN_PULSE);///1000
BottomRight_ESC.writeMicroseconds(MIN_PULSE);//1000
delay(2000);
}
}
void Drone_Controller::InitPID(PID_Values RotationRate, PID_Values PWM , PID_Values Yaw)
{
PitchRotationRate_PID.SetPID(RotationRate.Kp, RotationRate.Ki, RotationRate.Kd);//Input Angle Output Angle Rate
PitchPWM_PID.SetPID(PWM.Kp, PWM.Ki, PWM.Kd);//Input Angle Rate Output PWM
RollRotationRate_PID.SetPID(RotationRate.Kp, RotationRate.Ki, RotationRate.Kd);//Input Angle Output Angle Rate
RollPWM_PID.SetPID(PWM.Kp, PWM.Ki, PWM.Kd);//Input Angle Rate Output PWM
YawRotationPWM_PID.SetPID(Yaw.Kp,Yaw.Ki,Yaw.Kd);//Input Angle Rate Output PWM
YawRotationPWM_PID.SetOutputLimit(-350, 350);//PWM
PitchRotationRate_PID.SetOutputLimit(-350,350);//Angle Rate
PitchPWM_PID.SetOutputLimit(-350,350);//PWM
RollRotationRate_PID.SetOutputLimit(-350,350);//Angle Rate
RollPWM_PID.SetOutputLimit(-350,350);//PWM
}
void Drone_Controller::ApplyThrust(int PWM)
{
InternalMotorsPWM.TLeft = PWM;
InternalMotorsPWM.TRight= PWM;
InternalMotorsPWM.BLeft = PWM;
InternalMotorsPWM.BRight = PWM;
}
void Drone_Controller::AutoBalance(float DesirePitchAngle, float DesireRollAngle,float DesireYawRotateRate)
{
InternalGyro.GyroSignal();
//Pitch
CurrentPitchAngle = constrain(InternalGyro.CompAnglePitch,-20,20);///20degrees
CurrentPitchAngleRate = InternalGyro.RatePitch;
//Roll
CurrentRollAngle = constrain(InternalGyro.CompAngleRoll,-20,20);///20degrees
CurrentRollAngleRate = InternalGyro.RateRoll;
//Yaw
CurrentYawAngleRate = InternalGyro.RateYaw;
//Pitch PID
PitchRotationRate_PID.EntryPoint(DesirePitchAngle); ///Input Degree Output Angle Rate
PitchRotationRate_PID.Feedback(CurrentPitchAngle);
float DesirePitchRate = PitchRotationRate_PID.PID_Output();
PitchPWM_PID.EntryPoint(DesirePitchRate);///Input Angle Rate Output PWM
PitchPWM_PID.Feedback(CurrentPitchAngleRate);
float Pitch_OutputPWM = PitchPWM_PID.PID_Output();
//Roll PID
RollRotationRate_PID.EntryPoint(DesireRollAngle);///Input Degree Output Angle Rate
RollRotationRate_PID.Feedback(CurrentRollAngle);
float DesireRollRate = RollRotationRate_PID.PID_Output();
RollPWM_PID.EntryPoint(DesireRollRate);///Input Angle Rate Output PWM
RollPWM_PID.Feedback(CurrentRollAngleRate);
float Roll_OutputPWM = RollPWM_PID.PID_Output();
//Yaw
YawRotationPWM_PID.EntryPoint(DesireYawRotateRate);//Input Angle Rate Output PWM
YawRotationPWM_PID.Feedback(CurrentYawAngleRate);
float Yaw_OutputPWM = YawRotationPWM_PID.PID_Output();
InternalMotorsPWM.TLeft += Pitch_OutputPWM+ Roll_OutputPWM + Yaw_OutputPWM; //CCW
InternalMotorsPWM.BLeft += Pitch_OutputPWM + (-1*Roll_OutputPWM) + (-1*Yaw_OutputPWM);//CW
InternalMotorsPWM.TRight += (-1*Pitch_OutputPWM) + Roll_OutputPWM + (-1* Yaw_OutputPWM);//CW
InternalMotorsPWM.BRight += (-1*Pitch_OutputPWM) + (-1*Roll_OutputPWM)+ Yaw_OutputPWM;//CCW
if(fabs(CurrentPitchAngle) > PitchAngleProtection || fabs(CurrentRollAngle) > RollAngleProtection) //30Degrees
{
EnableProtection = true;
}
/*
Serial.print(" TL:");
Serial.print(InternalMotorsPWM.TLeft);
Serial.print(" TR:");
Serial.print(InternalMotorsPWM.TRight);
Serial.print(" BL:");
Serial.print(InternalMotorsPWM.BLeft);
Serial.print(" BR:");
Serial.print(InternalMotorsPWM.BRight);
Serial.print(" ");
Serial.print(DesireRollRate);
Serial.print(" ");
Serial.println(DesirePitchRate);
*/
}
void Drone_Controller::SubmitPWM()
{
Bounded();
if(EnableProtection)
{
ShutDownMotors();
return;
}
if(EnableFlight)
{
TopLeft_ESC.writeMicroseconds(InternalMotorsPWM.TLeft);
TopRight_ESC.writeMicroseconds(InternalMotorsPWM.TRight);
BottomLeft_ESC.writeMicroseconds(InternalMotorsPWM.BLeft);
BottomRight_ESC.writeMicroseconds(InternalMotorsPWM.BRight);
}
}
void Drone_Controller::ShutDownMotors()
{
InternalMotorsPWM.TLeft =MIN_PULSE;
InternalMotorsPWM.TRight=MIN_PULSE;
InternalMotorsPWM.BLeft =MIN_PULSE;
InternalMotorsPWM.BRight=MIN_PULSE;
if(EnableFlight)
{
TopLeft_ESC.writeMicroseconds(MIN_PULSE);
TopRight_ESC.writeMicroseconds(MIN_PULSE);
BottomLeft_ESC.writeMicroseconds(MIN_PULSE);
BottomRight_ESC.writeMicroseconds(MIN_PULSE);
}
}
void Drone_Controller::Bounded()
{
InternalMotorsPWM.TLeft = constrain(InternalMotorsPWM.TLeft, 1000, 1999);
InternalMotorsPWM.TRight = constrain(InternalMotorsPWM.TRight,1000,1999);
InternalMotorsPWM.BLeft = constrain(InternalMotorsPWM.BLeft, 1000, 1999);
InternalMotorsPWM.BRight = constrain(InternalMotorsPWM.BRight,1000,1999);
}
void Drone_Controller::PrintAll4Motors()
{
if(!EnableProtection)
{
/*
Serial.print("Pitch Angle: ");
Serial.print(CurrentPitchAngle);
Serial.print(" Roll Angle:");
Serial.print(CurrentRollAngle);
Serial.print(" Yaw Rate");
Serial.println(CurrentYawAngleRate);
Serial.print(" Top Left: ");
Serial.print(InternalMotorsPWM.TLeft);
Serial.print(" Top Right: ");
Serial.print(InternalMotorsPWM.TRight);
Serial.print(" Bottom Left: ");
Serial.print(InternalMotorsPWM.BLeft);
Serial.print(" Bottom Right: ");
Serial.println(InternalMotorsPWM.BRight);
delay(50);
*/
}
}
Some CMD Outputs:
| Roll Angle | Pitch Angle | Yaw Angle Rate | Top Left Motor(PWM) | Top Right Motor | Bottom Left Motor | Bottom Right Motor |
|---|---|---|---|---|---|---|
| 3.36 | 20.65 | -0.17 | 1437 | 1545 | 1454 | 1562 |
| 20.25 | 1.95 | -0.60 | 1445 | 1449 | 1549 | 1555 |
| 1.89 | -20.38 | -0.49 | 1550 | 1443 | 1556 | 1450 |
| -20.24 | -1.89 | 0.07 | 1566 | 1543 | 1456 | 1433 |
| 20.40 | -19.82 | 15.16 | 1457 | 1527 | 1497 | 1517 |
| 21.65 | 20.84 | -0.40 | 1388 | 1500 | 1498 | 1612 |
| -1.16 | 2.36 | 32.58 | 1471 | 1540 | 1511 | 1476 |
Roll is Top(Negative) Down(Postive)
Pitch is Left(Negavie) Right(Postive)
All help is appreciated, thanks
