aboutsummaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/includes/GPIO/leds.h9
-rw-r--r--src/common/includes/GPIO/usart.h (renamed from src/common/includes/Core/usart.h)9
-rw-r--r--src/common/includes/GPS/gps.h45
-rw-r--r--src/common/includes/GPS/minmea.h234
-rw-r--r--src/common/src/Core/common.c4
-rw-r--r--src/common/src/Core/main.c230
-rw-r--r--src/common/src/GPIO/leds.c1
-rw-r--r--src/common/src/GPIO/usart.c136
-rw-r--r--src/common/src/GPS/gps.c129
-rw-r--r--src/common/src/GPS/minmea.c572
10 files changed, 1274 insertions, 95 deletions
diff --git a/src/common/includes/GPIO/leds.h b/src/common/includes/GPIO/leds.h
new file mode 100644
index 0000000..70c57d2
--- /dev/null
+++ b/src/common/includes/GPIO/leds.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#define LED_GREEN 1
+#define LED_ORANGE 2
+#define LED_RED 3
+#define LED_BLUE 4
+
+void leds_turn_off(int);
+void leds_turn_on(int);
diff --git a/src/common/includes/Core/usart.h b/src/common/includes/GPIO/usart.h
index 9d9b59c..62c86c9 100644
--- a/src/common/includes/Core/usart.h
+++ b/src/common/includes/GPIO/usart.h
@@ -1,7 +1,7 @@
/*
* The MIT License (MIT)
*
- * Copyright (c) 2016 Matthias P. Braendli
+ * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -53,5 +53,12 @@ void usart_debug_puts(const char* str);
// Return 1 on success
int usart_get_nmea_sentence(char* nmea);
+void usart_debug_timestamp();
+
+void usart_gps_specific_init();
+
+void usart_process_char(char);
+void usart_gps_process_char(char);
+
#endif //__USART_H_
diff --git a/src/common/includes/GPS/gps.h b/src/common/includes/GPS/gps.h
new file mode 100644
index 0000000..768ade1
--- /dev/null
+++ b/src/common/includes/GPS/gps.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+*/
+
+#pragma once
+
+#include <stdint.h>
+#include <time.h>
+
+/* Setup GPS receiver over USART and parse time */
+
+/* USART connections:
+ * board TX to GPS RX on PD8
+ * board RX to GPS TX on PD9
+ */
+
+// Setup communication and GPS receiver
+void gps_init();
+
+// Return 1 of the GPS is receiving time
+int gps_locked();
+
+// Get current time from GPS
+// Returns 1 if time is valid, 0 otherwise
+int gps_utctime(struct tm *timeutc);
diff --git a/src/common/includes/GPS/minmea.h b/src/common/includes/GPS/minmea.h
new file mode 100644
index 0000000..6d7fe18
--- /dev/null
+++ b/src/common/includes/GPS/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/common/src/Core/common.c b/src/common/src/Core/common.c
index 2eee88f..8c63917 100644
--- a/src/common/src/Core/common.c
+++ b/src/common/src/Core/common.c
@@ -23,7 +23,7 @@
*/
#include "Core/common.h"
-#include "Core/usart.h"
+#include "GPIO/usart.h"
#include "FreeRTOS.h"
#include "timers.h"
/* #include "Core/gps.h" */
@@ -118,7 +118,7 @@ int local_time(struct tm *time)
{
const int local_time_offset=1; // hours
- int valid = 0; // TODO gps_utctime(time);
+ int valid = gps_utctime(time);
if (valid) {
time->tm_hour += local_time_offset;
diff --git a/src/common/src/Core/main.c b/src/common/src/Core/main.c
index ac41355..feac593 100644
--- a/src/common/src/Core/main.c
+++ b/src/common/src/Core/main.c
@@ -38,12 +38,13 @@
/* #include "cw.h" */
/* #include "pio.h" */
/* #include "i2c.h" */
-/* #include "gps.h" */
+#include "GPS/gps.h"
/* #include "fsm.h" */
-/* #include "common.h" */
-#include "Core/usart.h"
+#include "Core/common.h"
+#include "GPIO/usart.h"
/* #include "delay.h" */
/* #include "temperature.h" */
+#include "GPIO/leds.h"
#include "vc.h"
@@ -70,10 +71,15 @@ void vApplicationStackOverflowHook( TaskHandle_t xTask,
while (1) {};
}
+void * threadscheduler(void * arg) {
+ /* Start the RTOS Scheduler */
+ vTaskStartScheduler();
+}
+
int main(void) {
init();
/* delay_init(); */
- /* usart_init(); */
+ usart_init();
usart_debug_puts("\r\n******* glutt-o-matique version " GIT_VERSION " *******\r\n");
/* */
/* if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET) */
@@ -82,26 +88,54 @@ int main(void) {
/* } */
/* RCC_ClearFlag(); */
/* */
- /* TaskHandle_t task_handle; */
- /* xTaskCreate( */
- /* launcher_task, */
- /* "Launcher", */
- /* configMINIMAL_STACK_SIZE, */
- /* (void*) NULL, */
- /* tskIDLE_PRIORITY + 2UL, */
- /* &task_handle); */
- /* */
- /* if (!task_handle) { */
- /* trigger_fault(FAULT_SOURCE_MAIN); */
- /* } */
- /* */
- /* #<{(| Start the RTOS Scheduler |)}># */
- /* vTaskStartScheduler(); */
- /* */
- /* #<{(| HALT |)}># */
+ TaskHandle_t task_handle;
+ xTaskCreate(
+ launcher_task,
+ "Launcher",
+ configMINIMAL_STACK_SIZE,
+ (void*) NULL,
+ tskIDLE_PRIORITY + 2UL,
+ &task_handle);
+
+ if (!task_handle) {
+ trigger_fault(FAULT_SOURCE_MAIN);
+ }
+
+ vTaskStartScheduler();
+ /* pthread_t pth; */
+ /* pthread_create(&pth, NULL, threadscheduler, "processing..."); */
+
+ /* HALT */
while(1);
}
+
+static void test_task(void *pvParameters) {
+
+ int i = 0;
+
+ while(1) {
+ vTaskDelay(1000 / portTICK_RATE_MS);
+
+ if (i == 0) {
+ i = 1;
+ leds_turn_on(LED_RED);
+
+ } else {
+ i = 0;
+ leds_turn_off(LED_RED);
+ }
+
+ char * poney = "$GNRMC,202140.00,A,4719.59789,N,00830.92084,E,0.012,,310516,,,A*65\n";
+ gps_usart_send(poney);
+ /* char * poney2 = "$GNRMC,202141.00,A,4719.59788,N,00830.92085,E,0.012,,310516,,,A*64"; */
+ /* gps_usart_send(poney2); */
+ /* char * poney3 = "$GNRMC,202142.00,A,4719.59785,N,00830.92087,E,0.010,,310516,,,A*6A"; */
+ /* gps_usart_send(poney3); */
+ }
+
+}
+
// Launcher task is here to make sure the scheduler is
// already running when calling the init functions.
static void launcher_task(void *pvParameters)
@@ -118,15 +152,15 @@ static void launcher_task(void *pvParameters)
/* usart_debug_puts("common init\r\n"); */
/* common_init(); */
/* */
- /* usart_debug_puts("GPS init\r\n"); */
- /* gps_init(); */
+ usart_debug_puts("GPS init\r\n");
+ gps_init();
/* */
/* usart_debug_puts("DS18B20 init\r\n"); */
/* temperature_init(); */
/* */
/* usart_debug_puts("TaskButton init\r\n"); */
/* */
- /* TaskHandle_t task_handle; */
+ TaskHandle_t task_handle;
/* xTaskCreate( */
/* detect_button_press, */
/* "TaskButton", */
@@ -153,20 +187,20 @@ static void launcher_task(void *pvParameters)
/* trigger_fault(FAULT_SOURCE_MAIN); */
/* } */
/* */
- /* usart_debug_puts("TaskGPS init\r\n"); */
- /* */
- /* xTaskCreate( */
- /* gps_monit_task, */
- /* "TaskGPSMonit", */
- /* 4*configMINIMAL_STACK_SIZE, */
- /* (void*) NULL, */
- /* tskIDLE_PRIORITY + 2UL, */
- /* &task_handle); */
- /* */
- /* if (!task_handle) { */
- /* trigger_fault(FAULT_SOURCE_MAIN); */
- /* } */
- /* */
+ usart_debug_puts("TaskGPS init\r\n");
+
+ xTaskCreate(
+ gps_monit_task,
+ "TaskGPSMonit",
+ 4*configMINIMAL_STACK_SIZE,
+ (void*) NULL,
+ tskIDLE_PRIORITY + 2UL,
+ &task_handle);
+
+ if (!task_handle) {
+ trigger_fault(FAULT_SOURCE_MAIN);
+ }
+
/* usart_debug_puts("Audio init\r\n"); */
/* */
/* InitializeAudio(Audio16000HzSettings); */
@@ -179,19 +213,29 @@ static void launcher_task(void *pvParameters)
/* */
/* // By default, let's the audio off to save power */
/* AudioOff(); */
- /* */
- /* usart_debug_puts("Init done.\r\n"); */
- /* */
- /* #<{(| We are done now, suspend this task */
- /* * With FreeDOS' heap_1.c, we cannot delete it. */
- /* * See freertos.org -> More Advanced... -> Memory Management */
- /* * for more info. */
- /* |)}># */
- /* while (1) { */
- /* vTaskSuspend(NULL); */
- /* } */
+
+ usart_debug_puts("Init done.\r\n");
+
+ xTaskCreate(
+ test_task,
+ "TaskFSM",
+ 4*configMINIMAL_STACK_SIZE,
+ (void*) NULL,
+ tskIDLE_PRIORITY + 2UL,
+ &task_handle);
+
+ /* We are done now, suspend this task
+ * With FreeDOS' heap_1.c, we cannot delete it.
+ * See freertos.org -> More Advanced... -> Memory Management
+ * for more info.
+ */
+
+ while (1) {
+ vTaskSuspend(NULL);
+ }
}
+
static void detect_button_press(void *pvParameters)
{
/* int pin_high_count = 0; */
@@ -264,50 +308,52 @@ static void audio_callback(void* context, int select_buffer)
/* ProvideAudioBufferWithoutBlocking(samples, samples_len); */
}
-/* static struct tm gps_time; */
+static struct tm gps_time;
static void gps_monit_task(void *pvParameters) {
-/* GPIO_SetBits(GPIOD, GPIOD_BOARD_LED_BLUE); */
-/* */
-/* int t_gps_print_latch = 0; */
-/* */
-/* while (1) { */
-/* struct tm time; */
-/* int time_valid = local_time(&time); */
-/* */
-/* if (time_valid) { */
-/* if (time.tm_sec % 4 >= 2) { */
-/* GPIO_SetBits(GPIOD, GPIOD_BOARD_LED_BLUE); */
-/* } */
-/* else { */
-/* GPIO_ResetBits(GPIOD, GPIOD_BOARD_LED_BLUE); */
-/* } */
-/* */
-/* // Even hours: tm_trigger=1, odd hours: tm_trigger=0 */
-/* tm_trigger = (time.tm_hour + 1) % 2; */
-/* } */
-/* */
-/* gps_utctime(&gps_time); */
-/* */
-/* if (gps_time.tm_sec % 30 == 0 && t_gps_print_latch == 0) { */
-/* usart_debug("T_GPS %04d-%02d-%02d %02d:%02d:%02d\r\n", */
-/* gps_time.tm_year, gps_time.tm_mon, gps_time.tm_mday, */
-/* gps_time.tm_hour, gps_time.tm_min, gps_time.tm_sec); */
-/* */
-/* usart_debug("TIME %04d-%02d-%02d %02d:%02d:%02d\r\n", */
-/* time.tm_year, time.tm_mon, time.tm_mday, */
-/* time.tm_hour, time.tm_min, time.tm_sec); */
-/* */
-/* t_gps_print_latch = 1; */
-/* } */
-/* if (gps_time.tm_sec % 30 > 0) { */
-/* t_gps_print_latch = 0; */
-/* } */
-/* */
-/* vTaskDelay(100 / portTICK_RATE_MS); */
-/* */
-/* // Reload watchdog */
-/* IWDG_ReloadCounter(); */
-/* } */
+
+ leds_turn_on(LED_BLUE);
+
+ int t_gps_print_latch = 0;
+
+ while (1) {
+ struct tm time;
+ int time_valid = local_time(&time);
+
+ if (time_valid) {
+ printf("Valid\n");
+ if (time.tm_sec % 4 >= 2) {
+ leds_turn_on(LED_BLUE);
+ }
+ else {
+ leds_turn_off(LED_BLUE);
+ }
+
+ // Even hours: tm_trigger=1, odd hours: tm_trigger=0
+ tm_trigger = (time.tm_hour + 1) % 2;
+ }
+
+ gps_utctime(&gps_time);
+
+ if (gps_time.tm_sec % 30 == 0 && t_gps_print_latch == 0) {
+ usart_debug("T_GPS %04d-%02d-%02d %02d:%02d:%02d\r\n",
+ gps_time.tm_year, gps_time.tm_mon, gps_time.tm_mday,
+ gps_time.tm_hour, gps_time.tm_min, gps_time.tm_sec);
+
+ usart_debug("TIME %04d-%02d-%02d %02d:%02d:%02d\r\n",
+ time.tm_year, time.tm_mon, time.tm_mday,
+ time.tm_hour, time.tm_min, time.tm_sec);
+
+ t_gps_print_latch = 1;
+ }
+ if (gps_time.tm_sec % 30 > 0) {
+ t_gps_print_latch = 0;
+ }
+
+ vTaskDelay(100 / portTICK_RATE_MS);
+
+ // Reload watchdog //TODO
+ /* IWDG_ReloadCounter(); */
+ }
}
/* static struct fsm_input_signals_t fsm_input; */
diff --git a/src/common/src/GPIO/leds.c b/src/common/src/GPIO/leds.c
new file mode 100644
index 0000000..df2adea
--- /dev/null
+++ b/src/common/src/GPIO/leds.c
@@ -0,0 +1 @@
+#include "../../../common/includes/GPIO/leds.h"
diff --git a/src/common/src/GPIO/usart.c b/src/common/src/GPIO/usart.c
new file mode 100644
index 0000000..77272f8
--- /dev/null
+++ b/src/common/src/GPIO/usart.c
@@ -0,0 +1,136 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <inttypes.h>
+#include "Core/common.h"
+#include "GPIO/usart.h"
+#include "FreeRTOS.h"
+#include "task.h"
+#include "queue.h"
+
+// 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_gps_init() {
+ usart_nmea_queue = xQueueCreate(15, MAX_NMEA_SENTENCE_LEN);
+ if (usart_nmea_queue == 0) {
+ while(1); /* fatal error */
+ }
+
+ usart_gps_specific_init();
+
+}
+
+void usart_gps_puts(const char* str) {
+ vTaskSuspendAll();
+ return usart_puts(USART3, str);
+ xTaskResumeAll();
+}
+
+#define MAX_MSG_LEN 80
+static char usart_debug_message[MAX_MSG_LEN];
+
+void usart_debug_timestamp() {
+ // Don't call printf here, to reduce stack usage
+ uint64_t now = timestamp_now();
+ if (now == 0) {
+ usart_puts(USART2, "[0] ");
+ }
+ else {
+ char ts_str[64];
+ int i = 63;
+
+ ts_str[i--] = '\0';
+ ts_str[i--] = ' ';
+ ts_str[i--] = ']';
+
+ while (now > 0 && i >= 0) {
+ ts_str[i--] = '0' + (now % 10);
+ now /= 10;
+ }
+ ts_str[i] = '[';
+
+ usart_puts(USART2, &ts_str[i]);
+ }
+}
+
+void usart_debug(const char *format, ...) {
+ va_list list;
+ va_start(list, format);
+ vsnprintf(usart_debug_message, MAX_MSG_LEN-1, format, list);
+
+ vTaskSuspendAll();
+ usart_debug_timestamp();
+ usart_puts(USART2, usart_debug_message);
+ xTaskResumeAll();
+
+ va_end(list);
+}
+
+void usart_debug_puts(const char* str) {
+ vTaskSuspendAll();
+ usart_debug_timestamp();
+ usart_puts(USART2, str);
+ xTaskResumeAll();
+}
+
+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 usart_process_char(char c) {
+
+ if (c == 'h') {
+ usart_debug_puts("help: no commands supported yet!\r\n");
+ } else {
+ usart_debug("Unknown command %c\r\n", c);
+ }
+
+}
+
+void usart_gps_process_char(char c) {
+
+ if (nmea_sentence_last_written == 0) {
+ if (c == '$') {
+ // Likely new start of sentence
+ nmea_sentence[nmea_sentence_last_written] = c;
+ nmea_sentence_last_written++;
+ }
+ }
+ else if (nmea_sentence_last_written < MAX_NMEA_SENTENCE_LEN) {
+ nmea_sentence[nmea_sentence_last_written] = c;
+ nmea_sentence_last_written++;
+
+ if (c == '\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/common/src/GPS/gps.c b/src/common/src/GPS/gps.c
new file mode 100644
index 0000000..61c8c48
--- /dev/null
+++ b/src/common/src/GPS/gps.c
@@ -0,0 +1,129 @@
+/*
+ * 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 "FreeRTOS.h"
+#include "FreeRTOSConfig.h"
+#include "task.h"
+#include "semphr.h"
+#include "Core/common.h"
+#include "GPS/gps.h"
+#include "GPS/minmea.h"
+#include "GPIO/usart.h"
+
+
+TickType_t gps_timeutc_last_updated = 0;
+static struct tm gps_timeutc;
+static int gps_timeutc_valid;
+
+const TickType_t gps_data_validity_timeout = 10000ul / portTICK_PERIOD_MS;
+
+static void gps_task(void *pvParameters);
+
+SemaphoreHandle_t timeutc_semaphore;
+
+// Get current time from GPS
+int gps_utctime(struct tm *timeutc) {
+ int valid = 0;
+
+ xSemaphoreTake(timeutc_semaphore, portMAX_DELAY);
+ if (xTaskGetTickCount() - gps_timeutc_last_updated < gps_data_validity_timeout) {
+ timeutc->tm_year = gps_timeutc.tm_year;
+ timeutc->tm_mon = gps_timeutc.tm_mon;
+ timeutc->tm_mday = gps_timeutc.tm_mday;
+ timeutc->tm_hour = gps_timeutc.tm_hour;
+ timeutc->tm_min = gps_timeutc.tm_min;
+ timeutc->tm_sec = gps_timeutc.tm_sec;
+ valid = gps_timeutc_valid;
+ }
+ xSemaphoreGive(timeutc_semaphore);
+
+ return valid;
+}
+
+#define RXBUF_LEN MAX_NMEA_SENTENCE_LEN
+static char rxbuf[RXBUF_LEN];
+
+static void gps_task(void *pvParameters) {
+ // Periodically reinit the GPS
+ while (1) {
+ taskYIELD();
+
+ 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.tm_year = 2000 + frame.date.year;
+ gps_timeutc.tm_mon = frame.date.month;
+ gps_timeutc.tm_mday = frame.date.day;
+ gps_timeutc.tm_hour = frame.time.hours;
+ gps_timeutc.tm_min = frame.time.minutes;
+ gps_timeutc.tm_sec = frame.time.seconds;
+ gps_timeutc_valid = frame.valid;
+ gps_timeutc_last_updated = xTaskGetTickCount();
+ xSemaphoreGive(timeutc_semaphore);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void gps_init() {
+ gps_timeutc_valid = 0;
+
+ usart_gps_init();
+
+ timeutc_semaphore = xSemaphoreCreateBinary();
+
+ if (timeutc_semaphore == NULL) {
+ trigger_fault(FAULT_SOURCE_GPS);
+ } else {
+ xSemaphoreGive(timeutc_semaphore);
+ }
+
+ xTaskCreate(
+ gps_task,
+ "TaskGPS",
+ 4*configMINIMAL_STACK_SIZE,
+ (void*) NULL,
+ tskIDLE_PRIORITY + 2UL,
+ NULL);
+}
+
+// Return 1 of the GPS is receiving time
+int gps_locked() {
+ if (xTaskGetTickCount() - gps_timeutc_last_updated < gps_data_validity_timeout) {
+ return gps_timeutc_valid;
+ } else {
+ return 0;
+ }
+}
diff --git a/src/common/src/GPS/minmea.c b/src/common/src/GPS/minmea.c
new file mode 100644
index 0000000..8700725
--- /dev/null
+++ b/src/common/src/GPS/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 "GPS/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: */