-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ts2phc: Support using a GPS radio as the master clock.
Many GPS radios provide both a 1-PPS and time of day information via NMEA sentences. This patch introduces a ts2phc master that decodes the "recommended minimum data" sentence, RMC, which provides UTC time and a validity flag. Together with the file based leap second table, this sentence provides adequate time of day for determining the time of the PPS edge. Signed-off-by: Richard Cochran <[email protected]>
- Loading branch information
1 parent
43c51cf
commit 7486e6e
Showing
12 changed files
with
687 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/** | ||
* @file nmea.c | ||
* @note Copyright (C) 2020 Richard Cochran <[email protected]> | ||
* @note SPDX-License-Identifier: GPL-2.0+ | ||
*/ | ||
#include <malloc.h> | ||
#include <stdint.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#include "nmea.h" | ||
#include "print.h" | ||
|
||
#define NMEA_CHAR_MIN ' ' | ||
#define NMEA_CHAR_MAX '~' | ||
#define NMEA_MAX_LENGTH 256 | ||
|
||
enum nmea_state { | ||
NMEA_IDLE, | ||
NMEA_HAVE_DOLLAR, | ||
NMEA_HAVE_STARTG, | ||
NMEA_HAVE_STARTX, | ||
NMEA_HAVE_BODY, | ||
NMEA_HAVE_CSUMA, | ||
NMEA_HAVE_CSUM_MSB, | ||
NMEA_HAVE_CSUM_LSB, | ||
NMEA_HAVE_PENULTIMATE, | ||
}; | ||
|
||
struct nmea_parser { | ||
char sentence[NMEA_MAX_LENGTH + 1]; | ||
char payload_checksum[3]; | ||
enum nmea_state state; | ||
uint8_t checksum; | ||
int offset; | ||
}; | ||
|
||
static void nmea_reset(struct nmea_parser *np); | ||
|
||
static void nmea_accumulate(struct nmea_parser *np, char c) | ||
{ | ||
if (c < NMEA_CHAR_MIN || c > NMEA_CHAR_MAX) { | ||
nmea_reset(np); | ||
return; | ||
} | ||
if (np->offset == NMEA_MAX_LENGTH) { | ||
nmea_reset(np); | ||
} | ||
np->sentence[np->offset++] = c; | ||
np->checksum ^= c; | ||
} | ||
|
||
static int nmea_parse_symbol(struct nmea_parser *np, char c) | ||
{ | ||
switch (np->state) { | ||
case NMEA_IDLE: | ||
if (c == '$') { | ||
np->state = NMEA_HAVE_DOLLAR; | ||
} | ||
break; | ||
case NMEA_HAVE_DOLLAR: | ||
if (c == 'G') { | ||
np->state = NMEA_HAVE_STARTG; | ||
nmea_accumulate(np, c); | ||
} else { | ||
nmea_reset(np); | ||
} | ||
break; | ||
case NMEA_HAVE_STARTG: | ||
np->state = NMEA_HAVE_STARTX; | ||
nmea_accumulate(np, c); | ||
break; | ||
case NMEA_HAVE_STARTX: | ||
np->state = NMEA_HAVE_BODY; | ||
nmea_accumulate(np, c); | ||
break; | ||
case NMEA_HAVE_BODY: | ||
if (c == '*') { | ||
np->state = NMEA_HAVE_CSUMA; | ||
} else { | ||
nmea_accumulate(np, c); | ||
} | ||
break; | ||
case NMEA_HAVE_CSUMA: | ||
np->state = NMEA_HAVE_CSUM_MSB; | ||
np->payload_checksum[0] = c; | ||
break; | ||
case NMEA_HAVE_CSUM_MSB: | ||
np->state = NMEA_HAVE_CSUM_LSB; | ||
np->payload_checksum[1] = c; | ||
break; | ||
case NMEA_HAVE_CSUM_LSB: | ||
if (c == '\n') { | ||
/*skip the CR*/ | ||
return 0; | ||
} | ||
if (c == '\r') { | ||
np->state = NMEA_HAVE_PENULTIMATE; | ||
} else { | ||
nmea_reset(np); | ||
} | ||
break; | ||
case NMEA_HAVE_PENULTIMATE: | ||
if (c == '\n') { | ||
return 0; | ||
} | ||
nmea_reset(np); | ||
break; | ||
} | ||
return -1; | ||
} | ||
|
||
static void nmea_reset(struct nmea_parser *np) | ||
{ | ||
memset(np, 0, sizeof(*np)); | ||
} | ||
|
||
static int nmea_scan_rmc(struct nmea_parser *np, struct nmea_rmc *result) | ||
{ | ||
int cnt, i, msec = 0; | ||
char *ptr, status; | ||
uint8_t checksum; | ||
struct tm tm; | ||
|
||
pr_debug("nmea sentence: %s", np->sentence); | ||
cnt = sscanf(np->payload_checksum, "%02hhx", &checksum); | ||
if (cnt != 1) { | ||
return -1; | ||
} | ||
if (checksum != np->checksum) { | ||
pr_err("checksum mismatch 0x%02hhx != 0x%02hhx on %s", | ||
checksum, np->checksum, np->sentence); | ||
return -1; | ||
} | ||
cnt = sscanf(np->sentence, | ||
"G%*cRMC,%2d%2d%2d.%d,%c", | ||
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, &msec, &status); | ||
if (cnt != 5) { | ||
cnt = sscanf(np->sentence, | ||
"G%*cRMC,%2d%2d%2d,%c", | ||
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, &status); | ||
if (cnt != 4) { | ||
return -1; | ||
} | ||
} | ||
ptr = np->sentence; | ||
for (i = 0; i < 9; i++) { | ||
ptr = strchr(ptr, ','); | ||
if (!ptr) { | ||
return -1; | ||
} | ||
ptr++; | ||
} | ||
cnt = sscanf(ptr, "%2d%2d%2d", &tm.tm_mday, &tm.tm_mon, &tm.tm_year); | ||
if (cnt != 3) { | ||
return -1; | ||
} | ||
tm.tm_year += 100; | ||
tm.tm_mon--; | ||
result->ts.tv_sec = mktime(&tm); | ||
result->ts.tv_nsec = msec * 1000000UL; | ||
result->fix_valid = status == 'A' ? true : false; | ||
return 0; | ||
} | ||
|
||
int nmea_parse(struct nmea_parser *np, const char *ptr, int buflen, | ||
struct nmea_rmc *result, int *parsed) | ||
{ | ||
int count = 0; | ||
while (buflen) { | ||
if (!nmea_parse_symbol(np, *ptr)) { | ||
if (!nmea_scan_rmc(np, result)) { | ||
*parsed = count + 1; | ||
return 0; | ||
} | ||
nmea_reset(np); | ||
} | ||
buflen--; | ||
count++; | ||
ptr++; | ||
} | ||
*parsed = count; | ||
return -1; | ||
} | ||
|
||
struct nmea_parser *nmea_parser_create(void) | ||
{ | ||
struct nmea_parser *np; | ||
np = malloc(sizeof(*np)); | ||
if (!np) { | ||
return NULL; | ||
} | ||
nmea_reset(np); | ||
/* Ensure that mktime(3) returns a value in the UTC time scale. */ | ||
setenv("TZ", "UTC", 1); | ||
return np; | ||
} | ||
|
||
void nmea_parser_destroy(struct nmea_parser *np) | ||
{ | ||
free(np); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* @file nmea.h | ||
* @note Copyright (C) 2020 Richard Cochran <[email protected]> | ||
* @note SPDX-License-Identifier: GPL-2.0+ | ||
*/ | ||
#ifndef HAVE_NMEA_H | ||
#define HAVE_NMEA_H | ||
|
||
#include <stdbool.h> | ||
#include <time.h> | ||
|
||
/** Opaque type. */ | ||
struct nmea_parser; | ||
|
||
struct nmea_rmc { | ||
struct timespec ts; | ||
bool fix_valid; | ||
}; | ||
|
||
/** | ||
* Parses NMEA RMC sentences out of a given buffer. | ||
* @param np Pointer obtained via nmea_parser_create(). | ||
* @param buf Pointer to the data to be parsed. | ||
* @param buflen Length of 'buf' in bytes. | ||
* @param rmc Pointer to hold the result. | ||
* @param parsed Returns the number of bytes parsed, possibly less than buflen. | ||
* @return Zero on success, non-zero otherwise. | ||
*/ | ||
int nmea_parse(struct nmea_parser *np, const char *buf, int buflen, | ||
struct nmea_rmc *rmc, int *parsed); | ||
|
||
/** | ||
* Creates an instance of an NMEA parser. | ||
* @return Pointer to a new instance on success, NULL otherwise. | ||
*/ | ||
struct nmea_parser *nmea_parser_create(void); | ||
|
||
/** | ||
* Destroys an NMEA parser instance. | ||
* @param np Pointer obtained via nmea_parser_create(). | ||
*/ | ||
void nmea_parser_destroy(struct nmea_parser *np); | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* @file serial.c | ||
* @note Copyright (C) 2020 Richard Cochran <[email protected]> | ||
* @note SPDX-License-Identifier: GPL-2.0+ | ||
*/ | ||
#include <fcntl.h> | ||
#include <sys/stat.h> | ||
#include <sys/types.h> | ||
#include <termios.h> | ||
|
||
#include "print.h" | ||
#include "serial.h" | ||
|
||
#define CANONICAL 1 | ||
|
||
static int open_serial_baud(const char *name, tcflag_t baud, int icrnl, int hwfc) | ||
{ | ||
struct termios nterm; | ||
int fd; | ||
|
||
fd = open(name, O_RDWR | O_NOCTTY); | ||
if (fd < 0) { | ||
pr_err("cannot open %s : %m", name); | ||
return fd; | ||
} | ||
memset(&nterm, 0, sizeof(nterm)); | ||
|
||
/* Input Modes */ | ||
nterm.c_iflag = IGNPAR; /* Ignore framing errors and parity errors */ | ||
if (icrnl) { | ||
/* Translate carriage return to newline on input */ | ||
nterm.c_iflag |= ICRNL; | ||
} | ||
|
||
/* Output Modes */ | ||
nterm.c_oflag = 0; | ||
|
||
/* Control Modes */ | ||
nterm.c_cflag = baud; | ||
nterm.c_cflag |= CS8; /* Character size */ | ||
nterm.c_cflag |= CLOCAL; /* Ignore modem control lines */ | ||
nterm.c_cflag |= CREAD; /* Enable receiver */ | ||
if (hwfc) { | ||
/* Enable RTS/CTS (hardware) flow control */ | ||
nterm.c_cflag |= CRTSCTS; | ||
} | ||
|
||
/* Local Modes */ | ||
if (CANONICAL) { | ||
nterm.c_lflag = ICANON; /* Enable canonical mode */ | ||
} | ||
|
||
nterm.c_cc[VTIME] = 10; /* timeout is 10 deciseconds */ | ||
nterm.c_cc[VMIN] = 1; /* blocking read until N chars received */ | ||
tcflush(fd, TCIFLUSH); | ||
tcsetattr(fd, TCSANOW, &nterm); | ||
return fd; | ||
} | ||
|
||
int serial_open(const char *name, int bps, int icrnl, int hwfc) | ||
{ | ||
tcflag_t baud; | ||
|
||
switch (bps) { | ||
case 1200: | ||
baud = B1200; | ||
break; | ||
case 1800: | ||
baud = B1800; | ||
break; | ||
case 2400: | ||
baud = B2400; | ||
break; | ||
case 4800: | ||
baud = B4800; | ||
break; | ||
case 9600: | ||
baud = B9600; | ||
break; | ||
case 19200: | ||
baud = B19200; | ||
break; | ||
case 38400: | ||
baud = B38400; | ||
break; | ||
case 57600: | ||
baud = B57600; | ||
break; | ||
case 115200: | ||
baud = B115200; | ||
break; | ||
default: | ||
return -1; | ||
} | ||
return open_serial_baud(name, baud, icrnl, hwfc); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* @file serial.h | ||
* @note Copyright (C) 2020 Richard Cochran <[email protected]> | ||
* @note SPDX-License-Identifier: GPL-2.0+ | ||
*/ | ||
#ifndef HAVE_SERIAL_H | ||
#define HAVE_SERIAL_H | ||
|
||
/** | ||
* Opens a serial port device. | ||
* @param name Serial port device to open. | ||
* @param bps Baud rate in bits per second. | ||
* @param icrnl Pass 1 to map CR to NL on input, zero otherwise. | ||
* @param hwfc Pass 1 to enable hardware flow control, zero otherwise. | ||
* @return An open file descriptor on success, -1 otherwise. | ||
*/ | ||
int serial_open(const char *name, int bps, int icrnl, int hwfc); | ||
|
||
#endif |
Oops, something went wrong.