读取与解码

用于读取传感器数据且正在快速趋于稳定的 API 包括:

相比 采集与获取 的优势

这些 API 拓展了传感器的使用方式,支持更多传感器类型以及涉及传感器的数据流。这些是 Zephyr 面向未来的 API,解决了使用 采集与获取 时遇到的诸多问题。

sensor_read() 及类似函数将传感器的编码数据采集到调用者提供的缓冲区中。随后,解码操作( sensor_decode() )将传感器特定格式的编码数据解码为定点 q31_t 值,并按通道组织为向量。这样便可使用处理数据向量的定点 DSP 函数进行进一步处理(例如低通滤波、FFT、融合等)。

读取操作默认采用异步实现,并利用 实时 I/O(RTIO) 将异步请求串联起来,或从单个调用上下文中同时向多个传感器发起请求。

这使得处理传感器时可以采用非常实用的代码流程,例如:

  • 获取原始传感器数据后,可以不解码、稍后解码,或在另一个处理器上(例如手机)解码。

  • 直接从中断处理程序中发起传感器读取。无需专用线程,从而节省宝贵的栈空间。无需工作队列,从而避免引入不确定的延迟。从单个调用上下文(中断、线程或工作队列)中同时发起多个传感器的读取。

  • 针对乒乓缓冲(双缓冲)配置,向同一设备发起多次读取请求。

  • 构建从传感器开始的完整数据流流水线,以支持软件定义的虚拟传感器( 感知子系统 ),所有处理均可在单个线程中按照有向无环图(DAG)确定的顺序执行。

  • 还可以预先配置 DMA,使其由 GPIO 事件触发,从而在处理 FIFO 水位等传感器事件时完全无需 CPU 参与。

此外,还解决了 采集与获取 在内存和触发器处理方面的其他不足。

  • 触发器会产生入队事件,而非调用回调函数。

  • 触发器可配置为自动获取数据,还可以通过 GPIO 中断启动预先配置的 DMA 传输。

  • 大幅降低因回调函数和上下文切换长时间屏蔽中断而遗漏触发事件的可能性。

  • 通过将 FIFO 触发器关联到读取操作,将数据读入内存池分配的缓冲区,从而支持传感器 FIFO。

  • 所有传感器处理均可在用户模式(受内存保护)的线程中完成。

  • 更好地支持多个相同类型的传感器通道。

备注

要充分发挥 读取与解码 的优势,需要通过符合 实时 I/O(RTIO) 要求的通信接口访问传感器。通常,这意味着需要支持 实时 I/O(RTIO) 的 SPI 或 I2C 总线驱动。

轮询读取

使用 读取与解码 进行轮询读取时,可以为传感器实例化一个轮询 I/O 设备(类似于文件描述符),并指定要轮询的通道。随后发起阻塞或非阻塞读取请求,再根据需要将数据解码为定点值。

轮询温度传感器并打印其读数,可能是演示整个工作流程的最简单示例。

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

多传感器轮询读取

读取与解码的优势之一是能够在单个线程中并发读取多个传感器的多个通道。具体而言,针对所有传感器及其通道异步发起读取请求,每次读取完成后再解码相应的传感器数据。示例最能说明问题,下面的示例展示了如何使用多个具有多个温度通道的温度传感器来实现这一流程:

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

流式传输

使用 读取与解码 处理触发器时,需要设置流式 I/O 设备配置。流指定要捕获的一组触发器,以及是否在事件发生时同时采集数据。

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