GNSS Lower Half uORB Driver
The GNSS lower half driver is used to create uORB drivers for GNSS/GPS devices. The upper-half driver abstracts away the parsing and advertising of NMEA data from the device. The way this lower-half is instantiated is similar to lower half drivers in the uORB framework.
For an example on how to use this lower-half, see </components/drivers/special/sensors/l86xxx>
Application Programming Interface
To use the GNSS lower half in your driver, just include the following header:
#include <nuttx/sensors/gnss.h>
The GNSS driver works similarly to the uORB lower-half driver, where you must implement several operations for the upper-half to work. The first step is defining a custom type for your GNSS device, which must include a reference to the lower-half struct. In this case, the device is UART based, so several members are included to facilitate its operation.
/* Custom GNSS device type */
typedef struct
{
  FAR struct file uart;          /* UART interface to get data */
  struct gnss_lowerhalf_s lower; /* GNSS lower-half */
  bool enabled;                  /* Enabled state */
  char buffer[256];              /* UART read buffer */
  mutex_t lock;                  /* Device lock */
  sem_t run;                     /* Start/stop kthread */
} my_gnss_dev_s;
Then, you can create the operations table.
static const struct gnss_ops_s g_gnss_ops =
{
  .control = my_gnss_control,
  .activate = my_gnss_activate,
  .set_interval = my_gnss_set_interval,
}
These functions are very similar to the functions that you use for implementing a uORB lower-half.
- The - controlfunction is used to handle- IOCTLcommands that aren’t implemented by the upper-half
- The - activatefunction is used to enable/disable the device so that is saves power when not in use
- The - set_intervalfunction is used to set the sampling rate
It is up to you to implement these functions so that they meet the requirements of the upper-half driver.
For this implementation, because we are reading data from a UART interface, we
will read from the uart member of our my_gnss_dev_s struct into our read
buffer. This takes place in a kernel thread, which polls the UART interface.
Once the data is read, we just have to send it to the upper-half to parse and
publish as uORB data.
The data we provide to the upper-half does not have to be null-terminated strings, or even a complete NMEA sentence. The upper-half will parse as it goes, and wait until it has a full sentence in its own buffer before parsing.
Here is an excerpt of how to publish that data:
/* `dev` is a reference to our `my_gnss_dev_s` struct */
err = nxmutex_lock(&dev->lock);
if (err < 0)
  {
    snerr("Couldn't lock mutex\n");
    return err;
  }
bw = file_read(&dev->uart, dev->buffer, sizeof(dev->buffer));
if (bw <= 0)
  {
    snerr("No data on UART: %d\n", bw);
    nxmutex_unlock(&dev->lock);
    continue;
  }
/* Send data read to the lower half for parsing. Does not need to be a
 * full NMEA sentence to be handled.
 */
if (bw > 0)
  {
    dev->lower.push_data(dev->lower.priv, dev->buffer, bw, true);
  }
nxmutex_unlock(&dev->lock);
Once all of the above has been implemented, in your driver’s registration function, you’ll need to initialize the structure to your GNSS device in the registration function.
/* Registration function for this device type */
int mygnss_register(FAR char const *uartpath, int devno)
{
   FAR mygnss_dev_s *priv;
   int err;
   uint32_t nbuffers[SENSOR_GNSS_IDX_GNSS_MAX];
   /* This is a bare example not considering error handling */
   priv = kmm_zalloc(sizeof(my_gnss_dev_s));
   /* Initialize whatever specific members of your device struct need
    * initializing here...
    */
   priv->lower.ops = &g_gnss_ops; /* Ops table */
   priv->lower.priv = priv; /* Reference to your lower-half */
   /* This selects the buffer sizes for each of the buffers that handle these
    * sets of events. The index macros are included in the gnss header file
    */
   nbuffers[SENSOR_GNSS_IDX_GNSS] = 1;
   nbuffers[SENSOR_GNSS_IDX_GNSS_SATELLITE] = 1;
   nbuffers[SENSOR_GNSS_IDX_GNSS_MEASUREMENT] = 1;
   nbuffers[SENSOR_GNSS_IDX_GNSS_CLOCK] = 1;
   nbuffers[SENSOR_GNSS_IDX_GNSS_GEOFENCE] = 1;
   /* Register the lower-half driver with our information.
    * `SENSOR_GNSS_IDX_GNSS_MAX` is the length of our `nbuffers` array.
    */
   err =
       gnss_register(&priv->lower, devno, nbuffers, SENSOR_GNSS_IDX_GNSS_MAX);
   if (err < 0)
     {
       snerr("Failed to register myGNSS driver: %d\n", err);
       /* You should handle the error by cleaning up resources */
     }
   /* Here, handle starting up your kernel thread and any other error
    * cleanup.
    */
   return err;
}
Registration in Device Code
To register the driver in device code, simply call the registration function you wrote. You’ll have to include your header file.
#if defined(CONFIG_SENSORS_MYGNSS) /* Change for your GNSS driver */
#include <nuttx/sensors/mygnss.h>
#endif
/* Put this inside your board's real bringup function, where other drivers
 * are being registered.
 */
int my_board_bringup(void)
{
  #if defined(CONFIG_SENSORS_MYGNSS)
    /* Register myGNSS on USART0 */
    ret = l86xxx_register("/dev/ttyS0", 0);
    if (ret < 0) {
      syslog(LOG_ERR, "Failed to register myGNSS driver: %d\n", ret);
    }
  #endif
}
Operation
Now you should see several different uORB GNSS topics get published under
/dev/uorb. These correspond to all of the different buffer types you
initialized earlier, like geofence, clock, statellite, etc. The plain
sensor_gnss device will publish lots of data from the NMEA sentences.
You can use the uorb_listener application
(uorb uorb(micro object request broker)) to see if data is being published
correctly.
nsh> uorb_listener sensor_gnss
Monitor objects num:2
object_name:sensor_gnss, object_instance:0
sensor_gnss(now:237030000):timestamp:237030000,time_utc:1753502745,latitude:xxxxxxxx,longitude:xxxxxxxxxx,altitude:36.900002,altitude_ellipsoid:24.200001,0