aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/fsm/common.h7
-rw-r--r--src/fsm/gps.c114
-rw-r--r--src/fsm/main.c2
-rw-r--r--src/fsm/minmea.c572
-rw-r--r--src/fsm/minmea.h234
-rw-r--r--src/fsm/ubx.c157
-rw-r--r--src/fsm/ubx.h193
-rw-r--r--src/fsm/usart.c155
-rw-r--r--src/fsm/usart.h42
9 files changed, 1039 insertions, 437 deletions
diff --git a/src/fsm/common.h b/src/fsm/common.h
index 53da672..b33f1f8 100644
--- a/src/fsm/common.h
+++ b/src/fsm/common.h
@@ -41,9 +41,10 @@ uint64_t timestamp_now(void);
int random_bool(void);
// Fault handling mechanism
-#define FAULT_SOURCE_MAIN 1
-#define FAULT_SOURCE_GPS 2
-#define FAULT_SOURCE_I2C 3
+#define FAULT_SOURCE_MAIN 1
+#define FAULT_SOURCE_GPS 2
+#define FAULT_SOURCE_I2C 3
+#define FAULT_SOURCE_USART 4
void trigger_fault(int source);
#endif // _COMMON_H_
diff --git a/src/fsm/gps.c b/src/fsm/gps.c
index c0eab66..1908fb3 100644
--- a/src/fsm/gps.c
+++ b/src/fsm/gps.c
@@ -30,8 +30,8 @@
#include "semphr.h"
#include "common.h"
#include "gps.h"
-#include "i2c.h"
-#include "ubx.h"
+#include "usart.h"
+#include "minmea.h"
TickType_t gps_timeutc_last_updated = 0;
@@ -44,29 +44,7 @@ static int gps_fix = 0;
static void gps_task(void *pvParameters);
-// Callback functions for UBX parser
-static void gps_nav_sol(ubx_nav_sol_t *sol)
-{
- gps_fix_last_updated = xTaskGetTickCount();
- gps_fix = sol->GPSfix == GPSFIX_3D;
-}
-
-static void gps_tim_tm2(ubx_tim_tm2_t *posllh) {}
-
SemaphoreHandle_t timeutc_semaphore;
-static void gps_nav_timeutc(ubx_nav_timeutc_t *timeutc)
-{
- xSemaphoreTake(timeutc_semaphore, portMAX_DELAY);
- gps_timeutc_last_updated = xTaskGetTickCount();
- gps_timeutc.year = timeutc->year;
- gps_timeutc.month = timeutc->month;
- gps_timeutc.day = timeutc->day;
- gps_timeutc.hour = timeutc->hour;
- gps_timeutc.min = timeutc->min;
- gps_timeutc.sec = timeutc->sec;
- gps_timeutc.valid = timeutc->valid;
- xSemaphoreGive(timeutc_semaphore);
-}
// Get current time from GPS
void gps_utctime(struct gps_time_s *timeutc)
@@ -87,71 +65,41 @@ void gps_utctime(struct gps_time_s *timeutc)
xSemaphoreGive(timeutc_semaphore);
}
-
-const ubx_callbacks_t gps_ubx_cb = {
- gps_nav_sol,
- gps_nav_timeutc,
- gps_tim_tm2
-};
-
-
-#define RXBUF_LEN 128
-static uint8_t rxbuf[RXBUF_LEN];
-static uint8_t gps_init_messages[] = {
- UBX_ENABLE_NAV_SOL,
- UBX_ENABLE_NAV_TIMEUTC,
- UBX_ENABLE_TIM_TM2 };
+#define RXBUF_LEN MAX_NMEA_SENTENCE_LEN
+static char rxbuf[RXBUF_LEN];
static void gps_task(void *pvParameters)
{
// Periodically reinit the GPS
- const TickType_t init_timeout = 10000ul / portTICK_PERIOD_MS;
-
- const uint8_t address = 0xFD;
- int must_init_gps = 1;
-
- TickType_t time_last_init = xTaskGetTickCount();
-
while (1) {
taskYIELD();
- if (time_last_init + init_timeout >= xTaskGetTickCount()) {
- must_init_gps = 1;
- }
-
- i2c_transaction_start();
-
- if (must_init_gps) {
- i2c_write(GPS_I2C_ADDR,
- gps_init_messages,
- sizeof(gps_init_messages));
-
- must_init_gps = 0;
- }
-
- int bytes_read = i2c_read_from(GPS_I2C_ADDR, address, rxbuf, 2);
-
- if (bytes_read != 2) {
- i2c_transaction_end();
- continue;
- }
-
- uint16_t bytes_available = (rxbuf[0] << 8) | rxbuf[1];
-
- if (bytes_available) {
- if (bytes_available > RXBUF_LEN) {
- bytes_available = RXBUF_LEN;
+ int success = usart_get_nmea_sentence(rxbuf);
+
+ if (success) {
+ const int strict = 1;
+ switch (minmea_sentence_id(rxbuf, strict)) {
+ case MINMEA_SENTENCE_RMC:
+ {
+ struct minmea_sentence_rmc frame;
+ if (minmea_parse_rmc(&frame, rxbuf)) {
+ xSemaphoreTake(timeutc_semaphore, portMAX_DELAY);
+ gps_timeutc_last_updated = xTaskGetTickCount();
+ gps_timeutc.year = frame.date.year;
+ gps_timeutc.month = frame.date.month;
+ gps_timeutc.day = frame.date.day;
+ gps_timeutc.hour = frame.time.hours;
+ gps_timeutc.min = frame.time.minutes;
+ gps_timeutc.sec = frame.time.seconds;
+ gps_timeutc.valid = frame.valid;
+ gps_fix = frame.valid;
+ gps_fix_last_updated = xTaskGetTickCount();
+ xSemaphoreGive(timeutc_semaphore);
+ }
+ } break;
+ default:
+ break;
}
-
- bytes_read = i2c_read(GPS_I2C_ADDR, rxbuf, bytes_available);
- i2c_transaction_end();
-
- for (int i = 0; i < bytes_read; i++) {
- ubx_parse(rxbuf[i]);
- }
- }
- else {
- i2c_transaction_end();
}
}
}
@@ -160,7 +108,7 @@ void gps_init()
{
gps_timeutc.valid = 0;
- ubx_register(&gps_ubx_cb);
+ usart_init();
timeutc_semaphore = xSemaphoreCreateBinary();
@@ -184,7 +132,7 @@ void gps_init()
int gps_locked()
{
if (gps_fix_last_updated + gps_data_validity_timeout < xTaskGetTickCount()) {
- return (gps_fix == GPSFIX_3D) ? 1 : 0;
+ return gps_fix;
}
else {
return 0;
diff --git a/src/fsm/main.c b/src/fsm/main.c
index 339c1ed..655ab45 100644
--- a/src/fsm/main.c
+++ b/src/fsm/main.c
@@ -144,7 +144,7 @@ static void launcher_task(void *pvParameters)
}
InitializeAudio(Audio16000HzSettings);
- SetAudioVolume(164);
+ SetAudioVolume(210);
PlayAudioWithCallback(audio_callback, NULL);
diff --git a/src/fsm/minmea.c b/src/fsm/minmea.c
new file mode 100644
index 0000000..fc6aa43
--- /dev/null
+++ b/src/fsm/minmea.c
@@ -0,0 +1,572 @@
+/*
+ * Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
+ * This program is free software. It comes without any warranty, to the extent
+ * permitted by applicable law. You can redistribute it and/or modify it under
+ * the terms of the Do What The Fuck You Want To Public License, Version 2, as
+ * published by Sam Hocevar. See the COPYING file for more details.
+ */
+
+// We are lacking timegm, but we only handle UTC and use mktime instead. See
+// https://github.com/cloudyourcar/minmea-t/ README.md
+#define timegm mktime
+
+#include "minmea.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+#define boolstr(s) ((s) ? "true" : "false")
+
+static int hex2int(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return -1;
+}
+
+uint8_t minmea_checksum(const char *sentence)
+{
+ // Support senteces with or without the starting dollar sign.
+ if (*sentence == '$')
+ sentence++;
+
+ uint8_t checksum = 0x00;
+
+ // The optional checksum is an XOR of all bytes between "$" and "*".
+ while (*sentence && *sentence != '*')
+ checksum ^= *sentence++;
+
+ return checksum;
+}
+
+bool minmea_check(const char *sentence, bool strict)
+{
+ uint8_t checksum = 0x00;
+
+ // Sequence length is limited.
+ if (strlen(sentence) > MINMEA_MAX_LENGTH + 3)
+ return false;
+
+ // A valid sentence starts with "$".
+ if (*sentence++ != '$')
+ return false;
+
+ // The optional checksum is an XOR of all bytes between "$" and "*".
+ while (*sentence && *sentence != '*' && isprint((unsigned char) *sentence))
+ checksum ^= *sentence++;
+
+ // If checksum is present...
+ if (*sentence == '*') {
+ // Extract checksum.
+ sentence++;
+ int upper = hex2int(*sentence++);
+ if (upper == -1)
+ return false;
+ int lower = hex2int(*sentence++);
+ if (lower == -1)
+ return false;
+ int expected = upper << 4 | lower;
+
+ // Check for checksum mismatch.
+ if (checksum != expected)
+ return false;
+ } else if (strict) {
+ // Discard non-checksummed frames in strict mode.
+ return false;
+ }
+
+ // The only stuff allowed at this point is a newline.
+ if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n"))
+ return false;
+
+ return true;
+}
+
+static inline bool minmea_isfield(char c) {
+ return isprint((unsigned char) c) && c != ',' && c != '*';
+}
+
+bool minmea_scan(const char *sentence, const char *format, ...)
+{
+ bool result = false;
+ bool optional = false;
+ va_list ap;
+ va_start(ap, format);
+
+ const char *field = sentence;
+#define next_field() \
+ do { \
+ /* Progress to the next field. */ \
+ while (minmea_isfield(*sentence)) \
+ sentence++; \
+ /* Make sure there is a field there. */ \
+ if (*sentence == ',') { \
+ sentence++; \
+ field = sentence; \
+ } else { \
+ field = NULL; \
+ } \
+ } while (0)
+
+ while (*format) {
+ char type = *format++;
+
+ if (type == ';') {
+ // All further fields are optional.
+ optional = true;
+ continue;
+ }
+
+ if (!field && !optional) {
+ // Field requested but we ran out if input. Bail out.
+ goto parse_error;
+ }
+
+ switch (type) {
+ case 'c': { // Single character field (char).
+ char value = '\0';
+
+ if (field && minmea_isfield(*field))
+ value = *field;
+
+ *va_arg(ap, char *) = value;
+ } break;
+
+ case 'd': { // Single character direction field (int).
+ int value = 0;
+
+ if (field && minmea_isfield(*field)) {
+ switch (*field) {
+ case 'N':
+ case 'E':
+ value = 1;
+ break;
+ case 'S':
+ case 'W':
+ value = -1;
+ break;
+ default:
+ goto parse_error;
+ }
+ }
+
+ *va_arg(ap, int *) = value;
+ } break;
+
+ case 'f': { // Fractional value with scale (struct minmea_float).
+ int sign = 0;
+ int_least32_t value = -1;
+ int_least32_t scale = 0;
+
+ if (field) {
+ while (minmea_isfield(*field)) {
+ if (*field == '+' && !sign && value == -1) {
+ sign = 1;
+ } else if (*field == '-' && !sign && value == -1) {
+ sign = -1;
+ } else if (isdigit((unsigned char) *field)) {
+ int digit = *field - '0';
+ if (value == -1)
+ value = 0;
+ if (value > (INT_LEAST32_MAX-digit) / 10) {
+ /* we ran out of bits, what do we do? */
+ if (scale) {
+ /* truncate extra precision */
+ break;
+ } else {
+ /* integer overflow. bail out. */
+ goto parse_error;
+ }
+ }
+ value = (10 * value) + digit;
+ if (scale)
+ scale *= 10;
+ } else if (*field == '.' && scale == 0) {
+ scale = 1;
+ } else if (*field == ' ') {
+ /* Allow spaces at the start of the field. Not NMEA
+ * conformant, but some modules do this. */
+ if (sign != 0 || value != -1 || scale != 0)
+ goto parse_error;
+ } else {
+ goto parse_error;
+ }
+ field++;
+ }
+ }
+
+ if ((sign || scale) && value == -1)
+ goto parse_error;
+
+ if (value == -1) {
+ /* No digits were scanned. */
+ value = 0;
+ scale = 0;
+ } else if (scale == 0) {
+ /* No decimal point. */
+ scale = 1;
+ }
+ if (sign)
+ value *= sign;
+
+ *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale};
+ } break;
+
+ case 'i': { // Integer value, default 0 (int).
+ int value = 0;
+
+ if (field) {
+ char *endptr;
+ value = strtol(field, &endptr, 10);
+ if (minmea_isfield(*endptr))
+ goto parse_error;
+ }
+
+ *va_arg(ap, int *) = value;
+ } break;
+
+ case 's': { // String value (char *).
+ char *buf = va_arg(ap, char *);
+
+ if (field) {
+ while (minmea_isfield(*field))
+ *buf++ = *field++;
+ }
+
+ *buf = '\0';
+ } break;
+
+ case 't': { // NMEA talker+sentence identifier (char *).
+ // This field is always mandatory.
+ if (!field)
+ goto parse_error;
+
+ if (field[0] != '$')
+ goto parse_error;
+ for (int f=0; f<5; f++)
+ if (!minmea_isfield(field[1+f]))
+ goto parse_error;
+
+ char *buf = va_arg(ap, char *);
+ memcpy(buf, field+1, 5);
+ buf[5] = '\0';
+ } break;
+
+ case 'D': { // Date (int, int, int), -1 if empty.
+ struct minmea_date *date = va_arg(ap, struct minmea_date *);
+
+ int d = -1, m = -1, y = -1;
+
+ if (field && minmea_isfield(*field)) {
+ // Always six digits.
+ for (int f=0; f<6; f++)
+ if (!isdigit((unsigned char) field[f]))
+ goto parse_error;
+
+ d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
+ m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
+ y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
+ }
+
+ date->day = d;
+ date->month = m;
+ date->year = y;
+ } break;
+
+ case 'T': { // Time (int, int, int, int), -1 if empty.
+ struct minmea_time *time_ = va_arg(ap, struct minmea_time *);
+
+ int h = -1, i = -1, s = -1, u = -1;
+
+ if (field && minmea_isfield(*field)) {
+ // Minimum required: integer time.
+ for (int f=0; f<6; f++)
+ if (!isdigit((unsigned char) field[f]))
+ goto parse_error;
+
+ h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
+ i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
+ s = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
+ field += 6;
+
+ // Extra: fractional time. Saved as microseconds.
+ if (*field++ == '.') {
+ int value = 0;
+ int scale = 1000000;
+ while (isdigit((unsigned char) *field) && scale > 1) {
+ value = (value * 10) + (*field++ - '0');
+ scale /= 10;
+ }
+ u = value * scale;
+ } else {
+ u = 0;
+ }
+ }
+
+ time_->hours = h;
+ time_->minutes = i;
+ time_->seconds = s;
+ time_->microseconds = u;
+ } break;
+
+ case '_': { // Ignore the field.
+ } break;
+
+ default: { // Unknown.
+ goto parse_error;
+ } break;
+ }
+
+ next_field();
+ }
+
+ result = true;
+
+parse_error:
+ va_end(ap);
+ return result;
+}
+
+bool minmea_talker_id(char talker[3], const char *sentence)
+{
+ char type[6];
+ if (!minmea_scan(sentence, "t", type))
+ return false;
+
+ talker[0] = type[0];
+ talker[1] = type[1];
+ talker[2] = '\0';
+
+ return true;
+}
+
+enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict)
+{
+ if (!minmea_check(sentence, strict))
+ return MINMEA_INVALID;
+
+ char type[6];
+ if (!minmea_scan(sentence, "t", type))
+ return MINMEA_INVALID;
+
+ if (!strcmp(type+2, "RMC"))
+ return MINMEA_SENTENCE_RMC;
+ if (!strcmp(type+2, "GGA"))
+ return MINMEA_SENTENCE_GGA;
+ if (!strcmp(type+2, "GSA"))
+ return MINMEA_SENTENCE_GSA;
+ if (!strcmp(type+2, "GLL"))
+ return MINMEA_SENTENCE_GLL;
+ if (!strcmp(type+2, "GST"))
+ return MINMEA_SENTENCE_GST;
+ if (!strcmp(type+2, "GSV"))
+ return MINMEA_SENTENCE_GSV;
+
+ return MINMEA_UNKNOWN;
+}
+
+bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence)
+{
+ // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
+ char type[6];
+ char validity;
+ int latitude_direction;
+ int longitude_direction;
+ int variation_direction;
+ if (!minmea_scan(sentence, "tTcfdfdffDfd",
+ type,
+ &frame->time,
+ &validity,
+ &frame->latitude, &latitude_direction,
+ &frame->longitude, &longitude_direction,
+ &frame->speed,
+ &frame->course,
+ &frame->date,
+ &frame->variation, &variation_direction))
+ return false;
+ if (strcmp(type+2, "RMC"))
+ return false;
+
+ frame->valid = (validity == 'A');
+ frame->latitude.value *= latitude_direction;
+ frame->longitude.value *= longitude_direction;
+ frame->variation.value *= variation_direction;
+
+ return true;
+}
+
+bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence)
+{
+ // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
+ char type[6];
+ int latitude_direction;
+ int longitude_direction;
+
+ if (!minmea_scan(sentence, "tTfdfdiiffcfci_",
+ type,
+ &frame->time,
+ &frame->latitude, &latitude_direction,
+ &frame->longitude, &longitude_direction,
+ &frame->fix_quality,
+ &frame->satellites_tracked,
+ &frame->hdop,
+ &frame->altitude, &frame->altitude_units,
+ &frame->height, &frame->height_units,
+ &frame->dgps_age))
+ return false;
+ if (strcmp(type+2, "GGA"))
+ return false;
+
+ frame->latitude.value *= latitude_direction;
+ frame->longitude.value *= longitude_direction;
+
+ return true;
+}
+
+bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence)
+{
+ // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
+ char type[6];
+
+ if (!minmea_scan(sentence, "tciiiiiiiiiiiiifff",
+ type,
+ &frame->mode,
+ &frame->fix_type,
+ &frame->sats[0],
+ &frame->sats[1],
+ &frame->sats[2],
+ &frame->sats[3],
+ &frame->sats[4],
+ &frame->sats[5],
+ &frame->sats[6],
+ &frame->sats[7],
+ &frame->sats[8],
+ &frame->sats[9],
+ &frame->sats[10],
+ &frame->sats[11],
+ &frame->pdop,
+ &frame->hdop,
+ &frame->vdop))
+ return false;
+ if (strcmp(type+2, "GSA"))
+ return false;
+
+ return true;
+}
+
+bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence)
+{
+ // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$;
+ char type[6];
+ int latitude_direction;
+ int longitude_direction;
+
+ if (!minmea_scan(sentence, "tfdfdTc;c",
+ type,
+ &frame->latitude, &latitude_direction,
+ &frame->longitude, &longitude_direction,
+ &frame->time,
+ &frame->status,
+ &frame->mode))
+ return false;
+ if (strcmp(type+2, "GLL"))
+ return false;
+
+ frame->latitude.value *= latitude_direction;
+ frame->longitude.value *= longitude_direction;
+
+ return true;
+}
+
+bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence)
+{
+ // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58
+ char type[6];
+
+ if (!minmea_scan(sentence, "tTfffffff",
+ type,
+ &frame->time,
+ &frame->rms_deviation,
+ &frame->semi_major_deviation,
+ &frame->semi_minor_deviation,
+ &frame->semi_major_orientation,
+ &frame->latitude_error_deviation,
+ &frame->longitude_error_deviation,
+ &frame->altitude_error_deviation))
+ return false;
+ if (strcmp(type+2, "GST"))
+ return false;
+
+ return true;
+}
+
+bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence)
+{
+ // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
+ // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D
+ // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75
+ // $GPGSV,4,4,13,39,31,170,27*40
+ // $GPGSV,4,4,13*7B
+ char type[6];
+
+ if (!minmea_scan(sentence, "tiii;iiiiiiiiiiiiiiii",
+ type,
+ &frame->total_msgs,
+ &frame->msg_nr,
+ &frame->total_sats,
+ &frame->sats[0].nr,
+ &frame->sats[0].elevation,
+ &frame->sats[0].azimuth,
+ &frame->sats[0].snr,
+ &frame->sats[1].nr,
+ &frame->sats[1].elevation,
+ &frame->sats[1].azimuth,
+ &frame->sats[1].snr,
+ &frame->sats[2].nr,
+ &frame->sats[2].elevation,
+ &frame->sats[2].azimuth,
+ &frame->sats[2].snr,
+ &frame->sats[3].nr,
+ &frame->sats[3].elevation,
+ &frame->sats[3].azimuth,
+ &frame->sats[3].snr
+ )) {
+ return false;
+ }
+ if (strcmp(type+2, "GSV"))
+ return false;
+
+ return true;
+}
+
+int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_)
+{
+ if (date->year == -1 || time_->hours == -1)
+ return -1;
+
+ struct tm tm;
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = 2000 + date->year - 1900;
+ tm.tm_mon = date->month - 1;
+ tm.tm_mday = date->day;
+ tm.tm_hour = time_->hours;
+ tm.tm_min = time_->minutes;
+ tm.tm_sec = time_->seconds;
+
+ time_t timestamp = timegm(&tm); /* See README.md if your system lacks timegm(). */
+ if (timestamp != -1) {
+ ts->tv_sec = timestamp;
+ ts->tv_nsec = time_->microseconds * 1000;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/* vim: set ts=4 sw=4 et: */
diff --git a/src/fsm/minmea.h b/src/fsm/minmea.h
new file mode 100644
index 0000000..6d7fe18
--- /dev/null
+++ b/src/fsm/minmea.h
@@ -0,0 +1,234 @@
+/*
+ * Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
+ * This program is free software. It comes without any warranty, to the extent
+ * permitted by applicable law. You can redistribute it and/or modify it under
+ * the terms of the Do What The Fuck You Want To Public License, Version 2, as
+ * published by Sam Hocevar. See the COPYING file for more details.
+ */
+
+#ifndef MINMEA_H
+#define MINMEA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+
+#define MINMEA_MAX_LENGTH 80
+
+enum minmea_sentence_id {
+ MINMEA_INVALID = -1,
+ MINMEA_UNKNOWN = 0,
+ MINMEA_SENTENCE_RMC,
+ MINMEA_SENTENCE_GGA,
+ MINMEA_SENTENCE_GSA,
+ MINMEA_SENTENCE_GLL,
+ MINMEA_SENTENCE_GST,
+ MINMEA_SENTENCE_GSV,
+};
+
+struct minmea_float {
+ int_least32_t value;
+ int_least32_t scale;
+};
+
+struct minmea_date {
+ int day;
+ int month;
+ int year;
+};
+
+struct minmea_time {
+ int hours;
+ int minutes;
+ int seconds;
+ int microseconds;
+};
+
+struct minmea_sentence_rmc {
+ struct minmea_time time;
+ bool valid;
+ struct minmea_float latitude;
+ struct minmea_float longitude;
+ struct minmea_float speed;
+ struct minmea_float course;
+ struct minmea_date date;
+ struct minmea_float variation;
+};
+
+struct minmea_sentence_gga {
+ struct minmea_time time;
+ struct minmea_float latitude;
+ struct minmea_float longitude;
+ int fix_quality;
+ int satellites_tracked;
+ struct minmea_float hdop;
+ struct minmea_float altitude; char altitude_units;
+ struct minmea_float height; char height_units;
+ int dgps_age;
+};
+
+enum minmea_gll_status {
+ MINMEA_GLL_STATUS_DATA_VALID = 'A',
+ MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V',
+};
+
+enum minmea_gll_mode {
+ MINMEA_GLL_MODE_AUTONOMOUS = 'A',
+ MINMEA_GLL_MODE_DPGS = 'D',
+ MINMEA_GLL_MODE_DR = 'E',
+};
+
+struct minmea_sentence_gll {
+ struct minmea_float latitude;
+ struct minmea_float longitude;
+ struct minmea_time time;
+ char status;
+ char mode;
+};
+
+struct minmea_sentence_gst {
+ struct minmea_time time;
+ struct minmea_float rms_deviation;
+ struct minmea_float semi_major_deviation;
+ struct minmea_float semi_minor_deviation;
+ struct minmea_float semi_major_orientation;
+ struct minmea_float latitude_error_deviation;
+ struct minmea_float longitude_error_deviation;
+ struct minmea_float altitude_error_deviation;
+};
+
+enum minmea_gsa_mode {
+ MINMEA_GPGSA_MODE_AUTO = 'A',
+ MINMEA_GPGSA_MODE_FORCED = 'M',
+};
+
+enum minmea_gsa_fix_type {
+ MINMEA_GPGSA_FIX_NONE = 1,
+ MINMEA_GPGSA_FIX_2D = 2,
+ MINMEA_GPGSA_FIX_3D = 3,
+};
+
+struct minmea_sentence_gsa {
+ char mode;
+ int fix_type;
+ int sats[12];
+ struct minmea_float pdop;
+ struct minmea_float hdop;
+ struct minmea_float vdop;
+};
+
+struct minmea_sat_info {
+ int nr;
+ int elevation;
+ int azimuth;
+ int snr;
+};
+
+struct minmea_sentence_gsv {
+ int total_msgs;
+ int msg_nr;
+ int total_sats;
+ struct minmea_sat_info sats[4];
+};
+
+/**
+ * Calculate raw sentence checksum. Does not check sentence integrity.
+ */
+uint8_t minmea_checksum(const char *sentence);
+
+/**
+ * Check sentence validity and checksum. Returns true for valid sentences.
+ */
+bool minmea_check(const char *sentence, bool strict);
+
+/**
+ * Determine talker identifier.
+ */
+bool minmea_talker_id(char talker[3], const char *sentence);
+
+/**
+ * Determine sentence identifier.
+ */
+enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict);
+
+/**
+ * Scanf-like processor for NMEA sentences. Supports the following formats:
+ * c - single character (char *)
+ * d - direction, returned as 1/-1, default 0 (int *)
+ * f - fractional, returned as value + scale (int *, int *)
+ * i - decimal, default zero (int *)
+ * s - string (char *)
+ * t - talker identifier and type (char *)
+ * T - date/time stamp (int *, int *, int *)
+ * Returns true on success. See library source code for details.
+ */
+bool minmea_scan(const char *sentence, const char *format, ...);
+
+/*
+ * Parse a specific type of sentence. Return true on success.
+ */
+bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence);
+bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence);
+bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence);
+bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence);
+bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence);
+bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence);
+
+/**
+ * Convert GPS UTC date/time representation to a UNIX timestamp.
+ */
+int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_);
+
+/**
+ * Rescale a fixed-point value to a different scale. Rounds towards zero.
+ */
+static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale)
+{
+ if (f->scale == 0)
+ return 0;
+ if (f->scale == new_scale)
+ return f->value;
+ if (f->scale > new_scale)
+ return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale);
+ else
+ return f->value * (new_scale/f->scale);
+}
+
+/**
+ * Convert a fixed-point value to a floating-point value.
+ * Returns NaN for "unknown" values.
+ */
+static inline float minmea_tofloat(struct minmea_float *f)
+{
+ if (f->scale == 0)
+ return NAN;
+ return (float) f->value / (float) f->scale;
+}
+
+/**
+ * Convert a raw coordinate to a floating point DD.DDD... value.
+ * Returns NaN for "unknown" values.
+ */
+static inline float minmea_tocoord(struct minmea_float *f)
+{
+ if (f->scale == 0)
+ return NAN;
+ int_least32_t degrees = f->value / (f->scale * 100);
+ int_least32_t minutes = f->value % (f->scale * 100);
+ return (float) degrees + (float) minutes / (60 * f->scale);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINMEA_H */
+
+/* vim: set ts=4 sw=4 et: */
diff --git a/src/fsm/ubx.c b/src/fsm/ubx.c
deleted file mode 100644
index 19c427b..0000000
--- a/src/fsm/ubx.c
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Matthias P. Braendli
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
-*/
-
-#include <stdint.h>
-#include <string.h>
-#include "ubx.h"
-
-static ubx_callbacks_t ubx_cb;
-
-void ubx_register(const ubx_callbacks_t *cb)
-{
- memcpy(&ubx_cb, cb, sizeof(ubx_callbacks_t));
-}
-
-static ubx_nav_sol_t navsol_temp;
-static ubx_nav_timeutc_t navtimeutc_temp;
-static ubx_tim_tm2_t timtm2_temp;
-
-// State machine variables
-static ubx_state_t ubxState = UBXSTATE_SYNC1;
-static uint16_t msglen;
-static uint8_t* messagedata;
-static uint8_t msgclass;
-static uint8_t msgid;
-static uint8_t cka, ckb;
-
-int ubx_parse(const uint8_t c)
-{
- switch (ubxState)
- {
- case UBXSTATE_SYNC1:
- if (c == UBX_SYNC1_CHAR) {
- ubxState = UBXSTATE_SYNC2;
- }
- break;
-
- case UBXSTATE_SYNC2:
- if (c == UBX_SYNC2_CHAR) {
- ubxState = UBXSTATE_CLASS;
- }
- else if (c == UBX_SYNC1_CHAR) {
- ubxState = UBXSTATE_SYNC2;
- }
- else {
- ubxState = UBXSTATE_SYNC1;
- }
- break;
-
- case UBXSTATE_CLASS:
- ubxState = UBXSTATE_ID;
- msgclass = c;
-
- // re-init checksum
- cka = c;
- ckb = cka;
- break;
-
- case UBXSTATE_ID:
- msgid = c;
- ubxState = UBXSTATE_LEN1;
- if (msgclass == UBX_CLASS_NAV) {
- if (c == UBX_NAV_SOL) {
- messagedata = (uint8_t*)&navsol_temp;
- }
- else if (c == UBX_NAV_TIMEUTC) {
- messagedata = (uint8_t*)&navtimeutc_temp;
- }
- }
- else if (msgclass == UBX_CLASS_TIM && c == UBX_TIM_TM2) {
- messagedata = (uint8_t*)&timtm2_temp;
- }
- else {
- msgclass = 0;
- msgid = 0;
- ubxState = UBXSTATE_SYNC1;
- }
- cka += c;
- ckb += cka;
- break;
-
- case UBXSTATE_LEN1:
- msglen = (uint16_t)c;
- ubxState = UBXSTATE_LEN2;
- cka += c;
- ckb += cka;
- break;
-
- case UBXSTATE_LEN2:
- msglen |= ((uint16_t)c) << 8;
- ubxState = UBXSTATE_DATA;
- cka += c;
- ckb += cka;
- break;
-
- case UBXSTATE_DATA:
- cka += c;
- ckb += cka;
- if (--msglen) {
- *(messagedata++) = c;
- }
- else {
- ubxState = UBXSTATE_CKA;
- }
- break;
-
- case UBXSTATE_CKA:
- if (c == cka) {
- ubxState = UBXSTATE_CKB;
- }
- else {
- ubxState = UBXSTATE_SYNC1;
- }
- break;
-
- case UBXSTATE_CKB:
- if (c == ckb) {
- if (msgclass == UBX_CLASS_NAV) {
- if (msgid == UBX_NAV_SOL && ubx_cb.new_ubx_nav_sol) {
- ubx_cb.new_ubx_nav_sol(&navsol_temp);
- }
- else if (msgid == UBX_NAV_TIMEUTC && ubx_cb.new_ubx_nav_timeutc) {
- ubx_cb.new_ubx_nav_timeutc(&navtimeutc_temp);
- }
- }
- else if (msgclass == UBX_CLASS_TIM) {
- if (msgid == UBX_TIM_TM2 && ubx_cb.new_ubx_tim_tm2) {
- ubx_cb.new_ubx_tim_tm2(&timtm2_temp);
- }
- }
- }
-
- ubxState = UBXSTATE_SYNC1;
- }
- return ubxState;
-}
-
diff --git a/src/fsm/ubx.h b/src/fsm/ubx.h
deleted file mode 100644
index e80464e..0000000
--- a/src/fsm/ubx.h
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Matthias P. Braendli
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
-*/
-
-/* u-blox UBX protocol parser, register callbacks for specific messages using
- * ubx_register() and and push in chars with ubx_parse()
- */
-
-#ifndef __UBX_H_
-#define __UBX_H_
-
-#include <stdint.h>
-
-// message sync bytes
-#define UBX_SYNC1_CHAR 0xB5
-#define UBX_SYNC2_CHAR 0x62
-
-// UBX Class
-typedef enum app_ubx_class_e // {{{
-{
- UBX_CLASS_NAV = 0x01, // Navigation Results: Position, Speed, Time, Acc, Heading, DOP, SVs used
- UBX_CLASS_RXM = 0x02, // Receiver Manager Messages: Satellite Status, RTC Status
- UBX_CLASS_TRK = 0x03, // Tracking Information: Measurements, Commands
- UBX_CLASS_INF = 0x04, // Information Messages: Printf-Style Messages, with IDs such as Error, Warning, Notice
- UBX_CLASS_ACK = 0x05, // Ack/Nack Messages: as replies to CFG Input Messages
- UBX_CLASS_CFG = 0x06, // Configuration Input Messages: Set Dynamic Model, Set DOP Mask, Set Baud Rate, etc.
- UBX_CLASS_MON = 0x0A, // Monitoring Messages: Communication Status, CPU Load, Stack Usage, Task Status
- UBX_CLASS_TIM = 0x0D, // Timing Messages: Timepulse Output, Timemark Results
-} app_ubx_class_t; //}}}
-
-// UBX Message Id
-typedef enum app_ubx_id_e // {{{
-{
- // ACK
- UBX_ACK_NACK = 0x00, // Not acknowledge
- UBX_ACK_ACK = 0x01, // acknowledge
-
- // NAV
- UBX_NAV_STATUS = 0x03, // Navigation status
- UBX_NAV_SOL = 0x06, // Navigation solution
- UBX_NAV_PVT = 0x07, // Navigation PVT solution
- UBX_NAV_TIMEGPS = 0x20, // GPS time
- UBX_NAV_TIMEUTC = 0x21, // UTC time
-
- // TIM
- UBX_TIM_TM2 = 0x03, // timemark data
-} app_ubx_id_t; //}}}
-
-// ubx parser state
-typedef enum ubx_state_e
-{
- UBXSTATE_SYNC1,
- UBXSTATE_SYNC2,
- UBXSTATE_CLASS,
- UBXSTATE_ID,
- UBXSTATE_LEN1,
- UBXSTATE_LEN2,
- UBXSTATE_DATA,
- UBXSTATE_CKA,
- UBXSTATE_CKB
-} ubx_state_t;
-
-#define GPSFIX_3D 0x3
-
-// UBX messages to enable these messages on the I2C port
-#define UBX_ENABLE_NAV_SOL \
- 0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, \
- 0x01, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
- 0x17, 0xDB
-
-#define UBX_ENABLE_NAV_TIMEUTC \
- 0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, \
- 0x01, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
- 0x32, 0x98
-
-#define UBX_ENABLE_TIM_TM2 \
- 0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, \
- 0x0D, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
- 0x20, 0x26
-
-typedef struct ubx_nav_sol_s
-{
- uint32_t itow; // ms GPS Millisecond Time of Week
- int32_t frac; // ns remainder of rounded ms above
- int16_t week; // GPS week
- uint8_t GPSfix; // GPSfix Type, range 0..6
- uint8_t Flags; // Navigation Status Flags
- int32_t ECEF_X; // cm ECEF X coordinate
- int32_t ECEF_Y; // cm ECEF Y coordinate
- int32_t ECEF_Z; // cm ECEF Z coordinate
- int32_t PAcc; // cm 3D Position Accuracy Estimate
- int32_t ECEFVX; // cm/s ECEF X velocity
- int32_t ECEFVY; // cm/s ECEF Y velocity
- int32_t ECEFVZ; // cm/s ECEF Z velocity
- uint32_t SAcc; // cm/s Speed Accuracy Estimate
- uint16_t PDOP; // 0.01 Position DOP
- uint8_t res1; // reserved
- uint8_t numSV; // Number of SVs used in navigation solution
- uint32_t res2; // reserved
-} __attribute__((packed)) ubx_nav_sol_t;
-
-
-typedef struct ubx_nav_velned_s
-{
- uint32_t itow; // ms GPS Millisecond Time of Week
- int32_t VEL_N; // cm/s NED north velocity
- int32_t VEL_E; // cm/s NED east velocity
- int32_t VEL_D; // cm/s NED down velocity
- int32_t Speed; // cm/s Speed (3-D)
- int32_t GSpeed; // cm/s Ground Speed (2-D)
- int32_t Heading; // 1e-05 deg Heading 2-D
- uint32_t SAcc; // cm/s Speed Accuracy Estimate
- uint32_t CAcc; // deg Course / Heading Accuracy Estimate
-} __attribute__((packed)) ubx_nav_velned_t;
-
-typedef struct ubx_nav_posllh_s
-{
- uint32_t itow; // ms GPS Millisecond Time of Week
- int32_t LON; // 1e-07 deg Longitude
- int32_t LAT; // 1e-07 deg Latitude
- int32_t HEIGHT; // mm Height above Ellipsoid
- int32_t HMSL; // mm Height above mean sea level
- uint32_t Hacc; // mm Horizontal Accuracy Estimate
- uint32_t Vacc; // mm Vertical Accuracy Estimate
-} __attribute__((packed)) ubx_nav_posllh_t;
-
-typedef struct ubx_tim_tm2_s
-{
- uint8_t ch; // marker channel (0 or 1)
- uint8_t flags; // bitmask
- uint16_t count; // edge counter
- uint16_t wnoR; // week number of last rising edge
- uint16_t wnoF; // week number of last falling edge
- uint32_t towMsR; // tow in ms of last rising edge [ms]
- uint32_t towSubMsR; // millisecond fraction of tow of last rising edge [ns]
- uint32_t towMsF; // tow in ms of last rising edge [ms]
- uint32_t towSubMsF; // millisecond fraction of tow of last rising edge [ns]
- uint32_t accEst; // accuracy estimate [ns]
-} __attribute__((packed)) ubx_tim_tm2_t;
-
-
-typedef struct ubx_nav_timeutc_s
-{
- uint32_t itow; // GPS Millisecond Time of Week
- uint32_t tacc; // time accuracy estimate [ns]
- int32_t nano; // Nanoseconds of second
- uint16_t year; // Year, range 1999..2099 (UTC)
- uint8_t month; // Month, range 1..12 (UTC)
- uint8_t day; // Day of Month, range 1..31 (UTC)
- uint8_t hour; // Hour of Day, range 0..23 (UTC)
- uint8_t min; // Minute of Hour, range 0..59 (UTC)
- uint8_t sec; // Seconds of Minute, range 0..59 (UTC)
- uint8_t valid; // validity flag
-} __attribute__((packed)) ubx_nav_timeutc_t;
-
-
-typedef struct ubx_callbacks_s
-{
- void (*new_ubx_nav_sol)(ubx_nav_sol_t *sol);
- void (*new_ubx_nav_timeutc)(ubx_nav_timeutc_t *timeutc);
- void (*new_ubx_tim_tm2)(ubx_tim_tm2_t *posllh);
-} ubx_callbacks_t;
-
-/* Register callbacks when a complete message was received */
-void ubx_register(const ubx_callbacks_t *cb);
-
-/* Parse a new incoming byte, calls the callbacks if a complete message
- * was received. Returns the current parser state
- */
-int ubx_parse(const uint8_t c);
-
-#endif
-
diff --git a/src/fsm/usart.c b/src/fsm/usart.c
new file mode 100644
index 0000000..013c4db
--- /dev/null
+++ b/src/fsm/usart.c
@@ -0,0 +1,155 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Matthias P. Braendli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+#include "common.h"
+#include "usart.h"
+#include <stm32f4xx.h>
+#include <stm32f4xx_usart.h>
+#include <stm32f4xx_conf.h>
+#include "FreeRTOS.h"
+#include "task.h"
+#include "queue.h"
+
+// USART 3 on PD8 and PD9
+const uint16_t GPIOD_PIN_USART3_TX = GPIO_Pin_8;
+const uint16_t GPIOD_PIN_USART3_RX = GPIO_Pin_9;
+
+// The ISR writes into this buffer
+static char nmea_sentence[MAX_NMEA_SENTENCE_LEN];
+static int nmea_sentence_last_written = 0;
+
+// Once a completed NMEA sentence is received in the ISR,
+// it is appended to this queue
+static QueueHandle_t usart_nmea_queue;
+
+void usart_init()
+{
+ usart_nmea_queue = xQueueCreate(15, MAX_NMEA_SENTENCE_LEN);
+ if (usart_nmea_queue == 0) {
+ while(1); /* fatal error */
+ }
+
+ // Setup GPIO D and connect to USART 3
+ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
+
+ GPIO_InitTypeDef GPIO_InitStruct;
+ GPIO_InitStruct.GPIO_Pin = GPIOD_PIN_USART3_RX | GPIOD_PIN_USART3_TX;
+ GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
+ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
+ GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
+ GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
+ GPIO_Init(GPIOD, &GPIO_InitStruct);
+
+ GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_USART3);
+ GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_USART3);
+
+ // Setup USART3 for 9600,8,N,1
+
+ USART_InitTypeDef USART_InitStruct;
+ USART_InitStruct.USART_BaudRate = 9600;
+ USART_InitStruct.USART_WordLength = USART_WordLength_8b;
+ USART_InitStruct.USART_StopBits = USART_StopBits_1;
+ USART_InitStruct.USART_Parity = USART_Parity_No;
+ USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
+ USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
+ USART_Init(USART3, &USART_InitStruct);
+
+
+ // enable the USART3 receive interrupt
+ USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
+
+ NVIC_InitTypeDef NVIC_InitStructure;
+ NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
+ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
+ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
+ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
+ NVIC_Init(&NVIC_InitStructure);
+
+ NVIC_SetPriority(USART3_IRQn, 6);
+
+ // finally this enables the complete USART3 peripheral
+ USART_Cmd(USART3, ENABLE);
+}
+
+void usart_puts(const char* str)
+{
+ while(*str) {
+ // wait until data register is empty
+ while ( !(USART3->SR & 0x00000040) );
+ USART_SendData(USART3, *str);
+ str++;
+ }
+}
+
+int usart_get_nmea_sentence(char* nmea)
+{
+ return xQueueReceive(usart_nmea_queue, nmea, portMAX_DELAY);
+}
+
+
+static void usart_clear_nmea_buffer(void)
+{
+ for (int i = 0; i < MAX_NMEA_SENTENCE_LEN; i++) {
+ nmea_sentence[i] = '\0';
+ }
+ nmea_sentence_last_written = 0;
+}
+
+void USART3_IRQHandler(void)
+{
+ if (USART_GetITStatus(USART3, USART_IT_RXNE)) {
+ char t = USART3->DR;
+
+ if (nmea_sentence_last_written == 0) {
+ if (t == '$') {
+ // Likely new start of sentence
+ nmea_sentence[nmea_sentence_last_written] = t;
+ nmea_sentence_last_written++;
+ }
+ }
+ else if (nmea_sentence_last_written < MAX_NMEA_SENTENCE_LEN) {
+ nmea_sentence[nmea_sentence_last_written] = t;
+ nmea_sentence_last_written++;
+
+ if (t == '\n') {
+ int success = xQueueSendToBackFromISR(
+ usart_nmea_queue,
+ nmea_sentence,
+ NULL);
+
+ if (success == pdFALSE) {
+ trigger_fault(FAULT_SOURCE_USART);
+ }
+
+ usart_clear_nmea_buffer();
+ }
+ }
+ else {
+ // Buffer overrun without a meaningful NMEA message.
+ usart_clear_nmea_buffer();
+ }
+ }
+}
+
diff --git a/src/fsm/usart.h b/src/fsm/usart.h
new file mode 100644
index 0000000..4ab8b22
--- /dev/null
+++ b/src/fsm/usart.h
@@ -0,0 +1,42 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Matthias P. Braendli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+/* This handles the USART to the GPS receiver, and fills a queue of
+ * NMEA messages.
+ */
+
+#ifndef __USART_H_
+#define __USART_H_
+
+#define MAX_NMEA_SENTENCE_LEN 256
+
+void usart_init(void);
+
+void usart_puts(const char* str);
+
+// Get a MAX_NMEA_SENTENCE_LEN sized NMEA sentence
+int usart_get_nmea_sentence(char* nmea);
+
+#endif //__USART_H_
+