Gimbal con MPU6050, servos y PID

Buenos días.
Como proyecto me propuse hacer un pequeño gimbal con un MPU6050 como sensor y dos servomotores SG90 como actuadores. He encontrado mucha documentación sobre como obtener datos de este sensor y creo tener listo todo el código, además he creado una función para el control PID de modo que este me genere la posición que debo enviar a cada servomotor.

El problema es que su comportamiento no es el adecuado, tanto sin como con el PID, genera muchas oscilaciones y se estabiliza en un angulo que no es 0 (respecto el suelo). He intentado realizar el ajuste de las ganancias del control mediante el metodo de ziegler-nichols pero no logro obtener la oscilación requerida en su primera condición, con lo cual ahora mismo mis parámetros son más bien "a ojo".

Adjunto el video de su comportamiento y el código utilizado.

#include "I2Cdev.h" // Librerias I2C para controlar el mpu6050
#include "Wire.h"
#include "MPU6050.h"  // la libreria MPU6050.h necesita I2Cdev.h, I2Cdev.h necesita Wire.h
#include <Servo.h>  //Libreria Servomotor

MPU6050 sensor; //Definicion del sensor
Servo servo_x; //Definicion de los servomotores
Servo servo_y;

// Valores RAW (sin procesar) del acelerometro y giroscopio en los ejes x,y,z
int ax, ay, az;
int gx, gy, gz;

long tiempo_prev;
float dt;
double ang_x, ang_y;
float ang_x_prev, ang_y_prev;

////////Constantes del controlador////////
double kp = 450, ki = 10, kd =6;

////////Variables calculo PID////////
double Setpoint = 0;
unsigned long currentTime, previousTime;
double elapsedTime;
double error_x, lastError_x, cumError_x, rateError_x;
double error_y, lastError_y, cumError_y, rateError_y;

void setup() {
  Serial.begin(57600);    //Iniciando puerto serial
  Wire.begin();           //Iniciando I2C
  servo_x.attach(9);
  servo_x.write(90);
  servo_y.attach(8);
  servo_y.write(90);
  delay(1000);

  sensor.initialize();    //Iniciando el sensor

  ////////Ajuste de los OFFSETS////////
//  sensor.setXGyroOffset(147);
//  sensor.setYGyroOffset(0);
//  sensor.setZGyroOffset(15);
//  sensor.setXAccelOffset(-2751);
//  sensor.setYAccelOffset(-921);
//  sensor.setZAccelOffset(922);
  sensor.setXGyroOffset(164);
  sensor.setYGyroOffset(4);
  sensor.setZGyroOffset(18);
  sensor.setXAccelOffset(-2844);
  sensor.setYAccelOffset(-1035);
  sensor.setZAccelOffset(4683);

  Serial.println("ang_y, error");

    while (sensor.testConnection() != true) {
      Serial.println("Error al iniciar el sensor");
    }
    Serial.println("Sensor iniciado correctamente");

  delay(100);
}

void loop() {
  ////////Lectura de las aceleraciones y velocidades angulares////////
  sensor.getAcceleration(&ax, &ay, &az);
  sensor.getRotation(&gx, &gy, &gz);

  dt = (millis() - tiempo_prev) / 1000.0;
  tiempo_prev = millis();

  ////////Calcular los ángulos con acelerómetro////////
  float accel_ang_x = atan(ay / sqrt(pow(ax, 2) + pow(az, 2))) * (180.0 / 3.14);
  float accel_ang_y = atan(-ax / sqrt(pow(ay, 2) + pow(az, 2))) * (180.0 / 3.14);

  ////////Filtro de Kalman////////
  //Calcular ángulo de rotación con giroscopio y filtro complemento
  ang_x = 0.98 * (ang_x_prev + (gx / 131) * dt) + 0.02 * accel_ang_x;
  ang_y = 0.98 * (ang_y_prev + (gy / 131) * dt) + 0.02 * accel_ang_y;

  ang_x_prev = ang_x;
  ang_y_prev = ang_y;
  Serial.print(ang_y);
  Serial.print(" ");

  calcularPID(ang_x, ang_y); //Pasamos los ángulos a la función con los cuales calcularemos el error por referencia

  ////////Mostrar los valores por serial////////
  //  Serial.print(ang_x);
  //Serial.print(" ");
  //  Serial.println(ang_y);


  ////////Movimiento de los servomotores////////
  servo_x.write(90 + ang_x);
  servo_y.write(90 + ang_y);
  //////////////////////////////////////////
  //delay(10);

}

double calcularPID(double in_x, double in_y) {

  ////////Calcular los tiempos indispensables////////
  currentTime = millis(); // Tiempo actual
  elapsedTime = (double)(currentTime - previousTime); // Tiempo transcurrido

  ////////Calcular el error entre referencia y medición////////
  error_x = Setpoint - in_x;
  error_y = Setpoint - in_y;
  //  error_x =  in_x;
  //  error_y =  in_y;


  ////////Calcular la integral del error////////
  cumError_x += error_x * elapsedTime;
  rateError_x = (error_x - lastError_x) / elapsedTime;

  cumError_y += error_y * elapsedTime;
  rateError_y = (error_y - lastError_y) / elapsedTime;

  ////////Calcular la salida del PID////////
  double output_x = kp * error_x + ki * cumError_x + kd * rateError_x;
  double output_y = kp * error_y + ki * cumError_y + kd * rateError_y;

  ////////Almacenar el error anterior////////
  lastError_x = error_x;
  lastError_y = error_y;

  ////////Almacenar el tiempo anterior////////
  previousTime = currentTime;

  ////////Cambiar la variable////////
  in_x = output_x;
  in_y = error_y;
  Serial.println(in_y);
}

Gracias de antemano.

No se puede ver el vídeo

Captura de pantalla de 2020-12-12 13-25-20.png

Captura de pantalla de 2020-12-12 13-25-20.png

Perdón. Ya he modificado el link.
Lo dejo por aquí también por si acaso.
https://drive.google.com/file/d/1_qgKFicUHS5msH47AMjeBWBSRlgRXYFy/view?usp=sharing

¿Ese es todo el código?

Si, eso es.

Ok, si alguien te puede ayudar antes, mejor, yo estoy cerrando cuatrimestre en la u y bastante ocupado, pero me lo bajo para mirarlo despacio. No he usado los PID antes pero me ha picado la curiosidad.

Ya te digo, al menos yo necesito un buen rato, por si alguien se anima en corto plazo

Ok ¿Te ayudaron o está pendiente?

Ya está solucionado. El error estaba en la función, me faltó & al pasar los parámetros por referencia. Cuando tenga un rato libre posteo todo el código por si alguien quiere echarle un vistazo.

Gracias

Moderador:
Hola, bienvenido al foro Arduino.
En la sección proyectos tienes estos dos hilos que debiste haber leído antes de postear

Como tu consulta es para otra sección lo muevo a Software.

Normas del foro

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.