Hi Everyone,
Picked up. my hobby project once again after hitting a bit of a brick wall.
The project is an EDF powered rocket. It will be controlled by gimbaling the motor as opposed to thrust vectoring veins that are often used in similar projects. The brains of the project use an MPU9250 and a BMP180 for sensory readings.
Because I am able to actuate in both axises (axies?), at the same time to excerpt a diagonal thrust or thrust to stabilise in any direction, I'm hoping that Eulers angles won't need to be considered. For the time being stabilisation is the only goal, not navigation.
The problem When testing the gimbaling by applying a Proportional value to the PID loop the actuation only works along one axis. The other axis spits out the odd variation here or there but is in no way responsive to the movement. The project uses two PID loops which both pull data from the MPU9250 to generate a suitable output. See below code for the control system
#include "MPU9250.h"
#include <Arduino.h>
#include <ESP32Servo.h>
#include <SFE_BMP180.h>
#include <esp_now.h>
#include <WiFi.h>
#include <PID_v1.h>
#include "Filter.h"
#include "math.h"
//GlobalVariables --------------------------------------
//Set Min/Max values for PID Output
int MAXANGLE = 180;
int MINANGLE = 0;
//Set Servo Pins
int XPin = 16;
int YPin = 17;
int ESCPin = 4;
//PID Tuning Parameters
double Kp=1;
double Ki=0;
double Kd=0;
float RollOutput = 0;
float PitchOutput = 0;
float RawPitch = 0;
float RawRoll = 0;
float RawAltitude = 0;
float FilteredPitch = 0;
float FilteredRoll = 0;
float FilteredAltitude = 0;
//Create PID Variables
double SetpointPitch, InputPitch, OutputPitch;
double SetpointRoll, InputRoll, OutputRoll;
double PitchAVG = 0;
double RollAVG= 0;
float pitch = 0;
float roll = 0;
//Initialisers -----------------------------------------
//Create Servos
Servo PitchAxis;
Servo RollAxis;
Servo ESC;
//Create pressure sesor variables
SFE_BMP180 pressure;
//Create IMU Variable
MPU9250 mpu;
//Filter Variables
ExponentialFilter<long> FilterPitch(10, 0);
ExponentialFilter<long> FilterRoll(10, 0);
ExponentialFilter<long> FilterAltitude(10, 0);
double baseline;
PID PitchPID(&InputPitch, &OutputPitch, &SetpointPitch, Kp, Ki, Kd, DIRECT);
PID RollPID(&InputRoll, &OutputRoll, &SetpointRoll, Kp, Ki, Kd, DIRECT);
//--------------------------------------------------------
//RX Data struct
typedef struct struct_message {
int val;
} struct_message;
struct_message myData;
//Recieve ESP32 Data from controller
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
memcpy(&myData, incomingData, sizeof(myData));
}
//Pressure get funcion
double getPressure()
{
char status;
double T,P,p0,a;
status = pressure.startTemperature();
if (status != 0)
{
delay(status);
status = pressure.getTemperature(T);
if (status != 0)
{
status = pressure.startPressure(3);
if (status != 0)
{
delay(status);
status = pressure.getPressure(P,T);
if (status != 0)
{
return(P);
}
else Serial.println("error retrieving pressure measurement\n");
}
else Serial.println("error starting pressure measurement\n");
}
else Serial.println("error retrieving temperature measurement\n");
}
else Serial.println("error starting temperature measurement\n");
}
void print_calibration() {
Serial.println("< calibration parameters >");
Serial.println("accel bias [g]: ");
Serial.print(mpu.getAccBiasX() * 1000.f / (float)MPU9250::CALIB_ACCEL_SENSITIVITY);
Serial.print(", ");
Serial.print(mpu.getAccBiasY() * 1000.f / (float)MPU9250::CALIB_ACCEL_SENSITIVITY);
Serial.print(", ");
Serial.print(mpu.getAccBiasZ() * 1000.f / (float)MPU9250::CALIB_ACCEL_SENSITIVITY);
Serial.println();
Serial.println("gyro bias [deg/s]: ");
Serial.print(mpu.getGyroBiasX() / (float)MPU9250::CALIB_GYRO_SENSITIVITY);
Serial.print(", ");
Serial.print(mpu.getGyroBiasY() / (float)MPU9250::CALIB_GYRO_SENSITIVITY);
Serial.print(", ");
Serial.print(mpu.getGyroBiasZ() / (float)MPU9250::CALIB_GYRO_SENSITIVITY);
Serial.println();
}
void setup() {
Serial.begin(115200);
Wire.begin();
delay(2000);
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(OnDataRecv);
if (!mpu.setup(0x68)) { // change to your own address
while (1) {
Serial.println("MPU connection failed. Please check your connection with `connection_check` example.");
delay(5000);
}
}
if (pressure.begin()){
Serial.println("BMP180 init success");
}else{
Serial.println("BMP180 init fail (disconnected?)\n\n");
while(1); // Pause forever.
}
// calibrate anytime you want to
Serial.println("Accel Gyro calibration will start in 5sec.");
Serial.println("Please leave the device still on the flat plane.");
mpu.verbose(true);
delay(5000);
mpu.calibrateAccelGyro();
print_calibration();
mpu.verbose(false);
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
PitchAxis.setPeriodHertz(50);
PitchAxis.attach(XPin, 500, 2400);
RollAxis.setPeriodHertz(50);
RollAxis.attach(YPin, 500, 2400);
ESC.setPeriodHertz(50);
ESC.attach(ESCPin, 1000, 2000);
baseline = getPressure();
Serial.print("baseline pressure: ");
Serial.print(baseline);
Serial.println(" mb");
PitchPID.SetMode(AUTOMATIC);
RollPID.SetMode(AUTOMATIC);
SetpointPitch = 0;
SetpointRoll = 0;
PitchAxis.write(90);
RollAxis.write(90);
PitchPID.SetOutputLimits(-180, 180);
RollPID.SetOutputLimits(-180, 180);
}
void loop() {
if(mpu.update()){
float ax = mpu.getAccX() + 1;
float ay = mpu.getAccY();
float az = mpu.getAccZ() - 1;
pitch = 180 * atan (az/sqrt(ay*ay + ax*ax))/M_PI;
roll = 180 * atan (ay/sqrt(az*az + ax*ax))/M_PI;
double P;
P = getPressure();
RawPitch = pitch;
RawRoll = roll;
RawAltitude = pressure.altitude(P, baseline);
FilterPitch.Filter(RawPitch);
FilterRoll.Filter(RawRoll);
FilterAltitude.Filter(RawAltitude);
FilteredPitch = FilterPitch.Current();
FilteredRoll = FilterRoll.Current();
FilteredAltitude = FilterAltitude.Current();
InputPitch = RawPitch;
InputRoll = RawRoll;
PitchPID.Compute();
RollPID.Compute();
PitchOutput = OutputPitch + 90;
RollOutput = OutputPitch + 90;
if (PitchOutput >= MAXANGLE){
PitchOutput = MAXANGLE;
}
if (PitchOutput <= MINANGLE){
PitchOutput = MINANGLE;
}
if (RollOutput >= MAXANGLE){
RollOutput = MAXANGLE;
}
if (RollOutput <= MINANGLE){
RollOutput = MINANGLE;
}
PitchAxis.write(FilteredPitch + 90);
RollAxis.write(FilteredRoll + 90);
ESC.write(myData.val);
Serial.print("Pitch:");
Serial.print(FilteredPitch);
Serial.print(" , ");
Serial.print("PitchOutput:");
Serial.print(PitchOutput);
Serial.print(" , ");
Serial.print("RawPitchOutput:");
Serial.print(OutputPitch);
Serial.print(" , ");
Serial.print("Roll:");
Serial.print(FilteredRoll);
Serial.print(" , ");
Serial.print("RollOutput:");
Serial.print(RollOutput);
Serial.print(" , ");
Serial.print("RawRollOutput:");
Serial.println(OutputRoll);
}
}
I'm almost certain it's something blindingly obvious but i can't seem to pick out what's gone wrong...
Also any tips regarding filtering, I.E the best types of filters or how and when to apply the filters, would be greatly appreciated. I have come across quaternion filters and kalman filters which were included in the library I'm using, but this slow down the response time and readings of the IMU far too much so I ended up disabling it and using the exponential filter. So far it seems to do a good enough job of suppressing vibrations while still being sensitive enough to react when necessary.
Thanks in advance