Project tutorial
Autopilot for Sailing Boats (Automatic Steering System)

Autopilot for Sailing Boats (Automatic Steering System) © GPL3+

Let's take a break during navigation while Autopilot follows the route, control it with remote control!

  • 4,234 views
  • 8 comments
  • 33 respects

Components and supplies

About this project

Preface:

I love sailing alone because when a man is on the sea with his sailing boat he got everything he needs to evolve to a greater level. Sailing in raw sea with bad weather can be very hard but if he choose good weather days with sun and nice wind the enjoy will be at maximum.

Happiness means infinite horizons, perfect athletic technics, optimal choices, but also human things as a good coke and a tasty sandwich! Exactly in this time come in help Autopilot: it works instead of you meanwhile you have your 5:00pm tea & biscuits on the sea. :-)

What Autopilot can do for you:

A sailing boat does not have an engine and cannot go along a programmed path from harbour to the beach, then to the fishing spot, turn around the Lighthouse and back, all itself, it cannot.

Whole work is done by the Sailor, we have to understand it at this point: trimming sails, take under control weather and the wind source/speed, harden or release ropes, mind other boats, decide direction and steering... When the Sailor decide for a break, let say just 10 seconds or up a few minutes (the famous "tea time"), he switches Autopilot on. In a cup of seconds its GPS acquire position, speed and direction of the boat and is able to mantain the direction (route). The steering system, a stick connected to the rudder, usually moved by the expert Sailor hands, now is under control by Autopilot through the Stepper Motor connected to it by pulleys and ropes.

Control the rudder is a continuous work of fine or gross tuning. Smaller (lighter) is the boat and greater will be the changes of direction factors influences it: sea waves, direction and pressure of the wind, shifting of the weight on board by Sailor movements, sea currents. But the Sailor is always awake, even with autopilot on, making changes to the actual route by the way of remote control: there are 4 buttons on it, labelled +1 -1 +10 -10, for small or big changes in degrees, increasing or decreasing the value. These buttons are present on the Autopilot too, the green (right) and the red (left) ones. The blue button (middle) is for activate or deactivate the Autopilot, the pause. It is also a black button for setting up parameters in memory.

The circuit:

The main processing is done by MPU Arduino Uno. The other MPU, Arduino Nano, is the watchdog: I know it does exist a kind of watchdog inside the Uno but I liked to do it with and independent external processor, it is a life long dream I covered, I am happy now! Uno has to feed Nano by the way of pin 3 -> A0 putting it high/low, 5/0 volts, at least one time every 2.5 seconds (feedingInterval); if not, it means the Uno is "sleeping" or "blocked", and Nano resets Uno... It never happened yet, can you believe?

It is used a popular display in conjunction with a i2c circuit converter both soldered together, finally using just 4 wires to significantly spare digital pins to communicate with Uno. Also the way to connect buttons and remote control to is done by resistance voltage dividers to reach the goal to use as less MPU's ports as possibile; I chosen 1% precision resistors, the analog comparing values should be between values I put into the code; in case of no recognition of some buttons because you choose other kind of resistors, just make some changes to constants too (modify code at "checkRfRC()" and "checkHWButtons()"). The RF 433Mhz Remote Control (RC) circuit works well; to improve distance coverage and chances to success, I added a coil antenna you could made by yourself with a piece of copper wire; I tested it 10 meters away but I think it can work even at 20 meters or more, more than enough considering the target sailing boat I used to testing Autopilot has been just a 4.20 meters long.

For the GPS unit I used at the beginning a good EM406A but unfortunately I discovered it suffered of Week-Rollover-Bug, it was too old, then I had to change it with an excellent and popular Beitian BN-220T. With its configuration software please set it to "spit" out 2 times per second (2Hz) just necessary "$GNRMC" NMEA serial sentence. The GPS sends (TX) serial data to pin 0 (RX) of Uno. The data contains all navigation data used to calculate correction to do by the Motor: date, time, position latitude and longitude, true course, speed and validity of satellites fix. Due to the fact Arduino's IDE programming uses pin 0 (RX) port too, remember to disconnect temporary the GPS during this operation...

Another dream of mine was to use an EEPROM. The IC 2404 is a beautiful 512 bytes i2c integrated circuit I used to read/write in this memory chip some parameters for Stepper Motor movements I will explain later under "Software" paragraph.

Components List:

  • Arduino Uno as MPU
  • Arduino Nano as WatchDog
  • Beitian BN-220T GPS
  • Stepper Motor, model 23LM, 54 steps = 1/4 of revolution
  • Controller Keyes L298 for motor
  • RF433Mhz RC XD-YK04 + 4 buttons remote control + coil antenna
  • 6 Buttons normal open (2xRed, 2xGreen, 1xBlack and 1xBlue)
  • Switch for power on/off (White)
  • Female + Male 6 pin round connectors for external Stepper Motor
  • Buzzer
  • Display LCD1602 2x16 characters + i2c converter circuit
  • 3 LEDS (red, blue and yellow))
  • IC 24c04 i2c eeprom
  • IC 4051 multiplexer
  • Battery LiPo 2s 7.4v 2600mA
  • IC 7805 voltage regulator + heat dissipator
  • Thermistor NTC MF52-103 10k
  • Resettable Fuse 2A
  • 6x 1N4148 diods (D1-D6)
  • Resistors on Power shield (R1-R4=10k, R5=100k)
  • Resistors on Autopilot shield (R1=330, R2=1k, R3=2k, R4=5.1k, R5=1k, R6/R7/R14=330, R8-R13=10k, R15=10M)
  • Capacitors (C1=470uF 16v, C2=100n)
  • 2W 0.22 Ohm resistor (R6)
  • Male pins
  • Female long-pins headers
  • Case transparent and "waterproof"

There are a few sensors on the circuit all connected to Arduino Uno by the help of IC 4051 multiplexer. It is a Thermistor to take under control voltage regulator heating dissipator temperature, a 2W resistor and 4x10k as voltage dividers to calculate Ampere as power consumption of whole circuit. Also the battery voltage is taken under control: LiPo are known to be critical when single elements are discharged below 3.3v; this circuit has a two elements (2S) LiPo in one package, in case of low voltage (below 7.0v) the buzzer will inform you with short quick beeps. Do not wait too long to switch off, and recharge soon! The Leds: yellow one blinking at 1Hz let you know the WatchDog is working; the blue one is on when Autopilot is on, off if paused; the red led blinks when one of the remote control buttons is pressed.

All circuit works at 5.0v supplied by LiPo 2S 7.4v 2600mA/h battery and IC 7805 voltage regulator. Current should not be greater than 800mA, but usually it is around 100-450mA. Please put on it an heating dissipator. The Thermistor is placed on it and the buzzer will beep if temperature exceed 50°C.

The PCB Printed Circuit Boards and assembly:

Is used single face PCBs for that reason I had to include a few wire jumpers (the dotted line ones) to solve routes for whole circuits. Is shown here the components faces but below you have all files, components and solder faces, mirrored, for downloading and printing by the way of a laser printer on "yellow" or "blue" sheets. I used the yellow ones but they say the blue are better (but the price is significantly higher). When printing remember to disable toner saving settings, use instead 1200 dpi resolution to have deep real black result. The toner transfer process from magic sheets to PCBs is made by the use of an hot iron... Printing on both faces, also on the components face, makes it easy to recognise positioning of items and even makes the project "professional".

Both PCBs are sized to fit one over the other Arduino Uno as a stack: first Power unit then Autopilot unit over all.

My choice has been to put all things together, PCBs, MPUs, RC, Motor Driver circuit, battery, GPS, buttons, switch, wires, connectors, etc. thinking to reuse them one day: I did not solder them together, I used headers and popular Dupont wires/connections instead. There are around 200 unsoldered connections then, it means unexpected and unwanted malfunctions or different behaviour of the circuit can happen from time to time, it is normal. Suggestion is to solder everything for a more stable circuit!

Parameters Setup and display sensors values:

Pressing the black button on the side of the box it enters in Setup mode; this can be done also during active navigation, it is unnecessary to enter Pause first. The 1st page of the display shows battery voltage (V=7.83), power consumption (mA=177) and temperature of the thermistor sensor near dissipator (38°C); pressing again and again the black button you enter next pages; the 2nd, 3th, 4th and 5th page show the parameters listed below and you may change these values by the way of -1 and +1 buttons. The 6th page shows "Updating..." if you changed something, values are saved in the EEPROM memory.

  • Interval: i.e. 2000 mSec, is the time between one try and another by Stepper Motor to restore "H" heading to the "R" route, moving the rudder stick right or left;
  • Minimum: i.e. 2°, is the minimum amount of degrees out-of-route to have Autopilot intervention; up to this value the rudder stays at central position steady;
  • Max: i.e. 40°, is the maximum steering change at a time by Stepper Motor; if calculation made is for 50° change, in reality Stepper will move only 40°;
  • Coeffic.: i.e. 1.50 x °, is the coefficient for steering change at a time; if calculation made is for 40° change, in reality Stepper Motor will move for (40 x 1.50)=60°;

These parameters are necessary to fine tuning Autopilot when installed on the sailing boat. Responseness, sensitivity and smoothness depend by the diameter of the pulleys, how many pulleys, the diameter of main pulley on the Stepper Motor, the sensitivity of the rudder, how length is the rudder stick connected on it, and so on. Let install everything and let try and make experience on board. Of course choose a sunny beautiful day with light wind for all testing phase!

How it works "live":

You are navigating on the sea, on the lake or just around the harbour. It is the Tea Time and your coke and your favourite sandwich are waiting in the pocket. Here we are: switch Autopilot on and let it take satellite GPS fix, you should now read on the display actual speed in knots, clock and heading direction i.e. H270° (R=route to follow, H=actual heading) in degrees (remember 180°=south, 270°=west, 360° or 0°=north and 90°=east). R and H values are the same when in Pause mode (STOP is shown). Connect now the steering rope, from Stepper Motor to the rudder stick, and press blue button to start Autopilot steering; at this point Autopilot holds R=route direction and it takes under control what happen with H=heading. Heading number changes for sure, slowly or fast depending of weather conditions we already talked about. Autopilot then try to restore to R=route direction making corrections i.e. -10°, +5°, etc. until H value equals R value. You may decide some changes on Route and you can modify the number using red and green buttons on the unit (-1 -10 +1 +10) or by the way of remote control. To take back control of the steering you just have to press Pause blue button, disconnect the rope from the rudder stick continuing the job by your hands. Well done.

Software side:

The code is quite long but I hope it is clear enough to be easy understandable. In any case I would explain how it is doing. The sketch uses around 65% of program and around 45% of memory. Even using String class, mostly for Serial NMEA sentences manipulation, the whole elaboration flow is stable and solid; it uses "serialEvent()" to receive data from GPS two times per second, than calls "nmeaExtractData()" and finally it checks data packet with "nmea0183_checksum() to be sure of data integrity. If you use another make and model GPS be sure the sentences have the same structure or you have to make some changes here. For example EM406A uses "$GPRMC" packet id, BT220 uses "$GNRMC" instead... just a small name change... A useful link can help you with checksum test: https://nmeachecksum.eqth.net - Here an example of a complete NMEA sentence, it contains: id, time, validity, latitude, longitude, speed, true course, date, variation and checksum.

$GPRMC, 095836.000, A, 4551.9676, N, 01328.7118, E, 2.09, 341.84, 280519,, *08

During "Setup()" the EEPROM is checked: if new or unknown it is initialised (formatted). Parameters in memory are read/write as bytes: 0=0x29, 1=0x00, 2-3=interval, 4-5=min, 6-7=max, 8-11=coefficient (byte, byte, int, int, float). I handled with care EEPROM r/w operations, maybe too much defensive... Sensors are checked every 10 seconds through "readMuxSensors()" by multiplexer and can cause alarm if battery is low or temperature is high. The resolution of power consumption is low, steps of around 40mA. Hardware and RC buttons are checked continuously; what they do depends of the "IsSetup" boolean value as well as the display "RefreshDisplay()" does. The core of the code is the STEERING CONTROL section that calls "gomotor()" function to move the Stepper out and back; yes, it may moves the rudder 10° to the right and after interval value it moves back to zero rudder position, and so on after a new calculation round. As already said, steering work is made also during Setup because it affects just a few buttons and display behaviour. Whatchdog feeding is very simple but important: just puts on/off its Pin as soon as it can.

How to install on a Sailing Boat:

As it is shown in the picture below I chosen to place Autopilot and the Stepper Motor on the stern, both well fixed with bolts, etc.; a rope of 6mm diameter starts from main Motor pulley and go around two other pulleys placed on both sides. These two pulleys should be "hold" by two rings of bungee to keep slightly in tension the rope. At this point, finally, you have to decide how to connect the rope to the rudder stick; it has to be connected while you want Autopilot is in action, easy to connect and easy to disconnect. Keep Autopilot system away from water! :-)

News & Updates:

  • 10.05.2020, added for downloading .STEP 3D CAD project files for stepper pulley (by me) and mounting plate (by Andrew Barney), and a 3D preview picture of them.

DISCLAIMER & WARNINGS:

Lets say this is a game we are playing here, nothing to take serious! A few years ago I went for a long trip, 16 months, around the World on a sailing boat. We extensively navigated with a real autopilot in all weather conditions, even bad weather conditions. A real autopilot is something very strong both hardware and software you have to trust to a lot. This Arduino Autopilot is a fantastic game to play with and to spend time for fun.

Have fun!

Marco Zonca

Code

Autopilot sketch (for Uno)Arduino
/*
  This sketch act as Autopilot for small sailing boats, by Marco Zonca, 2019
  Arduino UNO as CPU, Arduino Nano as watchdog, GPS BT-220 nmea, stepper motor + controller, rf433Mhz RC, 6 buttons, buzzer, i2c display,
  2 leds, i2c 24c04 eeprom, Mux 4051 for sensors, lipo 2s 7.4v 2600mA, 7805 voltage regulator, Thermistor;
*/

#include <LiquidCrystal_I2C.h>
#include <NewTone.h>
#include <Stepper.h>
#include <Wire.h>

String inputString = "";
String nm_time = "00:00:00";
String nm_validity = "V";
String nm_latitude = "ddmm.mmmm'N";
String nm_longitude = "dddmm.mmmm'E";
String nm_knots = "0.0kn";
float nmf_knots = 0.0;
String nm_truecourse = "360";
float nmf_truecourse = 360;
String nm_date = "dd/mm/yyyy";
String nm_routetofollow ="000";
float nmf_routetofollow = 0;
unsigned long previousStearingMillis = 0;
unsigned long currentStearingMillis = 0;
unsigned long prevCheckSensorsMillis = 0;
unsigned long currCheckSensorsMillis = 0;
int CheckSensorsInterval = 10000;

bool stringComplete = false;
bool isfirstfix = true;
bool ispause = true;
bool isStearing = false;
bool isSetup = false;

int s=0;
int y=0;
int z=0;
int d=0;
int rfRemoteControlValue = 0;
int HWButtonValue = 0;
int SetupParameter = 0;
float calcmove = 0;
float cm = 0;
float Stearing = 0;
float prevStearing = 0;
float t = 0;
int EEdisk = 0x50;
int EEid1 = 0x29;
int EEid2 = 0x00;
unsigned int EEaddress = 0;
unsigned int EEbytes = 12;
byte EEdata[12];
byte EEbytedata;
int EEerr = 0;
float SensorVBatt=0;
float SensorVRes=0;
float SensorTemp=0;
float SensormAmp=0;

// following parameters are the defaults but are read/write in eeprom
// eeprom is initialized if at addresses 0 and 1 the content is different       addres  len   type    notes
// 0-255 bytes at 0x50 EEdisk, 256-512 bytes at 0x51 (not used)                ---------------------------------------------------------------
//                                                                              0       1B    byte    01001001 (0x29 as autopilot project id1)
//                                                                              1       1B    byte    00000000 (0x00       "          "   id2)
int StearingInterval = 2000;  // millis between try and back                    2       2B    int     StearingInterval  1000-5000 steps 100
int StearingMinToMove = 2;  // compass_degrees                                  4       2B    int     StearingMinToMove    0-20   steps   1
int StearingMaxMove = 40;  // compass_degrees                                   6       2B    int     StearingMaxMove     10-45   steps   1
float StearingCoeffMove = 1.5;  // used as (compass_degrees * coeff)            8       4B    float   StearingCoeffMove  0.1-4    steps 0.1
//                                                                              12      free
//
byte bStearingInterval[sizeof(int)];
byte bStearingMinToMove[sizeof(int)];
byte bStearingMaxMove[sizeof(int)];
byte bStearingCoeffMove[sizeof(float)];
int prev_StearingInterval=0;
int prev_StearingMinToMove=0;
int prev_StearingMaxMove=0;
float prev_StearingCoeffMove=0;

const int ledpausePin = 2;
const int watchDogPin = 3;
const int MuxSelBit0Pin = 7;  // 00=Vin 01=Vbatt 10=Temp
const int MuxSelBit1Pin = 6;  // 
const int motorsABenablePin = 13;
const int MuxIOPin = 14;
const int ButtonsPin = 15;
const int rfRemoteControlPin = 16;
const int speakerPin = 17;
const int RCleftbutton = 201;
const int RCrightbutton = 202;
const int RCleft10button = 203;
const int RCright10button = 204;
const int HWleftbutton = 101;
const int HWrightbutton = 102;
const int HWpausebutton = 103;
const int HWsetupbutton = 104;
const int HWleft10button = 105;
const int HWright10button = 106;
const int motorStepsPerRevolution = 200;  // 200 for model 23LM, 54 steps = 1/4 of revolution

LiquidCrystal_I2C lcd (0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
Stepper motor(motorStepsPerRevolution, 9, 10, 11, 12);

void setup() {
  Serial.begin(4800);
  lcd.begin(16,2);
  Wire.begin();
  motor.setSpeed(60);
  inputString.reserve(200);
  pinMode(motorsABenablePin, OUTPUT);
  pinMode(MuxSelBit0Pin, OUTPUT);
  pinMode(MuxSelBit1Pin, OUTPUT);
  digitalWrite(motorsABenablePin, LOW);
  digitalWrite(MuxSelBit0Pin, LOW);
  digitalWrite(MuxSelBit1Pin, LOW);
  pinMode(ledpausePin, OUTPUT);
  pinMode(watchDogPin, OUTPUT);
  digitalWrite(ledpausePin, LOW);
  digitalWrite(watchDogPin, LOW);
  
  // read+check EEPROM (formatting if new (or not identified))
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Memory check...");
  lcd.setCursor(0,1);
  for (s = 0; s < EEbytes; s ++) {
    EEaddress = s;
    EEbytedata = readEEPROM (EEdisk, EEaddress);
    EEdata[s] = EEbytedata;
  }
  if (EEerr) {
    lcd.print("E=");
    lcd.print(EEerr);
    delay(1000);
  }
  if ((EEdata[0] != EEid1) || (EEdata[1] != EEid2)) {
    lcd.print(" init! ");
    goupdateEEPROM();
    if (EEerr) {
      lcd.print("E=");
      lcd.print(EEerr);
      delay(1000);
    }
  }
  memcpy(bStearingInterval, EEdata+2, sizeof(int));
  memcpy(bStearingMinToMove, EEdata+4, sizeof(int));
  memcpy(bStearingMaxMove, EEdata+6, sizeof(int));
  memcpy(bStearingCoeffMove, EEdata+8, sizeof(float));
  StearingInterval = *((int*) bStearingInterval);
  StearingMinToMove = *((int*) bStearingMinToMove);
  StearingMaxMove = *((int*) bStearingMaxMove);
  StearingCoeffMove = *((float*) bStearingCoeffMove);
  prev_StearingInterval = StearingInterval;
  prev_StearingMinToMove = StearingMinToMove;
  prev_StearingMaxMove = StearingMaxMove;
  prev_StearingCoeffMove = StearingCoeffMove;
  lcd.print(" OK");
  delay(1000);
  lcd.clear();
  lcd.print("GPS reading...");
  delay(1000);
}

void loop() {
  // CHECK SENSORS -----------------
  currCheckSensorsMillis = millis();
  if (currCheckSensorsMillis - prevCheckSensorsMillis >= CheckSensorsInterval) {
    readMuxSensors();
    if ((SensorVBatt <= 7.0) || (SensorTemp >= 50)) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Alarm sensors! ");
      lcd.setCursor(1,1);
      lcd.print("V=");
      lcd.print(SensorVBatt);
      lcd.print("  ");
      lcd.print(int(SensorTemp));
      lcd.write(0xDF);
      lcd.print("C");
      NewTone (speakerPin,10);
      delay(1000);
      noNewTone();
    }
    prevCheckSensorsMillis = currCheckSensorsMillis;
  }
  // STEARING CONTROL ----------------
  currentStearingMillis = millis();
  if (currentStearingMillis - previousStearingMillis >= StearingInterval) {
    if (isStearing == false && ispause == false) {  // go try (move stearing)
      calcmove = nmf_routetofollow - nmf_truecourse;
      if (calcmove < (-180)) {
        calcmove = calcmove + 360;
      } else {
        if (calcmove > (+180)) {
          calcmove = calcmove - 360;
        }
      }
      if (abs(calcmove) >= StearingMinToMove) {
        if (abs(calcmove) >= StearingMaxMove) {
          if (calcmove < 0) {
              cm = (StearingMaxMove * -1);
              calcmove = cm;
            } else {
              cm = (StearingMaxMove * 1);
              calcmove = cm;
          }
        }
        Stearing = (calcmove * StearingCoeffMove);
        gomotor(int((Stearing * 216) / 360));  // 54 steps = 1/4 of revolution
        prevStearing = Stearing;
        isStearing = true;
      }
    } else {  // go back (move stearing to "zero" position)
      if (isStearing == true) {
        Stearing = (prevStearing * -1);
        gomotor(int((Stearing * 216) / 360));  // 54 steps = 1/4 of revolution
        Stearing = 0;
        prevStearing = 0;
        isStearing = false;
      }
    }
    previousStearingMillis = currentStearingMillis;
  }
  // RC RF BUTTONS ------------------
  rfRemoteControlValue = checkRfRC();
  if (rfRemoteControlValue) {
    switch (rfRemoteControlValue) {
      case RCleftbutton: // Left RC button
        goleft();
        break;
      case RCrightbutton: // Right RC button
        goright();
        break;
      case RCleft10button: // Left-10 RC button
        goleft10();
        break;
      case RCright10button: // Right+10 RC button
        goright10();
        break;
    }  
  }  
  // BUTTONS ------------------------
  HWButtonValue = checkHWButtons();
  if (HWButtonValue) {
    switch (HWButtonValue) {
      case HWleftbutton: // Left(-1) HW button
        if (isSetup == false) {
            goleft();
          } else {
            setupMinus();
        }
        break;
      case HWrightbutton: // Right(+1) HW button
        if (isSetup == false) {
            goright();
          } else {
            setupPlus();
        }
        break;
      case HWpausebutton: // Pause HW button
        gopause();
        break;
      case HWsetupbutton: // Setup HW button
        gosetup();
        break;
      case HWleft10button: // Left(-10) HW button
        goleft10();
        break;
      case HWright10button: // Right(+10) HW button
        goright10();
        break;
    }  
  }
  // GPS NMEA ------------------
  if (stringComplete == true) {  // received nmea sentence by serial port RX
    bool ret;
    ret = nmeaExtractData();
    inputString = "";
    stringComplete = false;
    if (ret == true) {
      RefreshDisplay();
    }
  }
  // WATCHDOG FEEDING ----------------
  if (digitalRead(watchDogPin) == LOW) {
    digitalWrite(watchDogPin, HIGH);
  } else {
    digitalWrite(watchDogPin, LOW);
  }
}

// read sensors on multiplexer
void readMuxSensors() {
  float Vo = 0;
  float n = 0;
  float n1 = 0;
  float v1ad = 0;
  float v2ad = 0;
  float corr = 0;
  float R1 = 10000;
  float logR2 = 0;
  float R2 = 0;
  float T = 0;
  float c1 = 1.009249522e-03;
  float c2 = 2.378405444e-04;
  float c3 = 2.019202697e-07;
  digitalWrite(MuxSelBit0Pin, LOW);  // 00=Vbatt
  digitalWrite(MuxSelBit1Pin, LOW);
  n = analogRead(MuxIOPin);
  v1ad=n;
  n1=(((10.00 * n) / 1023.00));
  SensorVBatt=(n1 + ((n1 * 0.0) /100));  // arbitrary correction (not active = 0.0%)
  digitalWrite(MuxSelBit0Pin, LOW);  // 01=Vres
  digitalWrite(MuxSelBit1Pin, HIGH);
  n = analogRead(MuxIOPin);
  v2ad=n;
  n1=(((10.00 * n) / 1023.00));
  SensorVRes=(n1 + ((n1 * 0.0) /100));  // arbitrary correction (not active = 0.0%)
  digitalWrite(MuxSelBit0Pin, HIGH);  // 10=NTC Temp
  digitalWrite(MuxSelBit1Pin, LOW);
  Vo = analogRead(MuxIOPin);
  R2 = R1 * (1023.0 / Vo - 1.0);
  logR2 = log(R2);
  T = (1.0 / (c1 + c2 * logR2 + c3 * logR2 * logR2 * logR2));
  SensorTemp = T - 273.15;  // Celsius
  n = (v1ad - v2ad);
  n1 =  (n / 0.22) * 1000.00;
  SensormAmp = (((10.00 * n1) / 1023.00));
}

// extract data from nmea inputString
bool nmeaExtractData() {
  bool ret = false;  //true if nmea sentence = $GNRMC and valid CHKSUM
  if ((inputString.substring(0,6) == "$GNRMC") && (inputString.substring(inputString.length()-4,inputString.length()-2) == nmea0183_checksum(inputString))) {
    y=0;
    for (s = 1; s < 11; s ++) { 
      y=inputString.indexOf(",",y);
      switch (s) {
      case 1: //time
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nm_time=inputString.substring(y+1,y+2+1)+":"+inputString.substring(y+1+2,y+4+1)+":"+inputString.substring(y+1+4,y+6+1);
        }
        y=z;
        break;
      case 2: //validity
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nm_validity=inputString.substring(y+1,y+1+1);
        }
        y=z;
        break;
      case 3: //latitude
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nm_latitude=inputString.substring(y+1,y+2+1)+""+inputString.substring(y+1+2,y+10+1)+"'";
        }
        y=z;
        break;
      case 4: //north/south
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nm_latitude=nm_latitude + inputString.substring(y+1,y+1+1);
        }
        y=z;
        break;
      case 5: //longitude
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nm_longitude=inputString.substring(y+1,y+3+1)+""+inputString.substring(y+1+3,y+11+1)+"'";
        }
        y=z;
        break;
      case 6: //east/west
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nm_longitude=nm_longitude + inputString.substring(y+1,y+1+1);
        }
        y=z;
        break;
      case 7:  //speed knots
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nmf_knots=inputString.substring(y+1,z).toFloat();
          t=roundOneDec(nmf_knots);
          nm_knots=String(t,1)+"kn";
        }
        y=z;
        break;
      case 8: //true course
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nmf_truecourse=inputString.substring(y+1,z).toFloat();
          d=nmf_truecourse;
          nm_truecourse=d;
        }
        y=z;
        break;
      case 9: //date
        z=inputString.indexOf(",",y+1);
        if (z>(y+1)) {
          nm_date=inputString.substring(y+1,y+2+1)+"/"+inputString.substring(y+1+2,y+4+1)+"/20"+inputString.substring(y+1+4,y+6+1);
        }
        y=z;
        break;
      case 10:
        // statements
        break;
      default:
        // statements
        break;
      }
    }
    if ((isfirstfix == true) || (ispause == true)) {
      nm_routetofollow=nm_truecourse;
      nmf_routetofollow=nmf_truecourse;
      isfirstfix=false;
    }
    ret=true;
  }
  return ret;
}

// increase(+) parameter value during setup
void setupPlus() {
  switch (SetupParameter) {
    case 2: //interval
      StearingInterval = (StearingInterval + 100);
      if (StearingInterval > 5000) {
        StearingInterval = 5000;
      }
      break;
    case 3: //min. to move
      StearingMinToMove = (StearingMinToMove + 1);
      if (StearingMinToMove > 20) {
        StearingMinToMove = 20;
      }
      break;
    case 4: //max. move
      StearingMaxMove = (StearingMaxMove + 1);
      if (StearingMaxMove > 45) {
        StearingMaxMove = 45;
      }
      break;
    case 5: //coefficient
      StearingCoeffMove = (StearingCoeffMove + 0.1);
      if (StearingCoeffMove > 4) {
        StearingCoeffMove = 4;
      }
      break;
  }
  delay(200);
  RefreshDisplay();
}

// decrease(-) parameter value during setup
void setupMinus() {
  switch (SetupParameter) {
    case 2: //interval
      StearingInterval = (StearingInterval - 100);
      if (StearingInterval < 1000) {
        StearingInterval = 1000;
      }
      break;
    case 3: //min. to move
      StearingMinToMove = (StearingMinToMove - 1);
      if (StearingMinToMove < 0) {
        StearingMinToMove = 0;
      }
      break;
    case 4: //max. move
      StearingMaxMove = (StearingMaxMove - 1);
      if (StearingMaxMove < 10) {
        StearingMaxMove = 10;
      }
      break;
    case 5: //coefficient
      StearingCoeffMove = (StearingCoeffMove - 0.1);
      if (StearingCoeffMove < 0.1) {
        StearingCoeffMove = 0.1;
      }
      break;
  }
  delay(200);
  RefreshDisplay();
}

// motor control (+)=forward (-)=backwards
void gomotor(int stepsToMove) {
  digitalWrite(motorsABenablePin, HIGH);
  motor.step(stepsToMove);
  digitalWrite(motorsABenablePin, LOW);
}

// refresh data on display
void RefreshDisplay() {
  if (isSetup == false) {  //---------normal
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("R"+nm_routetofollow);
    lcd.write(0xDF);
    lcd.print(" H"+nm_truecourse);
    lcd.write(0xDF);
    if (ispause == true) {
      lcd.print(" STOP");    
    } else {
      if (Stearing > 0) {
        lcd.print(" +");
      }
      if (Stearing == 0) {
        lcd.print("  ");
      }
      if (Stearing < 0) {
        lcd.print(" ");
      }
      lcd.print(int(Stearing));
    }
    lcd.setCursor(0,1);
    lcd.print(nm_time+" "+nm_knots);
  }
  if (isSetup == true) {  //-----------setup
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("setup: ");
    switch (SetupParameter) {
      case 1: //display sensors
        readMuxSensors(); 
        lcd.print("V=");
        lcd.print(SensorVBatt);
        lcd.setCursor(1,1);
        lcd.print("mA=");
        lcd.print(int(SensormAmp));
        lcd.print("  ");
        lcd.print(int(SensorTemp));
        lcd.write(0xDF);
        lcd.print("C");
        break;
      case 2: //interval
        lcd.print("interval");
        lcd.setCursor(7,1);
        lcd.print(StearingInterval);
        lcd.print(" mSec");
        break;
      case 3: //min. to move
        lcd.print("minimum");
        lcd.setCursor(7,1);
        lcd.print(StearingMinToMove);
        lcd.write(0xDF);
        break;
      case 4: //max. move
        lcd.print("max");
        lcd.setCursor(7,1);
        lcd.print(StearingMaxMove);
        lcd.write(0xDF);
       break;
      case 5: //coefficient
        lcd.print("coeffic.");
        lcd.setCursor(7,1);
        lcd.print(StearingCoeffMove);
        lcd.print(" x ");
        lcd.write(0xDF);
        break;
    }
  }
}
/*
  SerialEvent occurs whenever a new data comes in the hardware serial RX. This
  routine is run between each time loop() runs, so using delay inside loop can
  delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    inputString += inChar;
    // if the incoming character is a newline, set a flag so the main loop can
    // do something about it
    if (inChar == '\n') {
      stringComplete = true;
      }
    }
  }

//calculate checksum of nmea sentence
String nmea0183_checksum(String nmea_data) {
    int crc = 0;
    String chSumString = "";
    int i;
    // ignore the first $ sign, checksum in sentence
    for (i = 1; i < (nmea_data.length()-5); i ++) { // remove the - 5 if no "*" + cksum + cr + lf are present
        crc ^= nmea_data[i];
    }
    chSumString = String(crc,HEX);
    if (chSumString.length()==1) {
      chSumString="0"+chSumString.substring(0,1);
    }
    chSumString.toUpperCase();
    return chSumString;
}

//check RC which button is pressed
int checkRfRC() {
  int n = 0;
  int res = 0;
  n = analogRead(rfRemoteControlPin);
  if ((n>350) and (n<460)) { // button A
    res = RCleftbutton;
  }
  if ((n> 90) and (n<190)) { // button B
    res = RCrightbutton;
  }
  if ((n>540) and (n<640)) { // button C
    res = RCleft10button;
  }
  if ((n>225) and (n<325)) { // button D
    res = RCright10button;
  }
  return res;    
}

//check HW which button is pressed
int checkHWButtons() {
  int n = 0;
  int res = 0;
  n = analogRead(ButtonsPin);  
  //Serial.println(n); 
  if ((n>465) and (n<565)) { // button left
    res = HWleftbutton;
  }
  if ((n>290) and (n<390)) { // button right
    res = HWrightbutton;
  }
  if ((n>130) and (n<220)) { // button pause
    res = HWpausebutton;
  }
  if ((n>625) and (n<725)) { // button setup
    res = HWsetupbutton;
  }
  if ((n>975) and (n<1075)) { // button left-10
    res = HWleft10button;
  }
  if ((n>800) and (n<900)) { // button right+10
    res = HWright10button;
  }
  return res;    
}

void gosetup() {  // setup button
  if (isSetup == false) {
      SetupParameter = 1;
      isSetup = true;
    } else {
      if (SetupParameter < 5) {
          SetupParameter ++;
        } else {
          if (prev_StearingInterval != StearingInterval || prev_StearingMinToMove != StearingMinToMove || prev_StearingMaxMove != StearingMaxMove || prev_StearingCoeffMove != StearingCoeffMove) {
            lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("updating... ");
            delay(1000);
            goupdateEEPROM();
            if (EEerr) {
              lcd.print("E=");
              lcd.print(EEerr);
              delay(1000);
            }
            prev_StearingInterval = StearingInterval;
            prev_StearingMinToMove = StearingMinToMove;
            prev_StearingMaxMove = StearingMaxMove;
            prev_StearingCoeffMove = StearingCoeffMove;
          }
          isSetup = false;
      }
  }
  NewTone (speakerPin,2000);
  delay(200);
  noNewTone();
  RefreshDisplay();
}

void goupdateEEPROM() {
  EEaddress = 0;  //id1
  EEdata[0] = EEid1;
  EEbytedata = EEid1;
  writeEEPROM (EEdisk, EEaddress, EEbytedata);
  EEaddress = 1;  //id2
  EEdata[1] = EEid2;
  EEbytedata = EEid2;
  writeEEPROM (EEdisk, EEaddress, EEbytedata);
  memcpy(bStearingInterval, &StearingInterval, sizeof(int));
  memcpy(bStearingMinToMove, &StearingMinToMove, sizeof(int));
  memcpy(bStearingMaxMove, &StearingMaxMove, sizeof(int));
  memcpy(bStearingCoeffMove, &StearingCoeffMove, sizeof(float));
  memcpy(EEdata+2,bStearingInterval,sizeof(int));
  memcpy(EEdata+4,bStearingMinToMove,sizeof(int));
  memcpy(EEdata+6,bStearingMaxMove,sizeof(int));
  memcpy(EEdata+8,bStearingCoeffMove,sizeof(float));
  for (s = 2; s < EEbytes; s ++) {
    EEaddress = s;  //data
    EEbytedata = EEdata[s];
    writeEEPROM (EEdisk, EEaddress, EEbytedata);
  }
}

void goleft() {  // left button/RC
  if (ispause == false) {
    nmf_routetofollow --;
    if (nmf_routetofollow < 1) {
      nmf_routetofollow = 360;
    }
    d=nmf_routetofollow;
    nmf_routetofollow=d;
    nm_routetofollow=d;
    NewTone (speakerPin,400);
    delay(200);
    noNewTone();
  } else {
    NewTone (speakerPin,1000);
    delay(50);
    noNewTone();
  }
  RefreshDisplay();
}

void goleft10() {  // left 10x button/RC
  if (ispause == false) {
    for (s = 1; s < 11; s ++) {
      nmf_routetofollow --;
      if (nmf_routetofollow < 1) {
        nmf_routetofollow = 360;
      }
    }
    d=nmf_routetofollow;
    nmf_routetofollow=d;
    nm_routetofollow=d;
    NewTone (speakerPin,400);
    delay(200);
    noNewTone();
  } else {
    NewTone (speakerPin,1000);
    delay(50);
    noNewTone();
  }
  RefreshDisplay();
}

void goright() {  // right button/RC
  if (ispause == false) {
    nmf_routetofollow ++;
    if (nmf_routetofollow > 360) {
      nmf_routetofollow = 1;
    }
    d=nmf_routetofollow;
    nmf_routetofollow=d;
    nm_routetofollow=d;
    NewTone (speakerPin,800);
    delay(200);
    noNewTone();
  } else {
    NewTone (speakerPin,1000);
    delay(50);
    noNewTone();
  }
  RefreshDisplay();
}

void goright10() {  // right 10x button/RC
  if (ispause == false) {
    for (s = 1; s < 11; s ++) {
      nmf_routetofollow ++;
      if (nmf_routetofollow > 360) {
        nmf_routetofollow = 1;
      }
    }
    d=nmf_routetofollow;
    nmf_routetofollow=d;
    nm_routetofollow=d;
    NewTone (speakerPin,800);
    delay(200);
    noNewTone();
  } else {
    NewTone (speakerPin,1000);
    delay(50);
    noNewTone();
  }
  RefreshDisplay();
}

void gopause() {  // pause button/RC
  if (ispause == true) {
    ispause=false; 
    digitalWrite(ledpausePin, HIGH);
    NewTone (speakerPin,50);
    delay(200);
    NewTone (speakerPin,200);
    delay(800);
    noNewTone();
  } else {
    ispause=true; 
    digitalWrite(ledpausePin, LOW);
    NewTone (speakerPin,200);
    delay(200);
    NewTone (speakerPin,50);
    delay(800);
    noNewTone();
  }
  RefreshDisplay();
}

// reading eeprom
byte readEEPROM (int diskaddress, unsigned int memaddress) {
  byte rdata = 0x00;
  Wire.beginTransmission (diskaddress);
  Wire.write (memaddress);
  if (Wire.endTransmission () == 0) {
    Wire.requestFrom (diskaddress,1);
    if (Wire.available()) {
        rdata = Wire.read();
      } else {
        EEerr = 1; //"READ no data available"
    }
    } else {
      EEerr = 2; //"READ eTX error"
  }
  Wire.endTransmission (true);
  return rdata;
}

// writing eeprom
void writeEEPROM (int diskaddress, unsigned int memaddress, byte bytedata) {
  Wire.beginTransmission (diskaddress);
  Wire.write (memaddress);
  Wire.write (bytedata);
  if (Wire.endTransmission () != 0) {
    EEerr = 3; //"WRITING eTX error"
  }
  Wire.endTransmission (true);
  delay(5); 
}

// round zero decimal
float roundZeroDec(float f) {
  float y, d;
  y = f*1;
  d = y - (int)y;
  y = (float)(int)(f*1)/1;
  if (d >= 0.5) {
    y += 1;
   } else {
    if (d < -0.5) {
    y -= 1;
  }
  }
  return y;
}

// round one decimal
float roundOneDec(float f) {
  float y, d;
  y = f*10;
  d = y - (int)y;
  y = (float)(int)(f*10)/10;
  if (d >= 0.5) {
    y += 0.1;
   } else {
    if (d < -0.5) {
    y -= 0.1;
  }
  }
  return y;
}

// round two decimal
float roundTwoDec(float f) {
  float y, d;
  y = f*100;
  d = y - (int)y;
  y = (float)(int)(f*100)/100;
  if (d >= 0.5) {
    y += 0.01;
   } else {
    if (d < -0.5) {
    y -= 0.01;
  }
  }
  return y;
}
WatchDog sketch (for Nano)Arduino
/*
 * This sketch is a Watchdog to keep CLIENT under control, on Arduino NANO 3.0 by Marco Zonca
 * CLIENT must feed Whatcdog sooner then feedingInterval otherwise will be forced to restart
 * 
 */

const int feedingPin = 14;
const int ledPin =  15;
const int restartPin = 16;
const int buzzerPin = 17;
const long ledInterval = 1000;
const long feedingInterval = 2500;
const long timeForClientStart = 16000;

int ledState = LOW;
int previousFeedingState = LOW;
int feedingState = LOW;
unsigned long previousLedMillis = 0;
unsigned long previousFeedingMillis = 0;

void setup() {
  digitalWrite(restartPin, HIGH);  // LOW will force CLIENT to restart
  pinMode(ledPin, OUTPUT);
  pinMode(buzzerPin, OUTPUT);
  pinMode(restartPin, OUTPUT);
  pinMode(feedingPin, INPUT);
  delay(timeForClientStart);  // let time to CLIENT to start...
}

void loop() {
  unsigned long currentMillis = millis();
  // BLINK LED -------------------
  if (currentMillis - previousLedMillis >= ledInterval) {
    previousLedMillis = currentMillis;
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    digitalWrite(ledPin, ledState);
  }
  // CHECK THE FEEDING -------------------
  feedingState = digitalRead(feedingPin);  // CLIENT must set pin HIGH -> LOW frequently to prove it's alive
  if (feedingState == HIGH) {
    if (previousFeedingState == LOW) {
      previousFeedingMillis = currentMillis;
    }
    previousFeedingState = HIGH;
  } else {
    previousFeedingState = LOW;
  }
  if (currentMillis - previousFeedingMillis > feedingInterval) {  // CLIENT is sleeping
    ledState = HIGH;
    digitalWrite(ledPin, ledState);
    tone(buzzerPin,1500);
    delay(500);
    digitalWrite(restartPin, LOW);  //restart CLIENT
    tone(buzzerPin,1500);
    delay(500);
    digitalWrite(restartPin, HIGH);
    tone(buzzerPin,1500);
    delay(timeForClientStart);  // let CLIENT time to restart...
    noTone(buzzerPin);
    currentMillis = millis();
    previousFeedingState = LOW;
    previousFeedingMillis = currentMillis;
    previousLedMillis = currentMillis;
  }
}

Custom parts and enclosures

Stepper, pulley and mounting plate preview picture
Stepper mounting autopilot zat2pfigcj
Stepper plate by Andrew Barney
23lm-stepper-plate-v2_PlvJaff9Hl.step
Stepper pulley 56mm
23lm-stepper-pulley-56_UhsbaWbiBt.step

Schematics

Fritzing schematic diagram
autopilot-3_tlpOKX5hDz.fzz
Autopilot PCB, component face
Pcb ard autopilot 3 componentface 2m93ycjeof
Autopilot PCB, solder face
Pcb ard autopilot 3 solderface dflpynpjrq
Power PCB, component face
Pcb ard autopilot power componentface 4vc3pdhoyz
Power PCB, solder face
Pcb ard autopilot power solderface hadt3e3pnx

Comments

Similar projects you might like

Smart Parking System

Project tutorial by Husinul

  • 18,787 views
  • 18 comments
  • 38 respects

Smart Parking System

Project in progress by Md. Khairul Alam

  • 65,033 views
  • 37 comments
  • 130 respects

RCar | Robots for All!

Project tutorial by Luís Rita

  • 8,758 views
  • 2 comments
  • 43 respects

Intelligent Transportation System In City Bus Shelter

Project in progress by Team Technoid

  • 4,582 views
  • 0 comments
  • 13 respects

How to Make a Smart Central Lock System for Motorcycle

Project tutorial by Shubham Shinganapure

  • 4,286 views
  • 1 comment
  • 15 respects

Arduino-based Collision Detection Warning System

Project tutorial by vijendra kumar

  • 23,913 views
  • 4 comments
  • 47 respects
Add projectSign up / Login