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:
-
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.
-
Buy a complete new modern below-decks hydraulic autopilot.
Expensive; probably $2000 ?
-
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.
- 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.
-
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:
- 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.
- 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
- 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.]
- 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.)
- 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)
- 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.
- 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 ?]
- 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.
- 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]
- 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.