pid regulated rudder on a model boat

I am building a sketch to allow a boat to maintain a course using a compass.

My problem is the wild jittering of the servo. It is a hobby servo but I have used capacitors to assist it, and I may note that using radio control it works perfectly. I have replaced it with a better hobby one and the result is the same.

I have added a smoothing sketch to aid the variation in the compass, and have calibrated the compass. Then spent a lot of time playing with the pid values.

The end result is a pronounced wiggle and it is unusable as is. I have limited the travel of the servo but that is just giving up.

Interestingly when I do a serial print it calms down a bit.

using an uno, external pwer for the servo…

Any advice??

_3channels7strip.ino (3.92 KB)

Please post your code using code tags </>.

Make sure that the compass is not influenced by your circuitry. Check for stable readings when you operate the servo by RC.

You might be experiencing pilot induced oscillation. That could be caused by over correcting the heading. It is a very common problem with helicopter pilot trainees learning to hover.

Put the boat on a stand (stationary). Turn on the steering. Watch the rudder. Does it swing back and forth or stay stable? Then turn the boat a little. Does the rudder move and stop or oscillate?


#include <Servo.h>
#include <Wire.h>
#include <LSM303.h>
#include <PID_v1.h>


// PID
double Setpoint, Input, Output; // Define Variables we’ll be connecting to
double varKp = 4, varKi = 1, varKd = .5; // Define the Default Tuning Parameters
PID myPID(&Input, &Output, &Setpoint, varKp, varKi, varKd, DIRECT);

long PreviousSerialWriteMillis = 0; // will store last time LED was updated. don’t get this but can’t delete
long SerialWritInterval = 500; // interval at which to blink (milliseconds) as above


#define RUDDER_OUT_PIN 13 // Assigning channel out pin for connection to servo

/////Compass instructions
int storedHeading = 10; // Desired course starting point (works for me on the bench!)
int deltaHeading; // +/- Difference (Course +/- Desired Course)
int BoatHeading = 180; //no matter what the heading this will be 180
int BoatError; // + or - against 180
int BoatErrorSmooth; // boat error ‘smoothed.’
LSM303 compass; // Output from LSM303

// set up for smoothing comnpass readings
const int numReadings = 10; // no of readings that will be averaged.
int readings[numReadings]; // the readings from the analog input
int index = 0; // the index of the current reading
int total = 0; // the running total
int averagedepth = 0; // the average

Servo servoRudder2; // output to rudder from pid

void setup()


// compass
Wire.begin(); // Initiates I2C comunication
compass.init(); // Initiates LSM303
compass.enableDefault(); // Enable dafault parameters on LSM303
compass.m_min = (LSM303::vector<int16_t>) { -552, -649, -769 }; // Last parameter calibration readings made JUNE 2015
compass.m_max = (LSM303::vector<int16_t>) { +636, +632, +325 }; // Last parameter calibration readings made JUNE 2015

//pid set up
Setpoint = BoatHeading; // Example
myPID.SetMode(AUTOMATIC); // Turn the PID on
myPID.SetOutputLimits(40, 140); // min: Low end of the range. must be < max (double). max: High end of the range. must be > min (double)


void loop()
int heading = compass.heading();
int myHeading = storedHeading + 360; // the following fixes the 10-350 error
deltaHeading = heading - myHeading;
if (deltaHeading < 180) deltaHeading += 360;
if (deltaHeading < 180) deltaHeading += 360;
if (deltaHeading > 180) deltaHeading -= 360;

int BoatError = (180 + deltaHeading); // error is set against the 180. So if we want 85, and the actual 75, error is -10, which then is 170 against 180.
//Serial.print("Boat Error: ");
//delay (500); // good for printing only. disable when running.

// averaging routine for the pressure sensor
total = total - readings[index]; // subtract the last reading:
readings[index] = BoatError; // read from the sensor:
total = total + readings[index]; // add the reading to the total:
index = index + 1; // advance to the next position in the array:
if (index >= numReadings) // if we’re at the end of the array…
index = 0; // …wrap around to the beginning:
BoatErrorSmooth = total / numReadings; // calculate the average: boaterror, becomes boaterrorsmooth

// PID
Input = BoatErrorSmooth;

unsigned long currentMillis = millis();
if (currentMillis - PreviousSerialWriteMillis > SerialWritInterval) {
PreviousSerialWriteMillis = currentMillis;





Read reply #2. What is the value of the variable Output? Maybe you are adjusting the rudder too much?

edit: servoRudder2.attach(RUDDER_OUT_PIN) should be called only once in setup.

Johnredearth: Interestingly when I do a serial print it calms down a bit.

That would imply you are doing something too quickly or too often.

What model did you use to determine initial pid values.

Please edit your post to use code tags ([ code] and [ /code] without the blanks).

My problem is the wild jittering of the servo. It is a hobby servo but I have used capacitors to assist it,

If the model wiggles constantly, Kp could be too high. There are lots of tutorials on "PID tuning" on the web. Or you have a power supply problem. The Arduino cannot provide enough power for a servo, instead power the servo directly from a battery or other supply. Make sure all the grounds are connected together.

The default setting for the latest version of the PID library is to evaluate the control loop every 200 milliseconds. Since Serial.print seems to have an effect, you might try setting the evaluation time to a larger value.

I think 200 ms is too fast

This is my method.

Real world real boat.

set course

centre rudder

observe compass over time.

perturb rudder

observe compass again

repeat until drift is zero

record setting

set course and return to previous setting.

repeat whole cycle.

with a novice they always overdo the rudder on first attempt and wave around a bit as tim has already pointed out.

With practice you feel the boat and learn to pre empt coming onto bearing adjusting the rudder to suit.

Once you get the course holding right you can make allowance for this by adjusting the programme when making large angle turns.

In the real world due to varying tides and winds there is no fixed starting point (pid setting ?) ,although you know how fast the boat turns at various speeds.

This is less of a problem for models though.

My suggestion is to build a course holding routine first using small rudder movements first and put the larger setting course / change course inputs in an outer loop.

i.e. pid within a pid.

Real life auto pilots use adaptive , learning pid routines and do not rely on fixed values, some come with 7 axis gyros to make things even more interesting.

200 too fast? Where do you see that??

Johnredearth: 200 too fast? Where do you see that??

The default sample time of the PID library is 200ms. See SetSampleTime().

How are you powering the servo?

Johnredearth: 200 too fast? Where do you see that??

Boats have inertia. 200 Is fine for measurement but too fast to make a decision/correction imho.

OP has already said he has implemented a smoothing /averaging routine for his compass measurements.

My previous post works for me with some wobble.

My petard is prepared , feel free to hoist it.

I have just remembered. One of the reasons for using drift rather than averaging is that it copes much better with a noisy compass.


To be clear, this maintains a course with a varying heading, so the direction is constantly wobbling.