Add documentation about the new async read and decode APIs including some rough examples. Signed-off-by: Yuval Peress <peress@google.com>
231 lines
9.1 KiB
ReStructuredText
231 lines
9.1 KiB
ReStructuredText
.. _sensor_api:
|
|
|
|
Sensors
|
|
#######
|
|
|
|
The sensor subsystem exposes an API to uniformly access sensor devices.
|
|
Common operations are: reading data and executing code when specific
|
|
conditions are met.
|
|
|
|
Basic Operation
|
|
***************
|
|
|
|
Channels
|
|
========
|
|
|
|
Fundamentally, a channel is a quantity that a sensor device can measure.
|
|
|
|
Sensors can have multiple channels, either to represent different axes of
|
|
the same physical property (e.g. acceleration); or because they can measure
|
|
different properties altogether (ambient temperature, pressure and
|
|
humidity). Complex sensors cover both cases, so a single device can expose
|
|
three acceleration channels and a temperature one.
|
|
|
|
It is imperative that all sensors that support a given channel express
|
|
results in the same unit of measurement. Consult the
|
|
:ref:`sensor_api_reference` for all supported channels, along with their
|
|
description and units of measurement:
|
|
|
|
Values
|
|
======
|
|
|
|
Sensor stable APIs return results as :c:struct:`sensor_value`. This
|
|
representation avoids use of floating point values as they may not be
|
|
supported on certain setups.
|
|
|
|
A newer experimental (may change) API that can interpret raw sensor data is
|
|
available in parallel. This new API exposes raw encoded sensor data to the
|
|
application and provides a separate decoder to convert the data to a Q31 format
|
|
which is compatible with the Zephyr :ref:`zdsp_api`. The values represented are
|
|
in the range of (-1.0, 1.0) and require a shift operation in order to scale
|
|
them to their SI unit values. See :ref:`Async Read` for more information.
|
|
|
|
Fetching Values
|
|
===============
|
|
|
|
Getting a reading from a sensor requires two operations. First, an
|
|
application instructs the driver to fetch a sample of all its channels.
|
|
Then, individual channels may be read. In the case of channels with
|
|
multiple axes, they can be read in a single operation by supplying
|
|
the corresponding :literal:`_XYZ` channel type and a buffer of 3
|
|
:c:struct:`sensor_value` objects. This approach ensures consistency
|
|
of channels between reads and efficiency of communication by issuing a
|
|
single transaction on the underlying bus.
|
|
|
|
Below is an example illustrating the usage of the BME280 sensor, which
|
|
measures ambient temperature and atmospheric pressure. Note that
|
|
:c:func:`sensor_sample_fetch` is only called once, as it reads and
|
|
compensates data for both channels.
|
|
|
|
.. literalinclude:: ../../../samples/sensor/bme280/src/main.c
|
|
:language: c
|
|
:lines: 12-
|
|
:linenos:
|
|
|
|
.. _Async Read:
|
|
|
|
Async Read
|
|
==========
|
|
|
|
To enable the async APIs, use :kconfig:option:`CONFIG_SENSOR_ASYNC_API`.
|
|
|
|
Reading the sensors leverages the :ref:`rtio_api` subsystem. Applications
|
|
gain control of the data processing thread and even memory management. In order
|
|
to get started with reading the sensors, an IODev must be created via the
|
|
:c:macro:`SENSOR_DT_READ_IODEV`. Next, an RTIO context must be created. It is
|
|
strongly suggested that this context is created with a memory pool via
|
|
:c:macro:`RTIO_DEFINE_WITH_MEMPOOL`.
|
|
|
|
.. code-block:: C
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/sensor.h>
|
|
#include <zephyr/rtio/rtio.h>
|
|
|
|
static const struct device *lid_accel = DEVICE_DT_GET(DT_ALIAS(lid_accel));
|
|
SENSOR_DT_READ_IODEV(lid_accel_iodev, DT_ALIAS(lid_accel), SENSOR_CHAN_ACCEL_XYZ);
|
|
|
|
RTIO_DEFINE_WITH_MEMPOOL(sensors_rtio,
|
|
4, /* submission queue size */
|
|
4, /* completion queue size */
|
|
16, /* number of memory blocks */
|
|
32, /* size of each memory block */
|
|
4 /* memory alignment */
|
|
);
|
|
|
|
To trigger a read, the application simply needs to call :c:func:`sensor_read`
|
|
and pass the relevant IODev and RTIO context. Getting the result is done like
|
|
any other RTIO operation, by waiting on a completion queue event (CQE). In
|
|
order to help reduce some boilerplate code, the helper function
|
|
:c:func:`sensor_processing_with_callback` is provided. When called, the
|
|
function will block until a CQE becomes available from the provided RTIO
|
|
context. The appropriate buffers are extracted and the callback is called.
|
|
Once the callback is done, the memory is reclaimed by the memorypool. This
|
|
looks like:
|
|
|
|
.. code-block:: C
|
|
|
|
static void sensor_processing_callback(int result, uint8_t *buf,
|
|
uint32_t buf_len, void *userdata) {
|
|
// Process the data...
|
|
}
|
|
|
|
static void sensor_processing_thread(void *, void *, void *) {
|
|
while (true) {
|
|
sensor_processing_with_callback(&sensors_rtio, sensor_processing_callback);
|
|
}
|
|
}
|
|
K_THREAD_DEFINE(sensor_processing_tid, 1024, sensor_processing_thread,
|
|
NULL, NULL, NULL, 0, 0, 0);
|
|
|
|
.. note::
|
|
Helper functions to create custom length IODev nodes and ones that don't
|
|
have static bindings will be added soon.
|
|
|
|
Processing the Data
|
|
===================
|
|
|
|
Once data collection completes and the processing callback was called,
|
|
processing the data is done via the :c:struct:`sensor_decoder_api`. The API
|
|
provides a means for applications to control *when* to process the data and how
|
|
many resources to dedicate to the processing. The API is entirely self
|
|
contained and requires no system calls (even when
|
|
:kconfig:option:`CONFIG_USERSPACE` is enabled).
|
|
|
|
.. code-block:: C
|
|
|
|
static struct sensor_decoder_api *lid_accel_decoder = SENSOR_DECODER_DT_GET(DT_ALIAS(lid_accel));
|
|
|
|
static void sensor_processing_callback(int result, uint8_t *buf,
|
|
uint32_t buf_len, void *userdata) {
|
|
uint64_t timestamp;
|
|
sensor_frame_iterator_t fit = {0};
|
|
sensor_channel_iterator_t cit = {0};
|
|
enum sensor_channel channels[3];
|
|
q31_t values[3];
|
|
int8_t shift[3];
|
|
|
|
lid_accel_decoder->get_timestamp(buf, ×tamp);
|
|
lid_accel_decoder->decode(buf, &fit, &cit, channels, values, 3);
|
|
|
|
/* Values are now in q31_t format, we're going to convert them to micro-units */
|
|
|
|
/* First, we need to know by how much to shift the values */
|
|
lid_accel_decoder->get_shift(buf, channels[0], &shift[0]);
|
|
lid_accel_decoder->get_shift(buf, channels[1], &shift[1]);
|
|
lid_accel_decoder->get_shift(buf, channels[2], &shift[2]);
|
|
|
|
/* Shift the values to get the SI units */
|
|
int64_t scaled_values[] = {
|
|
(int64_t)values[0] << shift[0],
|
|
(int64_t)values[1] << shift[1],
|
|
(int64_t)values[2] << shift[2],
|
|
};
|
|
|
|
/*
|
|
* FIELD_GET(GENMASK64(63, 31), scaled_values[]) - will give the integer value
|
|
* FIELD_GET(GENMASK64(30, 0), scaled_values[]) / INT32_MAX - is the decimal value
|
|
*/
|
|
}
|
|
|
|
Configuration and Attributes
|
|
****************************
|
|
|
|
Setting the communication bus and address is considered the most basic
|
|
configuration for sensor devices. This setting is done at compile time, via
|
|
the configuration menu. If the sensor supports interrupts, the interrupt
|
|
lines and triggering parameters described below are also configured at
|
|
compile time.
|
|
|
|
Alongside these communication parameters, sensor chips typically expose
|
|
multiple parameters that control the accuracy and frequency of measurement.
|
|
In compliance with Zephyr's design goals, most of these values are
|
|
statically configured at compile time.
|
|
|
|
However, certain parameters could require runtime configuration, for
|
|
example, threshold values for interrupts. These values are configured via
|
|
attributes. The example in the following section showcases a sensor with an
|
|
interrupt line that is triggered when the temperature crosses a threshold.
|
|
The threshold is configured at runtime using an attribute.
|
|
|
|
Triggers
|
|
********
|
|
|
|
:dfn:`Triggers` in Zephyr refer to the interrupt lines of the sensor chips.
|
|
Many sensor chips support one or more triggers. Some examples of triggers
|
|
include: new data is ready for reading, a channel value has crossed a
|
|
threshold, or the device has sensed motion.
|
|
|
|
To configure a trigger, an application needs to supply a
|
|
:c:struct:`sensor_trigger` and a handler function. The structure contains the
|
|
trigger type and the channel on which the trigger must be configured.
|
|
|
|
Because most sensors are connected via SPI or I2C buses, it is not possible
|
|
to communicate with them from the interrupt execution context. The
|
|
execution of the trigger handler is deferred to a thread, so that data
|
|
fetching operations are possible. A driver can spawn its own thread to fetch
|
|
data, thus ensuring minimum latency. Alternatively, multiple sensor drivers
|
|
can share a system-wide thread. The shared thread approach increases the
|
|
latency of handling interrupts but uses less memory. You can configure which
|
|
approach to follow for each driver. Most drivers can entirely disable
|
|
triggers resulting in a smaller footprint.
|
|
|
|
The following example contains a trigger fired whenever temperature crosses
|
|
the 26 degree Celsius threshold. It also samples the temperature every
|
|
second. A real application would ideally disable periodic sampling in the
|
|
interest of saving power. Since the application has direct access to the
|
|
kernel config symbols, no trigger is registered when triggering was disabled
|
|
by the driver's configuration.
|
|
|
|
.. literalinclude:: ../../../samples/sensor/mcp9808/src/main.c
|
|
:language: c
|
|
:lines: 12-
|
|
:linenos:
|
|
|
|
.. _sensor_api_reference:
|
|
|
|
API Reference
|
|
**************
|
|
|
|
.. doxygengroup:: sensor_interface
|