diff --git a/doc/hardware/peripherals/sensor/accel_stream.c b/doc/hardware/peripherals/sensor/accel_stream.c new file mode 100644 index 00000000000..aac2e99318b --- /dev/null +++ b/doc/hardware/peripherals/sensor/accel_stream.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#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); + } +} diff --git a/doc/hardware/peripherals/sensor/attributes.rst b/doc/hardware/peripherals/sensor/attributes.rst new file mode 100644 index 00000000000..b0b9e5370d2 --- /dev/null +++ b/doc/hardware/peripherals/sensor/attributes.rst @@ -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"); + } diff --git a/doc/hardware/peripherals/sensor/channels.rst b/doc/hardware/peripherals/sensor/channels.rst new file mode 100644 index 00000000000..5019704ddd9 --- /dev/null +++ b/doc/hardware/peripherals/sensor/channels.rst @@ -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. diff --git a/doc/hardware/peripherals/sensor/device_tree.rst b/doc/hardware/peripherals/sensor/device_tree.rst new file mode 100644 index 00000000000..8aa99655b3a --- /dev/null +++ b/doc/hardware/peripherals/sensor/device_tree.rst @@ -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 + + &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 = ; /* Maximum SPI bus frequency */ + accel-pwr-mode = ; /* Low noise mode */ + accel-odr = ; /* 2000 Hz sampling */ + accel-fs = ; /* 16G scale */ + gyro-pwr-mode = ; /* Low noise mode */ + gyro-odr = ; /* 2000 Hz sampling */ + gyro-fs = ; /* 16G scale */ + }; + }; diff --git a/doc/hardware/peripherals/sensor/fetch_and_get.rst b/doc/hardware/peripherals/sensor/fetch_and_get.rst new file mode 100644 index 00000000000..9dc68cab677 --- /dev/null +++ b/doc/hardware/peripherals/sensor/fetch_and_get.rst @@ -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 diff --git a/doc/hardware/peripherals/sensor/index.rst b/doc/hardware/peripherals/sensor/index.rst index ec3cf2028fa..e9c4979cd07 100644 --- a/doc/hardware/peripherals/sensor/index.rst +++ b/doc/hardware/peripherals/sensor/index.rst @@ -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 - #include - #include - - 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 diff --git a/doc/hardware/peripherals/sensor/multiple_temp_polling.c b/doc/hardware/peripherals/sensor/multiple_temp_polling.c new file mode 100644 index 00000000000..cdcb43d8b56 --- /dev/null +++ b/doc/hardware/peripherals/sensor/multiple_temp_polling.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#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); +} diff --git a/doc/hardware/peripherals/sensor/power_management.rst b/doc/hardware/peripherals/sensor/power_management.rst new file mode 100644 index 00000000000..cf9e4aa1c72 --- /dev/null +++ b/doc/hardware/peripherals/sensor/power_management.rst @@ -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. diff --git a/doc/hardware/peripherals/sensor/read_and_decode.rst b/doc/hardware/peripherals/sensor/read_and_decode.rst new file mode 100644 index 00000000000..4a54d40dbd2 --- /dev/null +++ b/doc/hardware/peripherals/sensor/read_and_decode.rst @@ -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 diff --git a/doc/hardware/peripherals/sensor/tap_count.c b/doc/hardware/peripherals/sensor/tap_count.c new file mode 100644 index 00000000000..68a2be29afd --- /dev/null +++ b/doc/hardware/peripherals/sensor/tap_count.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + + +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; +} diff --git a/doc/hardware/peripherals/sensor/temp_polling.c b/doc/hardware/peripherals/sensor/temp_polling.c new file mode 100644 index 00000000000..f6c38cdecc8 --- /dev/null +++ b/doc/hardware/peripherals/sensor/temp_polling.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#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); + } +} diff --git a/doc/hardware/peripherals/sensor/triggers.rst b/doc/hardware/peripherals/sensor/triggers.rst new file mode 100644 index 00000000000..6dd78701032 --- /dev/null +++ b/doc/hardware/peripherals/sensor/triggers.rst @@ -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. diff --git a/doc/services/sensing/index.rst b/doc/services/sensing/index.rst index daf8a76a974..976599826d8 100644 --- a/doc/services/sensing/index.rst +++ b/doc/services/sensing/index.rst @@ -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 diff --git a/samples/modules/lvgl/accelerometer_chart/README.rst b/samples/modules/lvgl/accelerometer_chart/README.rst index 69f90b4f4de..a41f79a091b 100644 --- a/samples/modules/lvgl/accelerometer_chart/README.rst +++ b/samples/modules/lvgl/accelerometer_chart/README.rst @@ -7,7 +7,7 @@ Overview ******** -A sample application that demonstrates how to use LVGL and the :ref:`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 diff --git a/samples/sensor/bme280/README.rst b/samples/sensor/bme280/README.rst index f6372779154..3d32ae0d420 100644 --- a/samples/sensor/bme280/README.rst +++ b/samples/sensor/bme280/README.rst @@ -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: diff --git a/samples/sensor/ds18b20/README.rst b/samples/sensor/ds18b20/README.rst index 2e3610350da..a0f22636f40 100644 --- a/samples/sensor/ds18b20/README.rst +++ b/samples/sensor/ds18b20/README.rst @@ -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: