Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ jobs:
target: esp32s3
- path: 'components/gfps_service/example'
target: esp32s3
- path: 'components/gps/example'
target: esp32s3
- path: 'components/gt911/example'
target: esp32s3
- path: 'components/hid-rp/example'
Expand Down Expand Up @@ -165,6 +167,8 @@ jobs:
target: esp32s3
- path: 'components/mcp23x17/example'
target: esp32s3
- path: 'components/meshtastic/example'
target: esp32s3
- path: 'components/mt6701/example'
target: esp32s3
- path: 'components/neopixel/example'
Expand Down Expand Up @@ -219,6 +223,8 @@ jobs:
target: esp32s3
- path: 'components/state_machine/example'
target: esp32
- path: 'components/sx126x/example'
target: esp32s3
- path: 'components/t-deck/example'
target: esp32s3
- path: 'components/t-dongle-s3/example'
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/upload_components.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ jobs:
components/ft5x06
components/ftp
components/gfps_service
components/gps
components/gt911
components/hid_service
components/hid-rp
Expand All @@ -97,6 +98,7 @@ jobs:
components/matouch-rotary-display
components/max1704x
components/mcp23x17
components/meshtastic
components/monitor
components/motorgo-axis
components/motorgo-mini
Expand Down Expand Up @@ -128,6 +130,7 @@ jobs:
components/st25dv
components/st7123touch
components/state_machine
components/sx126x
components/t_keyboard
components/t-deck
components/t-dongle-s3
Expand Down
5 changes: 5 additions & 0 deletions components/gps/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES "base_component" "task" "driver" "esp_driver_uart"
)
25 changes: 25 additions & 0 deletions components/gps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# GPS Component

[![Badge](https://components.espressif.com/components/espp/gps/badge.svg)](https://components.espressif.com/components/espp/gps)

The `Gps` component provides a driver for UART-attached GNSS receivers which
output standard NMEA-0183 sentences, such as the ATGM336H (found on the
M5Stack Cardputer-Adv LoRa+GPS Cap) and the u-blox MIA-M10Q (found on the
LilyGo T-Deck Plus).

It contains two classes:

- `espp::NmeaParser`: a platform-independent, incremental NMEA-0183 parser
(RMC / GGA, any talker id) with checksum validation, which maintains a
`espp::GpsFix` (position, altitude, speed, course, satellites, hdop, UTC
date/time with unix-timestamp conversion).
- `espp::Gps`: a UART-attached wrapper which reads the NMEA stream in a
background task and provides the latest fix thread-safely (or via
callback). It also supports writing raw configuration commands to the
receiver.

## Example

The [example](./example) shows how to use the `espp::Gps` component to parse
GNSS data, configurable (via menuconfig) for the M5Stack Cardputer-Adv
LoRa+GPS Cap, the LilyGo T-Deck Plus, or custom hardware.
25 changes: 25 additions & 0 deletions components/gps/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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.20)

# NOTE: we don't use the IDF component manager for the examples in this
# repository since the examples use the local versions of the components
set(ENV{IDF_COMPONENT_MANAGER} "0")

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# add the component directories that we want to use
set(EXTRA_COMPONENT_DIRS
"../../../components/"
)

set(
COMPONENTS
"main esptool_py driver gps i2c logger task"
CACHE STRING
"List of components to include"
)

project(gps_example)

set(CMAKE_CXX_STANDARD 20)
26 changes: 26 additions & 0 deletions components/gps/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# GPS Example

This example shows how to use the `espp::Gps` component to parse NMEA data
from a UART-attached GNSS receiver and print the resulting fixes.

## How to use example

### Hardware Required

One of:

- M5Stack Cardputer-Adv with the LoRa+GPS Cap (ATGM336H, 115200 baud)
- LilyGo T-Deck Plus (u-blox MIA-M10Q, 9600 baud)
- Custom hardware with a UART NMEA GNSS receiver

Select the board with `idf.py menuconfig` (`Example Configuration >
Hardware`). Note that GNSS receivers need sky view to get a fix - expect no
fix indoors.

### Build and Flash

Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.

(To exit the serial monitor, type ``Ctrl-]``.)

See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
5 changes: 5 additions & 0 deletions components/gps/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "."
INCLUDE_DIRS "."
REQUIRES gps i2c logger
)
42 changes: 42 additions & 0 deletions components/gps/example/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
menu "Example Configuration"

choice EXAMPLE_HARDWARE
prompt "Hardware"
default EXAMPLE_HARDWARE_CARDPUTER_ADV
help
Select the hardware to run this example on.

config EXAMPLE_HARDWARE_CARDPUTER_ADV
depends on IDF_TARGET_ESP32S3
bool "M5Stack Cardputer-Adv + LoRa+GPS Cap (ATGM336H)"

config EXAMPLE_HARDWARE_TDECK_PLUS
depends on IDF_TARGET_ESP32S3
bool "LilyGo T-Deck Plus (u-blox MIA-M10Q)"

config EXAMPLE_HARDWARE_CUSTOM
bool "Custom"
endchoice

config EXAMPLE_GPS_RX_GPIO
int "GPS RX GPIO Num (connected to the receiver's TX)"
range 0 50
default 15 if EXAMPLE_HARDWARE_CARDPUTER_ADV
default 44 if EXAMPLE_HARDWARE_TDECK_PLUS
default 16 if EXAMPLE_HARDWARE_CUSTOM

config EXAMPLE_GPS_TX_GPIO
int "GPS TX GPIO Num (connected to the receiver's RX)"
range 0 50
default 13 if EXAMPLE_HARDWARE_CARDPUTER_ADV
default 43 if EXAMPLE_HARDWARE_TDECK_PLUS
default 17 if EXAMPLE_HARDWARE_CUSTOM

config EXAMPLE_GPS_BAUD_RATE
int "GPS baud rate"
range 4800 921600
default 115200 if EXAMPLE_HARDWARE_CARDPUTER_ADV
default 9600 if EXAMPLE_HARDWARE_TDECK_PLUS
default 9600 if EXAMPLE_HARDWARE_CUSTOM

endmenu
66 changes: 66 additions & 0 deletions components/gps/example/main/gps_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <chrono>
#include <thread>

#include "gps.hpp"
#include "logger.hpp"

#if CONFIG_EXAMPLE_HARDWARE_CARDPUTER_ADV
#include "i2c.hpp"
#endif

using namespace std::chrono_literals;

extern "C" void app_main(void) {
espp::Logger logger({.tag = "GPS Example", .level = espp::Logger::Verbosity::INFO});
logger.info("Starting example!");

#if CONFIG_EXAMPLE_HARDWARE_CARDPUTER_ADV
// The Cardputer-Adv LoRa+GPS Cap (U214) gates the GPS LNA behind P1 of a
// PI4IOE5V6408 I2C IO expander; drive it high for better sensitivity.
// (P0 is the LoRa antenna switch.)
espp::I2c i2c({.port = I2C_NUM_0,
.sda_io_num = GPIO_NUM_8,
.scl_io_num = GPIO_NUM_9,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE});
{
std::error_code ec;
auto expander = i2c.add_device<uint8_t>({.device_address = 0x43}, ec);
if (expander) {
const uint8_t init_regs[][2] = {
{0x03, 0x03}, // direction: P0, P1 outputs
{0x05, 0x03}, // output state: P0, P1 high
{0x07, 0x00}, // output high-impedance: disabled
};
for (const auto &reg : init_regs) {
expander->write(reg, 2, ec);
}
} else {
logger.warn("Could not configure IO expander: {}", ec.message());
}
}
#endif

//! [gps example]
espp::Gps gps({.uart_port = UART_NUM_1,
.tx_io_num = (gpio_num_t)CONFIG_EXAMPLE_GPS_TX_GPIO,
.rx_io_num = (gpio_num_t)CONFIG_EXAMPLE_GPS_RX_GPIO,
.baud_rate = CONFIG_EXAMPLE_GPS_BAUD_RATE,
.on_fix =
[&](const espp::GpsFix &fix) {
if (fix.valid) {
logger.info("Fix: {:.6f}, {:.6f} alt {:.1f} m, {} sats, hdop {:.1f}, "
"{:02}:{:02}:{:04.1f} UTC",
fix.latitude, fix.longitude, fix.altitude, fix.num_satellites,
fix.hdop, fix.hour, fix.minute, fix.second);
} else {
logger.info("Waiting for fix ({} sats in use)...", fix.num_satellites);
}
},
.log_level = espp::Logger::Verbosity::INFO});
//! [gps example]

while (true) {
std::this_thread::sleep_for(10s);
}
}
11 changes: 11 additions & 0 deletions components/gps/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CONFIG_IDF_TARGET="esp32s3"

CONFIG_FREERTOS_HZ=1000

# set compiler optimization level to -O2 (compile for performance)
CONFIG_COMPILER_OPTIMIZATION_PERF=y

# Common ESP-related
#
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384
23 changes: 23 additions & 0 deletions components/gps/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## IDF Component Manager Manifest File
license: "MIT"
description: "UART GNSS / GPS receiver component with NMEA-0183 parsing for ESP-IDF"
url: "https://github.com/esp-cpp/espp/tree/main/components/gps"
repository: "git://github.com/esp-cpp/espp.git"
maintainers:
- William Emfinger <waemfinger@gmail.com>
documentation: "https://esp-cpp.github.io/espp/wireless/gps.html"
examples:
- path: example
tags:
- cpp
- Component
- GPS
- GNSS
- NMEA
- UART
- Peripheral
dependencies:
idf:
version: '>=5.0'
espp/base_component: '>=1.0'
espp/task: '>=1.0'
105 changes: 105 additions & 0 deletions components/gps/include/gps.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>

#include <driver/gpio.h>
#include <driver/uart.h>

#include "base_component.hpp"
#include "nmea_parser.hpp"
#include "task.hpp"

namespace espp {
/// Driver for UART-attached GNSS receivers which output NMEA-0183 sentences,
/// such as the ATGM336H (M5Stack Cardputer-Adv LoRa+GPS Cap) or the u-blox
/// MIA-M10Q (LilyGo T-Deck Plus).
///
/// The driver installs the UART driver, then runs a task which reads the
/// NMEA stream, parses it (see espp::NmeaParser), and maintains the latest
/// fix, which can be retrieved thread-safely with fix() or delivered via the
/// fix callback.
///
/// \section gps_example Example
/// \snippet gps_example.cpp gps example
class Gps : public BaseComponent {
public:
/// Callback invoked whenever an RMC sentence has been parsed (i.e. once
/// per receiver update cycle), with the current fix.
typedef std::function<void(const GpsFix &fix)> fix_callback_fn;

/// Callback invoked for every valid NMEA sentence received (before
/// parsing), e.g. for logging or for parsing additional sentence types.
typedef std::function<void(std::string_view sentence)> sentence_callback_fn;

/// Configuration for the Gps driver.
struct Config {
uart_port_t uart_port = UART_NUM_1; ///< The UART port to use
gpio_num_t tx_io_num = GPIO_NUM_NC; ///< GPIO connected to the receiver's RX pin (for
///< sending configuration commands); may be NC
gpio_num_t rx_io_num = GPIO_NUM_NC; ///< GPIO connected to the receiver's TX pin
uint32_t baud_rate = 9600; ///< UART baud rate (9600 for most receivers; the
///< M5Stack LoRa+GPS Cap ships configured for 115200)
size_t rx_buffer_size = 2048; ///< UART RX ring buffer size
fix_callback_fn on_fix = nullptr; ///< Optional callback invoked on each fix update
sentence_callback_fn on_sentence = nullptr; ///< Optional callback invoked per NMEA sentence
bool auto_start = true; ///< Whether to install the driver and start the
///< read task on construction
espp::Task::BaseConfig task_config = {
.name = "gps",
.stack_size_bytes = 6 * 1024,
}; ///< Configuration for the reader task
Logger::Verbosity log_level{Logger::Verbosity::WARN}; ///< Log verbosity for the driver
};

/// Constructor
/// \param config The configuration for the driver
explicit Gps(const Config &config);

/// Destructor - stops the task and deletes the UART driver
~Gps();

/// Install the UART driver and start the reader task.
/// \param ec The error code to set if there is an error
/// \return True if started successfully
bool start(std::error_code &ec);

/// Stop the reader task and delete the UART driver.
/// \return True if stopped successfully
bool stop();

/// Whether the driver has been started
/// \return True if the driver is running
bool is_running() const { return running_; }

/// Get a copy of the latest fix data.
/// \return The latest fix
GpsFix fix() const;

/// Whether the receiver currently reports a valid fix
/// \return True if the latest RMC sentence reported a valid fix
bool has_fix() const;

/// Write raw data to the receiver (e.g. NMEA/PCAS/UBX configuration
/// commands). Requires tx_io_num to be set.
/// \param data The data to write
/// \param ec The error code to set if there is an error
/// \return True if the data was written
bool write(std::string_view data, std::error_code &ec);

protected:
bool read_task(std::mutex &m, std::condition_variable &cv);
void handle_line(std::string_view line);

Config config_;
std::atomic<bool> running_{false};
bool uart_installed_{false};
std::unique_ptr<espp::Task> task_;
mutable std::mutex fix_mutex_;
NmeaParser parser_;
std::string line_buffer_;
};
} // namespace espp
Loading
Loading