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
control
function is used to handleIOCTL
commands that aren’t implemented by the upper-halfThe
activate
function is used to enable/disable the device so that is saves power when not in useThe
set_interval
function 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