How to make a
GPS-based autopilot
control head
for your boat
         Please send any comments to me.

This page updated: March 2009
      




Overview section
Parts section
Test Programming section
Programming section


Current project status: programming phase.





Overview

My 35-year-old sailboat has a 35-year-old below-decks autopilot (Benmar 16B-3). The "control" parts of the autopilot are complicated, a mix of transistor-era elecronics and odd mechanical connections, and very balky and mostly broken. The "movement" parts of the autopilot (an electric motor, hydraulic pump, and hydraulic ram) still work okay.

Alternatives:
  1. Get the autopilot repaired by the manufacturer.

    Likely to be expensive, if feasible at all. And afterward I'll have a repaired 35-year-old autopilot.

  2. Buy a complete new modern below-decks hydraulic autopilot.

    Expensive; probably $2000 ?

  3. It might be possible to buy just the control head part (compass and buttons and intelligence electronics) of a modern autopilot and connect that to the "movement" parts of my old autopilot. The control head itself would cost $100-$200 used, maybe $400 new, and then a small dab of additional electrical stuff might be needed to connect it to my existing electrical motor.

  4. Make a full control head:

    Design and make a small circuit board containing a magnetic or fluxgate compass sensor or a GPS module, and control relays that can control the electric motor of my autopilot. I could design it simply to keep steering in whatever direction the boat was headed when the board was turned on, so it wouldn't need any kind of "user interface" (buttons and LEDs and display to let the user control it). Total parts cost might be $150-$300.

    But my circuit-design and circuit-building skills are nil. This also requires computer-programming skills; I have those.

  5. Connect GPS to "movement" parts of my old autopilot.

    I already have a GPS that can output NMEA 0183 information to steer to a waypoint. That is most of the "intelligence" of an autopilot control head; maybe I can connect that to the "movement" parts of the old autopilot.

    What is needed is some electronics that can read NMEA 0183 signals from the GPS and produce "steer right" and "steer left" electrical outputs to the electrical motor from my old autopilot.

I chose option number 5.



GPS-to-steering Alternatives:
  1. Commercial products:

    I have found one cheap-ish commercial product that connects GPS to steering: the GPS Smart Coupler.

    But it costs $250, and the output signals from it won't control an electrical motor directly (without some additional relays or other electrical stuff). And things could get complicated if the "steering sensitivity" of the two parts don't match.

  2. Make it myself:

    I could buy and program a small circuit board that will read NMEA 0183 output from my boat's GPS and control relays that can control the electric motor of my autopilot. Total parts cost might be $100.

    This requires computer-programming skills; I have those.

    A project that uses a PC in the middle (I don't want to do that): Steinar's "Make your own Auto Pilot".

I chose option number 2.





Parts

  1. GPS that can steer to a waypoint and output NMEA sentences.

    Looks like the GPS must output the "GPRMB" sentence, which contains the "cross-track error" info.
    Example: $GPRMB,A,0.66,L,003,004,4917.24,N,12309.57,W,001.3,052.5,000.5,V*0B<CR><LF>
    where "0.66,L" (always exactly 6 digits) is the cross-track error in NM and direction to steer.

    My boat has a Garmin 128 GPS, which can do this.
    (Some GPS's may provide a "GPAPB" or "GPXTE" sentence instead, which gives same "cross-track error" info. Some GPS's may provide proprietary sentences, starting with "P", to do the same.)

    Not sure how often these sentences are output by each GPS.

    Not sure what the Data Status (A/V) field means; one document says "A = OK, V = warning", another says "A = OK, V = Void (warning)", another says "A = Active, V = Void". Does that mean "A = on course, V = off course", "A = good info, V = ignore this sentence", or something else ?

    GPS NMEA 0183 output (RS-232) must be +/-12 volts; I've heard some (non-GPS) devices cheat and put out +/-5 volts.
    [VERIFY THAT MY GPS'S NMEA OUTPUT LEVEL IS +/-12 VOLTS; MANUAL DOESN'T SAY.]


  2. Circuit board (development board) that can read NMEA 0183 (RS-232) input and control relays for motor-control.

    I chose to use PIC-based boards, and bought this one:
    Microchip PIC-IO-A or PIC-IO-18 ($33; 18-pin; 4 x 10A relays)

    (I think my autopilot motor draws about 5A at 12VDC, well within the relay capacity. And I can gang together two relays for each steering direction, so I'd be using only 5A of a 20A capacity. My old autopilot does have a relay box for the motor, but it's fairly complicated and I'm not sure I want to rely on it.)

    12V input
    Status LED connected to RB5 (PORTB bit 5; marked "LED9" on board)
    Input status LEDs (marked "LED1" through "LED4" on board; RA4 = LED1/IN1, RB0 = LED2/IN2, RB3 = LED3/IN3, RB4 = LED4/IN4)
    Output/relay status LEDs (marked "LED5" through "LED8" on board; RA3 = LED5/RELAY1, RA2 = LED6/RELAY2, RA1 = LED7/RELAY3, RA0 = LED8/RELAY4)
    (Had to get the schematic image for the board to figure out the pin-to-LED assignments.)


  3. Microcontroller chip to plug into the circuit board:

    I chose PIC-type processors:
    (Must have one with a UART or USART, for doing RS-232.)
    (Not sure what memory size I'm going to need.)

    Wanted SparkFun PIC 16F88 (4K; $5), but ended up buying everything from Microchip, so got:
    PIC16F628A-I/P from Microchip (2K; $3; 18 DIP; 20 MHz)


  4. A PC (desktop or laptop) for writing and downloading the program that will run on the processor chip:

    I have a laptop with a USB port, no RS-232 port.
    This turns out to be important when you get to the "choose a programmer" step; programmers using USB are more expensive than those using RS-232, but laptop RS-232 ports or USB-to-RS-232 adapters may not work.


  5. Software to create the program that will run on the processor chip:

    I chose to write in the C language, so I need a C compiler that supports the chip I chose:
    HI-TECH C PRO for the PIC10/12/16 MCU Family (free)
    MPLAB Integrated Development Environment (free; includes debugger, simulator, two C compilers, interfaces to programmer boards)
    [DO I WANT TO TRY A SIMULATOR ?]


  6. Programmer board to install program from PC into PIC processor chip:

    (Programmers vary by: chips supported, type of connection to PC, type of power source, type of connection to chip or development board.)

    Microchip PX-200 ($51; USB)
    SparkFun PGM-00004 ($92; USB)
    Futurlec ($59; USB)

    I bought the Microchip PX-200.

    Had to buy special cable to connect programmer to development board; apparently there are no standards, so cable that came with programmer wasn't right. Cable cost $9.50 plus $4 shipping. Use beige RS232 cable, not grey one, and pins are connected as follows: MCLR on small board to pin 1 on PIC-IO-18 board, rest in order up to PGM on small board to pin 6 on PIC-IO-18 board.


  7. Software to drive programmer board:

    PICkit 2 came included with the programmer board, and is used by MPLAB IDE.

    IC-Prog (free)
    [DON'T SEE ANY OF THE USB PROGRAMMERS ABOVE ON THE IC-PROG SUPPORTED LIST]


  8. Miscellaneous:

    Male DB-9 connector to plug into circuit board, for NEMA input from GPS.
    Various wire.
    12V power plug to fit socket on target board.
    Power switch.
    Box to mount/hold/shield the circuit board.



Various resources for knowledge and parts:

Picture of programmer and target boards.





Test Programming



Small test program (in C programming language):
// Test program to blink LEDs on PIC-IO-18 (PIC-IO-A) board
// with PIC16F628A chip installed.

#include <pic.h>
#include <pic16f62xa.h>
#include <htc.h>	// required for delay routines

__CONFIG(UNPROTECT & LVPDIS & BORDIS & MCLREN & PWRTDIS & WDTDIS & HS);  // 0x3F2A
/* for PIC-IO-18 board, oscillator configuration == HS */

#ifndef _XTAL_FREQ
// required to calibrate __delay_us() and __delay_ms()
#define _XTAL_FREQ 4000000	// internal clock freq (4MHz), not freq (20MHz) of crystal on board
#endif

/*
for PIC-IO-18 board:
	output: RB5 = status LED9
	inputs: RA4 = LED1/IN1, RB0 = LED2/IN2, RB3 = LED3/IN3, RB4 = LED4/IN4
	outputs/relays: RA3 = LED5/RELAY1, RA2 = LED6/RELAY2, RA1 = LED7/RELAY3, RA0 = LED8/RELAY4
*/ 

main(void)
{
	unsigned int	i;

	TRISA = 0;		// all port A bits output
	TRISB = 0;		// all port B bits output
	PORTA = 0x00;		// turn all outputs off
	PORTB = 0x00;		// turn all outputs off

	for(;;) {
//		RA3 = 1;			// LED5/RELAY1
		RB5 = 1;			// LED9
		for(i = 50 ; --i ;) {
			__delay_ms(20);
		}
		RB5 = 0;			// LED9
		for(i = 500 ; --i ;) {
			__delay_ms(20);
		}
	}
}


Another small test program (in C programming language):
// Program to test timer interrupts
// on PIC-IO-18 (PIC-IO-A) board
// with PIC16F628A chip installed.

#include <pic.h>
#include <pic16f62xa.h>

__CONFIG(UNPROTECT & LVPDIS & BORDIS & MCLREN & PWRTDIS & WDTDIS & HS);  // 0x3F2A
/* for PIC-IO-18 board, oscillator configuration == HS */

/*
for PIC-IO-18 board:
	output: RB5 = status LED9
	inputs: RA4 = LED1/IN1, RB0 = LED2/IN2, RB3 = LED3/IN3, RB4 = LED4/IN4
	outputs/relays: RA3 = LED5/RELAY1, RA2 = LED6/RELAY2, RA1 = LED7/RELAY3, RA0 = LED8/RELAY4
*/ 

static long	lCount0;
static long	lCount1;
static bit	bLED0On;
static bit	bLED1On;

main(void)
{
	TRISA = 0;		// all port A bits output
	TRISB = 0;		// all port B bits output
	PORTA = 0x00;	// turn all outputs off
	PORTB = 0x00;	// turn all outputs off

	lCount0 = 0;
	lCount1 = 0;
	bLED0On = 0;
	bLED1On = 0;

	// Timer 0 is an 8-bit counter that interrupts
	// when it overflows from 0xFF to 0x00.
	// There is an 8-bit "prescaler", which counts
	// how many cycles (0-256) to ignore before incrementing
	// the counter each time.
	// This prescaler is shared with the watchdog timer.
	OPTION = 0b0111;	// Timer 0 prescale by 1:256
	T0CS = 0;		// Timer 0 increments on instruction clock
	T0IE = 1;		// Enable interrupt on TMR0 overflow

	// Timer 1 is a 16-bit counter that interrupts
	// when it overflows from 0xFFFF to 0x0000.
	// There is a 2-bit "prescaler", which counts
	// how many cycles (0-8) to ignore before incrementing
	// the counter each time.
	T1CKPS1 = 0;	// Timer 1 prescale by 1:1
	T1CKPS0 = 0;
	TMR1CS = 0;		// Timer 1 increments on instruction clock
	TMR1IE = 1;		// Enable interrupt on Timer 1 overflow
	TMR1ON = 1;		// Enable Timer 1

	// Timer 2: does not generate interrupts; feeds output
	// to serial port or CCP.

	PEIE = 1;		// enable peripheral interrupts; needed for Timer1
	GIE = 1;		// Global interrupt enable

	for(;;)
		NOP();		// do nothing
}


// interrupt function - the name is unimportant
static void interrupt
isr(void)			
{
	// timer 0 overflow ?
	if (T0IF) {
		lCount0++;
		if ((lCount0%32) == 0) {
			bLED0On = !bLED0On;
			RB5 = bLED0On;		// LED9
		}
		T0IF = 0;		// clear interrupt flag, ready for next
	}

	// timer 1 overflow ?
	else if (TMR1IF) {
		lCount1++;
		if ((lCount1%128) == 0) {
			bLED1On = !bLED1On;
			RA3 = bLED1On;		// LED5/RELAY1
		}
		TMR1IF = 0;		// clear interrupt flag, ready for next
	}

	// unexpected interrupt
	else {
		RA0 = 1;			// LED8/RELAY4
	}
}


Another small test program (in C programming language):
// Program to test serial port receive interrupts
// on PIC-IO-18 (PIC-IO-A) board
// with PIC16F628A chip installed.

#include <pic.h>
#include <pic16f62xa.h>

__CONFIG(UNPROTECT & LVPDIS & BORDIS & MCLREN & PWRTDIS & WDTDIS & HS);  // 0x3F2A
/* for PIC-IO-18 board, oscillator configuration == HS */

/*
for PIC-IO-18 board:
	output: RB5 = status LED9
	inputs: RA4 = LED1/IN1, RB0 = LED2/IN2, RB3 = LED3/IN3, RB4 = LED4/IN4
	outputs/relays: RA3 = LED5/RELAY1, RA2 = LED6/RELAY2, RA1 = LED7/RELAY3, RA0 = LED8/RELAY4
*/

// do RS232 9600 baud, 8 bits
#define BAUD 9600      
#define FOSC 20000000L
#define DIVIDER ((int)(FOSC/(16UL * BAUD) -1))// for >= 9600 baud
//#define DIVIDER ((int)(FOSC/(64UL * BAUD) -1))// for < 9600 baud

static unsigned char cInput;

static long	lCount;
static bit	bLEDOn;

main(void)
{
	unsigned char cInput;

	TRISA = 0;		// all port A bits output
	TRISB = 0;		// all port B bits output
	PORTA = 0x00;	// turn all outputs off
	PORTB = 0x00;	// turn all outputs off

	lCount = 0;
	bLEDOn = 0;

	// initialize communications
	TRISB = 0x06;	// RB1 and RB2 used by USART
	SPBRG = DIVIDER;// set baud rate
	RCSTA = 0x80;	// serial port enable, 8-bit
	TXSTA = 0x04;	// 8-bit, no-xmit, async, high-speed
	RCIE = 1;		// enable recv interrupts
	TXIE = 0;		// no xmit interrupts
	CREN = 1;		// continuous recv enable

	PEIE = 1;		// enable peripheral ints; needed for USART
	GIE = 1;		// Global interrupt enable

	for(;;)
		NOP();		// do nothing
}


// interrupt function - the name is unimportant
static void interrupt
isr(void)			
{
	// received char ?
	if (RCIF) {
		if (FERR) {
			// framing error
			unsigned char cGarbage = RCREG;	// clears FERR
			RA1 = 1;		// LED7/RELAY3
			RCIF = 0;
			return;
		}
		lCount++;
		cInput = RCREG;
		if (cInput == 'G') {
			RA3 = 1;		// LED5/RELAY1
			NOP();
			NOP();
			RA3 = 0;		// LED5/RELAY1
		}
		bLEDOn = !bLEDOn;
		RB5 = bLEDOn;		// LED9
		if (OERR) {
			// overrun error
			CREN = 0;
			CREN = 1;
			RA2 = 1;		// LED6/RELAY2
		}
		RCIF = 0;		// clear interrupt flag, ready for next
	}

	// unexpected interrupt
	else {
		RA0 = 1;			// LED8/RELAY4
	}
}






Programming



Continual LED indicators:
Got NMEA sentence from GPS (toggle state).
Got course error NMEA sentence from GPS (flash on for 1/4 second).
Steering left (on while relay is on).
Steering right (on while relay is on).
Software debug output (blink patterns).

Timer counters (action to do when counter gets down to zero):
Stop left steering.
Stop right steering.
Turn off "got NMEA sentence" LED.
Turn off "got course error NMEA sentence" LED.
Turn off software debug LED.

At startup:
Set relays to "off".
Set indicator LEDs to "off".
Set timer counters to zero.
Initialize timer to interrupt every 1/10th of a second.
Enable timer interrupts.
Cycle indicator LEDs to show that they work and board is running.
Cycle relays to show that whole system (except GPS/NMEA) is working.
Initialize course error to "0".
Initialize USART to receive at 4800 bps, no parity, and one stop bit.
Enable USART interrupts.

Main loop:
If "got GPRMB info" flag, copy "n.nn,L" or "n.nn,R" out of input buffer, zero out the buffer, and process the info (might want to average several over time ?).
Set steering relays appropriately.
Set indicator LEDs appropriately.
Set appropriate timer counter to turn off relays when done steering.

USART interrupt handler:
Check for USART error conditions.
Read char from USART.
Keep track of whether we are in a sentence, and whether it is a "$GPRMB,A" sentence.
If in a "$GPRMB,A" sentence and between 2nd and 4th commas (in "n.nn,L" or "n.nn,R"), add char to input buffer.
If LF char (end of NMEA sentence), turn on "got NMEA sentence" LED and set timer counter to turn it off. If it was a "$GPRMB,A" sentence, set "got GPRMB info" flag, turn on LED and set timer counter for it.

Timer interrupt handler:
For all timer counters:
If timer counter is greater than zero, decrement it. If now zero, do the action for the counter.





Home
Site Map     
1