You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by ac...@apache.org on 2025/07/08 00:18:21 UTC
(nuttx) branch master updated: drivers/sensors: add Quectel L86-XXX GNSS uORB sensor driver
This is an automated email from the ASF dual-hosted git repository.
acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new 0ce9e82d739 drivers/sensors: add Quectel L86-XXX GNSS uORB sensor driver
0ce9e82d739 is described below
commit 0ce9e82d739fd9ed5fa887ef64ea4ba32f48f75c
Author: Elias Hawa <el...@gmail.com>
AuthorDate: Thu May 29 16:23:48 2025 -0400
drivers/sensors: add Quectel L86-XXX GNSS uORB sensor driver
---
.../components/drivers/special/sensors.rst | 1 +
.../components/drivers/special/sensors/l86xxx.rst | 177 +++++
drivers/sensors/CMakeLists.txt | 4 +
drivers/sensors/Kconfig | 29 +
drivers/sensors/Make.defs | 5 +-
drivers/sensors/l86xxx_uorb.c | 840 +++++++++++++++++++++
include/nuttx/sensors/ioctl.h | 13 +
include/nuttx/sensors/l86xxx.h | 82 ++
8 files changed, 1150 insertions(+), 1 deletion(-)
diff --git a/Documentation/components/drivers/special/sensors.rst b/Documentation/components/drivers/special/sensors.rst
index 3f964aacc5c..d66d080814d 100644
--- a/Documentation/components/drivers/special/sensors.rst
+++ b/Documentation/components/drivers/special/sensors.rst
@@ -33,3 +33,4 @@ general interface.
sensors/sht4x.rst
sensors/lsm6dso32.rst
sensors/lis2mdl.rst
+ sensors/l86xxx.rst
diff --git a/Documentation/components/drivers/special/sensors/l86xxx.rst b/Documentation/components/drivers/special/sensors/l86xxx.rst
new file mode 100644
index 00000000000..5d8dd9ae285
--- /dev/null
+++ b/Documentation/components/drivers/special/sensors/l86xxx.rst
@@ -0,0 +1,177 @@
+=======
+L86-XXX
+=======
+
+.. tags:: experimental
+
+This driver provides support for the L86-XXX family of GNSS modules by
+Quectel via the :doc:`uorb </components/drivers/special/sensors/sensors_uorb>` interface.
+Functionality for this driver was tested using the Quectel L86-M33.
+
+.. warning::
+ This driver only contains preliminary support for a handful of proprietary
+ 'PMTK' commands There is no support for the entire suite of commands yet.
+ This driver also does not use the standard uORD GNSS upper half driver and
+ should eventually be modified such that it does. CONSIDER THIS DRIVER EXPERIMENTAL.
+
+Application Programming Interface
+=================================
+
+To register the device for use, you will need to enable the standard upper half
+serial drivers (``CONFIG_STANDARD_SERIAL``), since the L86-XXX driver requires
+the path to the UART interface the module is connected to. You will also need to
+ensure that the baud rate of the UART interface is set to 9600, which is the default
+baud rate of the L86-XXX series of GNSS modules.
+
+The driver supports changing the default baud rate and update rate of the GNSS module.
+As a result, you will also need to enable serial TERMIOS support (``CONFIG_SERIAL_TERMIOS``).
+The baud rate and update rate of the GNSS module can be configured using the ``L86_XXX_BAUD`` and ``L86_XXX_FIX_INT`` options respectively.
+Note that a faster update rate will require a higher baud rate to support it and the supported baud rates for the L86-XXX series of GNSS modules are:
+* 4800
+* 9600
+* 14400
+* 19200
+* 38400
+* 57600
+* 115200
+The baud rate and update rates of the module are changed at registration time.
+
+.. code-block:: c
+
+ #if defined(CONFIG_SENSORS_L86_XXX)
+ #include <nuttx/sensors/l86xxx.h>
+
+ /* Register L86-M33 on USART3 */
+
+ ret = l86xxx_register("/dev/l86m33", "/dev/ttyS2", 0);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Failed to register L86-M33: %d\n", ret);
+ }
+ #endif
+
+Once the driver is registered, it starts a thread that continuously reads raw output from the specified UART device and
+parses the output according to `NMEA <https://en.wikipedia.org/wiki/NMEA_0183>`_ standards using the
+`minmea <https://github.com/kosma/minmea>`_ library included in NuttX. The driver populates the ``sensor_gnss`` struct
+and pushes it to the appropriate event once all NMEA messages in its sequence have been read.
+
+
+**uORB commands**
+-----------------
+The driver implements the ``orb_activate``, ``orb_set_interval`` and, ``orb_ioctl`` operations to interact with the device.
+The latter is used to send proprietary 'PMTK' commands which are documented further below.
+
+**Activate**
+
+There are 4 modes that the L86-XXX GNSS modules can be in. Those are "Full On Mode", "Standby Mode", "Backup Mode", "Periodic Mode" and, "AlwaysLocateTM Mode".
+Calling ``orb_activate`` with ``enable`` set to false will enter the module into "Standby Mode".
+In "Standby Mode", the module doesn't output any NMEA messages but the internal core and I/O power domain are still active.
+
+The module can be re-enabled by calling ``orb_activate`` with ``enable`` set to true, which will hot start the module OR by
+sending any 'PMTK' command.
+
+**Set interval**
+
+The L86-XXX GNSS modules support interval rates from 1Hz to 10Hz (100ms - 10000ms). When using ``orb_set_interval``, be aware that
+increasing the interval of the module may also require and increase in baud rate. An example of how this is performed can be found in
+source code of this driver in the register function.
+
+Any interval rate outside of the supported range will result in a failed call to this function.
+
+**Control**
+
+The ``orb_ioctl`` interface allows one to send proprietary 'PMTK' commands to the L86-XXX GNSS module. It effectively works
+as a wrapper for the command framework outlined by Quectel. The return value of calls to ``orb_ioctl`` follow this pattern:
+
+* -EINVAL - Invalid packet
+* -ENOSYS - Unsupported packet type
+* -EIO - Valid packet, but action failed
+* 0 - Valid packet, action succeeded
+* Other - Command failed during writing
+
+The supported commands are their arguments are listed below.
+
+``SNIOC_HOT_START``
+-------------------
+Used to "Hot start" the GNSS module. Normally hot start means the GNSS module was powered down for less
+than 3 hours (RTC must be alive) and its ephemeris is still valid. As there is no need for downloading
+ephemeris, it is the fastest startup method.
+
+.. code-block:: c
+
+ orb_ioctl(sensor, SNIOC_HOT_START);
+
+``SNIOC_WARM_START``
+--------------------
+Used to "Warm start" the GNSS module. Warm start means the GNSS module has approximate information of time,
+position and coarse data on satellite positions, but it needs to download ephemeris until it can get a fix.
+
+.. code-block:: c
+
+ orb_ioctl(sensor, SNIOC_WARM_START);
+
+``SNIOC_COLD_START``
+--------------------
+Used to "Cold start" the GNSS module. Using this message will force the GNSS module to be restarted without
+any prior location information, including time, position, almanacs and ephemeris data.
+
+.. code-block:: c
+
+ orb_ioctl(sensor, SNIOC_COLD_START);
+
+``SNIOC_FULL_COLD_START``
+-------------------------
+Used to "Full cold start" the GNSS module. This is effectively the same as a cold restart, but additionally
+clears system and user configurations. In other words, this resets the GNSS module to its factory settings.
+When full-cold started, the GNSS module has no information on its last location.
+
+.. code-block:: c
+
+ orb_ioctl(sensor, SNIOC_FULL_COLD_START);
+
+``SNIOC_SET_INTERVAL``
+----------------------
+Used to modify the position fix interval of the GNSS module. The argument is an integer between 100 and 10000, default value is 1000.
+
+.. code-block:: c
+
+ orb_ioctl(sensor, SNIOC_SET_INTERVAL, 1000);
+
+``SNIOC_SET_BAUD``
+------------------
+.. note::
+
+ This feature requires termios support to be enabled(``CONFIG_SERIAL_TERMIOS``)
+
+Used to modify the baud rate of the GNSS module. The argument is an integer representing a supported baud rate, default value is 9600.
+Upon sending this command, the baud rate of the UART interface used to communicate with the module is also modified.
+Supported baud rates for the L86-XXX series of GNSS modules are:
+
+* 4800
+* 9600
+* 14400
+* 19200
+* 38400
+* 57600
+* 115200
+
+.. code-block:: c
+
+ orb_ioctl(sensor, SNIOC_SET_BAUD, 9600);
+
+``SNIOC_SET_OPERATIONAL_MODE``
+------------------------------
+Used to set the navigation mode of the GNSS module. The argument is an ``L86XXX_OPERATIONAL_MODE`` enum:
+
+* NORMAL - For general purpose
+* FITNESS - For instances in which low-speed movements (<5 m/s>) will affect calculations
+* AVIATION - For high-dynamic purposes that the large-acceleration movement will have more effect on the position calculation
+* BALLOON - For high-altitude balloon purposes that vertical movement will have more effect on the position calculation
+* STANDBY - Used to enter standby mode for power saving
+
+Default mode is NORMAL
+
+.. code-block:: c
+
+ orb_ioctl(sensor, SNIOC_SET_OPERATIONAL_MODE, NORMAL);
+
+
diff --git a/drivers/sensors/CMakeLists.txt b/drivers/sensors/CMakeLists.txt
index 539acdf13bd..9f4810a40ef 100644
--- a/drivers/sensors/CMakeLists.txt
+++ b/drivers/sensors/CMakeLists.txt
@@ -37,6 +37,10 @@ if(CONFIG_SENSORS)
list(APPEND SRCS nau7802.c)
endif()
+ if(CONFIG_SENSORS_L86_XXX)
+ list(APPEND SRCS l86xxx_uorb.c)
+ endif()
+
if(CONFIG_SENSORS_GNSS)
set_source_files_properties(
gnss_uorb.c DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/..
diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig
index 3cbbc3fd31b..dcd1bd6aec3 100644
--- a/drivers/sensors/Kconfig
+++ b/drivers/sensors/Kconfig
@@ -838,6 +838,35 @@ config KXTJ9_I2C_BUS_SPEED
endif # SENSORS_KXTJ9
+config SENSORS_L86_XXX
+ bool "Quectel L86-XXX GNSS support"
+ default n
+ depends on SERIAL && STANDARD_SERIAL && UORB
+ ---help---
+ Enable driver support for the L86-XXX series of GNSS modules.
+
+config SENSORS_L86_XXX_THREAD_STACKSIZE
+ int "Stack size for L86XXX module collection thread"
+ default 10000
+ depends on SENSORS_L86_XXX
+
+config L86_XXX_BAUD
+ int "Quectel L86-XXX baud rate"
+ default 9600
+ depends on SENSORS_L86_XXX && SERIAL_TERMIOS
+ range 4800 115200
+ ---help---
+ Supported values are: 4800, 9600, 14400, 19200, 38400, 57600 and 115200
+
+config L86_XXX_FIX_INT
+ int "Quectel L86-XXX fix interval rate"
+ default 1000
+ depends on SENSORS_L86_XXX && SERIAL_TERMIOS
+ range 100 10000
+ ---help---
+ Rate in ms at which module obtains satellite fix. Supported values
+ are integers between 100 10000
+
config SENSORS_LIS2DH
bool "STMicro LIS2DH device support"
default n
diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs
index 2cdfcfbb3ed..d3d7e0f554e 100644
--- a/drivers/sensors/Make.defs
+++ b/drivers/sensors/Make.defs
@@ -34,11 +34,14 @@ ifeq ($(CONFIG_SENSORS_RPMSG),y)
CSRCS += sensor_rpmsg.c
endif
-
ifeq ($(CONFIG_SENSORS_NAU7802),y)
CSRCS += nau7802.c
endif
+ifeq ($(CONFIG_SENSORS_L86_XXX),y)
+ CSRCS += l86xxx_uorb.c
+endif
+
ifeq ($(CONFIG_SENSORS_GNSS),y)
CSRCS += gnss_uorb.c
endif
diff --git a/drivers/sensors/l86xxx_uorb.c b/drivers/sensors/l86xxx_uorb.c
new file mode 100644
index 00000000000..219cdedae18
--- /dev/null
+++ b/drivers/sensors/l86xxx_uorb.c
@@ -0,0 +1,840 @@
+/****************************************************************************
+ * drivers/sensors/l86xxx_uorb.c
+ *
+ * NOTE: EXPERIMENTAL DRIVER
+ *
+ * Contributed by Carleton University InSpace
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/nuttx.h>
+#include <debug.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <math.h>
+#include <time.h>
+#include <termios.h>
+
+#include <nuttx/fs/fs.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/kthread.h>
+#include <nuttx/mutex.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/signal.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/sensors/sensor.h>
+#include <minmea/minmea.h>
+
+#include <nuttx/sensors/l86xxx.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#ifndef CONFIG_SENSORS_L86_XXX_THREAD_STACKSIZE
+#define CONFIG_SENSORS_L86_XXX_THREAD_STACKSIZE 10000
+#endif
+
+#ifndef CONFIG_L86_XXX_BAUD
+#define CONFIG_L86_XXX_BAUD 9600
+#endif
+
+#if CONFIG_L86_XXX_BAUD == 4800 || CONFIG_L86_XXX_BAUD == 9600 || CONFIG_L86_XXX_BAUD == 14400 || CONFIG_L86_XXX_BAUD == 19200 || CONFIG_L86_XXX_BAUD == 38400 || CONFIG_L86_XXX_BAUD == 57600 || CONFIG_L86_XXX_BAUD == 115200
+ #define L86_XXX_BAUD_RATE CONFIG_L86_XXX_BAUD
+#else
+ #error "Invalid baud rate. Supported baud rates are: 4800, 5600, 14400, 19200, 38400, 57600, 115200"
+#endif
+
+#if CONFIG_L86_XXX_FIX_INT < 100 || CONFIG_L86_XXX_FIX_INT > 10000
+ #error "Invalid fix interval rate. Values must be between 100 and 10000"
+#endif
+
+/* Helper to get array length */
+
+#define MINMEA_MAX_LENGTH 256
+
+/****************************************************************************
+ * Private Data Types
+ ****************************************************************************/
+
+/* GNSS device struct */
+
+typedef struct
+{
+ FAR struct file uart; /* UART interface */
+ struct sensor_lowerhalf_s lower; /* UORB lower-half */
+ mutex_t devlock; /* Exclusive access */
+ sem_t run; /* Start/stop collection thread */
+ bool enabled; /* If module has started */
+ char buffer[MINMEA_MAX_LENGTH]; /* Buffer for UART interface */
+ int bufbytes;
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+ int16_t crefs; /* Number of open references */
+#endif
+} l86xxx_dev_s;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int l86xxx_control(FAR struct sensor_lowerhalf_s *lower,
+ FAR struct file *filep, int cmd, unsigned long arg);
+static int l86xxx_activate(FAR struct sensor_lowerhalf_s *lower,
+ FAR struct file *filep, bool enable);
+static int l86xxx_set_interval(FAR struct sensor_lowerhalf_s *lower,
+ FAR struct file *filep,
+ FAR uint32_t *period_us);
+static int set_baud_rate(l86xxx_dev_s *dev, int br);
+static int send_command(l86xxx_dev_s *dev,
+ L86XXX_PMTK_COMMAND cmd, unsigned long arg);
+static int read_line(l86xxx_dev_s *dev);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct sensor_ops_s g_sensor_ops =
+{
+ .control = l86xxx_control,
+ .activate = l86xxx_activate,
+ .set_interval = l86xxx_set_interval,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+#ifdef CONFIG_SERIAL_TERMIOS
+/****************************************************************************
+ * Name: set_baud_rate
+ *
+ * Description:
+ * Sets baud rate of UART interface
+ *
+ * Arguments:
+ * dev - Pointer L86-XXX priv struct
+ * br - Baud rate
+ *
+ * Returns:
+ * -EINVAL - Invalid baud rate
+ * 0 - Command succeeded, baud rate changed
+ * Other - Error occurred during ioctl call
+ ****************************************************************************/
+
+static int set_baud_rate(l86xxx_dev_s *dev, int br)
+{
+ struct termios opt;
+ int err;
+ err = file_ioctl(&dev->uart, TCGETS, &opt);
+ if (err < 0)
+ {
+ snwarn("Couldn't get interface settings: %d\n", err);
+ return err;
+ }
+
+ cfmakeraw(&opt);
+
+ switch (br)
+ {
+ case 4800:
+ case 9600:
+ case 14400:
+ case 19200:
+ case 38400:
+ case 57600:
+ case 115200:
+ {
+ cfsetispeed(&opt, br);
+ cfsetospeed(&opt, br);
+ break;
+ }
+
+ default:
+ {
+ snerr("Invalid baud rate, %ld\n", br);
+ return -EINVAL;
+ }
+ }
+
+ err = file_ioctl(&dev->uart, TCSETS, &opt);
+ if (err < 0)
+ {
+ snerr("Couldn't change baud rate of U(S)ART interface: %d\n", err);
+ return err;
+ }
+
+ /* These calls to read_line will flush out the buffer
+ after the baud rate change
+ */
+
+ for (int i = 0; i < 5; ++i)
+ {
+ err = read_line(dev);
+ if (err < 0)
+ {
+ snerr("Problem occurred when flushing buffer %d\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+/****************************************************************************
+ * Name: send_command
+ *
+ * Description:
+ * Sends command L86-XXX GNSS device and waits for acknowledgement
+ * if command supports it.
+ *
+ * Arguments:
+ * dev - Pointer L86-XXX priv struct
+ * cmd - L86XXX_COMMAND enum
+ * arg - Dependent on command type. Could be used for preset
+ * enum, numeric args or struct pointers
+ *
+ * Returns:
+ * Flag defined by device
+ * -EINVAL - Invalid packet
+ * -ENOSYS - Unsupported packet type
+ * -EIO - Valid packet, but action failed
+ * 0 - Valid packet, action succeeded
+ * Other - Command failed during writing
+ ****************************************************************************/
+
+static int send_command(l86xxx_dev_s *dev,
+ L86XXX_PMTK_COMMAND cmd, unsigned long arg)
+{
+ char buf[50];
+ int bw1;
+ int bw2;
+ int err;
+ int ret;
+ uint8_t checksum;
+
+ nxmutex_lock(&dev->devlock);
+ switch (cmd)
+ {
+ case CMD_HOT_START:
+ case CMD_WARM_START:
+ case CMD_COLD_START:
+ case CMD_FULL_COLD_START:
+ {
+ bw1 = snprintf(buf, sizeof(buf), "$PMTK%d", cmd);
+ break;
+ }
+
+ case CMD_STANDBY_MODE:
+ {
+ bw1 = snprintf(buf, sizeof(buf), "$PMTK%d,0", cmd);
+ break;
+ }
+
+ case SET_NMEA_BAUDRATE:
+ {
+ bw1 = snprintf(buf, sizeof(buf), "$PMTK%d,%d", cmd, (int)arg);
+ break;
+ }
+
+ case SET_POS_FIX:
+ {
+ bw1 = snprintf(buf, sizeof(buf), "$PMTK%d,%d", cmd, (int)arg);
+ break;
+ }
+
+ case FR_MODE:
+ {
+ bw1 = snprintf(buf, sizeof(buf), "$PMTK%d,%d", cmd, (int)arg);
+ break;
+ }
+
+ default:
+ return -ENOSYS;
+ }
+
+ sninfo("Sending command: %s to L86", buf);
+
+ checksum = minmea_checksum(buf);
+ bw2 = snprintf(buf + bw1, sizeof(buf) - bw1, "*%02X\r\n", checksum);
+
+ err = file_write(&dev->uart, buf, bw1 + bw2);
+ if (err < 0)
+ {
+ snerr("Could not send command to device\n");
+ return err;
+ }
+
+ /* These commands do not send ACKs
+ so just return after they've been written
+ */
+
+ if (cmd == CMD_HOT_START ||
+ cmd == CMD_WARM_START ||
+ cmd == CMD_COLD_START ||
+ cmd == CMD_FULL_COLD_START)
+ {
+ nxmutex_unlock(&dev->devlock);
+ return 0;
+ }
+
+ /* Setting baud rate also doesn't send an ACK but the interface baud rate
+ needs to be updated
+ */
+
+ if (cmd == SET_NMEA_BAUDRATE)
+ {
+ nxsig_usleep(20000); /* Should wait for a bit before changing interface baud rate */
+ ret = set_baud_rate(dev, (int)arg);
+ nxmutex_unlock(&dev->devlock);
+ return ret;
+ }
+
+ /* Some commands will send ACKs,
+ wait for them here before unlocking the mutex
+ */
+
+ /* ACK message will be $PMTK001,<cmd num>,<flag>
+ flag num indicates success of command
+ 0 = Invalid packet
+ 1 = Unsupported packet type
+ 2 = Valid packet, but action failed
+ 3 = Valid packet, action succeeded
+ */
+
+ memset(buf, '\0', 50);
+ snprintf(buf, 50, "$PMTK001,%d", cmd);
+ sninfo("Waiting for ACK from L86...\n");
+ for (; ; )
+ {
+ read_line(dev);
+ if (strncmp(buf, dev->buffer, strlen(buf)) == 0) break;
+ }
+
+ sninfo("ACK received!\n");
+ nxmutex_unlock(&dev->devlock);
+
+ /* Flag num is always in position 13 of ack, subtract by '0'
+ to obtain return val
+ */
+
+ ret = dev->buffer[13] - '0';
+ if (ret == 1)
+ {
+ return -ENOSYS;
+ }
+ else if (ret == 2)
+ {
+ return -EIO;
+ }
+ else if (ret == 3)
+ {
+ return 0;
+ }
+ else
+ {
+ return -EINVAL;
+ }
+}
+
+/****************************************************************************
+ * Name: read_line
+ *
+ * Description:
+ * Reads line from UART interface and stores in priv buffer
+ *
+ * Arguments:
+ * dev - Pointer L86-XXX priv struct
+ *
+ * Returns:
+ * Negative number if reading from device failed, else number
+ * of bytes read
+ ****************************************************************************/
+
+static int read_line(l86xxx_dev_s *dev)
+{
+ memset(dev->buffer, '\0', sizeof(dev->buffer));
+ int line_len = 0;
+ int err;
+ char next_char;
+ do
+ {
+ err = file_read(&dev->uart, &next_char, 1);
+ if (err < 0)
+ {
+ snerr("Couldn't read from UART device: %d\n", err);
+ return err;
+ }
+
+ if (next_char != '\r' && next_char != '\n')
+ {
+ dev->buffer[line_len++] = next_char;
+ }
+ }
+ while (next_char != '\r' && next_char != '\n'
+ && line_len < sizeof(dev->buffer));
+ dev->buffer[line_len] = '\0';
+ return line_len;
+}
+
+/****************************************************************************
+ * Name: l86xxx_control
+ *
+ * Description:
+ * Send commands to the l86xxx GNSS module
+ *
+ * Returns:
+ * -ENOSYS if ioctl command is not supported,
+ * else return value from send_command
+ ****************************************************************************/
+
+static int l86xxx_control(FAR struct sensor_lowerhalf_s *lower,
+ FAR struct file *filep, int cmd, unsigned long arg)
+{
+ FAR l86xxx_dev_s *dev = container_of(lower, FAR l86xxx_dev_s, lower);
+ L86XXX_PMTK_COMMAND pmtk_cmd;
+ switch (cmd)
+ {
+ case SNIOC_HOT_START:
+ pmtk_cmd = CMD_HOT_START;
+ break;
+ case SNIOC_WARM_START:
+ pmtk_cmd = CMD_WARM_START;
+ break;
+ case SNIOC_COLD_START:
+ pmtk_cmd = CMD_COLD_START;
+ break;
+ case SNIOC_FULL_COLD_START:
+ pmtk_cmd = CMD_FULL_COLD_START;
+ break;
+ case SNIOC_SET_INTERVAL:
+ pmtk_cmd = SET_POS_FIX;
+ break;
+ case SNIOC_SET_BAUD:
+ pmtk_cmd = SET_NMEA_BAUDRATE;
+ break;
+ case SNIOC_SET_OPERATIONAL_MODE:
+ if (arg == STANDBY)
+ {
+ pmtk_cmd = CMD_STANDBY_MODE;
+ }
+ else
+ {
+ pmtk_cmd = FR_MODE;
+ }
+
+ break;
+ default:
+ snerr("Unsupported command\n");
+ return -ENOSYS;
+ }
+
+ return send_command(dev, pmtk_cmd, arg);
+}
+
+/****************************************************************************
+ * Name: l86xxx_activate
+ *
+ * Description:
+ * Puts GNSS module into hot start mode or standby mode depending
+ * on args
+ *
+ * Returns:
+ * Return value from send_command
+ ****************************************************************************/
+
+static int l86xxx_activate(FAR struct sensor_lowerhalf_s *lower,
+ FAR struct file *filep, bool enable)
+{
+ FAR l86xxx_dev_s *dev = container_of(lower, FAR l86xxx_dev_s, lower);
+
+ /* If not already enabled, start gps */
+
+ if (enable && !dev->enabled)
+ {
+ nxsem_post(&dev->run);
+ dev->enabled = true;
+ return send_command(dev, CMD_HOT_START, (int)NULL);
+ }
+
+ /* If not already disabled, send gps into standby mode */
+
+ else if (!enable && dev->enabled)
+ {
+ dev->enabled = false;
+ return send_command(dev, CMD_STANDBY_MODE, 0);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: l86xxx_set_interval
+ *
+ * Description:
+ * Set position fix interval of L86-XXX GNSS module
+ *
+ * Returns:
+ * -EINVAL if invalid interval, else return value from send_command
+ ****************************************************************************/
+
+static int l86xxx_set_interval(FAR struct sensor_lowerhalf_s *lower,
+ FAR struct file *filep,
+ FAR uint32_t *period_us)
+{
+ FAR l86xxx_dev_s *dev = container_of(lower, FAR l86xxx_dev_s, lower);
+ int fix_interval = *period_us;
+ if (fix_interval < 100 || fix_interval > 10000)
+ {
+ return -EINVAL;
+ }
+
+ int ret = send_command(dev, SET_POS_FIX, fix_interval);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: l86xxx_thread
+ *
+ * Description:
+ * Kernel thread to poll the l86xxx
+ ****************************************************************************/
+
+static int l86xxx_thread(int argc, FAR char *argv[])
+{
+ FAR l86xxx_dev_s *dev =
+ (FAR l86xxx_dev_s *)((uintptr_t)strtoul(argv[1], NULL, 16));
+ struct sensor_gnss gps;
+ memset(&gps, 0, sizeof(gps));
+ dev->enabled = true;
+ int err;
+ int bw;
+
+ /* Read full line of NMEA output */
+
+ for (; ; )
+ {
+ /* If the sensor is disabled
+ * wait until enabled with activate function
+ */
+
+ if (!dev->enabled)
+ {
+ err = nxsem_wait(&dev->run);
+ if (err < 0)
+ {
+ snerr("Couldn't wait on semaphore\n");
+ continue;
+ }
+ }
+
+ /* Mutex required because some commands send ACKS */
+
+ err = nxmutex_lock(&dev->devlock);
+ if (err < 0)
+ {
+ snerr("Couldn't lock mutex\n");
+ return err;
+ }
+
+ bw = read_line(dev);
+
+ /* Parse line based on NMEA sentence type */
+
+ if (bw > 0)
+ {
+ switch (minmea_sentence_id(dev->buffer, false))
+ {
+ /* Time data is obtained from RMC sentence */
+
+ case MINMEA_SENTENCE_RMC:
+ {
+ struct minmea_sentence_rmc frame;
+ struct tm tm;
+ if (minmea_check(dev->buffer, false) &&
+ minmea_parse_rmc(&frame, dev->buffer))
+ {
+ gps.timestamp = sensor_get_timestamp();
+ minmea_getdatetime(&tm, &frame.date, &frame.time);
+ gps.time_utc = mktime(&tm);
+ }
+ break;
+ }
+
+ /* Velocity data is obtained from VTG sentence */
+
+ case MINMEA_SENTENCE_VTG:
+ {
+ struct minmea_sentence_vtg frame;
+
+ if (minmea_parse_vtg(&frame, dev->buffer))
+ {
+ gps.ground_speed = minmea_tofloat(&frame.speed_kph) * 3.6; /* Convert speed in kph to mps */
+ gps.course = minmea_tofloat(&frame.true_track_degrees);
+ }
+ break;
+ }
+
+ /* 3D positional data is obtained from GGA sentence */
+
+ case MINMEA_SENTENCE_GGA:
+ {
+ struct minmea_sentence_gga frame;
+
+ if (minmea_parse_gga(&frame, dev->buffer))
+ {
+ gps.latitude = minmea_tocoord(&frame.latitude);
+ gps.longitude = minmea_tocoord(&frame.longitude);
+ gps.altitude = minmea_tofloat(&frame.altitude);
+ gps.altitude_ellipsoid = minmea_tofloat(&frame.height);
+ }
+ break;
+ }
+
+ /* Precision dilution and satellite data is obtained from
+ * GSA sentence
+ */
+
+ case MINMEA_SENTENCE_GSA:
+ {
+ struct minmea_sentence_gsa frame;
+
+ if (minmea_parse_gsa(&frame, dev->buffer))
+ {
+ gps.hdop = minmea_tofloat(&frame.hdop);
+ gps.pdop = minmea_tofloat(&frame.pdop);
+ gps.vdop = minmea_tofloat(&frame.vdop);
+ uint32_t sats = 0;
+ for (int i = 0; i < 12; ++i)
+ {
+ if (frame.sats[i] != 0)
+ {
+ ++sats;
+ }
+ }
+
+ gps.satellites_used = sats;
+ }
+ break;
+ }
+
+ /* GSV and GLL data are transmitted by the l86-XXX but do not
+ provide additional information. Since GLL is always the last
+ message transmitted, events will be pushed whenever that
+ sentence is read
+ */
+
+ case MINMEA_SENTENCE_GLL:
+ {
+ dev->lower.push_event(dev->lower.priv, &gps, sizeof(gps));
+ }
+
+ /* All remaining sentences are not transmitted by the module */
+
+ case MINMEA_SENTENCE_GSV:
+ case MINMEA_SENTENCE_GBS:
+ case MINMEA_SENTENCE_GST:
+ case MINMEA_SENTENCE_ZDA:
+ {
+ break;
+ }
+
+ case MINMEA_INVALID:
+ {
+ snerr("Read invalid NMEA statement: %s\n", dev->buffer);
+ break;
+ }
+
+ case MINMEA_UNKNOWN:
+ {
+ /* Message could be non-standard NMEA message,
+ in that case just ignore
+ */
+
+ if (strncmp("$GPTXT", dev->buffer, strlen("$GPTXT")) == 0)
+ {
+ break;
+ }
+
+ snwarn("Read unknown NMEA statement: %s %d\n",
+ dev->buffer, bw);
+ break;
+ }
+ }
+ }
+
+ nxmutex_unlock(&dev->devlock);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: l86xxx_register
+ *
+ * Description:
+ * Register the L86-XXX GNSS device driver.
+ *
+ * Arguments:
+ * uartpath - The path to the UART character driver connected to the
+ * transceiver
+ * devno - The device number to use for the topic (i.e. /dev/mag0)
+ ****************************************************************************/
+
+int l86xxx_register(FAR const char *uartpath, int devno)
+{
+ FAR l86xxx_dev_s *priv = NULL;
+ int err;
+ char *buf;
+
+ DEBUGASSERT(uartpath != NULL);
+
+ /* Initialize device structure */
+
+ priv = kmm_zalloc(sizeof(l86xxx_dev_s));
+ if (priv == NULL)
+ {
+ snerr("Failed to allocate instance of L86-XXX driver.\n");
+ return -ENOMEM;
+ }
+
+ memset(priv, 0, sizeof(l86xxx_dev_s));
+
+ /* Initialize mutex */
+
+ err = nxmutex_init(&priv->devlock);
+ if (err < 0)
+ {
+ snerr("Failed to initialize mutex for L86-XXX device: %d\n", err);
+ goto free_mem;
+ }
+
+ /* Initialize semaphore */
+
+ err = nxsem_init(&priv->run, 0, 0);
+ if (err < 0)
+ {
+ snerr("Failed to register L86-XXX driver: %d\n", err);
+ goto destroy_mutex;
+ }
+
+ /* Open UART interface for use */
+
+ err = file_open(&priv->uart, uartpath, O_RDWR | O_CLOEXEC);
+ if (err < 0)
+ {
+ wlerr("Failed to open UART interface %s for L86-XXX driver: %d\n",
+ uartpath, err);
+ goto destroy_sem;
+ }
+
+ /* Setup sensor with configured settings */
+
+ sninfo("Waiting for GNSS to start...\n");
+ buf = "$PMTK010,001*2E";
+ for (; ; )
+ {
+ read_line(priv);
+ if (strncmp(buf, priv->buffer, strlen(buf)) == 0) break;
+ }
+
+ sninfo("GNSS module started.\n");
+
+ #ifdef CONFIG_SERIAL_TERMIOS
+ err = send_command(priv, SET_NMEA_BAUDRATE, L86_XXX_BAUD_RATE);
+ if (err < 0)
+ {
+ snwarn("Couldn't set baud rate of device: %d\n", err);
+ }
+
+ err = send_command(priv, SET_POS_FIX, CONFIG_L86_XXX_FIX_INT);
+ if (err < 0)
+ {
+ snwarn("Couldn't set position fix interval, %d\n", err);
+ }
+
+ #endif
+
+ /* Register UORB Sensor */
+
+ priv->lower.ops = &g_sensor_ops;
+ priv->lower.type = SENSOR_TYPE_GNSS;
+
+ err = sensor_register(&priv->lower, devno);
+ if (err < 0)
+ {
+ snerr("Failed to register L86-XXX driver: %d\n", err);
+ goto close_file;
+ }
+
+ FAR char *argv[2];
+ char arg1[32];
+ snprintf(arg1, 16, "%p", priv);
+ argv[0] = arg1;
+ argv[1] = NULL;
+ err = kthread_create("l86xxx_thread", SCHED_PRIORITY_DEFAULT,
+ CONFIG_SENSORS_L86_XXX_THREAD_STACKSIZE,
+ l86xxx_thread, argv);
+
+ if (err < 0)
+ {
+ snerr("Failed to create the l86xxx notification kthread\n");
+ goto sensor_unreg;
+ }
+
+ sninfo("Registered L86-XXX driver with kernel polling thread"
+ " with baud rate %d and update rate %d",
+ L86_XXX_BAUD_RATE,
+ CONFIG_L86_XXX_FIX_INT);
+
+ /* Cleanup items on error */
+
+ if (err < 0)
+ {
+ sensor_unreg:
+ sensor_unregister(&priv->lower, devno);
+ close_file:
+ file_close(&priv->uart);
+ destroy_sem:
+ nxsem_destroy(&priv->run);
+ destroy_mutex:
+ nxmutex_destroy(&priv->devlock);
+ free_mem:
+ kmm_free(priv);
+ }
+
+ return err;
+}
diff --git a/include/nuttx/sensors/ioctl.h b/include/nuttx/sensors/ioctl.h
index 6b10c41d4f1..ff2e6b7d0ab 100644
--- a/include/nuttx/sensors/ioctl.h
+++ b/include/nuttx/sensors/ioctl.h
@@ -498,6 +498,19 @@
#define SNIOC_GET_CALIBVALUE _SNIOC(0x00A3)
+/* Command: SNIOC_SET_BAUD
+ * Description: Sets the baud rate of the sensor.
+ */
+
+#define SNIOC_SET_BAUD _SNIOC(0x00A4)
+
+/* IOCTL commands unique to the L86XXX and other GNSS modules */
+
+#define SNIOC_HOT_START _SNIOC(0X00A5)
+#define SNIOC_WARM_START _SNIOC(0X00A6)
+#define SNIOC_COLD_START _SNIOC(0X00A7)
+#define SNIOC_FULL_COLD_START _SNIOC(0X00A8)
+
/****************************************************************************
* Public types
****************************************************************************/
diff --git a/include/nuttx/sensors/l86xxx.h b/include/nuttx/sensors/l86xxx.h
new file mode 100644
index 00000000000..b2faf247cb6
--- /dev/null
+++ b/include/nuttx/sensors/l86xxx.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+ * include/nuttx/sensors/l86xxx.h
+ *
+ * NOTE: EXPERIMENTAL DRIVER
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you 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.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_SENSORS_L86XXX_H
+#define __INCLUDE_NUTTX_SENSORS_L86XXX_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/sensors/ioctl.h>
+
+/****************************************************************************
+ * Pre-Processor Declarations
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Data Types
+ ****************************************************************************/
+
+typedef enum
+{
+ CMD_HOT_START = 101,
+ CMD_WARM_START = 102,
+ CMD_COLD_START = 103,
+ CMD_FULL_COLD_START = 104,
+ CMD_STANDBY_MODE = 161,
+ SET_POS_FIX = 220,
+ SET_NMEA_BAUDRATE = 251,
+ FR_MODE = 886,
+} L86XXX_PMTK_COMMAND;
+
+typedef enum
+{
+ NORMAL = 0,
+ FITNESS = 1,
+ AVIATION = 2,
+ BALLOON = 3,
+ STANDBY = 4,
+} L86XXX_OPERATIONAL_MODE;
+
+/****************************************************************************
+ * Public Functions Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: l86xxx_register
+ *
+ * Description:
+ * Register the L86-XXX GNSS device driver.
+ *
+ * Arguments:
+ * uartpath - The path to the UART character driver connected to the
+ * GNSS module
+ * devno - The device number to use for the topic (i.e. /dev/mag0)
+ ****************************************************************************/
+
+int l86xxx_register(FAR const char *uartpath, int devno);
+
+#endif /* __INCLUDE_NUTTX_SENSORS_L86XXX_H */
\ No newline at end of file