example: add nmea0183 parser example

Add NMEA0183 Parser example to illustrate how to use uart event driver together with esp event library to get GPS information.
This commit is contained in:
morris 2018-11-28 18:49:18 +08:00
parent aae955d1ae
commit 8b7b5821c7
41 changed files with 1310 additions and 7 deletions

View file

@ -277,15 +277,17 @@ This galvanic isolated circuit does not require RTS pin control by software appl
Application Examples
--------------------
Configure UART settings and install UART driver to read/write using UART1 interface: :example:`peripherals/uart_echo`.
Configure UART settings and install UART driver to read/write using UART1 interface: :example:`peripherals/uart/uart_echo`.
Demonstration of how to report various communication events and how to use patern detection interrupts: :example:`peripherals/uart_events`.
Demonstration of how to report various communication events and how to use patern detection interrupts: :example:`peripherals/uart/uart_events`.
Transmitting and receiveing with the same UART in two separate FreeRTOS tasks: :example:`peripherals/uart_async_rxtxtasks`.
Transmitting and receiveing with the same UART in two separate FreeRTOS tasks: :example:`peripherals/uart/uart_async_rxtxtasks`.
Using synchronous I/O multiplexing for UART file descriptors: :example:`peripherals/uart_select`.
Using synchronous I/O multiplexing for UART file descriptors: :example:`peripherals/uart/uart_select`.
Setup of UART driver to communicate over RS485 interface in half-duplex mode: :example:`peripherals/uart_echo_rs485`. This example is similar to uart_echo but provide communication through RS485 interface chip connected to ESP32 pins.
Setup of UART driver to communicate over RS485 interface in half-duplex mode: :example:`peripherals/uart/uart_echo_rs485`. This example is similar to uart_echo but provide communication through RS485 interface chip connected to ESP32 pins.
Demonstration of how to get GPS information by parsing NMEA0183 statements received from GPS via UART peripheral: :example:`peripherals/uart/nmea0183_parser`.
API Reference
-------------

View file

@ -196,8 +196,11 @@ can be found in the :cpp:func:`esp_event_dump` API Reference.
Application Example
-------------------
Examples on using the ``esp_event`` library can be found on :example:`system/esp_event`. The examples cover event declaration, loop creation, handler registration
and unregistration and event posting.
Examples on using the ``esp_event`` library can be found in :example:`system/esp_event`. The examples cover event declaration, loop creation, handler registration and unregistration and event posting.
Other examples which also adopt esp_event library:
* :example:`NMEA Parser <peripherals/uart/nmea0183_parser>`, which will decode the statements received from GPS.
API Reference
-------------

View file

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(nmea_parser)

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := nmea_parser
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,110 @@
# NMEA Parser Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## Overview
This example will show how to parse NMEA-0183 data streams output from GPS/BDS/GLONASS modules based on ESP UART Event driver and ESP event loop library.
For the convenience of the presentation, this example will only parse the following basic statements:
* GGA
* GSA
* GSV
* RMC
* GLL
* VTG
See [Limitation for multiple navigation system](#Limitation) for more information about this example.
Usually, modules will also output some vendor specific statements which common nmea library can not cover. In this example, the NMEA Parser will propagate all unknown statements to the user, where a custom handler can parse information from it.
## How to use example
### Hardware Required
To run this example, you need an ESP32 dev board (e.g. ESP32-WROVER Kit) or ESP32 core board (e.g. ESP32-DevKitC). For test purpose, you also need a GPS module. Here we take the [ATGM332D-5N](http://www.icofchina.com/pro/mokuai/2016-08-01/5.html) as an example to show how to parse the NMEA statements and output common information such as UTC time, latitude, longitude, altitude, speed and so on.
#### Pin Assignment:
**Note:** The following pin assignments are used by default which can be changed in `nmea_parser_config_t` structure.
| ESP32 | GPS |
| ---------------- | --------------- |
| UART-TX (option) | GPS-RX (option) |
| UART-RX | GPS-TX |
| GND | GND |
| 5V | VCC |
**Note:** UART TX pin in ESP32 is not necessary if you only use uart to receive data.
### Configure the project
Enter `make menuconfig` if you are using GNU Make based build system or enter `idf.py menuconfig` if you are using CMake based build system. Then go into `Example Configuration` menu.
- Set the size of ring buffer used by uart driver in `NMEA Parser Ring Buffer Size` option.
- Set the stack size of the NMEA Parser task in `NMEA Parser Task Stack Size` option.
- Set the priority of the NMEA Parser task in `NMEA Parser Task Priority` option.
- In the `NMEA Statement support` submenu, you can choose the type of statements that you want to parse. **Note:** you should choose at least one statement to parse.
### Build and Flash
Enter `make -j4 flash monitor` if you are using GNU Make based build system or enter `idf.py build flash monitor` if you are using CMake based build system.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```bash
I (0) cpu_start: Starting scheduler on APP CPU.
I (317) uart: queue free spaces: 16
I (317) nmea_parser: NMEA Parser init OK
I (1067) gps_demo: 2018/12/4 13:59:34 =>
latitude = 31.20177°N
longtitude = 121.57933°E
altitude = 17.30m
speed = 0.370400m/s
W (1177) gps_demo: Unknown statement:$GPTXT,01,01,01,ANTENNA OK*35
I (2067) gps_demo: 2018/12/4 13:59:35 =>
latitude = 31.20177°N
longtitude = 121.57933°E
altitude = 17.30m
speed = 0.000000m/s
W (2177) gps_demo: Unknown statement:$GPTXT,01,01,01,ANTENNA OK*35
I (3067) gps_demo: 2018/12/4 13:59:36 =>
latitude = 31.20178°N
longtitude = 121.57933°E
altitude = 17.30m
speed = 0.000000m/s
W (3177) gps_demo: Unknown statement:$GPTXT,01,01,01,ANTENNA OK*35
I (4067) gps_demo: 2018/12/4 13:59:37 =>
latitude = 31.20178°N
longtitude = 121.57933°E
altitude = 17.30m
speed = 0.000000m/s
W (4177) gps_demo: Unknown statement:$GPTXT,01,01,01,ANTENNA OK*35
I (5067) gps_demo: 2018/12/4 13:59:38 =>
latitude = 31.20178°N
longtitude = 121.57933°E
altitude = 17.30m
speed = 0.685240m/s
W (5177) gps_demo: Unknown statement:$GPTXT,01,01,01,ANTENNA OK*35
```
As shown above, ESP32 finally got the information after parsed the NMEA0183 format statements. But as we didn't add `GPTXT` type statement in the library (that means it is UNKNOWN to NMEA Parser library), so it was propagated to user without any process.
## Troubleshooting
1. I cannot receive any statements from GPS although I have checked all the pin connections.
* Test your GPS via other terminal (e.g. minicom, putty) to check the right communication parameters (e.g. baudrate supported by GPS).
## Limitation
If the GPS module supports multiple satellite navigation system (e.g. GPS, BDS), then the satellite ids and descriptions may be delivered in different statements (e.g. GPGSV, BDGSV, GPGSA, BDGSA), depend on the version of NMEA protocol used by the GPS module. This example currently can only record id and description of satellites from one navigation system.
However, for other statements, this example can parse them correctly whatever the navigation system is.
### Steps to skip the limitation
1. Uncheck the `GSA` and `GSV` statements in menuconfig
2. In the `gps_event_handler` will get a signal called `GPS_UNKNOWN`, and the unknown statement itself (It's a deep copy of the original statement).
3. Manually parse the unknown statements and get the satellites' descriptions.
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)

View file

@ -0,0 +1,5 @@
set(COMPONENT_SRCS "nmea_parser_example_main.c"
"nmea_parser.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View file

@ -0,0 +1,85 @@
menu "Example Configuration"
config NMEA_PARSER_RING_BUFFER_SIZE
int "NMEA Parser Ring Buffer Size"
range 0 2048
default 1024
help
Size of the ring buffer used for UART Rx channel.
config NMEA_PARSER_TASK_STACK_SIZE
int "NMEA Parser Task Stack Size"
range 0 4096
default 2048
help
Stack size of NMEA Parser task.
config NMEA_PARSER_TASK_PRIORITY
int "NMEA Parser Task Priority"
range 0 24
default 2
help
Priority of NMEA Parser task.
menu "NMEA Statement Support"
comment "At least one statement must be selected"
config NMEA_STATEMENT_GGA
bool "GGA Statement"
default y
help
Enabling this option will parse the following parameter from GGA statement:
- Latitude, Longitude, Altitude;
- Number of satellites in use, fix status (no fix, GPS, DGPS), UTC time;
config NMEA_STATEMENT_GSA
bool "GSA Statement"
default y
help
Enabling this option will parse the following parameter from GSA statement:
- Position/Vertical/Horizontal dilution of precision;
- Fix mode (no fix, 2D, 3D fix);
- IDs of satellites in use;
config NMEA_STATEMENT_GSV
bool "GSV Statement"
default y
help
Enabling this option will parse the following parameter from GSV statement:
- Number of satellites in view;
- Optional details of each satellite in view;
config NMEA_STATEMENT_RMC
bool "RMC Statement"
default y
help
Enabling this option will parse the following parameter from RMC statement:
- Validity of GPS signal;
- Ground speed (knots) and course over ground (degrees);
- Magnetic variation;
- UTC date;
config NMEA_STATEMENT_GLL
bool "GLL Statement"
default y
help
Enabling this option will parse the following parameter from GLL statement:
- Latitude, Longitude;
- UTC time;
config NMEA_STATEMENT_VTG
bool "VTG Statement"
default y
help
Enabling this option will parse the following parameter from VTG statement:
- Ground speed (knots, km/h) and course over ground (degrees);
- Magnetic variation;
endmenu
endmenu

View file

@ -0,0 +1,792 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nmea_parser.h"
/**
* @brief NMEA Parser runtime buffer size
*
*/
#define NMEA_PARSER_RUNTIME_BUFFER_SIZE (CONFIG_NMEA_PARSER_RING_BUFFER_SIZE / 2)
#define NMEA_MAX_STATEMENT_ITEM_LENGTH (16)
#define NMEA_EVENT_LOOP_QUEUE_SIZE (16)
/**
* @brief Define of NMEA Parser Event base
*
*/
ESP_EVENT_DEFINE_BASE(ESP_NMEA_EVENT)
static const char *GPS_TAG = "nmea_parser";
/**
* @brief GPS parser library runtime structure
*
*/
typedef struct {
uint8_t item_pos; /*!< Current position in item */
uint8_t item_num; /*!< Current item number */
uint8_t asterisk; /*!< Asterisk detected flag */
uint8_t crc; /*!< Calculated CRC value */
uint8_t parsed_statement; /*!< OR'd of statements that have been parsed */
uint8_t sat_num; /*!< Satellite number */
uint8_t sat_count; /*!< Satellite count */
uint8_t cur_statement; /*!< Current statement ID */
uint32_t all_statements; /*!< All statements mask */
char item_str[NMEA_MAX_STATEMENT_ITEM_LENGTH]; /*!< Current item */
gps_t parent; /*!< Parent class */
uart_port_t uart_port; /*!< Uart port number */
uint8_t *buffer; /*!< Runtime buffer */
esp_event_loop_handle_t event_loop_hdl; /*!< Event loop handle */
TaskHandle_t tsk_hdl; /*!< NMEA Parser task handle */
QueueHandle_t event_queue; /*!< UART event queue handle */
} esp_gps_t;
/**
* @brief parse latitude or longitude
* format of latitude in NMEA is ddmm.sss and longitude is dddmm.sss
* @param esp_gps esp_gps_t type object
* @return float Latitude or Longitude value (unit: degree)
*/
static float parse_lat_long(esp_gps_t *esp_gps)
{
float ll = strtof(esp_gps->item_str, NULL);
int deg = ((int)ll) / 100;
float min = ll - (deg * 100);
ll = deg + min / 60.0f;
return ll;
}
/**
* @brief Converter two continuous numeric character into a uint8_t number
*
* @param digit_char numeric character
* @return uint8_t result of converting
*/
static inline uint8_t convert_two_digit2number(const char *digit_char)
{
return 10 * (digit_char[0] - '0') + (digit_char[1] - '0');
}
/**
* @brief Parse UTC time in GPS statements
*
* @param esp_gps esp_gps_t type object
*/
static void parse_utc_time(esp_gps_t *esp_gps)
{
esp_gps->parent.tim.hour = convert_two_digit2number(esp_gps->item_str + 0);
esp_gps->parent.tim.minute = convert_two_digit2number(esp_gps->item_str + 2);
esp_gps->parent.tim.second = convert_two_digit2number(esp_gps->item_str + 4);
if (esp_gps->item_str[6] == '.') {
uint16_t tmp = 0;
uint8_t i = 7;
while (esp_gps->item_str[i]) {
tmp = 10 * tmp + esp_gps->item_str[i] - '0';
i++;
}
esp_gps->parent.tim.thousand = tmp;
}
}
#if CONFIG_NMEA_STATEMENT_GGA
/**
* @brief Parse GGA statements
*
* @param esp_gps esp_gps_t type object
*/
static void parse_gga(esp_gps_t *esp_gps)
{
/* Process GGA statement */
switch (esp_gps->item_num) {
case 1: /* Process UTC time */
parse_utc_time(esp_gps);
break;
case 2: /* Latitude */
esp_gps->parent.latitude = parse_lat_long(esp_gps);
break;
case 3: /* Latitude north(1)/south(-1) information */
if (esp_gps->item_str[0] == 'S' || esp_gps->item_str[0] == 's') {
esp_gps->parent.latitude *= -1;
}
break;
case 4: /* Longitude */
esp_gps->parent.longitude = parse_lat_long(esp_gps);
break;
case 5: /* Longitude east(1)/west(-1) information */
if (esp_gps->item_str[0] == 'W' || esp_gps->item_str[0] == 'w') {
esp_gps->parent.longitude *= -1;
}
break;
case 6: /* Fix status */
esp_gps->parent.fix = (gps_fix_t)strtol(esp_gps->item_str, NULL, 10);
break;
case 7: /* Satellites in use */
esp_gps->parent.sats_in_use = (uint8_t)strtol(esp_gps->item_str, NULL, 10);
break;
case 8: /* HDOP */
esp_gps->parent.dop_h = strtof(esp_gps->item_str, NULL);
break;
case 9: /* Altitude */
esp_gps->parent.altitude = strtof(esp_gps->item_str, NULL);
break;
case 11: /* Altitude above ellipsoid */
esp_gps->parent.altitude += strtof(esp_gps->item_str, NULL);
break;
default:
break;
}
}
#endif
#if CONFIG_NMEA_STATEMENT_GSA
/**
* @brief Parse GSA statements
*
* @param esp_gps esp_gps_t type object
*/
static void parse_gsa(esp_gps_t *esp_gps)
{
/* Process GSA statement */
switch (esp_gps->item_num) {
case 2: /* Process fix mode */
esp_gps->parent.fix_mode = (gps_fix_mode_t)strtol(esp_gps->item_str, NULL, 10);
break;
case 15: /* Process PDOP */
esp_gps->parent.dop_p = strtof(esp_gps->item_str, NULL);
break;
case 16: /* Process HDOP */
esp_gps->parent.dop_h = strtof(esp_gps->item_str, NULL);
break;
case 17: /* Process VDOP */
esp_gps->parent.dop_v = strtof(esp_gps->item_str, NULL);
break;
default:
/* Parse satellite IDs */
if (esp_gps->item_num >= 3 && esp_gps->item_num <= 14) {
esp_gps->parent.sats_id_in_use[esp_gps->item_num - 3] = (uint8_t)strtol(esp_gps->item_str, NULL, 10);
}
break;
}
}
#endif
#if CONFIG_NMEA_STATEMENT_GSV
/**
* @brief Parse GSV statements
*
* @param esp_gps esp_gps_t type object
*/
static void parse_gsv(esp_gps_t *esp_gps)
{
/* Process GSV statement */
switch (esp_gps->item_num) {
case 1: /* total GSV numbers */
esp_gps->sat_count = (uint8_t)strtol(esp_gps->item_str, NULL, 10);
case 2: /* Current GSV statement number */
esp_gps->sat_num = (uint8_t)strtol(esp_gps->item_str, NULL, 10);
break;
case 3: /* Process satellites in view */
esp_gps->parent.sats_in_view = (uint8_t)strtol(esp_gps->item_str, NULL, 10);
break;
default:
if (esp_gps->item_num >= 4 && esp_gps->item_num <= 19) {
uint8_t item_num = esp_gps->item_num - 4; /* Normalize item number from 4-19 to 0-15 */
uint8_t index;
uint32_t value;
index = 4 * (esp_gps->sat_num - 1) + item_num / 4; /* Get array index */
if (index < GPS_MAX_SATELLITES_IN_VIEW) {
value = strtol(esp_gps->item_str, NULL, 10);
switch (item_num % 4) {
case 0:
esp_gps->parent.sats_desc_in_view[index].num = (uint8_t)value;
break;
case 1:
esp_gps->parent.sats_desc_in_view[index].elevation = (uint8_t)value;
break;
case 2:
esp_gps->parent.sats_desc_in_view[index].azimuth = (uint16_t)value;
break;
case 3:
esp_gps->parent.sats_desc_in_view[index].snr = (uint8_t)value;
break;
default:
break;
}
}
}
break;
}
}
#endif
#if CONFIG_NMEA_STATEMENT_RMC
/**
* @brief Parse RMC statements
*
* @param esp_gps esp_gps_t type object
*/
static void parse_rmc(esp_gps_t *esp_gps)
{
/* Process GPRMC statement */
switch (esp_gps->item_num) {
case 1:/* Process UTC time */
parse_utc_time(esp_gps);
break;
case 2: /* Process valid status */
esp_gps->parent.valid = (esp_gps->item_str[0] == 'A');
break;
case 3:/* Latitude */
esp_gps->parent.latitude = parse_lat_long(esp_gps);
break;
case 4: /* Latitude north(1)/south(-1) information */
if (esp_gps->item_str[0] == 'S' || esp_gps->item_str[0] == 's') {
esp_gps->parent.latitude *= -1;
}
break;
case 5: /* Longitude */
esp_gps->parent.longitude = parse_lat_long(esp_gps);
break;
case 6: /* Longitude east(1)/west(-1) information */
if (esp_gps->item_str[0] == 'W' || esp_gps->item_str[0] == 'w') {
esp_gps->parent.longitude *= -1;
}
break;
case 7: /* Process ground speed in unit m/s */
esp_gps->parent.speed = strtof(esp_gps->item_str, NULL) * 1.852;
break;
case 8: /* Process true course over ground */
esp_gps->parent.cog = strtof(esp_gps->item_str, NULL);
break;
case 9: /* Process date */
esp_gps->parent.date.day = convert_two_digit2number(esp_gps->item_str + 0);
esp_gps->parent.date.month = convert_two_digit2number(esp_gps->item_str + 2);
esp_gps->parent.date.year = convert_two_digit2number(esp_gps->item_str + 4);
break;
case 10: /* Process magnetic variation */
esp_gps->parent.variation = strtof(esp_gps->item_str, NULL);
break;
default:
break;
}
}
#endif
#if CONFIG_NMEA_STATEMENT_GLL
/**
* @brief Parse GLL statements
*
* @param esp_gps esp_gps_t type object
*/
static void parse_gll(esp_gps_t *esp_gps)
{
/* Process GPGLL statement */
switch (esp_gps->item_num) {
case 1:/* Latitude */
esp_gps->parent.latitude = parse_lat_long(esp_gps);
break;
case 2: /* Latitude north(1)/south(-1) information */
if (esp_gps->item_str[0] == 'S' || esp_gps->item_str[0] == 's') {
esp_gps->parent.latitude *= -1;
}
break;
case 3: /* Longitude */
esp_gps->parent.longitude = parse_lat_long(esp_gps);
break;
case 4: /* Longitude east(1)/west(-1) information */
if (esp_gps->item_str[0] == 'W' || esp_gps->item_str[0] == 'w') {
esp_gps->parent.longitude *= -1;
}
break;
case 5:/* Process UTC time */
parse_utc_time(esp_gps);
break;
case 6: /* Process valid status */
esp_gps->parent.valid = (esp_gps->item_str[0] == 'A');
break;
default:
break;
}
}
#endif
#if CONFIG_NMEA_STATEMENT_VTG
/**
* @brief Parse VTG statements
*
* @param esp_gps esp_gps_t type object
*/
static void parse_vtg(esp_gps_t *esp_gps)
{
/* Process GPVGT statement */
switch (esp_gps->item_num) {
case 1: /* Process true course over ground */
esp_gps->parent.cog = strtof(esp_gps->item_str, NULL);
break;
case 3:/* Process magnetic variation */
esp_gps->parent.variation = strtof(esp_gps->item_str, NULL);
break;
case 5:/* Process ground speed in unit m/s */
esp_gps->parent.speed = strtof(esp_gps->item_str, NULL) * 1.852;//knots to m/s
break;
case 7:/* Process ground speed in unit m/s */
esp_gps->parent.speed = strtof(esp_gps->item_str, NULL) / 3.6;//km/h to m/s
break;
default:
break;
}
}
#endif
/**
* @brief Parse received item
*
* @param esp_gps esp_gps_t type object
* @return esp_err_t ESP_OK on success, ESP_FAIL on error
*/
static esp_err_t parse_item(esp_gps_t *esp_gps)
{
esp_err_t err = ESP_OK;
/* start of a statement */
if (esp_gps->item_num == 0 && esp_gps->item_str[0] == '$') {
if (0) {
}
#if CONFIG_NMEA_STATEMENT_GGA
else if (strstr(esp_gps->item_str, "GGA")) {
esp_gps->cur_statement = STATEMENT_GGA;
}
#endif
#if CONFIG_NMEA_STATEMENT_GSA
else if (strstr(esp_gps->item_str, "GSA")) {
esp_gps->cur_statement = STATEMENT_GSA;
}
#endif
#if CONFIG_NMEA_STATEMENT_RMC
else if (strstr(esp_gps->item_str, "RMC")) {
esp_gps->cur_statement = STATEMENT_RMC;
}
#endif
#if CONFIG_NMEA_STATEMENT_GSV
else if (strstr(esp_gps->item_str, "GSV")) {
esp_gps->cur_statement = STATEMENT_GSV;
}
#endif
#if CONFIG_NMEA_STATEMENT_GLL
else if (strstr(esp_gps->item_str, "GLL")) {
esp_gps->cur_statement = STATEMENT_GLL;
}
#endif
#if CONFIG_NMEA_STATEMENT_VTG
else if (strstr(esp_gps->item_str, "VTG")) {
esp_gps->cur_statement = STATEMENT_VTG;
}
#endif
else {
esp_gps->cur_statement = STATEMENT_UNKNOWN;
}
goto out;
}
/* Parse each item, depend on the type of the statement */
if (esp_gps->cur_statement == STATEMENT_UNKNOWN) {
goto out;
}
#if CONFIG_NMEA_STATEMENT_GGA
else if (esp_gps->cur_statement == STATEMENT_GGA) {
parse_gga(esp_gps);
}
#endif
#if CONFIG_NMEA_STATEMENT_GSA
else if (esp_gps->cur_statement == STATEMENT_GSA) {
parse_gsa(esp_gps);
}
#endif
#if CONFIG_NMEA_STATEMENT_GSV
else if (esp_gps->cur_statement == STATEMENT_GSV) {
parse_gsv(esp_gps);
}
#endif
#if CONFIG_NMEA_STATEMENT_RMC
else if (esp_gps->cur_statement == STATEMENT_RMC) {
parse_rmc(esp_gps);
}
#endif
#if CONFIG_NMEA_STATEMENT_GLL
else if (esp_gps->cur_statement == STATEMENT_GLL) {
parse_gll(esp_gps);
}
#endif
#if CONFIG_NMEA_STATEMENT_VTG
else if (esp_gps->cur_statement == STATEMENT_VTG) {
parse_vtg(esp_gps);
}
#endif
else {
err = ESP_FAIL;
}
out:
return err;
}
/**
* @brief Parse NMEA statements from GPS receiver
*
* @param esp_gps esp_gps_t type object
* @param len number of bytes to decode
* @return esp_err_t ESP_OK on success, ESP_FAIL on error
*/
static esp_err_t gps_decode(esp_gps_t *esp_gps, size_t len)
{
const uint8_t *d = esp_gps->buffer;
while (*d) {
/* Start of a statement */
if (*d == '$') {
/* Reset runtime information */
esp_gps->asterisk = 0;
esp_gps->item_num = 0;
esp_gps->item_pos = 0;
esp_gps->cur_statement = 0;
esp_gps->crc = 0;
esp_gps->sat_count = 0;
esp_gps->sat_num = 0;
/* Add character to item */
esp_gps->item_str[esp_gps->item_pos++] = *d;
esp_gps->item_str[esp_gps->item_pos] = '\0';
}
/* Detect item separator character */
else if (*d == ',') {
/* Parse current item */
parse_item(esp_gps);
/* Add character to CRC computation */
esp_gps->crc ^= (uint8_t)(*d);
/* Start with next item */
esp_gps->item_pos = 0;
esp_gps->item_str[0] = '\0';
esp_gps->item_num++;
}
/* End of CRC computation */
else if (*d == '*') {
/* Parse current item */
parse_item(esp_gps);
/* Asterisk detected */
esp_gps->asterisk = 1;
/* Start with next item */
esp_gps->item_pos = 0;
esp_gps->item_str[0] = '\0';
esp_gps->item_num++;
}
/* End of statement */
else if (*d == '\r') {
/* Convert received CRC from string (hex) to number */
uint8_t crc = (uint8_t)strtol(esp_gps->item_str, NULL, 16);
/* CRC passed */
if (esp_gps->crc == crc) {
switch (esp_gps->cur_statement) {
#if CONFIG_NMEA_STATEMENT_GGA
case STATEMENT_GGA:
esp_gps->parsed_statement |= 1 << STATEMENT_GGA;
break;
#endif
#if CONFIG_NMEA_STATEMENT_GSA
case STATEMENT_GSA:
esp_gps->parsed_statement |= 1 << STATEMENT_GSA;
break;
#endif
#if CONFIG_NMEA_STATEMENT_RMC
case STATEMENT_RMC:
esp_gps->parsed_statement |= 1 << STATEMENT_RMC;
break;
#endif
#if CONFIG_NMEA_STATEMENT_GSV
case STATEMENT_GSV:
if (esp_gps->sat_num == esp_gps->sat_count) {
esp_gps->parsed_statement |= 1 << STATEMENT_GSV;
}
break;
#endif
#if CONFIG_NMEA_STATEMENT_GLL
case STATEMENT_GLL:
esp_gps->parsed_statement |= 1 << STATEMENT_GLL;
break;
#endif
#if CONFIG_NMEA_STATEMENT_VTG
case STATEMENT_VTG:
esp_gps->parsed_statement |= 1 << STATEMENT_VTG;
break;
#endif
default:
break;
}
/* Check if all statements have been parsed */
if (((esp_gps->parsed_statement) & esp_gps->all_statements) == esp_gps->all_statements) {
esp_gps->parsed_statement = 0;
/* Send signal to notify that GPS information has been updated */
esp_event_post_to(esp_gps->event_loop_hdl, ESP_NMEA_EVENT, GPS_UPDATE,
&(esp_gps->parent), sizeof(gps_t), 100 / portTICK_PERIOD_MS);
}
} else {
ESP_LOGD(GPS_TAG, "CRC Error for statement:%s", esp_gps->buffer);
}
if (esp_gps->cur_statement == STATEMENT_UNKNOWN) {
/* Send signal to notify that one unknown statement has been met */
esp_event_post_to(esp_gps->event_loop_hdl, ESP_NMEA_EVENT, GPS_UNKNOWN,
esp_gps->buffer, len, 100 / portTICK_PERIOD_MS);
}
}
/* Other non-space character */
else {
if (!(esp_gps->asterisk)) {
/* Add to CRC */
esp_gps->crc ^= (uint8_t)(*d);
}
/* Add character to item */
esp_gps->item_str[esp_gps->item_pos++] = *d;
esp_gps->item_str[esp_gps->item_pos] = '\0';
}
/* Process next character */
d++;
}
return ESP_OK;
}
/**
* @brief Handle when a pattern has been detected by uart
*
* @param esp_gps esp_gps_t type object
*/
static void esp_handle_uart_pattern(esp_gps_t *esp_gps)
{
int pos = uart_pattern_pop_pos(esp_gps->uart_port);
if (pos != -1) {
/* read one line(include '\n') */
int read_len = uart_read_bytes(esp_gps->uart_port, esp_gps->buffer, pos + 1, 100 / portTICK_PERIOD_MS);
/* make sure the line is a standard string */
esp_gps->buffer[read_len] = '\0';
/* Send new line to handle */
if (gps_decode(esp_gps, read_len + 1) != ESP_OK) {
ESP_LOGW(GPS_TAG, "GPS decode line failed");
}
} else {
ESP_LOGW(GPS_TAG, "Pattern Queue Size too small");
uart_flush_input(esp_gps->uart_port);
}
}
/**
* @brief NMEA Parser Task Entry
*
* @param arg argument
*/
static void nmea_parser_task_entry(void *arg)
{
esp_gps_t *esp_gps = (esp_gps_t *)arg;
uart_event_t event;
while (1) {
if (xQueueReceive(esp_gps->event_queue, &event, pdMS_TO_TICKS(200))) {
switch (event.type) {
case UART_DATA:
break;
case UART_FIFO_OVF:
ESP_LOGW(GPS_TAG, "HW FIFO Overflow");
uart_flush(esp_gps->uart_port);
xQueueReset(esp_gps->event_queue);
break;
case UART_BUFFER_FULL:
ESP_LOGW(GPS_TAG, "Ring Buffer Full");
uart_flush(esp_gps->uart_port);
xQueueReset(esp_gps->event_queue);
break;
case UART_BREAK:
ESP_LOGW(GPS_TAG, "Rx Break");
break;
case UART_PARITY_ERR:
ESP_LOGE(GPS_TAG, "Parity Error");
break;
case UART_FRAME_ERR:
ESP_LOGE(GPS_TAG, "Frame Error");
break;
case UART_PATTERN_DET:
esp_handle_uart_pattern(esp_gps);
break;
default:
ESP_LOGW(GPS_TAG, "unknown uart event type: %d", event.type);
break;
}
}
/* Drive the event loop */
esp_event_loop_run(esp_gps->event_loop_hdl, pdMS_TO_TICKS(50));
}
vTaskDelete(NULL);
}
/**
* @brief Init NMEA Parser
*
* @param config Configuration of NMEA Parser
* @return nmea_parser_handle_t handle of nmea_parser
*/
nmea_parser_handle_t nmea_parser_init(const nmea_parser_config_t *config)
{
esp_gps_t *esp_gps = calloc(1, sizeof(esp_gps_t));
if (!esp_gps) {
ESP_LOGE(GPS_TAG, "calloc memory for esp_fps failed");
goto err_gps;
}
esp_gps->buffer = calloc(1, NMEA_PARSER_RUNTIME_BUFFER_SIZE);
if (!esp_gps->buffer) {
ESP_LOGE(GPS_TAG, "calloc memory for runtime buffer failed");
goto err_buffer;
}
#if CONFIG_NMEA_STATEMENT_GSA
esp_gps->all_statements |= (1 << STATEMENT_GSA);
#endif
#if CONFIG_NMEA_STATEMENT_GSV
esp_gps->all_statements |= (1 << STATEMENT_GSV);
#endif
#if CONFIG_NMEA_STATEMENT_GGA
esp_gps->all_statements |= (1 << STATEMENT_GGA);
#endif
#if CONFIG_NMEA_STATEMENT_RMC
esp_gps->all_statements |= (1 << STATEMENT_RMC);
#endif
#if CONFIG_NMEA_STATEMENT_GLL
esp_gps->all_statements |= (1 << STATEMENT_GLL);
#endif
#if CONFIG_NMEA_STATEMENT_VTG
esp_gps->all_statements |= (1 << STATEMENT_VTG);
#endif
/* Set attributes */
esp_gps->uart_port = config->uart.uart_port;
esp_gps->all_statements &= 0xFE;
/* Install UART friver */
uart_config_t uart_config = {
.baud_rate = config->uart.baud_rate,
.data_bits = config->uart.data_bits,
.parity = config->uart.parity,
.stop_bits = config->uart.stop_bits,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
if (uart_param_config(esp_gps->uart_port, &uart_config) != ESP_OK) {
ESP_LOGE(GPS_TAG, "config uart parameter failed");
goto err_uart_config;
}
if (uart_set_pin(esp_gps->uart_port, UART_PIN_NO_CHANGE, config->uart.rx_pin,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE) != ESP_OK) {
ESP_LOGE(GPS_TAG, "config uart gpio failed");
goto err_uart_config;
}
if (uart_driver_install(esp_gps->uart_port, CONFIG_NMEA_PARSER_RING_BUFFER_SIZE, 0,
config->uart.event_queue_size, &esp_gps->event_queue, 0) != ESP_OK) {
ESP_LOGE(GPS_TAG, "install uart driver failed");
goto err_uart_install;
}
/* Set pattern interrupt, used to detect the end of a line */
uart_enable_pattern_det_intr(esp_gps->uart_port, '\n', 1, 10000, 10, 10);
/* Set pattern queue size */
uart_pattern_queue_reset(esp_gps->uart_port, config->uart.event_queue_size);
uart_flush(esp_gps->uart_port);
/* Create Event loop */
esp_event_loop_args_t loop_args = {
.queue_size = NMEA_EVENT_LOOP_QUEUE_SIZE,
.task_name = NULL
};
if (esp_event_loop_create(&loop_args, &esp_gps->event_loop_hdl) != ESP_OK) {
ESP_LOGE(GPS_TAG, "create event loop faild");
goto err_eloop;
}
/* Create NMEA Parser task */
BaseType_t err = xTaskCreate(
nmea_parser_task_entry,
"nmea_parser",
CONFIG_NMEA_PARSER_TASK_STACK_SIZE,
esp_gps,
CONFIG_NMEA_PARSER_TASK_PRIORITY,
&esp_gps->tsk_hdl);
if (err != pdTRUE) {
ESP_LOGE(GPS_TAG, "create NMEA Parser task failed");
goto err_task_create;
}
ESP_LOGI(GPS_TAG, "NMEA Parser init OK");
return esp_gps;
/*Error Handling*/
err_task_create:
esp_event_loop_delete(esp_gps->event_loop_hdl);
err_eloop:
err_uart_install:
uart_driver_delete(esp_gps->uart_port);
err_uart_config:
err_buffer:
free(esp_gps->buffer);
err_gps:
free(esp_gps);
return NULL;
}
/**
* @brief Deinit NMEA Parser
*
* @param nmea_hdl handle of NMEA parser
* @return esp_err_t ESP_OK on success,ESP_FAIL on error
*/
esp_err_t nmea_parser_deinit(nmea_parser_handle_t nmea_hdl)
{
esp_gps_t *esp_gps = (esp_gps_t *)nmea_hdl;
vTaskDelete(esp_gps->tsk_hdl);
esp_event_loop_delete(esp_gps->event_loop_hdl);
esp_err_t err = uart_driver_delete(esp_gps->uart_port);
free(esp_gps->buffer);
free(esp_gps);
return err;
}
/**
* @brief Add user defined handler for NMEA parser
*
* @param nmea_hdl handle of NMEA parser
* @param event_handler user defined event handler
* @param handler_args handler specific arguments
* @return esp_err_t
* - ESP_OK: Success
* - ESP_ERR_NO_MEM: Cannot allocate memory for the handler
* - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id
* - Others: Fail
*/
esp_err_t nmea_parser_add_handler(nmea_parser_handle_t nmea_hdl, esp_event_handler_t event_handler, void *handler_args)
{
esp_gps_t *esp_gps = (esp_gps_t *)nmea_hdl;
return esp_event_handler_register_with(esp_gps->event_loop_hdl, ESP_NMEA_EVENT, ESP_EVENT_ANY_ID,
event_handler, handler_args);
}
/**
* @brief Remove user defined handler for NMEA parser
*
* @param nmea_hdl handle of NMEA parser
* @param event_handler user defined event handler
* @return esp_err_t
* - ESP_OK: Success
* - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id
* - Others: Fail
*/
esp_err_t nmea_parser_remove_handler(nmea_parser_handle_t nmea_hdl, esp_event_handler_t event_handler)
{
esp_gps_t *esp_gps = (esp_gps_t *)nmea_hdl;
return esp_event_handler_unregister_with(esp_gps->event_loop_hdl, ESP_NMEA_EVENT, ESP_EVENT_ANY_ID, event_handler);
}

View file

@ -0,0 +1,218 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_types.h"
#include "esp_event.h"
#include "esp_err.h"
#include "driver/uart.h"
#define GPS_MAX_SATELLITES_IN_USE (12)
#define GPS_MAX_SATELLITES_IN_VIEW (16)
/**
* @brief Declare of NMEA Parser Event base
*
*/
ESP_EVENT_DECLARE_BASE(ESP_NMEA_EVENT)
/**
* @brief GPS fix type
*
*/
typedef enum {
GPS_FIX_INVALID, /*!< Not fixed */
GPS_FIX_GPS, /*!< GPS */
GPS_FIX_DGPS, /*!< Differential GPS */
} gps_fix_t;
/**
* @brief GPS fix mode
*
*/
typedef enum {
GPS_MODE_INVALID = 1, /*!< Not fixed */
GPS_MODE_2D, /*!< 2D GPS */
GPS_MODE_3D /*!< 3D GPS */
} gps_fix_mode_t;
/**
* @brief GPS satellite information
*
*/
typedef struct {
uint8_t num; /*!< Satellite number */
uint8_t elevation; /*!< Satellite elevation */
uint16_t azimuth; /*!< Satellite azimuth */
uint8_t snr; /*!< Satellite signal noise ratio */
} gps_satellite_t;
/**
* @brief GPS time
*
*/
typedef struct {
uint8_t hour; /*!< Hour */
uint8_t minute; /*!< Minute */
uint8_t second; /*!< Second */
uint16_t thousand; /*!< Thousand */
} gps_time_t;
/**
* @brief GPS date
*
*/
typedef struct {
uint8_t day; /*!< Day (start from 1) */
uint8_t month; /*!< Month (start from 1) */
uint16_t year; /*!< Year (start from 2000) */
} gps_date_t;
/**
* @brief NMEA Statement
*
*/
typedef enum {
STATEMENT_UNKNOWN = 0, /*!< Unknown statement */
STATEMENT_GGA, /*!< GGA */
STATEMENT_GSA, /*!< GSA */
STATEMENT_RMC, /*!< RMC */
STATEMENT_GSV, /*!< GSV */
STATEMENT_GLL, /*!< GLL */
STATEMENT_VTG /*!< VTG */
} nmea_statement_t;
/**
* @brief GPS object
*
*/
typedef struct {
float latitude; /*!< Latitude (degrees) */
float longitude; /*!< Longitude (degrees) */
float altitude; /*!< Altitude (meters) */
gps_fix_t fix; /*!< Fix status */
uint8_t sats_in_use; /*!< Number of satellites in use */
gps_time_t tim; /*!< time in UTC */
gps_fix_mode_t fix_mode; /*!< Fix mode */
uint8_t sats_id_in_use[GPS_MAX_SATELLITES_IN_USE]; /*!< ID list of satellite in use */
float dop_h; /*!< Horizontal dilution of precision */
float dop_p; /*!< Position dilution of precision */
float dop_v; /*!< Vertical dilution of precision */
uint8_t sats_in_view; /*!< Number of satellites in view */
gps_satellite_t sats_desc_in_view[GPS_MAX_SATELLITES_IN_VIEW]; /*!< Information of satellites in view */
gps_date_t date; /*!< Fix date */
bool valid; /*!< GPS validity */
float speed; /*!< Ground speed, unit: m/s */
float cog; /*!< Course over ground */
float variation; /*!< Magnetic variation */
} gps_t;
/**
* @brief Configuration of NMEA Parser
*
*/
typedef struct {
struct {
uart_port_t uart_port; /*!< UART port number */
uint32_t rx_pin; /*!< UART Rx Pin number */
uint32_t baud_rate; /*!< UART baud rate */
uart_word_length_t data_bits; /*!< UART data bits length */
uart_parity_t parity; /*!< UART parity */
uart_stop_bits_t stop_bits; /*!< UART stop bits length */
uint32_t event_queue_size; /*!< UART event queue size */
} uart; /*!< UART specific configuration */
} nmea_parser_config_t;
/**
* @brief NMEA Parser Handle
*
*/
typedef void *nmea_parser_handle_t;
/**
* @brief Default configuration for NMEA Parser
*
*/
#define NMEA_PARSER_CONFIG_DEFAULT() \
{ \
.uart = { \
.uart_port = UART_NUM_1, \
.rx_pin = 2, \
.baud_rate = 9600, \
.data_bits = UART_DATA_8_BITS, \
.parity = UART_PARITY_DISABLE, \
.stop_bits = UART_STOP_BITS_1, \
.event_queue_size = 16 \
} \
}
/**
* @brief NMEA Parser Event ID
*
*/
typedef enum {
GPS_UPDATE, /*!< GPS information has been updated */
GPS_UNKNOWN /*!< Unknown statements detected */
} nmea_event_id_t;
/**
* @brief Init NMEA Parser
*
* @param config Configuration of NMEA Parser
* @return nmea_parser_handle_t handle of NMEA parser
*/
nmea_parser_handle_t nmea_parser_init(const nmea_parser_config_t *config);
/**
* @brief Deinit NMEA Parser
*
* @param nmea_hdl handle of NMEA parser
* @return esp_err_t ESP_OK on success, ESP_FAIL on error
*/
esp_err_t nmea_parser_deinit(nmea_parser_handle_t nmea_hdl);
/**
* @brief Add user defined handler for NMEA parser
*
* @param nmea_hdl handle of NMEA parser
* @param event_handler user defined event handler
* @param handler_args handler specific arguments
* @return esp_err_t
* - ESP_OK: Success
* - ESP_ERR_NO_MEM: Cannot allocate memory for the handler
* - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id
* - Others: Fail
*/
esp_err_t nmea_parser_add_handler(nmea_parser_handle_t nmea_hdl, esp_event_handler_t event_handler, void *handler_args);
/**
* @brief Remove user defined handler for NMEA parser
*
* @param nmea_hdl handle of NMEA parser
* @param event_handler user defined event handler
* @return esp_err_t
* - ESP_OK: Success
* - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id
* - Others: Fail
*/
esp_err_t nmea_parser_remove_handler(nmea_parser_handle_t nmea_hdl, esp_event_handler_t event_handler);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,70 @@
/* NMEA Parser example, that decode data stream from GPS receiver
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nmea_parser.h"
static const char *TAG = "gps_demo";
#define TIME_ZONE (+8) //Beijing Time
#define YEAR_BASE (2000) //date in GPS starts from 2000
/**
* @brief GPS Event Handler
*
* @param event_handler_arg handler specific arguments
* @param event_base event base, here is fixed to ESP_NMEA_EVENT
* @param event_id event id
* @param event_data event specific arguments
*/
static void gps_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
gps_t *gps = NULL;
switch (event_id) {
case GPS_UPDATE:
gps = (gps_t *)event_data;
/* print information parsed from GPS statements */
ESP_LOGI(TAG, "%d/%d/%d %d:%d:%d => \r\n"
"\t\t\t\t\t\tlatitude = %.05f°N\r\n"
"\t\t\t\t\t\tlongtitude = %.05f°E\r\n"
"\t\t\t\t\t\taltitude = %.02fm\r\n"
"\t\t\t\t\t\tspeed = %fm/s",
gps->date.year + YEAR_BASE, gps->date.month, gps->date.day,
gps->tim.hour + TIME_ZONE, gps->tim.minute, gps->tim.second,
gps->latitude, gps->longitude, gps->altitude, gps->speed);
break;
case GPS_UNKNOWN:
/* print unknown statements */
ESP_LOGW(TAG, "Unknown statement:%s", (char *)event_data);
break;
default:
break;
}
}
void app_main()
{
/* NMEA parser configuration */
nmea_parser_config_t config = NMEA_PARSER_CONFIG_DEFAULT();
/* init NMEA parser library */
nmea_parser_handle_t nmea_hdl = nmea_parser_init(&config);
/* register event handler for NMEA parser library */
nmea_parser_add_handler(nmea_hdl, gps_event_handler, NULL);
vTaskDelay(10000 / portTICK_PERIOD_MS);
/* unregister event handler */
nmea_parser_remove_handler(nmea_hdl, gps_event_handler);
/* deinit NMEA parser library */
nmea_parser_deinit(nmea_hdl);
}

View file

@ -0,0 +1,3 @@
#
# Main Makefile. This is basically the same as a component makefile.
#