Synchronising to a Garmin GPS 18 LVC

You need both the NMEA 0183 data and PPS for accurate timekeeping.
Only the Garmin GPS 18 LVC and GPS 18 LVC-5m produce a PPS signal. The GPS 18-5 Hz produces 5 pulses per second.
These receivers appear to be no longer available. The GPS 18x LVC 5m appears to be similar.

Connecting the Garmin to a PC

The transmitted data from the Garmin is connected to the received data from the PC and vv. The PPS output is connected to the DCD pin.
Some people power the Garmin from the USB port. The USB data wires (green and white) are not used.
The black wires are connected to the RS232 ground pin.

GarminCableRS2329P25PUSBCable
+5VRed1Red
GNDBlackGND574Black
GNDBlackGND574
TXDWhiteRXD23
RXDGreenTXD32
PPSYellowDCD18
Garmin           9P 25P  USB

Red    ---------         Red

Black  ----*----         Black
           |
Black  ----*----  5  7

White  ---------  2  3

Green  ---------  3  2

Yellow ---------  1  8

Inside the cable there is a 3rd black wire and a shield. They seem to be connected to the other black wires. I did not connect the 3rd black wire, but I did connect the shield to the RS232 shield via a 47 nF capacitor. The USB shield is directly connected to the RS232 shield. I used a 25 pins connector connected to a RS232 tester.
The Garmin output voltage swings between 0 and +5 Volts, so the CD and RD LED's will flash red but not green.

Configuring the Garmin with Minicom

If connected directly to Minicom, the Garmin appears to be online once per second. This is because the PPS is tied to DCD.
The circuit below can be used to avoid line status confusion;

Garmin		Minicom

Male    	Female
		9P	25P

RXD-----RXD	2	 3

TXD-----TXD	3	 2

GND-----GND	5	 7

      +-DCD	1	 8
      |
      *-DTR	4	20
      |
      +-DSR	6	 6

      +-CTS	7	 5
      |
      +-RTS	8	 4

SH------SH

Start Minicom with '-o' to avoid sending an init string. Exit Minicom with 'Ctrl-A,Z,Q' instead of 'X' to avoid a reset.

According to the Garmin documentation (687 kB) these sentences are enabled by default;

SentenceOutput by Default?Maximum Characters
GPRMCY74
GPGGAY82
GPGSAY66
GPGSVY (PC and LVC only)70
PGRMEY (PC and LVC only)35
GPGLLN44
GPVTGY (18-5Hz only)42
PGRMVN32
PGRMFN82
PGRMBY (PC and LVC only)40
PGRMTN Once per minute50

Sending the following string will restore these defaults;

  $PGRMO,,4

In order to avoid typos I send this string as a file (Ctrl-A,Z,Y).
As it turns out, the PGRMT IS enabled by default.
It takes about 0.8 seconds to send these sentences at 4800 bps. With all sentences enabled it takes longer then one second. Which means that the time is available only once per TWO seconds.

Configuring NTPD

The entry for a refclock in ntp.conf is;

  server 127.127.Type.Unit_Number
TypeDescriptionIdent
20Generic NMEA GPS ReceiverGPS_NMEA
22PPS Clock DisciplinePPS
28Shared Memory DriverSHM

PPS only works with a PPS enabled kernel. This applies to both the GPS_NMEA and PPS driver; If your kernel supports PPS, the GPS_NMEA driver can take care of PPS to. Furthermore, the desired clock has to be compiled into NTPD.
If you want to use PPS with a kernel without PPS support, you need to use a Shared Memory Driver.

SHM

The setup below uses a shared memory driver for PPS and a GPS_NMEA driver for the time. This means that you have to split the cable in two; one port gets all signals, the other just PPS and GND.
The Garmin is a bit late, so I added a 185 ms time1 fudge;

server 127.127.20.0
fudge  127.127.20.0 time1 0.185
server 127.127.28.0 minpoll 4 prefer
fudge  127.127.28.0 refid PPS

The GPS_NMEA driver wants a symlink in /dev/ telling it which is the source of the time. EG ttyS1;

  gps0 -> ttyS1

I created a file '77-local.rules' in '/etc/udev/rules.d/' for this purpose. It contains the following line;

KERNEL=="ttyS1" SYMLINK+="gps0"

Replace 'ttyS1' with the tty you use.

A remote source, such as a remote GPSD, can also be used;

  gps0 -> 192.168.1.5:2947

David J. Schwartz's SHM driver (shm_linux_clock.c) reads data from ttyS0 and feeds it to the NTPD's shared memory. There is also a version (shm_splc.c) modified by Steven Bjork which supports parallel ports. And a newer version (shmpps.tar) by Philip M. White. You need to start this driver when the clock within 25 ms accurate.

Wheezy

Use of GPS_NMEA is problematic with Debian Wheezy, so I disabled it in ntp.conf.

GPSD

Alternatively you can use GPSD as a time source for NTPD. This only works properly with a 0.2 s PPS. GPSD however resets this to 0.1 s. To avoid this tell GPSD not to send anything to the Garmin with the '-b' option;
/etc/default/gpsd;

START_DAEMON="true"
DAEMON_OPTS="-b -n"
DEVICES="/dev/ttyS0"
USBAUTO="false"

The following lines in ntp.conf tell NTPD to use GPSD as a time source;

server 127.127.28.0 minpoll 4
fudge 127.127.28.0 time1 0.178 refid GPSa
server 127.127.28.1 minpoll 4 prefer
fudge 127.127.28.1 refid PPSa

GPS Week rollover

My Garmin works just fine. If you have any problems, have a look here.

Setserial

I added 'low_latency' to /var/lib/setserial/autoserial.conf to make things more accurate (ttyS0 is used for PPS);

 setserial /dev/ttyS0 uart 16550A port 0x03f8 irq  4 baud_base 115200 spd_normal skip_test low_latency

Clients

If you run an accurate timeserver on your LAN, you may want to reduce the poll interval on the clients a bit;

server ntp.example.org iburst minpoll 6 maxpoll 7

'iburst' makes NTPD start with a poll interval of one second.
'minpoll 6 maxpoll 7': Start with one poll per 64 seconds and the gradually move to one poll per 128 seconds.

Misc

When your Garmin doesn't have a clear view of the sky it will loose NMEA sync every now and then. When there is no NMEA sync for a longer period of time, the PPS will start to drift. The Garmin may also crash.
I wrote a script to take care of both. It runs from cron every five minutes;

#!/bin/bash

# Check PPS and if GPS NMEA is Acquired

# Files;
# Acquired status count file
ACNTFILE="/var/local/gps-chk/acqrd-cnt"
# Previous acquired status file
ASTATFILE="/var/local/gps-chk/acqrd-stat"
# PPS count file
PCNTFILE="/var/local/gps-chk/pps-cnt"

# Current acquired status
CURASTAT=$( /usr/local/sbin/chk-gps-acqrd.sh )
# Previous status
PREVASTAT=$( /bin/cat "${ASTATFILE}" )
# Not acquired status count
ASTATCNT=$( /bin/cat "${ACNTFILE}" )
# Max not acquired count
MAXACNT=2
# Current sync quality
CURSYNC=$( /usr/local/sbin/ntpeval )
# PPS count
PPSCNT=$( /bin/cat "${PCNTFILE}" )
# Max PPS count
MAXPCNT=1

# Check PPS;
REACH=$( /usr/bin/ntpq -p | egrep '^[ *+]SHM' | awk '{print $7}' )
# Force REACH 0 if not spresent
if [ ! $REACH ]
then
	REACH=0
fi

# Check PPS reach
if [ $(($REACH%2)) -eq 0 ]
then
	# Bits xxxx xxx0; even => Missing
	if [ $(($PPSCNT%$MAXPCNT)) -eq 0 ]
	then
		# Garmin may have crashed: Reset Garmin
		sleep 10
		echo $REACH | mail -s "Garmin PPS failed" Your_Email_Address
		logger "chk-gps: PPS Failed"
		/usr/local/sbin/sndnul -d /dev/Your_TtySx -t 2
		sleep 5
	fi
	let PPSCNT+=1
	echo $PPSCNT > "${PCNTFILE}"
	# Exit script
	exit 0
else
	# Bits xxxx xxx1; odd => OK
	if [ $PPSCNT -ne 0 ]
	then
		# Was missing
		logger "chk-gps: PPS OK"
		echo 0 > "${PCNTFILE}"
	fi
fi

# Check NMEA
if [ $CURASTAT -eq 0 ]
then
	# Not acquired
	let ASTATCNT+=1
	echo $ASTATCNT > "${ACNTFILE}"
	if [ $ASTATCNT -gt $MAXACNT ] && [ $CURSYNC -eq 0 ] && [ $PREVASTAT -ne 0 ]
	then
		# Reset Garmin
		logger "chk-gps: NMEA Failed"
		/usr/local/sbin/sndnul -d /dev/Your_TtySx -t 2
		sleep 5
		echo 0 > "${ASTATFILE}"
	fi
else
	# Acquired
	if [ $ASTATCNT -ne 0 ]
	then
		# Was not acquired
		echo 0 > "${ACNTFILE}"
	fi
	if [ $PREVASTAT -ne 1 ]
	then
		logger "chk-gps: NMEA OK"
		echo 1 > "${ASTATFILE}"
	fi
fi

The first part checks PPS. If there is no PPS for a longer period of time it will assume that the Garmin crashed.
The second part checks the NMEA sync. It uses a smal program 'ntpeval.c' to evaluate the time sync. If either of these checks fail it will power toggle the Garmin using a relay controlled by a serial port. The serial port is controlled by a small program 'sndnul.c' called from the script.