How to program a PID with a MPU6050 and a servo

Hi,

I programmed a PID for a MPU6050 and a servo. Obviously I am experiencing some problem with the Output. I summarize it this way:

Input Output
From 0 to 90 large value
From 90 to 180 0

Any guess?

Here is my code:
/********************************************************

  • PID Basic Example
    ********************************************************/

#include <PID_v1.h>

//Hereafter the code for the IMU+servo
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include "Servo.h"

MPU6050 mpu;
int16_t ax, ay, az;
int16_t gx, gy, gz;
Servo myservo;
int val, val2;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);

void setup()
{
//initialize the variables we're linked to
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
val = map(ay, -17000, 17000, 0, 255);
Input = val;
Setpoint = 90;
//turn the PID on
myPID.SetMode(AUTOMATIC);

//Hereafter the code for the IMU+servo
Wire.begin();
Serial.begin(38400);
Serial.println("Initialize MPU");
mpu.initialize();
Serial.println(mpu.testConnection() ? "Connected" : "Connection failed");
myservo.attach(9);

Serial.begin(9600); //initialize serial monitor
}

void loop()
{
//Hereafter the code for the IMU+servo
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
val = map(ay, -17000, 17000, 0, 255);

Input = val;
myPID.Compute();
//analogWrite(9,Output);
val2 = map(Output, 0, 255, 0, 179);
myservo.write(val2);

//Print some info for debug
Serial.print("Setpoint: ");
Serial.print(Setpoint);
Serial.print(" Intput: ");
Serial.print(val);
Serial.print(" Output: ");
Serial.println(Output);

delay(100);
}

Any guess?

You are doing something wrong.

Of course, your summary of the setpoint, input, and output data is useless, so we can't begin to guess what is wrong.

What is the servo moving?

Why is val a global variable, when it is only used (uselessly) in loop()? Why can't you just assign the output of map() to Input?

Is Output in the range 0 to 255? If not, map() is probably not doing what you expect.

There have been a few recent Threads about PID stuff.

This one has code for a balancing toy and may be close to what you want.

...R

lets start by changing

PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);

to

PID myPID(&Input, &Output, &Setpoint,1,0,0, DIRECT);

If you input 10 your output should be -10

If that works
what is your desired input range (-180 to +180)
and What is your desired output range
and I can help you more

Can you quantify "some problem with the output" please? Is it stuck at zero? Is it random for a constant input? For that constant input, what is your expected output?

Hi,

I adjusted the PID parameters to 1,0,0. I post the values I get with some explanations:

Setpoint: 122.00 Intput: 0 Output: 0.00
Setpoint: 122.00 Intput: 0 Output: 0.00
Setpoint: 122.00 Intput: 0 Output: 0.00
Setpoint: 122.00 Intput: 0 Output: 0.00
until here, Input should vary from 0 to 90, the Output as well. Both are fixed to 0!

After, the Input goes from 0 to 90 but should go from 90 to 180 and the output from 0 to 120 but should go from 90 to 180 as well.
Setpoint: 122.00 Intput: 8 Output: 12.00
Setpoint: 122.00 Intput: 26 Output: 38.00
Setpoint: 122.00 Intput: 47 Output: 67.00
Setpoint: 122.00 Intput: 51 Output: 73.00
Setpoint: 122.00 Intput: 56 Output: 80.00
Setpoint: 122.00 Intput: 82 Output: 117.00
Setpoint: 122.00 Intput: 79 Output: 113.00
Setpoint: 122.00 Intput: 81 Output: 116.00
Setpoint: 122.00 Intput: 81 Output: 116.00
Setpoint: 122.00 Intput: 85 Output: 122.00
Setpoint: 122.00 Intput: 75 Output: 108.00
Setpoint: 122.00 Intput: 83 Output: 119.00

And the code:

/********************************************************
 * PID Basic Example
 * Reading analog input 0 to control analog PWM output 3
 ********************************************************/

#include <PID_v1.h>


//Hereafter the code for the IMU+servo
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include "Servo.h"

MPU6050 mpu;
 
int16_t ax, ay, az;
int16_t gx, gy, gz;
 
Servo myservo;
 
int val;
//int prevVal;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,1,0,0, DIRECT);

void setup()
{
  //initialize the variables we're linked to

  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  Input = map(ay, -17000, 17000, 0, 255);   
  Setpoint = 122;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  //Hereafter the code for the IMU+servo
  Wire.begin();
  Serial.begin(38400);
 
  Serial.println("Initialize MPU");
  mpu.initialize();
  Serial.println(mpu.testConnection() ? "Connected" : "Connection failed");
  myservo.attach(9);
  
  Serial.begin(9600); //initialize serial monitor
}

void loop()
{
  //Hereafter the code for the IMU+servo 
  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

  Input = map(ay, -17000, 17000, 0, 255);
  myPID.Compute();
  val = map(Output, 0, 255, 0, 179);
  myservo.write(val);
  
  Serial.print("Setpoint: ");
  Serial.print(Setpoint);
  Serial.print(" Intput: ");
  Serial.print(val);
  Serial.print(" Output: ");
  Serial.println(Output);

  delay(100);  
}

Hi,

I found the problem. This is cause because Input is a double. It works fine with this code:

Val = map(ay, -17000, 17000, 0, 180);
 Input = (double)Val;

This was the question about the Input. But I have some problem with Output (the initial question). Why do Output just vary from 0 to Setpoint?

Here is some data where I changed the Setpoint to 160.
Setpoint: 160.00ay: -444Val: 88 Intput: 88.00 Output: 72.00
Setpoint: 160.00ay: 2628Val: 104 Intput: 104.00 Output: 56.00
Setpoint: 160.00ay: 3264Val: 107 Intput: 107.00 Output: 53.00
Setpoint: 160.00ay: 2408Val: 103 Intput: 103.00 Output: 57.00
Setpoint: 160.00ay: 772Val: 94 Intput: 94.00 Output: 66.00
Setpoint: 160.00ay: 1048Val: 96 Intput: 96.00 Output: 64.00
Setpoint: 160.00ay: -3120Val: 74 Intput: 74.00 Output: 86.00
Setpoint: 160.00ay: -6156Val: 58 Intput: 58.00 Output: 102.00
Setpoint: 160.00ay: -10672Val: 34 Intput: 34.00 Output: 126.00
Setpoint: 160.00ay: -12396Val: 25 Intput: 25.00 Output: 135.00
Setpoint: 160.00ay: -14348Val: 14 Intput: 14.00 Output: 146.00
Setpoint: 160.00ay: -15840Val: 7 Intput: 7.00 Output: 153.00
Setpoint: 160.00ay: -16816Val: 1 Intput: 1.00 Output: 159.00
Setpoint: 160.00ay: -16324Val: 4 Intput: 4.00 Output: 156.00
Setpoint: 160.00ay: -16728Val: 2 Intput: 2.00 Output: 158.00
Setpoint: 160.00ay: -16308Val: 4 Intput: 4.00 Output: 156.00
Setpoint: 160.00ay: -15780Val: 7 Intput: 7.00 Output: 153.00
Setpoint: 160.00ay: -16956Val: 1 Intput: 1.00 Output: 159.00
Setpoint: 160.00ay: -18516Val: -6 Intput: -6.00 Output: 166.00
Setpoint: 160.00ay: -13072Val: 21 Intput: 21.00 Output: 139.00
Setpoint: 160.00ay: -120Val: 89 Intput: 89.00 Output: 71.00
Setpoint: 160.00ay: 3316Val: 107 Intput: 107.00 Output: 53.00
Setpoint: 160.00ay: -1232Val: 84 Intput: 84.00 Output: 76.00
Setpoint: 160.00ay: 3340Val: 108 Intput: 108.00 Output: 52.00
Setpoint: 160.00ay: 10352Val: 145 Intput: 145.00 Output: 15.00
Setpoint: 160.00ay: 10236Val: 144 Intput: 144.00 Output: 16.00
Setpoint: 160.00ay: 16164Val: 175 Intput: 175.00 Output: 0.00
Setpoint: 160.00ay: 14008Val: 164 Intput: 164.00 Output: 0.00
Setpoint: 160.00ay: 16524Val: 177 Intput: 177.00 Output: 0.00
Setpoint: 160.00ay: 15332Val: 171 Intput: 171.00 Output: 0.00
Setpoint: 160.00ay: 16436Val: 177 Intput: 177.00 Output: 0.00
Setpoint: 160.00ay: 15132Val: 170 Intput: 170.00 Output: 0.00
Setpoint: 160.00ay: 15976Val: 174 Intput: 174.00 Output: 0.00

With the code:

/********************************************************
 * PID Basic Example
 * Reading analog Input 0 to control analog PWM output 3
 ********************************************************/

#include <PID_v1.h>


//Hereafter the code for the IMU+servo
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include "Servo.h"

MPU6050 mpu;
 
int16_t ax, ay, az;
int16_t gx, gy, gz;
 
Servo myservo;
 
int Val, Target;
//int prevVal;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,1,0,0, DIRECT);

void setup()
{
  //initialize the variables we're linked to

  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  Input = map(ay, -17000, 17000, 0, 255);   
  Setpoint = 160;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  //Hereafter the code for the IMU+servo
  Wire.begin();
  Serial.begin(38400);
 
  Serial.println("Initialize MPU");
  mpu.initialize();
  Serial.println(mpu.testConnection() ? "Connected" : "Connection failed");
  myservo.attach(9);
  
  Serial.begin(9600); //initialize serial monitor
  //myPID.SetOutputLimits(0, 180);
}

void loop()
{
  //Hereafter the code for the IMU+servo 
  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

  Val = map(ay, -17000, 17000, 1, 180);
  Input = (double)Val;
  myPID.Compute();
  myservo.write(Output);
  Serial.print("Setpoint: ");
  Serial.print(Setpoint);
  Serial.print("ay: ");
  Serial.print(ay);
  Serial.print("Val: ");
  Serial.print(Val);
  Serial.print(" Intput: ");
  Serial.print(Input);
  Serial.print(" Output: ");
  Serial.println(Output);

  delay(100);  
}

Ok we are getting closer.
The function you are using to retrieve information from the MPU doesn't get an angular measurement it is the angular rate
In other words if you have your MPU6050 fully calibrated and it is setting still the values you will get back will all be zero except for the accelerometer reading gravity.
Get raw 6-axis motion sensor readings (accel/gyro).

/** Get raw 6-axis motion sensor readings (accel/gyro).
 * Retrieves all currently available motion sensor values.
 * @param ax 16-bit signed integer container for accelerometer X-axis value
 * @param ay 16-bit signed integer container for accelerometer Y-axis value
 * @param az 16-bit signed integer container for accelerometer Z-axis value
 * @param gx 16-bit signed integer container for gyroscope X-axis value
 * @param gy 16-bit signed integer container for gyroscope Y-axis value
 * @param gz 16-bit signed integer container for gyroscope Z-axis value
 * @see getAcceleration()
 * @see getRotation()
 * @see MPU6050_RA_ACCEL_XOUT_H
 */
void MPU6050::getMotion6(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz) {
    I2Cdev::readBytes(devAddr, MPU6050_RA_ACCEL_XOUT_H, 14, buffer);
    *ax = (((int16_t)buffer[0]) << 8) | buffer[1];
    *ay = (((int16_t)buffer[2]) << 8) | buffer[3];
    *az = (((int16_t)buffer[4]) << 8) | buffer[5];
    *gx = (((int16_t)buffer[8]) << 8) | buffer[9];
    *gy = (((int16_t)buffer[10]) << 8) | buffer[11];
    *gz = (((int16_t)buffer[12]) << 8) | buffer[13];
}

And to help you understand PID I broke down each of the equations and ran some numbers through them to show you how changing each affects the overall control. This will help you when it comes to tuning what ever you are trying to control
What are you trying to control?
Z

Basic_PID_Test.ino (8.98 KB)

I found the problem. This is cause because Input is a double. It works fine with this code:

That is nonsense. Casting val (an int) to a double and then storing the result in a double accomplishes nothing. The value will not be somehow magically augmented because you did an explicit cast. The compiler will do an implicit cast (exactly the same one you explicitly added).

You made some OTHER change that solved the problem.

focusing on PID and its math

with direct acting PID Setpoint will be opposite of input so with setpoint:
setpoint = 160
if the Input = 160 the output = 0;
if the Input = 170 the output = -10;
if the input = 150 the output = 10;

we are not in REVERSE so kp > zero

  if(controllerDirection ==REVERSE)
   {
      kp = (0 - kp);
      ki = (0 - ki);
      kd = (0 - kd);
   }

Default PID_v1 limits are

PID::SetOutputLimits(0, 255);

so your output is restricted to 0~255 range
you restricted your input to 0 ~ 255:

 Input = map(ay, -17000, 17000, 0, 255);
in PID::Compute(){
//...
double error = *mySetpoint - input;

// and

double output = kp * error + ITerm- kd * dInput;
//...
}

options:
Reverse your PID

PID myPID(&Input, &Output, &Setpoint,1,0,0, REVERSE);

a positive input will produce a positive output

change the output scale

myPID.SetOutputLimits(-255, 255);

add integral into the equation to shift the output (Start with just a little 0.1 may be too much to start with you may try 0.001 (Remember they ar floats so decimal numbers are valid and incredibly useful when tuning PID)

PID myPID(&Input, &Output, &Setpoint, 1.0 , 0.1 , 0.0 , DIRECT);

What will happen is the output will slowly rise to achieve setpoint

Knowing what you are controlling will help tremendously
your desired input ranges and what units they are
your desired output ranges and what they are driving (we know its an actuator but what type?)

Z

Hi,

my problem was with the Output. PID actually calculate a difference. I added the code hereafter for the servo's move and it works:
myPID.Compute();
Target = Output + Setpoint ;
myservo.write(Target);

@Z I run your file. This is very helpful. I watch some videos to feel how to set the parameters before, but with these examples, this gets clear for me.

Then I need to find the PID parameters. I want to set a fly controller on a kite which is controlled like a kite for kitesurfing. The servo pulls either the far left or far right back side of the kite to make it turn.

Here, this is not easy to find rough values for the PID. I feel that the movement of the servo should be large because the kite turns slowly. I would say a Kp like 2, 3.

For the Ki, as I said, the kite turns very slowly. I would say a relative big value for Ki as well, maybe 0,2.

Then for Kd, I would say nothing for the moment, or I don't know. Maybe 0,1?

medeslip:
Hi,

my problem was with the Output. PID actually calculate a difference. I added the code hereafter for the servo's move and it works:
myPID.Compute();
Target = Output + Setpoint ;
myservo.write(Target);

@Z I run your file. This is very helpful. I watch some videos to feel how to set the parameters before, but with these examples, this gets clear for me.

Then I need to find the PID parameters. I want to set a fly controller on a kite which is controlled like a kite for kitesurfing. The servo pulls either the far left or far right back side of the kite to make it turn.

Here, this is not easy to find rough values for the PID. I feel that the movement of the servo should be large because the kite turns slowly. I would say a Kp like 2, 3.

For the Ki, as I said, the kite turns very slowly. I would say a relative big value for Ki as well, maybe 0,2.

Then for Kd, I would say nothing for the moment, or I don't know. Maybe 0,1?

This is great!!!
Kite Control :slight_smile:
How to understand and setup PID controls
Ignore Kd for now it will be used to quickly land setpoint later set it to zero Kd=0

start by using small KI something like .5 or even .05 to start with this will allow your kite to discover balance
Kp will be different if your control input range is bigger than your desired output range Kp will be smaller than 1
if your input range is smaller than your output range your Kp will be bigger than 1

Now with a question about how you are reading your MPU6050. Would you like to read actual Yaw Pitch and Roll from the unit?
or is Y accelerometer good enough for what you are trying to control?

Z