Skip to content
Snippets Groups Projects
Verified Commit 00750677 authored by rahix's avatar rahix
Browse files

feat(epicardium): Add sensor streams


Signed-off-by: default avatarRahix <rahix@rahix.de>
parents 6fe49323 f70b699f
No related branches found
No related tags found
No related merge requests found
Sensor Streams
==============
Sensor drivers can make their data available to core 1 in a stream-like format.
This allows batch-reading many samples and shoud reduce pressure on the
Epicardium API this way. Sensor streams are read on core 1 using
:c:func:`epic_stream_read`.
This page intends to document how to add this stream interface to a sensor driver.
It also serves as a reference of existing streams. For that, take a look at the
definitions in the :c:type:`stream_descriptor` enum.
Adding a new Stream
-------------------
The list of possible sensor streams must be known at compile time. Each stream
gets a unique ID in the :c:type:`stream_descriptor` enum. Please do not assign
IDs manually but instead let the enum assign sequencial IDs. :c:macro:`SD_MAX`
must always be the highest stream ID. Additionally, please document what this
stream is for using a doc-comment so it shows up on this page.
When a sensor driver enables data collection, it should also register its
respective stream. This is done using a :c:type:`stream_info` object. Pass
this object to :c:func:`stream_register` to make your stream available. Your
driver must guarantee the :c:member:`stream_info.queue` handle to be valid until
deregistration using :c:func:`stream_deregister`.
Definitions
-----------
.. c:autodoc:: epicardium/modules/stream.h
......@@ -35,6 +35,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
debugger
pycardium-guide
memorymap
epicardium/sensor-streams
.. toctree::
:maxdepth: 1
......@@ -43,5 +44,3 @@ Last but not least, if you want to start hacking the lower-level firmware, the
epicardium/overview
epicardium/api
epicardium-guide
#ifndef _EPICARDIUM_H
#define _EPICARDIUM_H
#include <stdint.h>
#include <stddef.h>
#include <errno.h>
#ifndef API
#define API(id, def) def
......@@ -12,6 +14,7 @@
#define API_LEDS_SET 0x3
#define API_VIBRA_SET 0x4
#define API_VIBRA_VIBRATE 0x5
#define API_STREAM_READ 0x6
/* clang-format on */
/**
......@@ -55,6 +58,68 @@ API(API_UART_READ, char epic_uart_read_chr(void));
*/
API(API_LEDS_SET, void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b));
/**
* Sensor Data Streams
* ===================
* A few of card10's sensors can do continuous measurements. To allow
* performant access to their data, the following function is made for generic
* access to streams.
*/
/**
* Read sensor data into a buffer. ``epic_stream_read()`` will read as many
* sensor samples into the provided buffer as possible and return the number of
* samples written. If no samples are available, ``epic_stream_read()`` will
* return ``0`` immediately.
*
* ``epic_stream_read()`` expects the provided buffer to have a size which is a
* multiple of the sample size for the given stream. For the sample-format and
* size, please consult the sensors documentation.
*
* Before reading the internal sensor sample queue, ``epic_stream_read()`` will
* call a sensor specific *poll* function to allow the sensor driver to fetch
* new samples from its hardware. This should, however, never take a long
* amount of time.
*
* :param int sd: Sensor Descriptor. You get sensor descriptors as return
* values when activating the respective sensors.
* :param void* buf: Buffer where sensor data should be read into.
* :param size_t count: How many bytes to read at max. Note that fewer bytes
* might be read. In most cases, this should be ``sizeof(buf)``.
* :return: Number of data packets read (**not** number of bytes) or a negative
* error value. Possible errors:
*
* - ``-ENODEV``: Sensor is not currently available.
* - ``-EBADF``: The given sensor descriptor is unknown.
* - ``-EINVAL``: ``count`` is not a multiple of the sensor's sample size.
* - ``-EBUSY``: The descriptor table lock could not be acquired.
*
* **Example**:
*
* .. code-block:: cpp
*
* #include "epicardium.h"
*
* struct foo_measurement sensor_data[16];
* int foo_sd, n;
*
* foo_sd = epic_foo_sensor_enable(9001);
*
* while (1) {
* n = epic_stream_read(
* foo_sd,
* &sensor_data,
* sizeof(sensor_data)
* );
*
* // Print out the measured sensor samples
* for (int i = 0; i < n; i++) {
* printf("Measured: %?\n", sensor_data[i]);
* }
* }
*/
API(API_STREAM_READ, int epic_stream_read(int sd, void *buf, size_t count));
/**
* Misc
* ====
......
......@@ -11,6 +11,7 @@
#include "api/dispatcher.h"
#include "modules/modules.h"
#include "modules/log.h"
#include "modules/stream.h"
#include <Heart.h>
#include "GUI_Paint.h"
......@@ -53,6 +54,7 @@ int main(void)
}
fatfs_init();
stream_init();
LOG_INFO("startup", "Initializing tasks ...");
......
......@@ -4,5 +4,6 @@ module_sources = files(
'log.c',
'pmic.c',
'serial.c',
'stream.c',
'vibra.c',
)
......@@ -15,4 +15,3 @@ void vSerialTask(void *pvParameters);
void vPmicTask(void *pvParameters);
#endif /* MODULES_H */
#include <string.h>
#include "FreeRTOS.h"
#include "semphr.h"
#include "epicardium.h"
#include "modules/log.h"
#include "modules/stream.h"
/* Internal buffer of registered streams */
static struct stream_info *stream_table[SD_MAX];
/* Lock for modifying the stream info table */
static StaticSemaphore_t stream_table_lock_data;
static SemaphoreHandle_t stream_table_lock;
int stream_init()
{
memset(stream_table, 0x00, sizeof(stream_table));
stream_table_lock =
xSemaphoreCreateMutexStatic(&stream_table_lock_data);
return 0;
}
int stream_register(int sd, struct stream_info *stream)
{
if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) {
LOG_WARN("stream", "Lock contention error");
return -EBUSY;
}
if (sd < 0 || sd >= SD_MAX) {
return -EINVAL;
}
if (stream_table[sd] != NULL) {
/* Stream already registered */
return -EACCES;
}
stream_table[sd] = stream;
xSemaphoreGive(stream_table_lock);
return 0;
}
int stream_deregister(int sd, struct stream_info *stream)
{
if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) {
LOG_WARN("stream", "Lock contention error");
return -EBUSY;
}
if (sd < 0 || sd >= SD_MAX) {
return -EINVAL;
}
if (stream_table[sd] != stream) {
/* Stream registered by someone else */
return -EACCES;
}
stream_table[sd] = NULL;
xSemaphoreGive(stream_table_lock);
return 0;
}
int epic_stream_read(int sd, void *buf, size_t count)
{
/*
* TODO: In theory, multiple reads on different streams can happen
* simulaneously. I don't know what the most efficient implementation
* of this would look like.
*/
if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) {
LOG_WARN("stream", "Lock contention error");
return -EBUSY;
}
if (sd < 0 || sd >= SD_MAX) {
return -EBADF;
}
struct stream_info *stream = stream_table[sd];
if (stream == NULL) {
return -ENODEV;
}
/* Poll the stream, if a poll_stream function exists */
if (stream->poll_stream != NULL) {
int ret = stream->poll_stream();
if (ret < 0) {
return ret;
}
}
/* Check buffer size is a multiple of the data packet size */
if (count % stream->item_size != 0) {
return -EINVAL;
}
size_t i;
for (i = 0; i < count; i += stream->item_size) {
if (!xQueueReceive(stream->queue, buf + i, 0)) {
break;
}
}
xSemaphoreGive(stream_table_lock);
return i / stream->item_size;
}
#ifndef STREAM_H
#define STREAM_H
#include <stddef.h>
#include <stdint.h>
#include "FreeRTOS.h"
#include "queue.h"
/* Time to wait for the descriptor table lock to become available */
#define STREAM_MUTEX_WAIT pdMS_TO_TICKS(100)
/**
* **Stream Descriptors**:
*
* This enum defines all known stream descriptors. Internally, the stream
* module allocates an array slot for each ID defined here. For that to
* work, :c:macro:`SD_MAX` must be greater than the highest defined ID.
* Please keep IDs in sequential order.
*/
enum stream_descriptor {
/** Highest descriptor must always be ``SD_MAX``. */
SD_MAX,
};
/**
* Stream Information Object.
*
* This struct contains the information necessary for :c:func:`epic_stream_read`
* to read from a sensor's stream. This consists of:
*/
struct stream_info {
/**
* A FreeRTOS queue handle.
*
* Management of this queue is the sensor drivers responsibility.
*/
QueueHandle_t queue;
/** The size of one data packet (= queue element). */
size_t item_size;
/**
* An optional function to call before performing the read. Set to
* ``NULL`` if unused.
*
* ``poll_stream()`` is intended for sensors who passively collect data.
* A sensor driver might for example retrieve the latest samples in this
* function instead of actively polling in a task loop.
*
* The function registered here should never block for a longer time.
*/
int (*poll_stream)();
};
/**
* Register a stream so it can be read from Epicardium API.
*
* :param int sd: Stream Descriptor. Must be from the :c:type:`stream_descriptor` enum.
* :param stream_info stream: Stream info.
* :returns: ``0`` on success or a negative value on error. Possible errors:
*
* - ``-EINVAL``: Out of range sensor descriptor.
* - ``-EACCES``: Stream already registered.
* - ``-EBUSY``: The descriptor lock could not be acquired.
*/
int stream_register(int sd, struct stream_info *stream);
/**
* Deregister a stream.
*
* :param int sd: Stream Descriptor.
* :param stream_info stream: The stream which should be registered for the
* stream ``sd``. If a different stream is registered, this function
* will return an error.
* :returns: ``0`` on success or a negative value on error. Possible errors:
*
* - ``-EINVAL``: Out of range sensor descriptor.
* - ``-EACCES``: Stream ``stream`` was not registered for ``sd``.
* - ``-EBUSY``: The descriptor lock could not be acquired.
*/
int stream_deregister(int sd, struct stream_info *stream);
/*
* Initialize stream interface. Called by main().
*/
int stream_init();
#endif /* STREAM_H */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment