Swapping direction of servo travel with PID loop

HI guys, first of all thanks to this group as its helped massively in getting my project to where it is now.

BACKGROUND - I'm in the process for building an autonomous boat which travels to a set of predefined coordinates. My program is just about finished and working well. It comprises of 1 UNO which does the bulk of the work by using GPS and a compass to calculate a heading error. The PID loop then takes care of feeding a servo and angle.

However, I've realised that the the PID is outputting in the opposite direction to that it should. eg. when the rudder needs to go left its going right. The degree of rudder and everything else are perfect so I'm happy with the numbers but there is an obvious error I cant get my head around.




#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <LSM303.h>
#include <PWMServo.h>
#include <PID_v1.h>




// SERVO PID CONTROL
double Setpoint, Input, Output;
double Kp = 1.2, Ki = 0.0, Kd = 0.0;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE);
#define PIN_OUTPUT 10

// VOLTAGE
#define ANALOG_IN_PIN A0

// Floats for ADC voltage & Input voltage
float adc_voltage = 0.0;
float in_voltage = 0.0;

// Floats for resistor values in divider (in ohms)
float R1 = 30000.0;
float R2 = 7500.0;

// Float for Reference Voltage
float ref_voltage = 4.20;

// Integer for ADC value
int adc_value = 0;

///AMPS
int analogPin = A1;  // Current sensor output

const int averageValue = 500;
long int sensorValue = 0;  // variable to store the sensor value read

float voltage = 0;
float current = 0;

// GPS
static const int RXPin = 6, TXPin = 7;  //GPS TX & RX
SoftwareSerial ss(RXPin, TXPin);        //GPS TX & RX

// Create objects
TinyGPSPlus gps;
LSM303 compass;
PWMServo myservo;
PWMServo ESC;

// GPS buadrate
static const uint32_t GPSBaud = 9600;

// ESC inputs - 180 = full power etc
int full_power = 101;


void setup()

{
  Serial.begin(115200);
  ss.begin(GPSBaud);
  Wire.begin();
  compass.init();
  compass.enableDefault();
  myservo.attach(SERVO_PIN_B);  // myservo.attach(SERVO_PIN_B, 1000, 2000);
  ESC.attach(SERVO_PIN_A);      //myservo.attach(SERVO_PIN_A, 1000, 2000);

  Setpoint = 0;  // Desired bearing is 0° off bearing to base


  myPID.SetMode(AUTOMATIC);


  compass.m_min = (LSM303::vector<int16_t>){ -480, -465, -509 };
  compass.m_max = (LSM303::vector<int16_t>){ +659, +703, +429 };

  Serial.println(F("Time          ,  Current location  ,   Destination   ,  Distance to desination  ,   Course to destination    ,  Heading   ,   Heading error  ,  Speed(kph)  ,  GPS aquired course   ,   Rudder input  ,  System amps  , System voltage    "));
}

void loop() {


  while (ss.available() > 0) {
    gps.encode(ss.read());
    compass.read();
  }


  if (gps.location.isUpdated()) {

    // NAVIGATION FOMULAS
    static const double Destination_lat = 55.294285, Destination_lng = -1.580930;  /// 55.294285458482264, -1.5809302852304756
    double distanceToDestination = TinyGPSPlus::distanceBetween(gps.location.lat(), gps.location.lng(), Destination_lat, Destination_lng);
    double courseToDestination = TinyGPSPlus::courseTo(gps.location.lat(), gps.location.lng(), Destination_lat, Destination_lng);
    double heading = compass.heading();
    int heading_error_INT = round(heading - courseToDestination);
    int heading_error = (heading_error_INT + 540) % 360 - 180;  //int heading_error = (bearing-heading+540)%360 -180;

    //  Serial prints


    Serial.print(gps.time.hour());
    Serial.print(F(":"));
    Serial.print(gps.time.minute());
    Serial.print(F(":"));
    Serial.print(gps.time.second());
    Serial.print("       ,        ");
    Serial.print(gps.location.lat(), 6);
    Serial.print(",");
    Serial.print(gps.location.lng(), 6);
    Serial.print(",");
    Serial.print(Destination_lat, 6);
    Serial.print(",");
    Serial.print(Destination_lng, 6);
    Serial.print(",");
    Serial.print(distanceToDestination / 1000);
    Serial.print(",");
    Serial.print(courseToDestination);
    Serial.print(",");
    Serial.print(heading);
    Serial.print(",");
    Serial.print(heading_error);
    Serial.print(",");
    Serial.print(gps.speed.kmph());
    Serial.print(",");
    Serial.print(gps.course.deg());




    for (int i = 0; i < averageValue; i++) {
      sensorValue += analogRead(analogPin);
    }

    sensorValue = sensorValue / averageValue;
    voltage = sensorValue * 4.20 / 1024.0;
    current = (voltage - 2.5) / 0.185;

    adc_value = analogRead(ANALOG_IN_PIN);
    adc_voltage = (adc_value * ref_voltage) / 1024.0;
    in_voltage = adc_voltage / (R2 / (R1 + R2));

    Serial.print(current);
    Serial.print(",");
    Serial.print(in_voltage, 2);




    // PID RUDDER LOOP
    if (heading_error >= -180 && heading_error <= 0) {
      Input = heading_error;
      myPID.SetOutputLimits(40, 90);
      Serial.print("LEFT");
    }
    if (heading_error >= 0 && heading_error <= 180) {
      Input = heading_error;
      myPID.SetOutputLimits(90, 140);
      Serial.print("RIGHT");
    }
    myPID.Compute();
    myservo.write(Output);
    Serial.print(",");
    Serial.println(Output);
  }
}

There's your left and right. Just swap them if they're backwards. Don't you generally turn the rudder to the left in order to turn the boat to the right?

PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE);

Have you tried changing REVERSE to DIRECT?

I’ve tried all sorts and each time the PID does something completely irregular.

I’ve tried it in reverse and direct. In direct it set the rudder as if the set point was off by about 90 degrees.

If I swap the if statements but leave the pid set limits the same then again it’s starts something weird like above ^^

I’m nearly 100% sure it’s a mapping issue with the set limits I’m doing but can’t seem to nail it !!

PID is not meant to be used with "mapping". Arbitrarily altering the output generally causes the PID algorithm to malfunction.

At most, you can use constrain() to prevent the output from exceeding practical limits.

When implementing PID in any new situation, an excellent and time-saving approach is to put in Serial.print statements to make certain that the outputs are sensible, for a range of inputs and setpoints. If the outputs aren't sensible, the basic setup is wrong.

1 Like

Thanks for your replies. I’ll have another go when I get the chance and try and implement some more serial prints.

Out of interest. How does it matter which way round I set the output limits ?

I.e is setlimit(40,90) the same as setlimit(90,40)

?

Try the approach I mentioned to see if the output makes sense.

On the other hand, setting arbitrary limits without understanding if and how the algorithm works is bound to lead to the sort of problems you are having.

Remove the limits for now and make sure that the basic approach is working, namely: OUTPUT is proportional to ERROR, and in the right direction to correct for ERROR.

Not on this planet.......

Have you looked at ardupilot? It has a "boat" section.

I have no reason to use anything else as what I have is working perfectly. Just need to fix this bug and I’m on my way :wink:

Please tell me in what sense this is a PID, when only P is !=0:

Yeah sure. Well my PID loop was only put into my program about a day ago. Before that it was a set of if statements to drive the servo based on heading error however this was a bit crude.

Thus building in a PID loop. Since it’s only been a day since building this into my script and noticing that the rudder is working on the opposite direction then I’m sure you’ll agree I’d be way ahead of myself implementing anything other than 0,0 in “I” and “D” of the loop.

I’m sure you will agree.

So I've here I'm printing a string from my NAV script which is Desired heading, Current heading , Heading error.

Then underneath that we have Input and Output of the PID. The numbers are all correct in that when heading error is 0 then Output is 90 (centre on my servo) and vice versa to the limits stated i.e 40 is hard one way and 140 id hard the other way. So I'm happy with this part. Still, the problem remains that the servo is going the opposite direction to what I want

UPDATE, managed to sort this. As mentioned above it was case of swapping from Reverse to Direct and then changing the arguments in the if statements to the opposite way round.

Thanks for the help

I've been driving boats around rivers for a while. I know that I push the handle on the motor (rudder) to the port side of the boat and that turns us to the starboard direction.

I thought you said it was backwards. So what are we doing here then?

I actually don't what you are doing here to be honest ???

All the other comments have not been super sensitive to the English language and the way I wrote something and have actually been rather helpful.

P.s. you may have been "driving" boats on a river for a while but if you cant see the connection between a tiller going left and a rudder going right then you need to get you boat out the water and have a look how it made ! If you push a tiller left the rudder will go right and the boat will steer right. If you push your tiller right then the rudder will go to the left and steer the boat left. Stop being so pedantic !

Thanks to the other contributors :slight_smile:

1 Like

Why so aggressive? I just wanted to understand what you have and try to help. Jeez man, chill out a little. Don't take it so seriously. You'll end up with a heart attack before you're old.

You didn't want to offer help, you didn't even want to understand. You just trolled and that, fine. I can handle it. I also wanted to point out your mistakes which I hope now I have made clear for you :slight_smile:

Where? Where did I "troll" you?

Did I have a picture of how you had your servo attached to your rudder? How do I know? I just know you said left was right and right was left and I was trying to offer suggestions as to what might have happened.