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:
parent
2979cf5efb
commit
9f85a70a04
16 changed files with 667 additions and 205 deletions
102
doc/hardware/peripherals/sensor/accel_stream.c
Normal file
102
doc/hardware/peripherals/sensor/accel_stream.c
Normal 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);
|
||||
}
|
||||
}
|
39
doc/hardware/peripherals/sensor/attributes.rst
Normal file
39
doc/hardware/peripherals/sensor/attributes.rst
Normal 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");
|
||||
}
|
19
doc/hardware/peripherals/sensor/channels.rst
Normal file
19
doc/hardware/peripherals/sensor/channels.rst
Normal 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.
|
29
doc/hardware/peripherals/sensor/device_tree.rst
Normal file
29
doc/hardware/peripherals/sensor/device_tree.rst
Normal 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 */
|
||||
};
|
||||
};
|
75
doc/hardware/peripherals/sensor/fetch_and_get.rst
Normal file
75
doc/hardware/peripherals/sensor/fetch_and_get.rst
Normal 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
|
|
@ -1,231 +1,106 @@
|
|||
.. _sensor_api:
|
||||
.. _sensor:
|
||||
|
||||
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.
|
||||
The sensor driver API provides functionality to uniformly read, configure,
|
||||
and setup event handling for devices that take real world measurements in
|
||||
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
|
||||
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.
|
||||
.. _sensor-using:
|
||||
|
||||
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:
|
||||
Using Sensors
|
||||
*************
|
||||
|
||||
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);
|
||||
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
|
||||
be device or channel specific.
|
||||
|
||||
.. note::
|
||||
Helper functions to create custom length IODev nodes and ones that don't
|
||||
have static bindings will be added soon.
|
||||
Getting samples from sensors using the sensor API today can be done in one of
|
||||
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,
|
||||
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).
|
||||
attributes.rst
|
||||
channels.rst
|
||||
triggers.rst
|
||||
power_management.rst
|
||||
device_tree.rst
|
||||
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,
|
||||
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];
|
||||
Implementing Sensor Drivers
|
||||
***************************
|
||||
|
||||
lid_accel_decoder->get_timestamp(buf, ×tamp);
|
||||
lid_accel_decoder->decode(buf, &fit, &cit, channels, values, 3);
|
||||
.. note::
|
||||
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 */
|
||||
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]);
|
||||
* SHOULD implement attribute setting in a blocking manner.
|
||||
* SHOULD provide the ability to get and set channel scale if supported by the
|
||||
device.
|
||||
* SHOULD provide the ability to get and set channel sampling rate if supported
|
||||
by the device.
|
||||
|
||||
/* 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],
|
||||
};
|
||||
Implementing Fetch and Get
|
||||
==========================
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
}
|
||||
* SHOULD implement :c:type:`sensor_sample_fetch_t` as a blocking call that
|
||||
stashes the specified channels (or all sensor channels) as driver instance
|
||||
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
|
||||
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.
|
||||
* MUST implement :c:type:`sensor_submit_t` as a non-blocking call.
|
||||
* SHOULD implement :c:type:`sensor_submit_t` using :ref:`rtio` to do non-blocking bus transfers if possible.
|
||||
* MAY implement :c:type:`sensor_submit_t` using a work queue if
|
||||
:ref:`rtio` is unsupported by the bus.
|
||||
* 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
|
||||
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:
|
||||
.. _sensor-api-reference:
|
||||
|
||||
API Reference
|
||||
**************
|
||||
***************
|
||||
|
||||
.. doxygengroup:: sensor_interface
|
||||
.. doxygengroup:: sensor_emulator_backend
|
||||
|
|
100
doc/hardware/peripherals/sensor/multiple_temp_polling.c
Normal file
100
doc/hardware/peripherals/sensor/multiple_temp_polling.c
Normal 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);
|
||||
}
|
20
doc/hardware/peripherals/sensor/power_management.rst
Normal file
20
doc/hardware/peripherals/sensor/power_management.rst
Normal 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.
|
103
doc/hardware/peripherals/sensor/read_and_decode.rst
Normal file
103
doc/hardware/peripherals/sensor/read_and_decode.rst
Normal 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
|
46
doc/hardware/peripherals/sensor/tap_count.c
Normal file
46
doc/hardware/peripherals/sensor/tap_count.c
Normal 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;
|
||||
}
|
41
doc/hardware/peripherals/sensor/temp_polling.c
Normal file
41
doc/hardware/peripherals/sensor/temp_polling.c
Normal 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);
|
||||
}
|
||||
}
|
13
doc/hardware/peripherals/sensor/triggers.rst
Normal file
13
doc/hardware/peripherals/sensor/triggers.rst
Normal 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.
|
|
@ -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+).
|
||||
|
||||
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
|
||||
kernel space and could support various customizations and sensor
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
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.
|
||||
|
||||
This sample creates a line chart with three series, one for each axis of the accelerometer. An LVGL
|
||||
|
|
|
@ -6,7 +6,7 @@ BME280 Humidity and Pressure Sensor
|
|||
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:
|
||||
|
|
|
@ -6,7 +6,7 @@ DS18B20 1-Wire Temperature Sensor
|
|||
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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue