hardware/peripherals/sensor/read_and_decode.html · hardware/peripherals/sensor/read_and_decode.rst 공식 원문 ↗

Zephyr 3.7.0 · Hardware · Peripherals · Sensor · Read and Decode

Read and Decode

RTIO·mempool·fixed-point vector·DMA로 polling과 streaming data flow를 구성합니다.

Official pathhardware/peripherals/sensor/read_and_decode.html
Source filehardware/peripherals/sensor/read_and_decode.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Blocking/non-blocking polling, multi-sensor concurrency, trigger stream 예제를 모두 보존합니다.

Acquisition

async · RTIO · mempool

Decode

q31 vector · DSP

Flow

polling · streaming

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 3491836FB803F440C0FD620D4647B070CF447840099291832B1B29A89799F658
.. _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:: ../../../../../hardware/peripherals/sensor/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:: ../../../../../hardware/peripherals/sensor/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:: ../../../../../hardware/peripherals/sensor/accel_stream.c
   :language: c

Part 3

한국어 전문 번역

Sensor data를 읽는 빠르게 안정화 중인 experimental API는 다음과 같습니다.

  • sensor_read()
  • sensor_read_async_mempool()
  • sensor_get_decoder()
  • sensor_decode()

Benefits over Fetch and Get

이 API는 더 다양한 sensor, sensor type, sensor data flow를 사용할 수 있게 합니다. Zephyr의 미래 지향 API이며 Fetch and Get 방식에서 드러난 여러 문제를 해결합니다.

sensor_read() 계열 함수는 caller가 제공한 buffer로 sensor-encoded data를 가져옵니다. 그 뒤 sensor_decode()가 sensor-specific encoded data를 channel별 vector 형태의 fixed-point q31_t 값으로 decode합니다. Low-pass filter, FFT, fusion 같은 vector fixed-point DSP function으로 후속 처리할 수 있습니다.

Read 구현은 기본적으로 asynchronous이며 RTIO를 활용합니다. 한 context에서 asynchronous request를 chain하거나 여러 sensor에 대한 request를 동시에 시작할 수 있습니다.

이 구조로 가능한 유용한 sensor code flow는 다음과 같습니다.

  • Raw sensor data를 얻고 전혀 decode하지 않거나, 나중에 또는 phone 같은 별도 processor에서 decode합니다.
  • Interrupt handler에서 sensor read를 직접 시작합니다. Dedicated thread와 stack이 필요 없고 variable latency를 만드는 work queue도 필요 없습니다. 단일 interrupt·thread·work-queue context에서 여러 sensor read를 동시에 시작할 수 있습니다.
  • 같은 device에 여러 read를 요청해 ping-pong(double buffering)을 구성합니다.
  • 단일 thread에서 DAG process ordering으로 software-defined virtual sensor를 포함하는 전체 sensor data-flow pipeline을 구성합니다.
  • GPIO event가 발생하면 DMA가 시작되도록 미리 program해 FIFO watermark 같은 sensor event 처리 loop에서 CPU를 완전히 제외할 가능성이 있습니다.

Fetch and Get의 memory·trigger handling 관련 단점도 다음과 같이 해결합니다.

  • Trigger는 callback이 아니라 enqueue된 event가 됩니다.
  • Trigger가 자동으로 data를 fetch하도록 설정할 수 있으며 GPIO interrupt에서 pre-programmed DMA transfer를 enable할 가능성이 있습니다.
  • Callback과 context switching이 interrupt mask를 오래 유지해 trigger를 놓칠 가능성이 훨씬 낮습니다.
  • FIFO trigger를 mempool-allocated buffer read에 연결해 sensor FIFO를 지원합니다.
  • 모든 sensor processing을 memory-protected user-mode thread에서 수행할 수 있습니다.
  • 같은 type의 sensor channel 여러 개를 더 잘 지원합니다.

Polling Read

원하는 polling channel을 지정한 sensor polling I/O device를 file descriptor와 비슷하게 생성하면 Read and Decode로 polling read를 수행할 수 있습니다. Blocking 또는 non-blocking read를 요청한 뒤 선택적으로 data를 fixed-point value로 decode합니다.

Temperature sensor를 polling하고 값을 출력하는 다음 예가 가장 단순한 동작을 보여줍니다.

/*
 * 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);
	}
}

Polling Read with Multiple Sensors

Read and Decode의 장점 가운데 하나는 한 thread에서 channel이 여러 개인 sensor 여러 개를 동시에 읽는 기능입니다. 모든 sensor와 channel의 read request를 asynchronous로 시작하고 각 read가 끝나면 sensor data를 decode합니다. 다음 예는 여러 temperature channel이 있는 여러 temperature sensor를 처리합니다.

/*
 * 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);
}

Streaming

Read and Decode에서 trigger를 처리하려면 stream I/O device configuration을 구성합니다. Stream은 capture할 trigger 집합과 event와 함께 data도 capture할지를 지정합니다.

/*
 * 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);
	}
}

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.