Documentation: update sensors documentation

update sensors documentation:

- add info about different sensor frameworks in one place
- fix headers style for sensors_uorb.rst: headers
- fix long lines for sensors_uorb.rst so it's possible to read this file in terminal IDE
- add code sections for sensors_uorb.rst

Signed-off-by: raiden00pl <raiden00@railab.me>
This commit is contained in:
raiden00pl 2025-01-15 09:25:17 +01:00 committed by Xiang Xiao
parent 4558db18e4
commit 0652cbf304
6 changed files with 775 additions and 574 deletions

View file

@ -38,7 +38,6 @@ following section.
rptun.rst
rwbuffer.rst
sensors.rst
sensors_uorb.rst
segger.rst
spi.rst
syslog.rst

View file

@ -1,440 +1,32 @@
.. warning:: This list is incomplete. See drivers/sensors for a full list of
supported sensors
==============
Sensor Drivers
==============
Common Sensor Register Interface
================================
Contributed by Bob Feretich
Background and problem statement:
The capabilities and performance of modern sensors have grown tremendously.
Most sensors are now capable of some degree of autonomous behavior and
several permit the user to load firmware into them and perform as
nanocontrollers. Other sensors have very sophisticated built-in digital
filters that can be programmed with hundreds of parameters.
Currently most sensor drivers in the NuttX drivers/sensors
directory implement file_ops open(), close(), and read() functions.
The open() function initializes the sensor and places it in a mode where
it can transfer live data in a default configuration. The close() function
places the sensor in a low power shutdown mode. The read() function
returns the most recent data sample from the sensor's most used data
output registers. The write() function is rarely implemented and when it
is there is no consistency in its use. The lseek() and poll() functions
seem to be completely ignored. This results in the sensors being operated
in only their most primitive modes using a fixed "default configuration".
To work around this problem sensor drivers have implemented ioctl()
functions to perform configuration, program the sensor, and manage
autonomous activity. Ioctls provide a method where user programs can
tunnel through a high level driver to access and control device specific
features. The problem with using ioctls is that before the ioctl interface
can be used, the sensor driver must be opened; and the open() function
causes the driver to start performing these primitive actions, so before
ioctls can manage the drivers as desired, ioctls must first be used to
undo the generic actions caused by the open() function. Another major
issue is that there is no consistency from sensor to sensor on ioctl
definitions, not even for the most common sensor actions like writing a
sensor control register or reading a sensor status register.
Purpose:
The purpose of the "Common Sensor Register Interface" is to implement a
consistent and more useful definition of file_ops interface and to make the
file_ops open() function more flexible in establishing the initial
operational state of the sensor. Compatibility for user applications that
implement the current open(), close(), read() interface will be
maintained; and the much greater capabilities of modern sensors will
become accessible through this interface.
Scope:
Applicable to I2C and SPI attached sensors, and some serial port attached
sensors.
The file_ops interface definition:
open(): This function performs the below actions...
1) Reads the sensors ID register. If the sensor responds with an
unexpected value, then...
a) The driver's write() function is disabled.
b) The open function initializes the driver instance, so
that read() and lseek() operations may be performed to enable
problem diagnoses, but the sensor hardware is not initialized.
(No write operations are performed to the sensor.)
c) The errno global variable is set to positive ENODEV
("No such device").
d) The open() function returns successfully with a file_handle.
Note that the calling routine should clear errno before
calling open(). (The file_ops rules prevent drivers from
setting errno to zero.)
2) The other file_ops functions are enabled.
3) The driver's "current reg address" state variable is set to the
sensor's first sensor data output register. (This will make
calls to read() return live sensor data and maintain compatibility
with existing user programs.)
4) If the driver supports a default worker task and an interrupt
handler is specified by in the sensor configuration structure, then
the default worker task is bound to the default worker task.
5) The sensor configuration structure (that was provided to the driver
registration function) is examined to determine whether a custom
sensor configuration is specified. (The custom configuration is
basically an array of (device_reg_address, value) pairs that are
written to the sensor via "single register write" operations.
If a custom sensor configuration was specified, then that
configuration is written to the sensor, otherwise the "default
sensor configuration" is written to the sensor.
(A side effect of writing this data may result in interrupts
occurring and data being transferred to/from the worker task.)
6) The open() function returns successfully with a file_handle.
``close()``: This function stops sensor activity and places it in a low
power mode. The file_ops interface functions are disabled for this
instance of the sensor driver. (Except for open())
``read()``: The action of this function is dependent on whether a "default
worker task" is running and the value of the driver's "current reg
address" state variable.
If a "default worker task" is running,
AND the driver's "current reg address" is equal to the value of
the first sensor data output register,
AND the number of bytes to be read is less than or equal to the
number of bytes in a "default worker task" sample,
Then data is copied from the "default worker task's" sample memory to
the caller's provided buffer.
Otherwise, this function transfers data from sensor registers to the
data buffer provided by the caller. The first byte read is from the
sensor register address specified by the sensor's "current reg
address". The addresses of subsequent bytes to be read are context
sensitive. If more than bus transfer is needed to complete the read,
then a "multi-byte" (sometimes called "burst mode") data transfer
will be used to fill the buffer.
See the sensor's datasheet to determine the auto-increment
behavior of a "multi-byte" data transfers.
Note: That most sensors collect only a few bytes of data per sample.
Small data transfers occurring over a high speed bus (like SPI and some
high speed i2c and serial interfaces) are much more efficient when
collected directly from the sensor hardware than by using a worker task
as an intermediary.
``write()``: This function transfers data from the data buffer provided by
the caller to sensor registers. The first byte written is to the
sensor register address specified by the sensor's "current reg
address". The addresses of subsequent bytes to be read are context
sensitive. If more than bus transfer is needed to complete the write,
then a "multi-byte" (sometimes called "burst mode") data
transfer will be used to transfer data from the buffer.
See the sensor's datasheet to determine the auto-increment
behavior of a "multi-byte" data transfers.
Note: If write() function was disabled, then no writes will be performed
and the function will return 0 (characters transferred) and errno
is set to -EROFS ("read-only file system").
``lseek()``: This function sets the value of the sensor's "current reg address"
(seek_address). The open() function initializes the "current reg address"
to the first sensor data output register, so unless the user needs
to change the sensor configuration, lseek() does not need to be
called. Neither read() nor write() change the sensor's "current reg
address".
The definition of lseek is...::
off_t lseek(int fd, off_t offset, int whence);
For whence == SEEK_SET, the sensor's "current reg address" will be set
to offset.
For whence == SEEK_CUR, offset will be added to the sensor's "current
reg address".
For whence == SEEK_END, offset is ignored and the sensor's "current
reg address" is set to the first sensor data output register.
lseek() will return an error if the resulting "current reg address"
is invalid for the sensor.
``ioctl()``: Ioctls() may still be used and this interface make no attempt to
regulate them. But, it is expected that far fewer ioctls will be needed.
The above interface can be used to fully configure a sensor to the needs
of an application, including the ability to load firmware into sensor
state machines
Sensor Cluster Driver Interface
===============================
Contributed by Bob Feretich
Background and problem statement:
Most microcontrollers can support SPI bus transfers at 8 MHz or greater.
Most SPI attached sensors can support a 10 MHz SPI bus. Most tri-axis
accelerometers, tri-axis gyroscopes, or tri-axis magnetometers use only 6
bytes per sample. Many sensors use less than 6 bytes per sample. On an 8
MHz SPI bus it takes about 8 microseconds to transfer a 6 byte sample.
(This time includes a command byte, 6 data bytes, and chip select select
setup and hold.) So, for the below discussion keep in mind that the sensor
sample collection work we want to perform should ideally take 8 microseconds
per sample.
The drivers in the drivers/sensors directory support only the user space
file_ops interface (accessing drivers through the POSIX open/read/close
functions using a file descriptor). Also these drivers typically start
their own worker task to perform sensor data collection, even when their
sensors only transfer a few bytes of data per sample and those transfers
are being made over a high performance bus.
Using the current implementation...
1) A sensor "data ready" or timer interrupt occurs.
2) Context is saved and and the driver's interrupt handler is scheduled
to run.
3) The NuttX scheduler dispatches the driver's interrupt handler task.
4) The driver's interrupt handler task posts to a semaphore that the
driver's worker task is waiting on.
5) NuttX restores the context for the driver's worker task and starts it
running.
6) The driver's worker task starts the i/o to collect the sample.) (This is
where the 8 microseconds of real work gets performed.) And waits on a
SPI data transfer complete semaphore.
7) The NuttX saves the context of the driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Note that this is a good thing. This task is probably performing some
other real work. We want this to happen during the data transfer.
8) The completion of the data transfer causes an interrupt. NuttX saves the
current context and restores the driver's worker task's context.
9) The driver's worker task goes to sleep waiting on the semaphore for the
next sensor "data ready" or timer interrupt.
10) The NuttX saves the context of the driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Independently with the above...
a) The sensor application program performs a file_ops read() to collect a
sample.
b) The NuttX high level driver receives control, performs a thin layer of
housekeeping and calls the sensor driver's read function.
c) The sensor driver's read function copies the most recent sample from the
worker task's data area to the application's buffer and returns.
d) The NuttX high level driver receives control, performs a thin layer of
housekeeping and returns.
e) The application processes the sample.
Using a 216 MHz STM32F7 with no other activity occurring, we have timed the
above the elapsed time for the above to be on average 45 microseconds.
Most sensor applications process data from multiple sensors. (An 9-DoF IMU
is typically represented as three sensors (accelerometer, gyroscope, and
magnetometer). In this case there are three copies of 1-10 occurring in
parallel.
In applications where live data is being used, the context switch
thrashing and cache pollution of this approach cripples system
performance. In applications where sensor FIFO data is being used and
therefore a large amount of data is collected per iteration, the non "zero
copy" nature of the data collection becomes a performance issue.
Purpose:
The "Sensor Cluster Driver Interface" provides a standard mechanism for
an application to collect data from multiple sensor drivers in a much more
efficient manner. It significantly reduces the number of running tasks and
the context thrashing and cache pollution caused by them. It also permits
"zero copy" collection of sensor data.
The Sensor Cluster Driver Interface uses a single "worker task" to be shared
by an arbitrary number of drivers. This shared worker task is a kernel
task that is registered like a driver, supports a driver interface to
application programs, and collects data from multiple sensors (a cluster of
sensors), we refer to it a "Sensor Cluster Driver".
Its goal is to change the sequence of events detailed above to...
1) A sensor "data ready" or timer interrupt occurs.
2) Context is saved and and the cluster driver's interrupt handler is
scheduled to run.
3) The NuttX scheduler dispatches the cluster driver's interrupt handler
task.
4) The cluster driver's interrupt handler task posts to a semaphore that
the cluster driver's worker task is waiting on.
5) NuttX restores the context for the driver's worker task and starts it
running.
6) The cluster driver's worker task starts the i/o to collect the sample.
There are two choices here. Programmed I/O (PIO) or DMA. If PIO is
fastest for a small sample size, but it will lock up the processor for
the full duration of the transfer; it can only transfer from one
sensor at a time; and the worker task should manually yield control
occasionally to permit other tasks to run. DMA has higher start and
completion overhead, but it is much faster for long transfers, can
perform simultaneous transfers from sensors on different buses, and
automatically releases the processor while the transfer is occurring.
For this reason our drivers allows the worker task to choose between
PIO (driver_read()) and DMA (driver_exchange()), a common extension to
the sensor_cluster_operations_s structure. So either way after one or
more transfers we yield control and move to the next step. Note that
the data is being transferred directly into the buffer provided by the
application program; so no copy needs to be performed.
7) The NuttX saves the context of the cluster driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Again note that this is a good thing. This task is probably performing
some other real work. We want this to happen during the data transfer.
8) The completion of the last of the previous data transfers causes an
interrupt. NuttX saves the current context and restores the cluster
driver's worker task's context. If there is more sensor data to
collect, then goto Step 6. Otherwise it posts to a semaphore that
will wake the application.
9) The driver's worker task goes to sleep waiting on the semaphore for the
next sensor "data ready" or timer interrupt.
10) The NuttX saves the context of the driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Independently with the above...
a) The sensor application program performs a file_ops read() to collect a
sample.
b) The NuttX high level driver receives control, performs a thin layer of
housekeeping and calls the sensor driver's read function.
c) The sensor driver's read function copies the most recent sample from the
worker task's data area to the application's buffer and returns.
d) The NuttX high level driver receives control, performs a thin layer of
housekeeping and returns.
e) The application processes the sample.
So when collecting data from three sensors, this mechanism saved...
* the handling of 2 sensor "data ready" or timer interrupts (Steps 1 - 4).
* 2 occurrences of waking and scheduling of a worker task (Step 5).
* 2 context switches to other tasks (Step 9 & 10)
* if the three sensors were on separate buses, then 2 occurrences of
Steps 6 - 8 could have also been saved.
* An extra copy operation of the collected sensor data.
* The cache pollution caused by 2 competing worker tasks.
Definitions:
"Leaf Driver" - a kernel driver that implements the "Sensor Cluster Driver
Interface" so that it can be called by Cluster drivers.
"Cluster Driver" - a kernel driver that uses the "Sensor Cluster Driver
Interface" to call leaf drivers.
"Entry-Point Vector" - an array of function addresses to which a leaf driver
will permit calls by a Cluster Driver.
"Leaf Driver Instance Handle" - a pointer to an opaque Leaf Driver structure
that identifies an instance of the leaf driver. Leaf Drivers store this
handle in its configuration structure during registration.
Sensor Cluster Interface description:
* The definition of an entry-point vector. This is similar to the
entry-point vector that is provided to the file-ops high level driver.
This entry-point vector must include the sensor_cluster_operations_s
structure as its first member.
* The the definition of an driver entry-point vector member in the leaf
driver's configuration structure. The leaf driver registration function
must store the address of its entry-point vector in this field.
* The the definition of an instance handle member in the leaf drivers
configuration structure. The leaf driver registration function must store
a handle (opaque pointer) to the instance of the leaf driver being
registered in this field. Note that this should be the same handle that
the leaf driver supplies to NuttX to register itself. The cluster driver
will include this handle as a parameter in calls made to the leaf driver.
.. code-block:: C
struct sensor_cluster_operations_s
{
CODE int (*driver_open)(FAR void *instance_handle, int32_t arg);
CODE int (*driver_close)(FAR void *instance_handle, int32_t arg);
CODE ssize_t (*driver_read)(FAR void *instance_handle, FAR char *buffer,
size_t buflen);
CODE ssize_t (*driver_write)(FAR void *instance_handle,
FAR const char *buffer, size_t buflen);
CODE off_t (*driver_seek)(FAR void *instance_handle, off_t offset,
int whence);
CODE int (*driver_ioctl)(FAR void *instance_handle, int cmd,
unsigned long arg);
CODE int (*driver_suspend)(FAR void *instance_handle, int32_t arg);
CODE int (*driver_resume)(FAR void *instance_handle, int32_t arg);
};
Note that the sensor_cluster_operations_s strongly resembles the NuttX fs.h
file_operations structures. This permits the current file_operations
functions to become thin wrappers around these functions.
``driver_open()`` Same as the fs.h open() except that arg can be specify
permitting more flexibility in sensor configuration and initial operation.
when arg = 0 the function of driver_open() must be identical to open().
``driver_close()`` Same as the fs.h close() except that arg can be specify
permitting more flexibility in selecting a sensor low power state.
when arg = 0 the function of driver_close() must be identical to close().
``driver_read()`` Same as the fs.h read().
``driver_write()`` Same as the fs.h write(). Optional. Set to NULL if not
supported.
``driver_seek()`` Same as the fs.h seek(). Optional. Set to NULL if not
supported.
``driver_ioctl()`` Same as the fs.h ioctl(). Optional. Set to NULL if not
supported.
``driver_suspend()`` and ``driver_resume()`` Optional. Set to NULL if not
supported. It is common for sensor applications to conserve power and
send their microcontroller into a low power sleep state. It seems
appropriate to reserve these spots for future use. These driver entry
points exist in Linux and Windows. Since microcontrollers and sensors
get more capable every year, there should soon be a requirement for
these entry points. Discussion on how to standardize their use and
implementation should
be taken up independently from this driver document.
Note that all drivers are encouraged to extend their entry-point vectors
beyond this common segment. For example it may be beneficial for the
worker task to select between programmed i/o and DMA data transfer
routines. Unregulated extensions to the Entry-Point Vector should be
encouraged to maximize the benefits of a sensor's features.
Operation:
Board logic (configs directory) will register the cluster driver. The
cluster driver will register the leaf drivers that it will call.
This means that the cluster driver has access to the leaf driver's
configuration structures and can pass the Leaf Driver Instance Handle to
the leaf driver as a parameter in calls made via the Entry-Point Vector.
Either board logic or an application program may open() the cluster
driver. The cluster driver open() calls the open() function of the leaf
drivers. The cluster driver open() or read() function can launch the
shared worker task that collects the data.
The cluster driver close() function calls the close functions of the leaf
drivers.
Implemented Drivers
===================
Currently in NuttX we have 3 different approaches to sensor interfaces:
.. toctree::
:glob:
:maxdepth: 1
sensors/*
sensors/sensors_uorb.rst
sensors/sensors_legacy.rst
sensors/sensors_cluster.rst
The preferred way for implementing new sensors is
the :ref:`New sensor framework <new_sensor_framework>`, which provides the most
general interafce.
.. attach files to avoid warinigs, but don't show them here !
.. toctree::
:hidden:
sensors/adt7320.rst
sensors/adxl345.rst
sensors/adxl362.rst
sensors/adxl372.rst
sensors/aht10.rst
sensors/ak09912.rst
sensors/lsm330.rst
sensors/mcp9600.rst
sensors/mpl115a.rst
sensors/sht4x.rst

View file

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Before After
Before After

View file

@ -0,0 +1,439 @@
.. warning:: This interface is not commonly used in NuttX and is not
recomended for implementing new sensors. It should be removed
in the future.
=========================
Sensor Cluster (Obsolote)
=========================
Common Sensor Register Interface
================================
Contributed by Bob Feretich
Background and problem statement:
The capabilities and performance of modern sensors have grown tremendously.
Most sensors are now capable of some degree of autonomous behavior and
several permit the user to load firmware into them and perform as
nanocontrollers. Other sensors have very sophisticated built-in digital
filters that can be programmed with hundreds of parameters.
Currently most sensor drivers in the NuttX drivers/sensors
directory implement file_ops open(), close(), and read() functions.
The open() function initializes the sensor and places it in a mode where
it can transfer live data in a default configuration. The close() function
places the sensor in a low power shutdown mode. The read() function
returns the most recent data sample from the sensor's most used data
output registers. The write() function is rarely implemented and when it
is there is no consistency in its use. The lseek() and poll() functions
seem to be completely ignored. This results in the sensors being operated
in only their most primitive modes using a fixed "default configuration".
To work around this problem sensor drivers have implemented ioctl()
functions to perform configuration, program the sensor, and manage
autonomous activity. Ioctls provide a method where user programs can
tunnel through a high level driver to access and control device specific
features. The problem with using ioctls is that before the ioctl interface
can be used, the sensor driver must be opened; and the open() function
causes the driver to start performing these primitive actions, so before
ioctls can manage the drivers as desired, ioctls must first be used to
undo the generic actions caused by the open() function. Another major
issue is that there is no consistency from sensor to sensor on ioctl
definitions, not even for the most common sensor actions like writing a
sensor control register or reading a sensor status register.
Purpose:
The purpose of the "Common Sensor Register Interface" is to implement a
consistent and more useful definition of file_ops interface and to make the
file_ops open() function more flexible in establishing the initial
operational state of the sensor. Compatibility for user applications that
implement the current open(), close(), read() interface will be
maintained; and the much greater capabilities of modern sensors will
become accessible through this interface.
Scope:
Applicable to I2C and SPI attached sensors, and some serial port attached
sensors.
The file_ops interface definition:
open(): This function performs the below actions...
1) Reads the sensors ID register. If the sensor responds with an
unexpected value, then...
a) The driver's write() function is disabled.
b) The open function initializes the driver instance, so
that read() and lseek() operations may be performed to enable
problem diagnoses, but the sensor hardware is not initialized.
(No write operations are performed to the sensor.)
c) The errno global variable is set to positive ENODEV
("No such device").
d) The open() function returns successfully with a file_handle.
Note that the calling routine should clear errno before
calling open(). (The file_ops rules prevent drivers from
setting errno to zero.)
2) The other file_ops functions are enabled.
3) The driver's "current reg address" state variable is set to the
sensor's first sensor data output register. (This will make
calls to read() return live sensor data and maintain compatibility
with existing user programs.)
4) If the driver supports a default worker task and an interrupt
handler is specified by in the sensor configuration structure, then
the default worker task is bound to the default worker task.
5) The sensor configuration structure (that was provided to the driver
registration function) is examined to determine whether a custom
sensor configuration is specified. (The custom configuration is
basically an array of (device_reg_address, value) pairs that are
written to the sensor via "single register write" operations.
If a custom sensor configuration was specified, then that
configuration is written to the sensor, otherwise the "default
sensor configuration" is written to the sensor.
(A side effect of writing this data may result in interrupts
occurring and data being transferred to/from the worker task.)
6) The open() function returns successfully with a file_handle.
``close()``: This function stops sensor activity and places it in a low
power mode. The file_ops interface functions are disabled for this
instance of the sensor driver. (Except for open())
``read()``: The action of this function is dependent on whether a "default
worker task" is running and the value of the driver's "current reg
address" state variable.
If a "default worker task" is running,
AND the driver's "current reg address" is equal to the value of
the first sensor data output register,
AND the number of bytes to be read is less than or equal to the
number of bytes in a "default worker task" sample,
Then data is copied from the "default worker task's" sample memory to
the caller's provided buffer.
Otherwise, this function transfers data from sensor registers to the
data buffer provided by the caller. The first byte read is from the
sensor register address specified by the sensor's "current reg
address". The addresses of subsequent bytes to be read are context
sensitive. If more than bus transfer is needed to complete the read,
then a "multi-byte" (sometimes called "burst mode") data transfer
will be used to fill the buffer.
See the sensor's datasheet to determine the auto-increment
behavior of a "multi-byte" data transfers.
Note: That most sensors collect only a few bytes of data per sample.
Small data transfers occurring over a high speed bus (like SPI and some
high speed i2c and serial interfaces) are much more efficient when
collected directly from the sensor hardware than by using a worker task
as an intermediary.
``write()``: This function transfers data from the data buffer provided by
the caller to sensor registers. The first byte written is to the
sensor register address specified by the sensor's "current reg
address". The addresses of subsequent bytes to be read are context
sensitive. If more than bus transfer is needed to complete the write,
then a "multi-byte" (sometimes called "burst mode") data
transfer will be used to transfer data from the buffer.
See the sensor's datasheet to determine the auto-increment
behavior of a "multi-byte" data transfers.
Note: If write() function was disabled, then no writes will be performed
and the function will return 0 (characters transferred) and errno
is set to -EROFS ("read-only file system").
``lseek()``: This function sets the value of the sensor's "current reg address"
(seek_address). The open() function initializes the "current reg address"
to the first sensor data output register, so unless the user needs
to change the sensor configuration, lseek() does not need to be
called. Neither read() nor write() change the sensor's "current reg
address".
The definition of lseek is...::
off_t lseek(int fd, off_t offset, int whence);
For whence == SEEK_SET, the sensor's "current reg address" will be set
to offset.
For whence == SEEK_CUR, offset will be added to the sensor's "current
reg address".
For whence == SEEK_END, offset is ignored and the sensor's "current
reg address" is set to the first sensor data output register.
lseek() will return an error if the resulting "current reg address"
is invalid for the sensor.
``ioctl()``: Ioctls() may still be used and this interface make no attempt to
regulate them. But, it is expected that far fewer ioctls will be needed.
The above interface can be used to fully configure a sensor to the needs
of an application, including the ability to load firmware into sensor
state machines
Sensor Cluster Driver Interface
===============================
Contributed by Bob Feretich
Background and problem statement:
Most microcontrollers can support SPI bus transfers at 8 MHz or greater.
Most SPI attached sensors can support a 10 MHz SPI bus. Most tri-axis
accelerometers, tri-axis gyroscopes, or tri-axis magnetometers use only 6
bytes per sample. Many sensors use less than 6 bytes per sample. On an 8
MHz SPI bus it takes about 8 microseconds to transfer a 6 byte sample.
(This time includes a command byte, 6 data bytes, and chip select select
setup and hold.) So, for the below discussion keep in mind that the sensor
sample collection work we want to perform should ideally take 8 microseconds
per sample.
The drivers in the drivers/sensors directory support only the user space
file_ops interface (accessing drivers through the POSIX open/read/close
functions using a file descriptor). Also these drivers typically start
their own worker task to perform sensor data collection, even when their
sensors only transfer a few bytes of data per sample and those transfers
are being made over a high performance bus.
Using the current implementation...
1) A sensor "data ready" or timer interrupt occurs.
2) Context is saved and and the driver's interrupt handler is scheduled
to run.
3) The NuttX scheduler dispatches the driver's interrupt handler task.
4) The driver's interrupt handler task posts to a semaphore that the
driver's worker task is waiting on.
5) NuttX restores the context for the driver's worker task and starts it
running.
6) The driver's worker task starts the i/o to collect the sample.) (This is
where the 8 microseconds of real work gets performed.) And waits on a
SPI data transfer complete semaphore.
7) The NuttX saves the context of the driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Note that this is a good thing. This task is probably performing some
other real work. We want this to happen during the data transfer.
8) The completion of the data transfer causes an interrupt. NuttX saves the
current context and restores the driver's worker task's context.
9) The driver's worker task goes to sleep waiting on the semaphore for the
next sensor "data ready" or timer interrupt.
10) The NuttX saves the context of the driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Independently with the above...
a) The sensor application program performs a file_ops read() to collect a
sample.
b) The NuttX high level driver receives control, performs a thin layer of
housekeeping and calls the sensor driver's read function.
c) The sensor driver's read function copies the most recent sample from the
worker task's data area to the application's buffer and returns.
d) The NuttX high level driver receives control, performs a thin layer of
housekeeping and returns.
e) The application processes the sample.
Using a 216 MHz STM32F7 with no other activity occurring, we have timed the
above the elapsed time for the above to be on average 45 microseconds.
Most sensor applications process data from multiple sensors. (An 9-DoF IMU
is typically represented as three sensors (accelerometer, gyroscope, and
magnetometer). In this case there are three copies of 1-10 occurring in
parallel.
In applications where live data is being used, the context switch
thrashing and cache pollution of this approach cripples system
performance. In applications where sensor FIFO data is being used and
therefore a large amount of data is collected per iteration, the non "zero
copy" nature of the data collection becomes a performance issue.
Purpose:
The "Sensor Cluster Driver Interface" provides a standard mechanism for
an application to collect data from multiple sensor drivers in a much more
efficient manner. It significantly reduces the number of running tasks and
the context thrashing and cache pollution caused by them. It also permits
"zero copy" collection of sensor data.
The Sensor Cluster Driver Interface uses a single "worker task" to be shared
by an arbitrary number of drivers. This shared worker task is a kernel
task that is registered like a driver, supports a driver interface to
application programs, and collects data from multiple sensors (a cluster of
sensors), we refer to it a "Sensor Cluster Driver".
Its goal is to change the sequence of events detailed above to...
1) A sensor "data ready" or timer interrupt occurs.
2) Context is saved and and the cluster driver's interrupt handler is
scheduled to run.
3) The NuttX scheduler dispatches the cluster driver's interrupt handler
task.
4) The cluster driver's interrupt handler task posts to a semaphore that
the cluster driver's worker task is waiting on.
5) NuttX restores the context for the driver's worker task and starts it
running.
6) The cluster driver's worker task starts the i/o to collect the sample.
There are two choices here. Programmed I/O (PIO) or DMA. If PIO is
fastest for a small sample size, but it will lock up the processor for
the full duration of the transfer; it can only transfer from one
sensor at a time; and the worker task should manually yield control
occasionally to permit other tasks to run. DMA has higher start and
completion overhead, but it is much faster for long transfers, can
perform simultaneous transfers from sensors on different buses, and
automatically releases the processor while the transfer is occurring.
For this reason our drivers allows the worker task to choose between
PIO (driver_read()) and DMA (driver_exchange()), a common extension to
the sensor_cluster_operations_s structure. So either way after one or
more transfers we yield control and move to the next step. Note that
the data is being transferred directly into the buffer provided by the
application program; so no copy needs to be performed.
7) The NuttX saves the context of the cluster driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Again note that this is a good thing. This task is probably performing
some other real work. We want this to happen during the data transfer.
8) The completion of the last of the previous data transfers causes an
interrupt. NuttX saves the current context and restores the cluster
driver's worker task's context. If there is more sensor data to
collect, then goto Step 6. Otherwise it posts to a semaphore that
will wake the application.
9) The driver's worker task goes to sleep waiting on the semaphore for the
next sensor "data ready" or timer interrupt.
10) The NuttX saves the context of the driver's worker task, and the
scheduler dispatches some other task to run while we are waiting.
Independently with the above...
a) The sensor application program performs a file_ops read() to collect a
sample.
b) The NuttX high level driver receives control, performs a thin layer of
housekeeping and calls the sensor driver's read function.
c) The sensor driver's read function copies the most recent sample from the
worker task's data area to the application's buffer and returns.
d) The NuttX high level driver receives control, performs a thin layer of
housekeeping and returns.
e) The application processes the sample.
So when collecting data from three sensors, this mechanism saved...
* the handling of 2 sensor "data ready" or timer interrupts (Steps 1 - 4).
* 2 occurrences of waking and scheduling of a worker task (Step 5).
* 2 context switches to other tasks (Step 9 & 10)
* if the three sensors were on separate buses, then 2 occurrences of
Steps 6 - 8 could have also been saved.
* An extra copy operation of the collected sensor data.
* The cache pollution caused by 2 competing worker tasks.
Definitions:
"Leaf Driver" - a kernel driver that implements the "Sensor Cluster Driver
Interface" so that it can be called by Cluster drivers.
"Cluster Driver" - a kernel driver that uses the "Sensor Cluster Driver
Interface" to call leaf drivers.
"Entry-Point Vector" - an array of function addresses to which a leaf driver
will permit calls by a Cluster Driver.
"Leaf Driver Instance Handle" - a pointer to an opaque Leaf Driver structure
that identifies an instance of the leaf driver. Leaf Drivers store this
handle in its configuration structure during registration.
Sensor Cluster Interface description:
* The definition of an entry-point vector. This is similar to the
entry-point vector that is provided to the file-ops high level driver.
This entry-point vector must include the sensor_cluster_operations_s
structure as its first member.
* The the definition of an driver entry-point vector member in the leaf
driver's configuration structure. The leaf driver registration function
must store the address of its entry-point vector in this field.
* The the definition of an instance handle member in the leaf drivers
configuration structure. The leaf driver registration function must store
a handle (opaque pointer) to the instance of the leaf driver being
registered in this field. Note that this should be the same handle that
the leaf driver supplies to NuttX to register itself. The cluster driver
will include this handle as a parameter in calls made to the leaf driver.
.. code-block:: C
struct sensor_cluster_operations_s
{
CODE int (*driver_open)(FAR void *instance_handle, int32_t arg);
CODE int (*driver_close)(FAR void *instance_handle, int32_t arg);
CODE ssize_t (*driver_read)(FAR void *instance_handle, FAR char *buffer,
size_t buflen);
CODE ssize_t (*driver_write)(FAR void *instance_handle,
FAR const char *buffer, size_t buflen);
CODE off_t (*driver_seek)(FAR void *instance_handle, off_t offset,
int whence);
CODE int (*driver_ioctl)(FAR void *instance_handle, int cmd,
unsigned long arg);
CODE int (*driver_suspend)(FAR void *instance_handle, int32_t arg);
CODE int (*driver_resume)(FAR void *instance_handle, int32_t arg);
};
Note that the sensor_cluster_operations_s strongly resembles the NuttX fs.h
file_operations structures. This permits the current file_operations
functions to become thin wrappers around these functions.
``driver_open()`` Same as the fs.h open() except that arg can be specify
permitting more flexibility in sensor configuration and initial operation.
when arg = 0 the function of driver_open() must be identical to open().
``driver_close()`` Same as the fs.h close() except that arg can be specify
permitting more flexibility in selecting a sensor low power state.
when arg = 0 the function of driver_close() must be identical to close().
``driver_read()`` Same as the fs.h read().
``driver_write()`` Same as the fs.h write(). Optional. Set to NULL if not
supported.
``driver_seek()`` Same as the fs.h seek(). Optional. Set to NULL if not
supported.
``driver_ioctl()`` Same as the fs.h ioctl(). Optional. Set to NULL if not
supported.
``driver_suspend()`` and ``driver_resume()`` Optional. Set to NULL if not
supported. It is common for sensor applications to conserve power and
send their microcontroller into a low power sleep state. It seems
appropriate to reserve these spots for future use. These driver entry
points exist in Linux and Windows. Since microcontrollers and sensors
get more capable every year, there should soon be a requirement for
these entry points. Discussion on how to standardize their use and
implementation should
be taken up independently from this driver document.
Note that all drivers are encouraged to extend their entry-point vectors
beyond this common segment. For example it may be beneficial for the
worker task to select between programmed i/o and DMA data transfer
routines. Unregulated extensions to the Entry-Point Vector should be
encouraged to maximize the benefits of a sensor's features.
Operation:
Board logic (configs directory) will register the cluster driver. The
cluster driver will register the leaf drivers that it will call.
This means that the cluster driver has access to the leaf driver's
configuration structures and can pass the Leaf Driver Instance Handle to
the leaf driver as a parameter in calls made via the Entry-Point Vector.
Either board logic or an application program may open() the cluster
driver. The cluster driver open() calls the open() function of the leaf
drivers. The cluster driver open() or read() function can launch the
shared worker task that collects the data.
The cluster driver close() function calls the close functions of the leaf
drivers.
Implemented Drivers
===================
- adxl372 :doc:`adxl372`
- lsm330 :doc:`lsm330`

View file

@ -0,0 +1,81 @@
=====================
Sensor Legacy Drivers
=====================
The old sensors implementation where the character device interface is not
standardized in any way.
This approach is not recommended for new drivers, because without a
standarized interface, creating portable application is imposible.
Implemented Drivers
===================
Drivers that are available also with the new sensor framework are marked with ``[*]``.
- :doc:`adt7320`
- adxl345
- :doc:`aht10`
- :doc:`ak09912`
- amg88xx
- apds9922
- apds9960
- as5048a
- as5048b
- as726x
- bh1749nuc [*]
- bh1750fvi
- bmg160
- bmi088 [*]
- bmi160 [*]
- bmi270 [*]
- bmp180 [*]
- dhtxx
- fxos8700cq
- hall3ph
- hc_sr04
- hdc1008
- hts221
- ina219
- ina226
- ina3221
- isl29023
- kxtj9
- lis2dh
- lis331dl
- lis3dh
- lis3dsh
- lis3mdl
- lm75
- lm92
- lps25h
- lsm303agr
- lsm6dsl
- lsm9ds1
- ltc4151
- max31855
- max31865
- max44009
- max6675
- mb7040
- :doc:`mcp9600`
- mcp9844
- mlx90393
- mlx90614
- :doc:`mpl115a`
- mpu60x0
- ms58xx
- msa301
- qencoder
- scd30
- scd41
- sgp30
- sht21
- sht3x
- :doc:`sht4x`
- sps30
- t67xx
- veml6070
- vl53l1x
- xen1210
- zerocross

View file

@ -1,38 +1,55 @@
============================
Sensor Driver Model For uORB
============================
.. _new_sensor_framework:
=====================
Sensor "uORB" Drivers
=====================
NuttX, in order to uniformly manage all sensors, reuse common code, and
reduce space occupation, extracts the common parts of all sensor drivers
into an **upper half layer** that provides general functionalities.
The **lower half layer** is responsible for the actual interaction with sensor registers.
The **lower half layer** is responsible for the actual interaction with sensor
registers.
NuttX sensor drivers focus more on physical sensors. For virtually fused
sensors generated through integration, they are automatically created through
application advertisements or subscriptions. For devices like IMUs, which integrate
multiple sensors into one unit, multiple lower halves need to be instantiated within the
driver, and device nodes are registered separately through the API (sensor_register)
provided by the upper half.
multiple sensors into one unit, multiple lower halves need to be instantiated
within the driver, and device nodes are registered separately through the API
(sensor_register) provided by the upper half.
Naming
======
The name used for this component in NuttX may be misleading because this sensor
framework is not dependent on "uORB" in any way. Sensors implemented in this way
can be used as general character drivers with a standardized interface.
**Driver Model**
^^^^^^^^^^^^^^^^
================
The NuttX Sensor Upperhalf Driver is primarily responsible for registering device nodes,
implementing the struct file_operations, multi-user access, ring buffer management,
low power consumption, and downsampling logic. The Lowerhalf Driver is divided into rpmsg
half and a general lower half. The rpmsg half is responsible for cross-core subscription
and publication with remote CPUs, while the general lower half is responsible for interacting
with sensor hardware. The main actions performed by the general lower half include a set of
sensor operations such as activate, set_interval, batch, selftest, set_calibvalue, calibrate,
and control. Under interrupt or polling mechanisms, sensor events are sent to the ring buffer
in the upper layer.
The NuttX Sensor Upperhalf Driver is primarily responsible for registering device
nodes, implementing the struct file_operations, multi-user access, ring buffer
management, low power consumption, and downsampling logic. The Lowerhalf Driver
is divided into rpmsg half and a general lower half. The rpmsg half is responsible
for cross-core subscription and publication with remote CPUs, while the general
lower half is responsible for interacting with sensor hardware. The main actions
performed by the general lower half include a set of sensor operations such as
``activate``, ``set_interval``, ``batch``, ``selftest``, ``set_calibvalue``,
``calibrate``, and ``control``. Under interrupt or polling mechanisms,
sensor events are sent to the ring buffer in the upper layer.
.. image:: sensor_driver_model.png
:width: 800px
:align: center
Problems to solve
=================
The current implementation uses the ``float`` type which may make it difficult
to use on platforms without FPU support.
**Code**
^^^^^^^^
========
::
@ -48,66 +65,75 @@ in the upper layer.
**Data Structures**
^^^^^^^^^^^^^^^^^^^
===================
**Sensor Types**
----------------
NuttX defines 50+ types of sensors, covering most physical sensors. All type definitions
are located in include/nuttx/uorb.h. If a new type needs to be added, a comment must be
provided for the new type, explaining the purpose and units of the sensor.
NuttX defines 50+ types of sensors, covering most physical sensors. All type
definitions are located in include/nuttx/uorb.h. If a new type needs to be added,
a comment must be provided for the new type, explaining the purpose and units of
the sensor.
**SENSOR_TYPE_CUSTOM**
This is a custom type used for irregular sensor devices where the event structure changes or is dynamically altered. It is registered using sensor_custom_register.
This is a custom type used for irregular sensor devices where the event structure
changes or is dynamically altered. It is registered using ``sensor_custom_register``.
**SENSOR_TYPE_ACCELEROMETER**
Accelerometer, used to measure the acceleration vector of the device. Units: m/s^2.
Event data structure: (This indicates that there is a specific data structure for accelerometer events, but the actual structure is not provided in the text you gave.)
Accelerometer, used to measure the acceleration vector of the device. Units: m/s=2.
Event data structure: (This indicates that there is a specific data structure for
accelerometer events, but the actual structure is not provided in the text you gave.)
(won't introduce them one by one since there are many)
**Sensor Topic Definition**
---------------------------
The data structure for sensors, which is also the topic structure for uORB, is defined
in include/nuttx/uorb.h.
The data structure for sensors, which is also the topic structure for uORB, is
defined in ``include/nuttx/uorb.h``.
**Lower Half Structure**
------------------------
This structure serves as a bridge between the sensor driver's upper half and lower half.
Both the upper half and lower half populate this structure, with the lower half responsible
for synchronizing configuration information and the upper half for exposing data reporting interfaces.
This structure serves as a bridge between the sensor driver's upper half and
lower half. Both the upper half and lower half populate this structure, with
the lower half responsible for synchronizing configuration information and
the upper half for exposing data reporting interfaces.
The lower part highlighted in red is filled by the lower half driver, while the rest is
filled by the upper half.
The lower part highlighted in red is filled by the lower half driver, while
the rest is filled by the upper half.
``type`` indicates the sensor type: SENSOR_TYPE_XXX;
``type`` indicates the sensor type: ``SENSOR_TYPE_XXX``
``nbuffer`` specifies the length of the ring buffer in the upper half driver;
``uncalibrated`` indicates whether the data reported by the lower half driver is uncalibrated.
If true, it means uncalibrated data is reported, and the registered device node will have a suffix "_uncal".
``uncalibrated`` indicates whether the data reported by the lower half driver is
uncalibrated. If true, it means uncalibrated data is reported, and the registered
device node will have a suffix ``_uncal``.
``ops`` represents the set of sensor operations implemented by the lower half driver.
``push_event`` and ``notify_event`` are not used simultaneously and are filled by the upper half.
``push_event`` and ``notify_event`` are not used simultaneously and are filled
by the upper half.
``push_event`` works in conjunction with the ring buffer for the lower half to report data to the ring buffer;
``push_event`` works in conjunction with the ring buffer for the lower half to
report data to the ring buffer;
``notify_event`` is used in conjunction with fetch to notify the upper half that data is ready when actively
``notify_event`` is used in conjunction with fetch to notify the upper half that
data is ready when actively
pulling data in a blocking operation.
``sensor_lock`` and ``sensor_unlock`` are filled by the upper half and exported to the lower half to avoid
recursive deadlock issues. Currently, they are only used for sensor_rpmsg.
``sensor_lock`` and ``sensor_unlock`` are filled by the upper half and exported
to the lower half to avoid recursive deadlock issues. Currently, they are only
used for sensor_rpmsg.
``priv`` is filled by the upper half and represents the upper context.
``persist`` indicates whether the topic is a notification-type topic.
::
.. code:: C
struct sensor_lowerhalf_s
{
@ -141,16 +167,17 @@ including registration and timestamp acquisition.
For the 50+ types of sensors, the sensor_register function can be used to register
a character device. The parameter dev represents the handle of the lower half,
and devno is the index of the device name. If the registration is successful,
a node will be created under /dev/topic, for example: /dev/topic/sensor_accel0.
a node will be created under ``/dev/{topic}``, for example: ``/dev/topic/sensor_accel0``.
If it fails, an error code will be returned.
For custom special-type drivers, the ``sensor_custom_register`` function needs to be
used to register a character device. The parameter dev is the handle of the lower half,
path is the path of the character device, and esize is the element size of the data reported
by the sensor. If the registration is successful, a character device node will be created
at the specified path. If it fails, an error code will be returned.
For custom special-type drivers, the ``sensor_custom_register`` function needs
to be used to register a character device. The parameter dev is the handle of
the lower half, path is the path of the character device, and esize is
the element size of the data reported by the sensor. If the registration is
successful, a character device node will be created at the specified path.
If it fails, an error code will be returned.
::
.. code:: C
int sensor_register(FAR struct sensor_lowerhalf_s *dev, int devno);
void sensor_unregister(FAR struct sensor_lowerhalf_s *dev, int devno);
@ -165,30 +192,32 @@ at the specified path. If it fails, an error code will be returned.
The function returns a timestamp with microsecond precision.
::
static inline uint64_t sensor_get_timestamp(void)
.. code:: C
static inline uint64_t sensor_get_timestamp(void);
**Sensor Driver Operation Set**
-------------------------------
Sensor driver frameworks for different systems and platforms always revolve around
sensor characteristics, and the NuttX Sensor is no exception. For sensors, common
operations include: opening/closing, initializing range/resolution/filtering,
Sensor driver frameworks for different systems and platforms always revolve
around sensor characteristics, and the NuttX Sensor is no exception. For sensors,
common operations include: opening/closing, initializing range/resolution/filtering,
setting the sampling rate (ODR)/hardware FIFO/operating mode, and interrupt control.
Based on practical applications and references from other systems, several key points
have been selected to form the sensor operation set. For those without the need for
dynamic changes, they can simply be passed as parameters to the initialization function.
Based on practical applications and references from other systems, several key
points have been selected to form the sensor operation set. For those without
the need for dynamic changes, they can simply be passed as parameters to the
initialization function.
**Opening/Closing**
~~~~~~~~~~~~~~~~~~~
When the caller invokes open and close, the corresponding open and close
in the lower half will be called, with parameters being lower and filep respectively.
filep contains user information, so the driver can differentiate between different users.
Currently, this interface is only used by the sensor_rpmsg lower half.
in the lower half will be called, with parameters being lower and filep
respectively. filep contains user information, so the driver can differentiate
between different users. Currently, this interface is only used by the sensor_rpmsg
lower half.
::
.. code:: C
CODE int (*open)(FAR struct sensor_lowerhalf_s *lower,
FAR struct file *filep);
@ -199,10 +228,11 @@ Currently, this interface is only used by the sensor_rpmsg lower half.
**Activating/Deactivating the Sensor**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When the caller invokes open, if it is a subscriber, it will call activate in the lower half
to activate the sensor. When close is called, deactivate is invoked to turn off the sensor.
When the caller invokes open, if it is a subscriber, it will call activate in
the lower half to activate the sensor. When close is called, deactivate is
invoked to turn off the sensor.
::
.. code:: C
CODE int (*activate)(FAR struct sensor_lowerhalf_s *lower,
FAR struct file *filep, bool enable);
@ -210,16 +240,22 @@ to activate the sensor. When close is called, deactivate is invoked to turn off
**Setting the Sampling Rate**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Applications (including the Sensor service) set the sampling rate of the sensor through the
system call ioctl.
Applications (including the Sensor service) set the sampling rate of the sensor
through the system call ioctl.
Call flow: ioctl(fd, SNIOC_SET_INTERVAL, &interval) -> vfs -> sensor_ioctl -> set_interval().
Call flow:
The sampling interval between consecutive samples of the sensor is set in microseconds.
If period_us exceeds the range of min_delay and max_delay, it will be adjusted. When modifying
the sampling rate, it should be ensured that the data that has already been prepared is not lost.
#. ``ioctl(fd, SNIOC_SET_INTERVAL, &interval)``
#. vfs
#. ``sensor_ioctl``
#. ``set_interval()``.
::
The sampling interval between consecutive samples of the sensor is set in
microseconds. If period_us exceeds the range of min_delay and max_delay, it
will be adjusted. When modifying the sampling rate, it should be ensured that
the data that has already been prepared is not lost.
.. code:: C
CODE int (*batch)(FAR struct sensor_lowerhalf_s *lower,
FAR struct file *filep,
@ -231,7 +267,7 @@ the sampling rate, it should be ensured that the data that has already been prep
To proactively obtain sensor data, set to NULL if using interrupt or polling methods.
::
.. code:: C
CODE int (*fetch)(FAR struct sensor_lowerhalf_s *lower,
FAR struct file *filep,
@ -242,7 +278,7 @@ To proactively obtain sensor data, set to NULL if using interrupt or polling met
The sensor self-test is mainly used for factory testing and aging purposes.
::
.. code:: C
CODE int (*selftest)(FAR struct sensor_lowerhalf_s *lower,
FAR struct file *filep,
@ -254,7 +290,7 @@ The sensor self-test is mainly used for factory testing and aging purposes.
Trigger calibration with calibrate and return calibration data to arg.
Use set_calibvalue to set calibration data to the underlying sensor.
::
.. code:: C
CODE int (*calibrate)(FAR struct sensor_lowerhalf_s *lower,
FAR struct file *filep,
@ -268,9 +304,10 @@ Use set_calibvalue to set calibration data to the underlying sensor.
**Sensor Information**
~~~~~~~~~~~~~~~~~~~~~~
Use get_info to proactively obtain sensor information data, with the return value being sensor_device_info_s.
Use get_info to proactively obtain sensor information data, with the return value
being ``sensor_device_info_s``.
::
.. code:: C
struct sensor_device_info_s
{
@ -284,7 +321,7 @@ Use get_info to proactively obtain sensor information data, with the return valu
uint32_t fifo_max_event_count;
char name[SENSOR_INFO_NAME_SIZE];
char vendor[SENSOR_INFO_NAME_SIZE];
}
};
CODE int (*get_info)(FAR struct sensor_lowerhalf_s *lower,
FAR struct file *filep,
@ -293,114 +330,167 @@ Use get_info to proactively obtain sensor information data, with the return valu
**Custom Control**
~~~~~~~~~~~~~~~~~~
In addition to the above controls, if certain sensor control requirements are still not met, the control
command with custom controls can be used. Call flow: ioctl(fd, custom macro cmd, custom parameters) -> vfs ->
sensor_ioctl -> control().
In addition to the above controls, if certain sensor control requirements are
still not met, the control command with custom controls can be used.
::
Call flow:
CODE int (*control)(FAR struct file *filep,
FAR struct sensor_lowerhalf_s *lower,
int cmd, unsigned long arg);
#. ``ioctl(fd, custom macro cmd, custom parameters)``
#. vfs
#. ``sensor_ioctl``
#. ``control()``.
.. code:: C
CODE int (*control)(FAR struct file *filep,
FAR struct sensor_lowerhalf_s *lower,
int cmd, unsigned long arg);
**Downsampling**
----------------
The downsampling capability of Vela Sensor is provided by the sensor upper half at the driver layer,
supporting both aligned and unaligned downsampling mechanisms. When the publisher pushes the main line
index each time, the subscriber retrieves data from its own index. If the difference between the two
indexes exceeds the length of the internal queue, data will be lost. Otherwise, the next theoretical
data point is calculated based on the subscription frequency, the publication frequency factor, and the current index.
The downsampling capability of Vela Sensor is provided by the sensor upper half
at the driver layer, supporting both aligned and unaligned downsampling mechanisms.
When the publisher pushes the main line index each time, the subscriber retrieves
data from its own index. If the difference between the two indexes exceeds the
length of the internal queue, data will be lost. Otherwise, the next theoretical
data point is calculated based on the subscription frequency, the publication
frequency factor, and the current index.
**Multi-Core Mechanism**
------------------------
The cross-core capability of Vela Sensor is provided by the sensor rpmsg lower half at the driver layer,
which is primarily responsible for sending or receiving subscription and broadcast messages from other cores.
The cross-core capability of Vela Sensor is provided by the sensor rpmsg lower
half at the driver layer, which is primarily responsible for sending or receiving
subscription and broadcast messages from other cores.
**Publishing Topics**
---------------------
When a local application publishes a topic for the first time, it broadcasts the message to all cores.
If there are subscriptions on other cores, they bind with each other. A stub is created locally to represent
the subscription on a remote core, and a proxy is created on the remote core to represent the local publisher.
All subsequent communication between them is determined by the context of the stub and proxy.
When a local application publishes a topic for the first time, it broadcasts
the message to all cores. If there are subscriptions on other cores, they bind
with each other. A stub is created locally to represent the subscription on a
remote core, and a proxy is created on the remote core to represent the local
publisher. All subsequent communication between them is determined by the
context of the stub and proxy.
**Subscribing to Topics**
-------------------------
When a local application subscribes to a topic for the first time, if the message is broadcast to all cores and there
are publishers on other cores, they bind with each other and communicate through stubs and proxies.
When a local application subscribes to a topic for the first time, if the message
is broadcast to all cores and there are publishers on other cores, they bind with
each other and communicate through stubs and proxies.
**Remote Control**
------------------
When a local subscriber modifies the sampling rate and the publisher of that topic is remote, the local proxy will
publish this sampling rate to the remote stub. Upon receiving this control information, the stub sets it for the
actual physical hardware. The same applies to other controls.
When a local subscriber modifies the sampling rate and the publisher of that
topic is remote, the local proxy will publish this sampling rate to the remote
stub. Upon receiving this control information, the stub sets it for the actual
physical hardware. The same applies to other controls.
**Remote Message Publishing**
-----------------------------
When local data is published, the sensor rpmsg lower half collects all messages within a sampling interval that
does not exceed half of the fastest topic's interval and sends them to other cores together, reducing IPC
occurrences and saving power consumption.
When local data is published, the sensor rpmsg lower half collects all messages
within a sampling interval that does not exceed half of the fastest topic's
interval and sends them to other cores together, reducing IPC occurrences and
saving power consumption.
**Subscription and Publication Order**
--------------------------------------
There is no order restriction for advertising and subscribing to topics. For notification-type topics, even
if the advertisement is canceled immediately after data publication, other cores can still successfully obtain
the latest data. For general-purpose topics, subscribing after publication will only allow reading of data published
after the subscription.
There is no order restriction for advertising and subscribing to topics.
For notification-type topics, even if the advertisement is canceled immediately
after data publication, other cores can still successfully obtain the latest data.
For general-purpose topics, subscribing after publication will only allow reading
of data published after the subscription.
**Programming Modes**
^^^^^^^^^^^^^^^^^^^^^
=====================
NuttX Sensor drivers support three data retrieval methods: proactive, interrupt-driven, and polling. The proactive
method allows filling sensor events using a buffer passed in by the app, reducing memory copy operations.
The interrupt-driven and polling methods open an internal circular buffer, where each event is automatically pushed
upon generation. The size of the buffer is set by sensor_lowerhalf_s::buffer_size. For sensors with high sampling rates,
it is recommended to set the buffer size for 2-3 events, while for sensors with low sampling rates, setting it for 1 event
is sufficient.
NuttX Sensor drivers support three data retrieval methods: proactive,
interrupt-driven, and polling. The proactive method allows filling sensor events
using a buffer passed in by the app, reducing memory copy operations.
The interrupt-driven and polling methods open an internal circular buffer, where
each event is automatically pushed upon generation. The size of the buffer is set
by sensor_lowerhalf_s::buffer_size. For sensors with high sampling rates, it is
recommended to set the buffer size for 2-3 events, while for sensors with low
sampling rates, setting it for 1 event is sufficient.
**Proactive Retrieval**
-----------------------
This method is recommended for sensors with low sampling rates and small data volumes. The sensor_ops_s::fetch function must be implemented.
The call flow is: read(fd, buf, len) -> vfs -> sensor_read -> fetch().
It is not advisable to use the fetch method to retrieve sensor data. When the caller invokes read, accessing the bus to obtain data has two
disadvantages: the bus speed is low, which may block the upper layer; and the retrieved data may be old and not representative of the current state.
This method is recommended for sensors with low sampling rates and small data
volumes. The ``sensor_ops_s::fetch`` function must be implemented.
When using the fetch function, the upper layer will automatically disable the circular buffer and can directly use the user-space buffer to store
register data, reducing memory copy operations. When the character device node is opened in non-blocking mode, the fetch operation will directly
read the registers via the I2C/SPI bus, and the poll operation will always succeed. When opened in blocking mode, if there is no ready data when
read is called, the poll function can be used to monitor it. If a POLLIN event occurs, the read function should be called immediately to retrieve
the data.
The call flow is:
#. ``read(fd, buf, len)``
#. vfs
#. ``sensor_read``
#. ``fetch()``
It is not advisable to use the fetch method to retrieve sensor data. When the
caller invokes read, accessing the bus to obtain data has two disadvantages:
the bus speed is low, which may block the upper layer; and the retrieved data
may be old and not representative of the current state.
When using the fetch function, the upper layer will automatically disable
the circular buffer and can directly use the user-space buffer to store register
data, reducing memory copy operations. When the character device node is opened
in non-blocking mode, the fetch operation will directly read the registers via
the I2C/SPI bus, and the poll operation will always succeed. When opened in
blocking mode, if there is no ready data when read is called, the poll function
can be used to monitor it. If a POLLIN event occurs, the read function should
be called immediately to retrieve the data.
**Interrupt-Driven Retrieval**
------------------------------
For sensors with hardware interrupts, sensor data can be read via the bus in the interrupt's bottom half, and the event can be pushed to the upper
layer's circular buffer using sensor_lowerhalf_s::push_event. When using the internal circular buffer, data generated in each interrupt's bottom
half is pushed to the upper layer's circular buffer. Upper-layer applications read data directly from the buffer. When the buffer has no data,
it will depend on the blocking flag in f_oflags to determine whether to wait. Common sensors operate in interrupt mode. When an interrupt occurs,
a worker is scheduled to start the bottom half, which then retrieves sensor data via buses such as I2C or SPI and calls the push_event interface
to push the data to the upper half's buffer. It is recommended to configure an interrupt pin for sensors with a sampling rate higher than 25Hz.
For sensors with hardware interrupts, sensor data can be read via the bus in
the interrupt's bottom half, and the event can be pushed to the upper layer's
circular buffer using ``sensor_lowerhalf_s::push_event``. When using the internal
circular buffer, data generated in each interrupt's bottom half is pushed to
the upper layer's circular buffer. Upper-layer applications read data directly
from the buffer. When the buffer has no data, it will depend on the blocking
flag in f_oflags to determine whether to wait. Common sensors operate in
interrupt mode. When an interrupt occurs, a worker is scheduled to start
the bottom half, which then retrieves sensor data via buses such as
I2C or SPI and calls the push_event interface to push the data to the upper
half's buffer. It is recommended to configure an interrupt pin for sensors
with a sampling rate higher than 25Hz.
**Polling Retrieval**
---------------------
For sensors without hardware interrupts, data generated by the sensor can be collected through periodic polling, with the polling period typically
varying based on the sampling rate.
For sensors without hardware interrupts, data generated by the sensor can be
collected through periodic polling, with the polling period typically varying
based on the sampling rate.
**Example Driver**
^^^^^^^^^^^^^^^^^^
::
drivers/sensors/
adxl362_uorb.c bh1749nuc_uorb.c bmi088_uorb.c bmi270_uorb.c bmp180_uorb.c ds18b20_uorb.c fs3000_uorb.c goldfish_gnss_uorb.c hyt271_uorb.c lsm9ds1_uorb.c mpu9250_uorb.c wtgahrs2_uorb.c
adxl372_uorb.c bme680_uorb.c bmi160_uorb.c bmm150_uorb.c bmp280_uorb.c fakesensor_uorb.c gnss_uorb.c goldfish_sensor_uorb.c l3gd20_uorb.c ltr308_uorb.c ms56xx_uorb.c
Implemented Drivers
===================
- :doc:`adxl362`
- :doc:`adxl372`
- bh1749nuc
- bme680
- bmi088
- bmi160
- bmi270
- bmm150
- bmp180
- bmp280
- ds18b20
- fakesensor
- fs3000
- gnss
- goldfish_gnss
- goldfish_sensor
- hyt271
- l3gd20
- lsm9ds1
- ltr308
- mpu9250
- ms56xx
- wtgahrs2