diff options
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/includes/GPIO/leds.h | 9 | ||||
-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.h | 45 | ||||
-rw-r--r-- | src/common/includes/GPS/minmea.h | 234 | ||||
-rw-r--r-- | src/common/src/Core/common.c | 4 | ||||
-rw-r--r-- | src/common/src/Core/main.c | 230 | ||||
-rw-r--r-- | src/common/src/GPIO/leds.c | 1 | ||||
-rw-r--r-- | src/common/src/GPIO/usart.c | 136 | ||||
-rw-r--r-- | src/common/src/GPS/gps.c | 129 | ||||
-rw-r--r-- | src/common/src/GPS/minmea.c | 572 |
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: */ |