From 65c523fe5deca4cdc8406a96b1472d45b5745e6e Mon Sep 17 00:00:00 2001 From: Yousefyasser Date: Sat, 18 Jan 2025 22:34:57 +0200 Subject: [PATCH 01/10] feat: add GPS UART module with NMEA parsing and checksum validation Co-authored-by: NightsThunder Co-authored-by: RasheedAtia --- uart/gps_uart/CMakeLists.txt | 12 +++ uart/gps_uart/gps_uart.c | 159 +++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 uart/gps_uart/CMakeLists.txt create mode 100644 uart/gps_uart/gps_uart.c diff --git a/uart/gps_uart/CMakeLists.txt b/uart/gps_uart/CMakeLists.txt new file mode 100644 index 000000000..fd7a70a1e --- /dev/null +++ b/uart/gps_uart/CMakeLists.txt @@ -0,0 +1,12 @@ +# Tell CMake where to find the executable source file +add_executable(gps_uart gps_uart.c) + +# Link to pico_stdlib (gpio, time, etc. functions) +target_link_libraries(gps_uart pico_stdlib hardware_uart) + +# Enable usb output, disable uart output +pico_enable_stdio_usb(gps_uart 1) +pico_enable_stdio_uart(gps_uart 1) + +# Create map/bin/hex/uf2 files +pico_add_extra_outputs(gps_uart) \ No newline at end of file diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c new file mode 100644 index 000000000..40d3a4c79 --- /dev/null +++ b/uart/gps_uart/gps_uart.c @@ -0,0 +1,159 @@ +#include "hardware/uart.h" +#include "pico/stdlib.h" + +#include +#include +#include +#include + + +#define UART_ID uart1 +#define BAUD_RATE 9600 +#define UART_TX_PIN 4 +#define UART_RX_PIN 5 +#define MAX_NMEA_LENGTH 256 + +typedef struct +{ + float latitude; + float longitude; + bool is_valid; +} GPSData; + + +void uart_gps_init() +{ + uart_init(UART_ID, BAUD_RATE); + + gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); + gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART); + + uart_set_hw_flow(UART_ID, false, false); + uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE); + uart_set_fifo_enabled(UART_ID, true); +} + +bool validate_nmea_checksum(char *nmea_string) +{ + int len = strlen(nmea_string); + printf("Checksum Validation - String Length: %d\n", len); + printf("Full NMEA String: %s", nmea_string); + + if (len < 7) + { + printf("Invalid: Too short (< 7 chars)\n"); + return false; + } + + char *checksum_ptr = strchr(nmea_string, '*'); + if (!checksum_ptr) + { + printf("Invalid: No checksum marker (*) found\n"); + return false; + } + + uint8_t calculated_checksum = 0; + for (char *p = nmea_string + 1; p < checksum_ptr; p++) + { + calculated_checksum ^= *p; + } + + char hex_checksum[3]; + snprintf(hex_checksum, sizeof(hex_checksum), "%02X", calculated_checksum); + + printf("Calculated Checksum: %s\n", hex_checksum); + printf("Received Checksum: %s\n", checksum_ptr + 1); + + bool is_valid = strncmp(hex_checksum, checksum_ptr + 1, 2) == 0; + printf("Checksum Validation: %s\n", is_valid ? "VALID" : "INVALID"); + + return is_valid; +} + +void convert_nmea_to_decimal(char *nmea_coord, float *decimal_coord) +{ + float degrees = atof(nmea_coord) / 100.0; + int int_degrees = (int)degrees; + float minutes = (degrees - int_degrees) * 100.0; + + *decimal_coord = int_degrees + (minutes / 60.0); +} + +bool parse_nmea_gps(char *nmea_string, GPSData *gps_data) +{ + if (!validate_nmea_checksum(nmea_string)) + { + printf("Invalid NMEA Checksum\n"); + return false; + } + + // Ignore all NMEA strings that are not of type GPRMC + if (strncmp(nmea_string, "$GPRMC", 6) != 0) + { + return false; + } + + char *token; + char *tokens[12] = {0}; + int token_count = 0; + + // Tokenize the string + token = strtok(nmea_string, ","); + while (token != NULL && token_count < 12) + { + tokens[token_count++] = token; + + token = strtok(NULL, ","); + } + + // Check if we have enough tokens and data is valid + if (token_count >= 12 && strcmp(tokens[2], "A") == 0) + { + if (strlen(tokens[3]) > 0) + { + convert_nmea_to_decimal(tokens[3], &gps_data->latitude); + if (tokens[4][0] == 'S') + gps_data->latitude = -gps_data->latitude; + } + + if (strlen(tokens[5]) > 0) + { + convert_nmea_to_decimal(tokens[5], &gps_data->longitude); + if (tokens[6][0] == 'W') + gps_data->longitude = -gps_data->longitude; + } + + gps_data->is_valid = true; + return true; + } +} + +void process_gps_data(GPSData *gps_data) +{ + char nmea_buffer[MAX_NMEA_LENGTH]; + int chars_read = 0; + + while (uart_is_readable(UART_ID) && chars_read < MAX_NMEA_LENGTH - 1) + { + char received_char = uart_getc(UART_ID); + nmea_buffer[chars_read] = received_char; + + // NMEA strings are terminated by a newline character + if ((int)received_char == 10) + { + nmea_buffer[chars_read + 1] = '\0'; + + if (parse_nmea_gps(nmea_buffer, gps_data)) + { + printf("Valid GPS Data Received\n"); + } + + chars_read = 0; + break; + } + else + { + chars_read++; + } + } +} \ No newline at end of file From 2d830ea3da689aed81631e9e59014899184467eb Mon Sep 17 00:00:00 2001 From: Yousefyasser Date: Tue, 21 Jan 2025 14:55:37 +0200 Subject: [PATCH 02/10] feat: implement main function for GPS data processing and output --- uart/gps_uart/gps_uart.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c index 40d3a4c79..0002f44d5 100644 --- a/uart/gps_uart/gps_uart.c +++ b/uart/gps_uart/gps_uart.c @@ -6,7 +6,8 @@ #include #include - +// uart id and tx/rx pins could vary according to the microcontroller used +// please refer to the datasheet #define UART_ID uart1 #define BAUD_RATE 9600 #define UART_TX_PIN 4 @@ -156,4 +157,35 @@ void process_gps_data(GPSData *gps_data) chars_read++; } } -} \ No newline at end of file +} + +int main() +{ + stdio_init_all(); + + uart_gps_init(); + + // recommended cold start waiting time (could vary based on the gps module) + sleep_ms(7 * 60 * 1000); + + GPSData gps_data = {0}; + + while (1) + { + process_gps_data(&gps_data); + + if (gps_data.is_valid) + { + printf("GPS Location:\n"); + printf("Latitude: %.6f\n", gps_data.latitude); + printf("Longitude: %.6f\n", gps_data.longitude); + gps_data.is_valid = false; + } + + // sufficient waiting time required between each gps reading and the next one + sleep_ms(30 * 1000); + tight_loop_contents(); + } + + return 0; +} From 02db2a3fe214852b04d670e6049943429ecea952 Mon Sep 17 00:00:00 2001 From: Yousefyasser Date: Tue, 21 Jan 2025 15:05:55 +0200 Subject: [PATCH 03/10] doc: add detailed documentation for GPS UART driver functions --- uart/gps_uart/gps_uart.c | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c index 0002f44d5..66e59a7ff 100644 --- a/uart/gps_uart/gps_uart.c +++ b/uart/gps_uart/gps_uart.c @@ -1,3 +1,10 @@ +/** + * @file gps_uart.c + * @brief A GPS driver that parses NMEA sentences from a GPS module. + * @author Yousef Yasser, Rasheed Atia, Seif Abbas + * @date 2025-01-21 + */ + #include "hardware/uart.h" #include "pico/stdlib.h" @@ -22,6 +29,12 @@ typedef struct } GPSData; +/** + * @brief Initializes the UART interface for GPS communication. + * + * This function sets up the UART interface with the specified baud rate, + * configures the TX and RX pins, and enables the UART FIFO. + */ void uart_gps_init() { uart_init(UART_ID, BAUD_RATE); @@ -34,6 +47,16 @@ void uart_gps_init() uart_set_fifo_enabled(UART_ID, true); } + +/** + * @brief Validates the checksum of an NMEA sentence. + * + * This function calculates the checksum of the NMEA sentence and compares it + * with the checksum provided in the sentence. If they match, the sentence is valid. + * + * @param nmea_string The NMEA sentence to validate. + * @return true if the checksum is valid, false otherwise. + */ bool validate_nmea_checksum(char *nmea_string) { int len = strlen(nmea_string); @@ -71,6 +94,16 @@ bool validate_nmea_checksum(char *nmea_string) return is_valid; } + +/** + * @brief Converts an NMEA coordinate to decimal degrees. + * + * This function converts an NMEA-formatted coordinate (DDMM.MMMM) to + * decimal degrees (DD.DDDDDD). + * + * @param nmea_coord The NMEA coordinate string to convert. + * @param decimal_coord Pointer to store the converted decimal coordinate. + */ void convert_nmea_to_decimal(char *nmea_coord, float *decimal_coord) { float degrees = atof(nmea_coord) / 100.0; @@ -80,6 +113,17 @@ void convert_nmea_to_decimal(char *nmea_coord, float *decimal_coord) *decimal_coord = int_degrees + (minutes / 60.0); } + +/** + * @brief Parses an NMEA sentence and extracts GPS data. + * + * This function parses an NMEA sentence (currently only $GPRMC) and extracts + * latitude, longitude, and validity information. It also validates the checksum. + * + * @param nmea_string The NMEA sentence to parse. + * @param gps_data Pointer to the GPSData structure to store the parsed data. + * @return true if the sentence was successfully parsed, false otherwise. + */ bool parse_nmea_gps(char *nmea_string, GPSData *gps_data) { if (!validate_nmea_checksum(nmea_string)) @@ -129,6 +173,15 @@ bool parse_nmea_gps(char *nmea_string, GPSData *gps_data) } } + +/** + * @brief Processes incoming GPS data from the UART interface. + * + * This function reads NMEA sentences from the UART interface, validates them, + * and updates the GPSData structure if a valid sentence is received. + * + * @param gps_data Pointer to the GPSData structure to store the parsed data. + */ void process_gps_data(GPSData *gps_data) { char nmea_buffer[MAX_NMEA_LENGTH]; @@ -159,6 +212,12 @@ void process_gps_data(GPSData *gps_data) } } +/** + * @brief Main function for the GPS driver example. + * + * This function initializes the UART interface, waits for the GPS module to + * cold start, and continuously processes and prints GPS data. + */ int main() { stdio_init_all(); From 6edf379adb307e238f0960cd59c50046eb8d702e Mon Sep 17 00:00:00 2001 From: Yousefyasser Date: Tue, 21 Jan 2025 15:52:57 +0200 Subject: [PATCH 04/10] refactor: remove unnecessary tight_loop_contents call --- uart/gps_uart/gps_uart.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c index 66e59a7ff..810428c33 100644 --- a/uart/gps_uart/gps_uart.c +++ b/uart/gps_uart/gps_uart.c @@ -1,7 +1,7 @@ /** * @file gps_uart.c * @brief A GPS driver that parses NMEA sentences from a GPS module. - * @author Yousef Yasser, Rasheed Atia, Seif Abbas + * @author Yousef Yasser, Rasheed Atia, Seifeldin Khaled * @date 2025-01-21 */ @@ -243,7 +243,6 @@ int main() // sufficient waiting time required between each gps reading and the next one sleep_ms(30 * 1000); - tight_loop_contents(); } return 0; From 8b0a166494eab1ac2f9233f499fe655c7e0d6598 Mon Sep 17 00:00:00 2001 From: Yousefyasser Date: Tue, 21 Jan 2025 16:05:09 +0200 Subject: [PATCH 05/10] doc: add SPDX license identifier to GPS UART driver file --- uart/gps_uart/gps_uart.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c index 810428c33..45c6fc22c 100644 --- a/uart/gps_uart/gps_uart.c +++ b/uart/gps_uart/gps_uart.c @@ -3,6 +3,8 @@ * @brief A GPS driver that parses NMEA sentences from a GPS module. * @author Yousef Yasser, Rasheed Atia, Seifeldin Khaled * @date 2025-01-21 + * + * SPDX-License-Identifier: MIT */ #include "hardware/uart.h" From 6b364a4cb0b917edb791f3393ac2551c759815a6 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 13 Feb 2025 19:57:01 +0000 Subject: [PATCH 06/10] Add GPS example to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ab13be68e..f44666566 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,7 @@ App|Description [hello_uart](uart/hello_uart) | Print some text from one of the UART serial ports, without going through `stdio`. [lcd_uart](uart/lcd_uart) | Display text and symbols on a 16x02 RGB LCD display via UART [uart_advanced](uart/uart_advanced) | Use some other UART features like RX interrupts, hardware control flow, and data formats other than 8n1. +[gps_uart](uart/gps_uart) | Interpret standard NMEA data received from a GPS device connected to a UART. ### Universal From fe7b5b6af7203b62d329d1572044df21725fc0da Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 13 Feb 2025 19:58:19 +0000 Subject: [PATCH 07/10] Add gps_uart to the build --- uart/CMakeLists.txt | 1 + uart/gps_uart/CMakeLists.txt | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/uart/CMakeLists.txt b/uart/CMakeLists.txt index 4105d43de..ebae1aa4c 100644 --- a/uart/CMakeLists.txt +++ b/uart/CMakeLists.txt @@ -2,6 +2,7 @@ if (TARGET hardware_uart) add_subdirectory_exclude_platforms(hello_uart) add_subdirectory_exclude_platforms(lcd_uart host) add_subdirectory_exclude_platforms(uart_advanced host) + add_subdirectory_exclude_platforms(gps_uart host) else() message("Skipping UART examples as hardware_uart is unavailable on this platform") endif() \ No newline at end of file diff --git a/uart/gps_uart/CMakeLists.txt b/uart/gps_uart/CMakeLists.txt index fd7a70a1e..5084c584e 100644 --- a/uart/gps_uart/CMakeLists.txt +++ b/uart/gps_uart/CMakeLists.txt @@ -4,9 +4,5 @@ add_executable(gps_uart gps_uart.c) # Link to pico_stdlib (gpio, time, etc. functions) target_link_libraries(gps_uart pico_stdlib hardware_uart) -# Enable usb output, disable uart output -pico_enable_stdio_usb(gps_uart 1) -pico_enable_stdio_uart(gps_uart 1) - # Create map/bin/hex/uf2 files pico_add_extra_outputs(gps_uart) \ No newline at end of file From 929c036f6fc7f8fe4e721fe55f59c2d128a89029 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 13 Feb 2025 20:00:33 +0000 Subject: [PATCH 08/10] Improve parsing This was failing to read whole lines for me. And for some reason my device is putting zeros on the head of strings. --- uart/gps_uart/gps_uart.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c index 45c6fc22c..6f6af4fe3 100644 --- a/uart/gps_uart/gps_uart.c +++ b/uart/gps_uart/gps_uart.c @@ -154,7 +154,7 @@ bool parse_nmea_gps(char *nmea_string, GPSData *gps_data) } // Check if we have enough tokens and data is valid - if (token_count >= 12 && strcmp(tokens[2], "A") == 0) + if (token_count >= 7 && strcmp(tokens[2], "A") == 0) { if (strlen(tokens[3]) > 0) { @@ -173,6 +173,7 @@ bool parse_nmea_gps(char *nmea_string, GPSData *gps_data) gps_data->is_valid = true; return true; } + return false; } @@ -189,9 +190,16 @@ void process_gps_data(GPSData *gps_data) char nmea_buffer[MAX_NMEA_LENGTH]; int chars_read = 0; - while (uart_is_readable(UART_ID) && chars_read < MAX_NMEA_LENGTH - 1) + while (true) { + // string too long + if (chars_read >= MAX_NMEA_LENGTH - 1) + break; char received_char = uart_getc(UART_ID); + // ignore zero bytes + if (received_char == 0) { + continue; + } nmea_buffer[chars_read] = received_char; // NMEA strings are terminated by a newline character @@ -203,8 +211,6 @@ void process_gps_data(GPSData *gps_data) { printf("Valid GPS Data Received\n"); } - - chars_read = 0; break; } else From bdd61c590751944f11c42c2e8b5dfa9414821857 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 13 Feb 2025 20:02:08 +0000 Subject: [PATCH 09/10] Remove sleeps This seem unnecessary. --- uart/gps_uart/gps_uart.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c index 6f6af4fe3..6717b51d6 100644 --- a/uart/gps_uart/gps_uart.c +++ b/uart/gps_uart/gps_uart.c @@ -232,9 +232,6 @@ int main() uart_gps_init(); - // recommended cold start waiting time (could vary based on the gps module) - sleep_ms(7 * 60 * 1000); - GPSData gps_data = {0}; while (1) @@ -248,9 +245,6 @@ int main() printf("Longitude: %.6f\n", gps_data.longitude); gps_data.is_valid = false; } - - // sufficient waiting time required between each gps reading and the next one - sleep_ms(30 * 1000); } return 0; From 4e17299d0607fadbf75f344915c0a8e9486a19ea Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 13 Feb 2025 20:02:38 +0000 Subject: [PATCH 10/10] Remove debugging output. Make the results clearer. --- uart/gps_uart/gps_uart.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/uart/gps_uart/gps_uart.c b/uart/gps_uart/gps_uart.c index 6717b51d6..1fe39388d 100644 --- a/uart/gps_uart/gps_uart.c +++ b/uart/gps_uart/gps_uart.c @@ -30,6 +30,14 @@ typedef struct bool is_valid; } GPSData; +#if 0 +#define DEBUG_printf printf +#else +#define DEBUG_printf(...) +#endif + +#define INFO_printf printf +#define ERROR_printf printf /** * @brief Initializes the UART interface for GPS communication. @@ -62,19 +70,19 @@ void uart_gps_init() bool validate_nmea_checksum(char *nmea_string) { int len = strlen(nmea_string); - printf("Checksum Validation - String Length: %d\n", len); - printf("Full NMEA String: %s", nmea_string); + DEBUG_printf("Checksum Validation - String Length: %d\n", len); + INFO_printf("Full NMEA String: %s", nmea_string); if (len < 7) { - printf("Invalid: Too short (< 7 chars)\n"); + ERROR_printf("Invalid: Too short (< 7 chars)\n"); return false; } char *checksum_ptr = strchr(nmea_string, '*'); if (!checksum_ptr) { - printf("Invalid: No checksum marker (*) found\n"); + ERROR_printf("Invalid: No checksum marker (*) found\n"); return false; } @@ -87,12 +95,13 @@ bool validate_nmea_checksum(char *nmea_string) char hex_checksum[3]; snprintf(hex_checksum, sizeof(hex_checksum), "%02X", calculated_checksum); - printf("Calculated Checksum: %s\n", hex_checksum); - printf("Received Checksum: %s\n", checksum_ptr + 1); + DEBUG_printf("Calculated Checksum: %s\n", hex_checksum); + DEBUG_printf("Received Checksum: %s\n", checksum_ptr + 1); bool is_valid = strncmp(hex_checksum, checksum_ptr + 1, 2) == 0; - printf("Checksum Validation: %s\n", is_valid ? "VALID" : "INVALID"); - + if (!is_valid) { + ERROR_printf("Checksum Validation: INVALID\n"); + } return is_valid; } @@ -130,7 +139,7 @@ bool parse_nmea_gps(char *nmea_string, GPSData *gps_data) { if (!validate_nmea_checksum(nmea_string)) { - printf("Invalid NMEA Checksum\n"); + ERROR_printf("Invalid NMEA Checksum\n"); return false; } @@ -209,7 +218,7 @@ void process_gps_data(GPSData *gps_data) if (parse_nmea_gps(nmea_buffer, gps_data)) { - printf("Valid GPS Data Received\n"); + INFO_printf("Valid GPS Data Received\n"); } break; } @@ -233,16 +242,16 @@ int main() uart_gps_init(); GPSData gps_data = {0}; - + + INFO_printf("Waiting for NMEA data\n"); while (1) { process_gps_data(&gps_data); if (gps_data.is_valid) { - printf("GPS Location:\n"); - printf("Latitude: %.6f\n", gps_data.latitude); - printf("Longitude: %.6f\n", gps_data.longitude); + INFO_printf("Latitude: %.6f\n", gps_data.latitude); + INFO_printf("Longitude: %.6f\n", gps_data.longitude); gps_data.is_valid = false; } }