doc: Revamp sensor docs

Docs now start at a high level, discussing sensors, attributes,
channels, and reading data from them.

Followed by more detailed usage guides in how to solve common problems
sensor users may run into.

Additionally renames the sensor_api sphinx tag to sensor

Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
Tom Burdick 2024-06-04 16:39:55 -05:00 committed by Anas Nashif
commit 9f85a70a04
16 changed files with 667 additions and 205 deletions

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2024 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
#define ACCEL_TRIGGERS \
{ SENSOR_TRIG_DRDY, SENSOR_STREAM_DATA_INCLUDE }, \
{ SENSOR_TRIG_TAP, SENSOR_STREAM_DATA_NOP }
#define ACCEL_ALIAS(id) DT_ALIAS(CONCAT(accel, id))
#define ACCEL_IODEV_SYM(id) CONCAT(accel_iodev, id)
#define ACCEL_IODEV_PTR(id) &ACCEL_IODEV_SYM(id)
#define ACCEL_DEFINE_IODEV(id) \
SENSOR_DT_STREAM_IODEV( \
ACCEL_IODEV_SYM(id), \
ACCEL_ALIAS(id), \
ACCEL_TRIGGERS \
);
#define NUM_SENSORS 2
LISTIFY(NUM_SENSORS, ACCEL_DEFINE_IODEV, (;));
struct sensor_iodev *iodevs[NUM_SENSORS] = { LISTIFY(NUM_SENSORS, ACCEL_IODEV_PTR, (,)) };
RTIO_DEFINE_WITH_MEMPOOL(accel_ctx, NUM_SENSORS, NUM_SENSORS, NUM_SENSORS, 16, sizeof(void *));
int main(void)
{
int rc;
uint32_t accel_frame_iter = 0;
struct sensor_three_axis_data accel_data[2] = {0};
struct sensor_decoder_api *decoder;
struct rtio_cqe *cqe;
uint8_t *buf;
uint32_t buf_len;
struct rtio_sqe *handles[2];
/* Start the streams */
for (int i = 0; i < NUM_SENSORS; i++) {
sensor_stream(iodevs[i], &accel_ctx, NULL, &handles[i]);
}
while (1) {
cqe = rtio_cqe_consume_block(&accel_ctx);
if (cqe->result != 0) {
printk("async read failed %d\n", cqe->result);
return;
}
rc = rtio_cqe_get_mempool_buffer(&accel_ctx, cqe, &buf, &buf_len);
if (rc != 0) {
printk("get mempool buffer failed %d\n", rc);
return;
}
struct device *sensor = ((struct sensor_read_config *)
((struct rtio_iodev *)cqe->userdata)->data)->sensor;
rtio_cqe_release(&accel_ctx, cqe);
rc = sensor_get_decoder(sensor, &decoder);
if (rc != 0) {
printk("sensor_get_decoder failed %d\n", rc);
return;
}
/* Frame iterator values when data comes from a FIFO */
uint32_t accel_fit = 0;
/* Number of accelerometer data frames */
uint32_t frame_count;
rc = decoder->get_frame_count(buf, {SENSOR_CHAN_ACCEL_XYZ, 0},
&frame_count);
if (rc != 0) {
printk("sensor_get_decoder failed %d\n", rc);
return;
}
/* If a tap has occurred lets print it out */
if (decoder->has_trigger(buf, SENSOR_TRIG_TAP)) {
printk("Tap! Sensor %s\n", dev->name);
}
/* Decode all available accelerometer sample frames */
for (int i = 0; i < frame_count; i++) {
decoder->decode(buf, {SENSOR_CHAN_ACCEL_XYZ, 0},
accel_fit, 1, &accel_data);
printk("Accel data for %s " PRIsensor_three_axis_data "\n",
dev->name,
PRIsensor_three_axis_data_arg(accel_data, 0));
}
rtio_release_buffer(&accel_ctx, buf, buf_len);
}
}

View file

@ -0,0 +1,39 @@
.. _sensor-attribute:
Sensor Attributes
#################
:dfn:`Attributes`, enumerated in :c:enum:`sensor_attribute`, are immutable and
mutable properties of a sensor and its channels.
Attributes allow for obtaining metadata and changing configuration of a sensor.
Common configuration parameters like channel scale, sampling frequency, adjusting
channel offsets, signal filtering, power modes, on chip buffers, and event
handling options are very common. Attributes provide a flexible API for
inspecting and manipulating such device properties.
Attributes are specified using :c:enum:`sensor_attribute` which can be used with
:c:func:`sensor_attr_get` and :c:func:`sensor_attr_set` to get and set a sensors
attributes.
A quick example...
.. code-block:: c
const struct device *accel_dev = DEVICE_DT_GET(DT_ALIAS(accel0));
struct sensor_value accel_sample_rate;
int rc;
rc = sensor_attr_get(accel_dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_SAMPLING_FREQUENCY, &accel_sample_rate);
if (rc != 0) {
printk("Failed to get sampling frequency\n");
}
printk("Sample rate for accel %p is %d.06%d\n", accel_dev, accel_sample_rate.val1, accel_sample_rate.val2*1000000);
accel_sample_rate.val1 = 2000;
rc = sensor_attr_set(accel_dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_SAMPLING_FREQUENCY, accel_sample_rate);
if (rc != 0) {
printk("Failed to set sampling frequency\n");
}

View file

@ -0,0 +1,19 @@
.. _sensor-channel:
Sensor Channels
###############
:dfn:`Channels`, enumerated in :c:enum:`sensor_channel`, are quantities that
a sensor device can measure.
Sensors may 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). Sensors may have multiple channels of the same measurement type to
enable measuring many readings of perhaps temperature, light intensity, amperage,
voltage, or capacitance for example.
A channel is specified in Zephyr using a :c:struct:`sensor_chan_spec` which is a
pair containing both the channel type (:c:enum:`sensor_channel`) and channel index.
At times only :c:enum:`sensor_channel` is used but this should be considered
historical since the introduction of :c:struct:`sensor_chan_spec` for Zephyr 3.7.

View file

@ -0,0 +1,29 @@
Device Tree
###########
In the context of sensors device tree provides the initial hardware configuration
for sensors on a per device level. Each device must specify a device tree binding
in Zephyr, and ideally, a set of hardware configuration options for things such
as channel power modes, data rates, filters, decimation, and scales. These can
then be used in a boards devicetree to configure a sensor to its initial state.
.. code-block:: dts
#include <zephyr/dt-bindings/icm42688.h>
&spi0 {
/* SPI bus options here, not shown */
accel_gyro0: icm42688p@0 {
compatible = "invensense,icm42688";
reg = <0>;
int-gpios = <&pioc 6 GPIO_ACTIVE_HIGH>; /* SoC specific pin to select for interrupt line */
spi-max-frequency = <DT_FREQ_M(24)>; /* Maximum SPI bus frequency */
accel-pwr-mode = <ICM42688_ACCEL_LN>; /* Low noise mode */
accel-odr = <ICM42688_ACCEL_ODR_2000>; /* 2000 Hz sampling */
accel-fs = <ICM42688_ACCEL_FS_16>; /* 16G scale */
gyro-pwr-mode = <ICM42688_GYRO_LN>; /* Low noise mode */
gyro-odr = <ICM42688_GYRO_ODR_2000>; /* 2000 Hz sampling */
gyro-fs = <ICM42688_GYRO_FS_16>; /* 16G scale */
};
};

View file

@ -0,0 +1,75 @@
.. _sensor-fetch-and-get:
Fetch and Get
#############
The stable and long existing APIs for reading sensor data and handling triggers
are:
* :c:func:`sensor_sample_fetch`
* :c:func:`sensor_sample_fetch_chan`
* :c:func:`sensor_channel_get`
* :c:func:`sensor_trigger_set`
These functions work together. The fetch APIs block the calling context which
must be a thread until the requested :c:enum:`sensor_channel` (or all channels)
has been obtained and stored into the driver instance's private data.
The channel data most recently fetched can then be obtained as a
:c:struct:`sensor_value` by calling :c:func:`sensor_channel_get` for each channel
type.
.. warning::
It should be noted that calling fetch and get from multiple contexts without
a locking mechanism is undefined and most sensor drivers do not attempt to
internally provide exclusive access to the device during or between these
calls.
Polling
*******
Using fetch and get sensor can be read in a polling manner from software threads.
.. literalinclude:: ../../../../samples/sensor/magn_polling/src/main.c
:language: c
Triggers
********
Triggers in the stable API require enabling triggers with a device
specific Kconfig. The device specific Kconfig typically allows selecting the
context the trigger runs. The application then needs to register a callback with
a function signature matching :c:type:`sensor_trigger_handler_t` using
:c:func:`sensor_trigger_set` for the specific triggers (events) to listen for.
.. note::
Triggers may not be set from user mode threads, and the callback is not
run in a user mode context.
There are typically two options provided for each driver where to run trigger
handlers. Either the trigger handler is run using the system work queue thread
(:ref:`workqueues_v2`) or a dedicated thread. A great example can be found in
the BMI160 driver which has Kconfig options for selecting a trigger mode.
See :kconfig:option:`CONFIG_BMI160_TRIGGER_NONE`,
:kconfig:option:`CONFIG_BMI160_TRIGGER_GLOBAL_THREAD` (work queue),
:kconfig:option:`CONFIG_BMI160_TRIGGER_OWN_THREAD` (dedicated thread).
Some notable attributes of using a driver dedicated thread vs the system work
queue.
* Driver dedicated threads have dedicated stack (RAM) which only gets used for
that single trigger handler function.
* Driver dedicated threads *do* get their own priority typically which lets you
prioritize trigger handling among other threads.
* Driver dedicated threads will not have head of line blocking if the driver
needs time to handle the trigger.
.. note::
In all cases it's very likely there will be variable delays from the actual
interrupt to your callback function being run. In the work queue
(GLOBAL_THREAD) case the work queue itself can be the source of variable
latency!
.. literalinclude:: tap_count.c
:language: c

View file

@ -1,231 +1,106 @@
.. _sensor_api: .. _sensor:
Sensors Sensors
####### #######
The sensor subsystem exposes an API to uniformly access sensor devices. The sensor driver API provides functionality to uniformly read, configure,
Common operations are: reading data and executing code when specific and setup event handling for devices that take real world measurements in
conditions are met. meaningful units.
Basic Operation Sensors range from very simple temperature-reading devices that must be polled
*************** with a fixed scale to complex devices taking in readings from multitudes of
sensors and themselves producing new inferred sensor data such as step counts,
presence detection, orientation, and more.
Channels Supporting this wide breadth of devices is a demanding task and the sensor API
======== attempts to provide a uniform interface to them.
Fundamentally, a channel is a quantity that a sensor device can measure.
Sensors can have multiple channels, either to represent different axes of .. _sensor-using:
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 Using Sensors
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 Using sensors from an application there are some APIs and terms that are helpful
====== to understand. Sensors in Zephyr are composed of :ref:`sensor-channel`,
:ref:`sensor-attribute`, and :ref:`sensor-trigger`. Attributes and triggers may
Sensor stable APIs return results as :c:struct:`sensor_value`. This be device or channel specific.
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:: .. note::
Helper functions to create custom length IODev nodes and ones that don't Getting samples from sensors using the sensor API today can be done in one of
have static bindings will be added soon. two ways. A stable and long-lived API :ref:`sensor-fetch-and-get`, or a newer
but rapidly stabilizing API :ref:`sensor-read-and-decode`. It's expected that
in the near future :ref:`sensor-fetch-and-get` will be deprecated in favor of
:ref:`sensor-read-and-decode`. Triggers are handled entirely differently for
:ref:`sensor-fetch-and-get` or :ref:`sensor-read-and-decode` and the
differences are noted in each of those sections.
Processing the Data .. toctree::
=================== :maxdepth: 1
Once data collection completes and the processing callback was called, attributes.rst
processing the data is done via the :c:struct:`sensor_decoder_api`. The API channels.rst
provides a means for applications to control *when* to process the data and how triggers.rst
many resources to dedicate to the processing. The API is entirely self power_management.rst
contained and requires no system calls (even when device_tree.rst
:kconfig:option:`CONFIG_USERSPACE` is enabled). fetch_and_get.rst
read_and_decode.rst
.. code-block:: C
static struct sensor_decoder_api *lid_accel_decoder = SENSOR_DECODER_DT_GET(DT_ALIAS(lid_accel)); .. _sensor-implementing:
static void sensor_processing_callback(int result, uint8_t *buf, Implementing Sensor Drivers
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, &timestamp); .. note::
lid_accel_decoder->decode(buf, &fit, &cit, channels, values, 3); Implementing the driver side of the sensor API requires an understanding how
the sensor APIs are used. Please read through :ref:`sensor-using` first!
/* Values are now in q31_t format, we're going to convert them to micro-units */ Implementing Attributes
=======================
/* First, we need to know by how much to shift the values */ * SHOULD implement attribute setting in a blocking manner.
lid_accel_decoder->get_shift(buf, channels[0], &shift[0]); * SHOULD provide the ability to get and set channel scale if supported by the
lid_accel_decoder->get_shift(buf, channels[1], &shift[1]); device.
lid_accel_decoder->get_shift(buf, channels[2], &shift[2]); * SHOULD provide the ability to get and set channel sampling rate if supported
by the device.
/* Shift the values to get the SI units */ Implementing Fetch and Get
int64_t scaled_values[] = { ==========================
(int64_t)values[0] << shift[0],
(int64_t)values[1] << shift[1],
(int64_t)values[2] << shift[2],
};
/* * SHOULD implement :c:type:`sensor_sample_fetch_t` as a blocking call that
* FIELD_GET(GENMASK64(63, 31), scaled_values[]) - will give the integer value stashes the specified channels (or all sensor channels) as driver instance
* FIELD_GET(GENMASK64(30, 0), scaled_values[]) / INT32_MAX - is the decimal value data.
*/ * SHOULD implement :c:type:`sensor_channel_get_t` without side effects
} manipulating driver state returning stashed sensor readings.
* SHOULD implement :c:type:`sensor_trigger_set_t` storing the address of the
:c:struct:`sensor_trigger` rather than copying the contents. This is so
:c:macro:`CONTAINER_OF` may be used for trigger callback context.
Configuration and Attributes Implementing Read and Decode
**************************** ============================
Setting the communication bus and address is considered the most basic * MUST implement :c:type:`sensor_submit_t` as a non-blocking call.
configuration for sensor devices. This setting is done at compile time, via * SHOULD implement :c:type:`sensor_submit_t` using :ref:`rtio` to do non-blocking bus transfers if possible.
the configuration menu. If the sensor supports interrupts, the interrupt * MAY implement :c:type:`sensor_submit_t` using a work queue if
lines and triggering parameters described below are also configured at :ref:`rtio` is unsupported by the bus.
compile time. * SHOULD implement :c:type:`sensor_submit_t` checking the :c:struct:`rtio_sqe`
is of type :c:enum:`RTIO_SQE_RX` (read request).
* SHOULD implement :c:type:`sensor_submit_t` checking all requested channels
supported or respond with an error if not.
* SHOULD implement :c:type:`sensor_submit_t` checking the provided buffer
is large enough for the requested channels.
* SHOULD implement :c:type:`sensor_submit_t` in a way that directly reads into
the provided buffer avoiding copying of any kind, with few exceptions.
* MUST implement :c:struct:`sensor_decoder_api` with pure stateless functions. All state needed to convert the raw sensor readings into
fixed point SI united values must be in the provided buffer.
* MUST implement :c:type:`sensor_get_decoder_t` returning the
:c:struct:`sensor_decoder_api` for that device type.
Alongside these communication parameters, sensor chips typically expose .. _sensor-api-reference:
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 API Reference
************** ***************
.. doxygengroup:: sensor_interface .. doxygengroup:: sensor_interface
.. doxygengroup:: sensor_emulator_backend .. doxygengroup:: sensor_emulator_backend

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2024 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
#define TEMP_CHANNELS \
{ SENSOR_CHAN_AMBIENT_TEMP, 0 }, \
{ SENSOR_CHAN_AMBIENT_TEMP, 1 }
#define TEMP_ALIAS(id) DT_ALIAS(CONCAT(temp, id))
#define TEMP_IODEV_SYM(id) CONCAT(temp_iodev, id)
#define TEMP_IODEV_PTR(id) &TEMP_IODEV_SYM(id)
#define TEMP_DEFINE_IODEV(id) \
SENSOR_DT_READ_IODEV( \
TEMP_IODEV_SYM(id), \
TEMP_ALIAS(id), \
TEMP_CHANNELS \
);
#define NUM_SENSORS 2
LISTIFY(NUM_SENSORS, TEMP_DEFINE_IODEV, (;));
struct sensor_iodev *iodevs[NUM_SENSORS] = { LISTIFY(NUM_SENSORS, TEMP_IODEV_PTR, (,)) };
RTIO_DEFINE_WITH_MEMPOOL(temp_ctx, NUM_SENSORS, NUM_SENSORS, NUM_SENSORS, 8, sizeof(void *));
int main(void)
{
int rc;
uint32_t temp_frame_iter = 0;
struct sensor_q31_data temp_data[2] = {0};
struct sensor_decoder_api *decoder;
struct rtio_cqe *cqe;
uint8_t *buf;
uint32_t buf_len;
while (1) {
/* Non-Blocking read for each sensor */
for (int i = 0; i < NUM_SENSORS; i++) {
rc = sensor_read_async_mempool(iodevs[i], &temp_ctx, iodevs[i]);
if (rc != 0) {
printk("sensor_read() failed %d\n", rc);
return;
}
}
/* Wait for read completions */
for (int i = 0; i < NUM_SENSORS; i++) {
cqe = rtio_cqe_consume_block(&temp_ctx);
if (cqe->result != 0) {
printk("async read failed %d\n", cqe->result);
return;
}
/* Get the associated mempool buffer with the completion */
rc = rtio_cqe_get_mempool_buffer(&temp_ctx, cqe, &buf, &buf_len);
if (rc != 0) {
printk("get mempool buffer failed %d\n", rc);
return;
}
struct device *sensor = ((struct sensor_read_config *)
((struct rtio_iodev *)cqe->userdata)->data)->sensor;
/* Done with the completion event, release it */
rtio_cqe_release(&temp_ctx, cqe);
rc = sensor_get_decoder(sensor, &decoder);
if (rc != 0) {
printk("sensor_get_decoder failed %d\n", rc);
return;
}
/* Frame iterators, one per channel we are decoding */
uint32_t temp_fits[2] = { 0, 0 };
decoder->decode(buf, {SENSOR_CHAN_AMBIENT_TEMP, 0},
&temp_fits[0], 1, &temp_data[0]);
decoder->decode(buf, {SENSOR_CHAN_AMBIENT_TEMP, 1},
&temp_fits[1], 1, &temp_data[1]);
/* Done with the buffer, release it */
rtio_release_buffer(&temp_ctx, buf, buf_len);
printk("Temperature for %s channel 0 " PRIsensor_q31_data ", channel 1 "
PRIsensor_q31_data "\n",
dev->name,
PRIsensor_q31_data_arg(temp_data[0], 0),
PRIsensor_q31_data_arg(temp_data[1], 0));
}
}
k_msleep(1);
}

View file

@ -0,0 +1,20 @@
Power Management
================
Power management of sensors is often a non-trivial task as sensors may have multiple power states
for various channels. Some sensors may allow for low noise, low power, or suspending channels
potentially saving quite a bit of power at the cost of noise or sampling speed performance. In very
low power states sensors may lose their state, turning off even the digital logic portion of the device.
All this is to say that power management of sensors is typically application specific! Often the
channel states are mutable using :ref:`sensor-attribute`. While total device suspending and resume
can be done using the power management ref counting APIs if the device implements the necessary
functionality.
Most likely the API sensors should use for their fully suspended/resume power states is
:ref:`pm-device-runtime` using explicit calls at an application level to :c:func:`pm_device_runtime_get`
and :c:func:`pm_device_runtime_put`.
In the future, with :ref:`sensor-read-and-decode` its possible that automatic management of device power management
would be possible in the streaming case as the application informs the driver of usage at all times
through requests to read on given events.

View file

@ -0,0 +1,103 @@
.. _sensor-read-and-decode:
Read and Decode
###############
The quickly stabilizing experimental APIs for reading sensor data are:
* :c:func:`sensor_read`
* :c:func:`sensor_read_async_mempool`
* :c:func:`sensor_get_decoder`
* :c:func:`sensor_decode`
Benefits over :ref:`sensor-fetch-and-get`
*********************************************************
These APIs allow for a wider usage of sensors, sensor types, and data flows with
sensors. These are the future looking APIs in Zephyr and solve many issues
that have been run into with :ref:`sensor-fetch-and-get`.
:c:func:`sensor_read` and similar functions acquire sensor encoded data into
a buffer provided by the caller. Decode (:c:func:`sensor_decode`) then
decodes the sensor specific encoded data into fixed point :c:type:`q31_t` values
as vectors per channel. This allows further processing using fixed point DSP
functions that work on vectors of data to be done (e.g. low-pass filters, FFT,
fusion, etc).
Reading is by default asynchronous in its implementation and takes advantage of
:ref:`rtio` to enable chaining asynchronous requests, or starting requests
against many sensors simultaneously from a single call context.
This enables incredibly useful code flows when working with sensors such as:
* Obtaining the raw sensor data, decoding never, later, or on a separate
processor (e.g. a phone).
* Starting a read for sensors directly from an interrupt handler. No dedicated
thread needed saving precious stack space. No work queue needed introducing
variable latency. Starting a read for multiple sensors simultaneously from a
single call context (interrupt/thread/work queue).
* Requesting multiple reads to the same device for Ping-Pong (double buffering)
setups.
* Creating entire pipelines of data flow from sensors allowing for software
defined virtual sensors (:ref:`sensing`) all from a single thread with DAG
process ordering.
* Potentially pre-programming DMAs to trigger on GPIO events, leaving the CPU
entirely out of the loop in handling sensor events like FIFO watermarks.
Additionally, other shortcomings of :ref:`sensor-fetch-and-get` related to memory
and trigger handling are solved.
* Triggers result in enqueued events, not callbacks.
* Triggers can be setup to automatically fetch data. Potentially
enabling pre-programmed DMA transfers on GPIO interrupts.
* Far less likely triggers are missed due to long held interrupt masks from
callbacks and context swapping.
* Sensor FIFOs supported by wiring up FIFO triggers to read data into
mempool allocated buffers.
* All sensor processing can be done in user mode (memory protected) threads.
* Multiple sensor channels of the same type are better supported.
.. note::
For `Read and Decode`_ benefits to be fully realized requires
:ref:`rtio` compliant communication access to the sensor. Typically this means
an :ref:`rtio` enabled bus driver for SPI or I2C.
Polling Read
************
Polling reads with `Read and Decode`_ can be accomplished by instantiating a
polling I/O device (akin to a file descriptor) for the sensor with the desired
channels to poll. Requesting either blocking or non-blocking reads, then
optionally decoding the data into fixed point values.
Polling a temperature sensor and printing its readout is likely the simplest
sample to show how this all works.
.. literalinclude:: temp_polling.c
:language: c
Polling Read with Multiple Sensors
**********************************
One of the benefits of Read and Decode is the ability to concurrently read many
sensors with many channels in one thread. Effectively read requests are started
asynchronously for all sensors and their channels. When each read completes we
then decode the sensor data. Examples speak loudly and so a sample showing how
this might work with multiple temperature sensors with multiple temperature
channels:
.. literalinclude:: multiple_temp_polling.c
:language: c
Streaming
*********
Handling triggers with `Read and Decode`_ works by setting up a stream I/O device
configuration. A stream specifies the set of triggers to capture and if data
should be captured with the event.
.. literalinclude:: accel_stream.c
:language: c

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
const struct device *const accel0 = DEVICE_DT_GET(DT_ALIAS(accel0));
static struct tap_count_state {
struct sensor_trigger trig;
uint32_t count;
} tap_count_state = {
.trig = {
.chan = SENSOR_CHAN_ACCEL_XYZ,
.type = SENSOR_TRIG_TAP,
},
.count = 0,
};
void tap_handler(const struct device *dev, const struct sensor_trigger *trig)
{
struct tap_count_state *state = CONTAINER_OF(trig, struct tap_count_state, trig);
state->count++;
printk("Tap! Total Taps: %u\n", state->count);
}
int main(void)
{
int rc;
printk("Tap Counter Example (%s)\n", CONFIG_ARCH);
rc = sensor_trigger_set(accel0, &tap_count_state.trig, tap_handler);
if (rc != 0) {
printk("Failed to set trigger handler for taps, error %d\n", rc);
return rc;
}
return rc;
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2024 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
#define TEMP_CHANNEL {SENSOR_CHAN_AMBIENT_TEMP, 0}
const struct device *const temp0 = DEVICE_DT_GET(DT_ALIAS(temp0));
SENSOR_DT_READ_IODEV(temp_iodev, DT_ALIAS(temp0), {TEMP_CHANNEL});
RTIO_DEFINE(temp_ctx, 1, 1);
int main(void)
{
int rc;
uint8_t buf[8];
uint32_t temp_frame_iter = 0;
struct sensor_q31_data temp_data = {0};
struct sensor_decode_context temp_decoder = SENSOR_DECODE_CONTEXT_INIT(
SENSOR_DECODER_DT_GET(DT_ALIAS(temp0)), buf, SENSOR_CHAN_AMBIENT_TEMP, 0);
while (1) {
/* Blocking read */
rc = sensor_read(temp_iodev, &temp_ctx, buf, sizeof(buf));
if (rc != 0) {
printk("sensor_read() failed %d\n", rc);
}
/* Decode the data into a single q31 */
sensor_decode(&temp_decoder, &temp_data, 1);
printk("Temperature " PRIsensor_q31_data "\n",
PRIsensor_q31_data_arg(temp_data, 0));
k_msleep(1);
}
}

View file

@ -0,0 +1,13 @@
.. _sensor-trigger:
Sensor Triggers
###############
:dfn:`Triggers`, enumerated in :c:enum:`sensor_trigger_type`, are sensor
generated events. Typically sensors allow setting up these events to cause
digital line signaling for easy capture by a micro controller. The events can
then commonly be inspected by reading registers to determine which event caused
the digital line signaling to occur.
There are many kinds of triggers sensors provide, from informative ones such as
data ready to physical events such as taps or steps.

View file

@ -25,7 +25,7 @@ The sensing subsystem relies on Zephyr sensor device APIs (existing version or u
to leverage Zephyr's large library of sensor device drivers (100+). to leverage Zephyr's large library of sensor device drivers (100+).
Use of the sensing subsystem is optional. Applications that only need to access simple sensors Use of the sensing subsystem is optional. Applications that only need to access simple sensors
devices can use the Zephyr :ref:`sensor_api` API directly. devices can use the Zephyr :ref:`sensor` API directly.
Since the sensing subsystem is separated from device driver layer or Since the sensing subsystem is separated from device driver layer or
kernel space and could support various customizations and sensor kernel space and could support various customizations and sensor

View file

@ -7,7 +7,7 @@
Overview Overview
******** ********
A sample application that demonstrates how to use LVGL and the :ref:`sensor API <sensor_api>` to A sample application that demonstrates how to use LVGL and the :ref:`sensor` to
display acceleration data on a line chart that is updated in real time. display acceleration data on a line chart that is updated in real time.
This sample creates a line chart with three series, one for each axis of the accelerometer. An LVGL This sample creates a line chart with three series, one for each axis of the accelerometer. An LVGL

View file

@ -6,7 +6,7 @@ BME280 Humidity and Pressure Sensor
Overview Overview
******** ********
This sample shows how to use the Zephyr :ref:`sensor_api` API driver for the This sample shows how to use the Zephyr :ref:`sensor` API driver for the
`Bosch BME280`_ environmental sensor. `Bosch BME280`_ environmental sensor.
.. _Bosch BME280: .. _Bosch BME280:

View file

@ -6,7 +6,7 @@ DS18B20 1-Wire Temperature Sensor
Overview Overview
******** ********
This sample shows how to use the Zephyr :ref:`sensor_api` API driver for the This sample shows how to use the Zephyr :ref:`sensor` API driver for the
`Maxim DS18B20`_ temperature sensor. `Maxim DS18B20`_ temperature sensor.
.. _Maxim DS18B20: .. _Maxim DS18B20: