BSP430  20141115
Board Support Package for MSP430 microcontrollers
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
Sensors: SkyTraq Venus 638FLPx as Time Source

The SkyTraq Venus 638FLPx is a GPS sensor which provides a one-pulse-per-second output synchronized to GPS time. This example uses that to capture the ACLK counter and determine the relationship between ulBSP430uptime() and UTC time.

Example output:

15:28.278: Wokeup with 3, lost 0 octets in 0/d 0/a
        fix 2 GPS week 1721 sec 402481: 1357228065.040: Thu Jan  3 15:47:45 2013
        UTT-to-UTC offset -81 after 837 s; UTT  gained  0:00.002  in 13:57.000
15:29.280: Wokeup with 3, lost 0 octets in 0/d 0/a
        fix 2 GPS week 1721 sec 402482: 1357228066.040: Thu Jan  3 15:47:46 2013
        UTT-to-UTC offset -81 after 838 s; UTT  gained  0:00.002  in 13:58.000
Note
The implementation for this sensor makes use of Fragpool, a library designed to manage packetized data in embedded communications stacks.

Venus 638FLPx Time Synchronization

GPS data is emitted either in a block of NMEA sentences or a single binary sSkyTraqMsgOut_NAV_DATA packet, once per second, at a fixed interval relative to power-up or reset. Multiple NMEA sentences in a block reference the same time.

On power up, the timestamp starts at 2006-06-28T11:59:45Z and increments in whole seconds. Once GPS transmissions start getting decoded there is a jump to the approximate time at a 1 second resolution. A second adjustment to full precision is made when synchronization is complete. This latter adjustment may be more or less than one second, depending on the error. Sometime after that 1PPS starts showing up.

For NMEA sentences the time is UTC and has a precision of 1ms.

The binary sSkyTraqMsgOut_NAV_DATA message represents time in two components: the GPS week, and the GPS second-of-week as a multiple of 0.01s. The GPS week is not provided modulo 1024, so no week rollover need be considered. However, the packet does not include the GPS-to-UTC offset. As of 2012-07-01T00:00:00 GPS is 16 seconds ahead of UTC, meaning that 1600 must be subtracted from the GPS centisecond to provide the UTC centisecond.

Analysis suggests that timestamps lag the 1PPS signal by between 100ms and 200ms. Thus a timestamp with a fractional part of 0.650 seconds might start transmission 800ms after the 1pps that it describes.

If the Venus is restarted with no-mode-change in sSkyTraqMsgIn_RESTART.mode synchronization resumes within a few seconds and timestamps are synchronized to near the whole second, although the 100ms+ lag remains. This is still worth doing as it reduces the likelihood that a timestamp is associated with the wrong 1PPS event.

main.c

#include <bsp430/clock.h>
#include <bsp430/serial.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#define GPS_STATE_1PPS 0x01
#define GPS_STATE_RX 0x02
#define GPS_STATE_TX_DONE 0x04
#define GPS_STATE_SAW_NMEA 0x08
typedef struct sAppGPSState {
unsigned int flags;
uint8_t keep_fix;
uint8_t last_fix;
unsigned int last_week_be;
unsigned int dropped_fix;
unsigned int app_lost_pkts;
unsigned int driver_lost_pkts;
unsigned int lost_octets;
const uint8_t * msg;
unsigned int msg_len;
unsigned long rx_utt;
unsigned long pps_tt;
int tx_rc;
} sAppGPSState;
static volatile sAppGPSState gps_state_;
static int
gps_serial_cb (const uint8_t * msg,
size_t len,
unsigned long rx_utt)
{
int rc;
uSkyTraqMsg * up = (uSkyTraqMsg *)msg;
unsigned int flags = GPS_STATE_RX;
/* The going-in assumption is the driver should wake the
* application, but branches below might override this. */
if (NULL == msg) {
/* Record a packet lost within the driver itself. */
++gps_state_.driver_lost_pkts;
gps_state_.lost_octets += len;
} else if (NULL == gps_state_.msg) {
switch (up->generic.mid) {
/* We don't decode NMEA messages; drop it and flag it so the
* application can switch the GPS to binary mode. */
flags = GPS_STATE_SAW_NMEA;
break;
/* Drop data with inadequate GPS fix unless either the fix
* level or the gross time has changed since the last
* notification. */
if ((up->out.nav_data.fix_mode < gps_state_.keep_fix)
&& (up->out.nav_data.fix_mode == gps_state_.last_fix)
&& (up->out.nav_data.gps_week_be == gps_state_.last_week_be)) {
flags = 0;
rc = 0;
gps_state_.dropped_fix += 1;
break;
}
/* Record the filter-related information from this packet and
* pass it on to the application. */
gps_state_.last_fix = up->out.nav_data.fix_mode;
gps_state_.last_week_be = up->out.nav_data.gps_week_be;
goto accept;
/* Ignore the ACKs that don't have a valid MID. Accept the
* others. */
if (0 == up->out.ack.in_mid) {
rc = 0;
flags = 0;
break;
}
/*FALLTHRU*/
default:
/* Accept everything else */
accept:
gps_state_.msg = msg;
gps_state_.msg_len = len;
gps_state_.rx_utt = rx_utt;
break;
}
} else {
/* We only buffer a single packet and the application hasn't
* completed processing the last one we received. */
++gps_state_.app_lost_pkts;
gps_state_.lost_octets += len;
}
/* Free this message if it isn't now the responsibility of the
* application. */
if ((NULL != msg) && (gps_state_.msg != msg)) {
}
gps_state_.flags |= flags;
return rc;
}
static int
gps_pps_cb (unsigned long pps_tck)
{
gps_state_.flags |= GPS_STATE_1PPS;
gps_state_.pps_tt = pps_tck;
/* Don't wake up on 1PPS; we'll wake on the subsequent message that
* says what time the 1PPS occurred. */
return 0;
}
static int
gps_tx_complete (const uint8_t * msg,
int rc)
{
gps_state_.flags |= GPS_STATE_TX_DONE;
gps_state_.tx_rc = rc;
}
static uSkyTraqMsgIn tx_message;
void main ()
{
int rc;
size_t tx_length;
const uint8_t * msg_to_release;
unsigned int num_sync = 0;
time_t sync_utc = 0;
unsigned long sync_utt = 0;
unsigned long min_lag = 0;
unsigned long max_lag = 0;
unsigned long utt_per_sec;
cprintf("\n\nrtc " __DATE__ " " __TIME__ "\n");
cprintf("Uptime clock uses %lu Hz\n", utt_per_sec);
{
int rv;
memset(&config, 0, sizeof(config));
config.nmea_serial = APP_NMEA_UART_PERIPH_HANDLE;
config.nmea_baud = APP_NMEA_BAUD_RATE;
config.pps_port = APP_PPS_PORT_PERIPH_HANDLE;
config.pps_port_bit = APP_PPS_PORT_BIT;
config.pps_ccidx = APP_PPS_CCIDX;
config.pps_ccis = APP_PPS_CCIS;
config.serial_cb = gps_serial_cb;
config.pps_cb = gps_pps_cb;
cprintf("Connect NMEA serial data at %lu baud to %s at:\n\t%s\n",
(unsigned long)config.nmea_baud,
cprintf("Connect 1PPS to %s.%u on %s.%u\n",
config.pps_ccidx,
rv = iBSP430gpsInitialize_ni(&config, NULL);
cprintf("GPS init got %d\n", rv);
}
/* Queue up a version message so we have evidence the sensor is
* connected and responsive. */
{
struct sSkyTraqMsgIn_SW_VERSION * mp = &tx_message.sw_version;
memset(mp, 0, sizeof(*mp));
mp->type = 1;
tx_length = sizeof(*mp);
cprintf("Queued SW version query\n");
}
/* Initialize the application state. To reduce noise during startup
* discard NAV_DATA messages with fixes below 2 (=3D) that don't
* change from the last fix displayed. */
memset((void*)&gps_state_, 0, sizeof(gps_state_));
gps_state_.keep_fix = 2;
gps_state_.last_fix = -1;
sync_utc = 0;
msg_to_release = NULL;
while (1) {
sAppGPSState state;
unsigned long now_utt;
do {
/* Free any driver-provided buffer that we're done with. */
if (NULL != msg_to_release) {
vBSP430gpsReleaseMessage_ni(msg_to_release);
msg_to_release = NULL;
}
/* Initiate any pending transmission */
if (0 < tx_length) {
rc = iBSP430gpsTransmit_ni((uint8_t*)&tx_message, tx_length, gps_tx_complete);
cprintf("TX MID %u request got %d\n", tx_message.generic.mid, rc);
/* Clear the request only if the request was accepted.
* There's error recovery that could be done here. */
if (0 == rc) {
tx_length = 0;
}
}
/* Wait 30 seconds or until something happens. */
BSP430_UPTIME_DELAY_MS_NI(30000, LPM0_bits, gps_state_.flags);
/* Capture the driver state then clear the pieces that we accept
* responsibility for. */
state = gps_state_;
gps_state_.flags = 0;
gps_state_.msg = NULL;
/* Grab the current time while we have interrupts disabled. */
now_utt = ulBSP430uptime_ni();
} while (0);
cprintf("%s: Wokeup with %x, lost %u octets in %u/d %u/a\n",
xBSP430uptimeAsText(now_utt, timestamp),
state.flags, state.lost_octets, state.driver_lost_pkts, state.app_lost_pkts);
if (NULL != state.msg) {
uSkyTraqMsg * up = (uSkyTraqMsg *)state.msg;
xBSP430uptimeAsText(state.rx_utt, timestamp);
switch (up->generic.mid) {
/* Emit any NMEA messages that passed the callback filter */
cprintf("\t%s [%u]: %s\n", timestamp, state.msg_len, state.msg);
break;
/* Display command acknowledgements */
cprintf("%s: %s %u\n", timestamp, (eSkyTraqMIDout_NACK == up->generic.mid) ? "NACK" : "ACK", up->out.ack.in_mid);
break;
/* Display the software version */
cprintf("\t%s: SW version kernel %08lx odm %08lx rev %08lx\n",
timestamp,
up->out.sw_version.kernel,
up->out.sw_version.odm,
up->out.sw_version.revision);
break;
struct tm when_tm;
char cbuf[26];
struct sSkyTraqMsgOut_NAV_DATA * np = &up->out.nav_data;
uint16_t gps_week = BSP430_CORE_SWAP_16(np->gps_week_be);
uint32_t gps_csow = BSP430_CORE_SWAP_32(np->gps_tow_cs_be);
uint16_t msec = (gps_csow % 100) * 10;
uint32_t gps_sow = gps_csow / 100;
time_t when_utc;
when_utc = xBSP430gpsConvertGPStoUTC_ni(gps_week, gps_sow);
gmtime_r(&when_utc, &when_tm);
cprintf("\tfix %u GPS week %d sec %lu: %lu.%03u: %s",
np->fix_mode, gps_week, gps_sow, when_utc, msec, ctime_r(&when_utc, cbuf));
/* Reset if we've lost the GPS fix */
if (0 == np->fix_mode) {
sync_utc = 0;
}
if (state.flags & GPS_STATE_1PPS) {
unsigned long lag = state.rx_utt - state.pps_tt;
int show_lag = (0 == when_tm.tm_sec);
/* Maintain statistics on the delta between the 1PPS and
* the start of the following message, which is expected
* to reflect the time at the most recent 1PPS. This is
* partly why we use NAV_DATA: so we get one message per
* second, rather than a set of NMEA messages. Display
* those statistics when they change, or once per
* minute. */
if ((0 == min_lag) || (lag < min_lag)) {
min_lag = lag;
show_lag = 1;
}
if (lag > max_lag) {
max_lag = lag;
show_lag = 1;
}
if (show_lag) {
cprintf("\tmin lag %lu = %s;", min_lag, xBSP430uptimeAsText(min_lag, timestamp));
cprintf(" max lag %lu = %s\n", max_lag, xBSP430uptimeAsText(max_lag, timestamp));
}
/* The lag between when a message arrives and the
* timestamp encoded in it is normally about 150ms +/-
* 50ms. If the fractional second in the timestamp is
* more than 50ms away from the whole second assume the
* sensor is gratuitously delaying the notification so
* that the update is spaced consistently with previous
* updates. This means the delay gets mixed up with the
* lag so we can't be sure that the timestamp doesn't
* refer to a different 1PPS event. (The offset does
* drift, so give it 50ms leeway.)
*
* If it looks like the sensor is introducing gratuitous
* delays, use a hot restart that preserves mode to change
* the update basis so the timestamp is re-aligned with
* the 1PPS. It'll still be about 150ms late, but there
* won't be such a risk that the notification is for a
* previous 1PPS event. (Don't do this if the 1PPS isn't
* valid since the reset might prevent the unit from
* resynchronizing quickly. Also don't try it if the
* transmit buffer isn't available.) */
if (((50 <= msec) && (msec <= 950))
&& (2 <= np->fix_mode)) {
cprintf("\tExcessive timestamp offset detected: %u ms\n", msec);
if (0 == tx_message.generic.mid) {
struct sSkyTraqMsgIn_RESTART * mp = &tx_message.restart;
memset(mp, 0, sizeof(*mp));
mp->mode = 0;
mp->year_be = BSP430_CORE_SWAP_16(when_tm.tm_year);
mp->mon = when_tm.tm_mon - 1;
mp->mday = when_tm.tm_mday;
mp->hour = when_tm.tm_hour;
mp->min = when_tm.tm_min;
mp->sec = when_tm.tm_sec;
tx_length = sizeof(*mp);
cprintf("** Queued restart command\n");
}
} else if (0 == sync_utc) {
++num_sync;
sync_utc = when_utc;
sync_utt = state.pps_tt;
cprintf("** First sync: %lu POSIX == %lu uptime\n", sync_utc, sync_utt);
} else {
unsigned int sync_duration_s = (when_utc - sync_utc);
unsigned long sync_duration_utt = sync_duration_s * utt_per_sec;
unsigned long expected_utt = sync_utt + sync_duration_utt;
long drift_utt = (long)state.pps_tt - (long)expected_utt;
/* Calculate the drift of uptime clock away from UTC
* since the last synchronization. */
cprintf("\tUTT-to-UTC offset %ld after %u s; UTT ", drift_utt, sync_duration_s);
if (0 > drift_utt) {
cprintf(" gained");
drift_utt = -drift_utt;
} else {
cprintf(" lost");
}
cprintf(" %s ", xBSP430uptimeAsText(drift_utt, timestamp));
cprintf(" in %s\n", xBSP430uptimeAsText(sync_duration_utt, timestamp));
/* Reset synchronization epoch 90 seconds after first
* sync to eliminate accumulated error when uptime clock
* needed to warm up to stability and the GPS provided a
* synchronization before that completed. */
if ((1 == num_sync) && (90 == sync_duration_s)) {
cprintf("** Reset sync\n");
++num_sync;
sync_utc = when_utc;
sync_utt = state.pps_tt;
}
}
}
break;
}
default:
/* Some unrecognized binary message. */
cprintf("\t%s [%u]: Unhandled mid %u\n", xBSP430uptimeAsText(state.rx_utt, timestamp), state.msg_len, up->generic.mid);
break;
}
msg_to_release = state.msg;
}
if (state.flags & GPS_STATE_1PPS) {
#if 0
cprintf("\t1PPS at %lu\n", state.pps_tt);
#endif
}
if (state.flags & GPS_STATE_TX_DONE) {
/* Mark the transmission buffer as available */
tx_message.generic.mid = 0;
cprintf("\tTX completed %d\n", state.tx_rc);
}
/* If we got an NMEA text message and the transmission buffer is
* available, send a command to put the sensor into binary-output
* mode. */
if ((state.flags & GPS_STATE_SAW_NMEA) && (0 == tx_message.generic.mid)) {
struct sSkyTraqMsgIn_CFG_FORMAT * mp = &tx_message.cfg_format;
memset(mp, 0, sizeof(*mp));
mp->type = 2;
tx_length = sizeof(*mp);
cprintf("Queued format=binary command\n");
}
}
}

bsp430_config.h

/* Use a crystal if one is installed. Much more accurate timing
* results. */
#define BSP430_PLATFORM_BOOT_CONFIGURE_LFXT1 1
/* Application does output: support spin-for-jumper */
#define configBSP430_PLATFORM_SPIN_FOR_JUMPER 1
/* Support console buffered input and output at a higher-than-normal
* data rate. */
#define configBSP430_CONSOLE 1
#define BSP430_CONSOLE_TX_BUFFER_SIZE 100
#define BSP430_CONSOLE_RX_BUFFER_SIZE 16
#define BSP430_CONSOLE_BAUD_RATE 115200
/* Add support to tell the user where to connect things */
#define configBSP430_PLATFORM_PERIPHERAL_HELP 1
/* Monitor uptime and provide generic timer, with delay support */
#define configBSP430_UPTIME 1
#define configBSP430_UPTIME_DELAY 1
/* Need a CC register, preferably on the uptime timer, to use for 1PPS
* capture. Remember that CC0 is for RTOS switching, CC1 is the
* default for configBSP430_UPTIME_DELAY, and CC2 is the default for
* BSP430_TIMER_VALID_COUNTER_READ_CCIDX. If your TA0 only has three
* capture/compare registers, you may want to override
* BSP430_UPTIME_TIMER_PERIPH_CPPID to select a more capable timer. */
#if ((BSP430_PLATFORM_TRXEB - 0) \
|| (BSP430_PLATFORM_EXP430F5438 - 0))
/* TA0.3 == P1.4 CCI1A */
#define APP_PPS_CCIDX 3
#define APP_PPS_CCIS CCIS_0
#define APP_PPS_PORT_PERIPH_HANDLE BSP430_PERIPH_PORT1
#define APP_PPS_PORT_BIT BIT4
#define configBSP430_HAL_PORT1 1
#endif
/* Platform-specific UART for NMEA data */
#if (BSP430_PLATFORM_TRXEB - 0)
#define configBSP430_HAL_USCI5_A0 1
#define APP_NMEA_UART_PERIPH_HANDLE BSP430_PERIPH_USCI5_A0
#endif /* Platform */
/* Site-specific baud rate for NMEA data */
#ifndef APP_NMEA_BAUD_RATE
#define APP_NMEA_BAUD_RATE 9600
#endif /* APP_NMEA_BAUD_RATE */
/* Get platform defaults */

Makefile

PLATFORM ?= trxeb
TEST_PLATFORMS=trxeb
MODULES=$(MODULES_PLATFORM)
MODULES += $(MODULES_UPTIME)
MODULES += $(MODULES_CONSOLE)
MODULES += periph/port
VPATH += $(BSP430_ROOT)/src/sensors
MODULES += sensors/skytraq
CPPFLAGS += -Ifragpool/include
SRC=main.c fragpool/src/fragpool.c
include $(BSP430_ROOT)/make/Makefile.common