diff --git a/Documentation/epicardium/sensor-streams.rst b/Documentation/epicardium/sensor-streams.rst new file mode 100644 index 0000000000000000000000000000000000000000..a111402d74a13430bc21aeb16ab768ad2602d750 --- /dev/null +++ b/Documentation/epicardium/sensor-streams.rst @@ -0,0 +1,29 @@ +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 diff --git a/Documentation/index.rst b/Documentation/index.rst index 1cbba07da9e948adfe3a5bb67fce17b0698f26ce..d9a7339a5fcb8f241fad2a66395d91ed318123f9 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -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 - - diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 7886cc5f0bb28fbc2ba5d818e43ef5f7879c0e50..4e52d85c51c38d897f38dbee03ed96cbbb7b0de1 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -1,6 +1,8 @@ #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 * ==== diff --git a/epicardium/main.c b/epicardium/main.c index 0117add3c1290474fba9f8cb6bb88c18339057a0..51d365f2c0a754b1432dfe5ae14ddf95de620aee 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -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 ..."); diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index dfc27d00574c49ec1bdbeffc99321f3bb4f1758e..a038fb21d7f5ef7c650a10851503150a27c0b1eb 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -4,5 +4,6 @@ module_sources = files( 'log.c', 'pmic.c', 'serial.c', + 'stream.c', 'vibra.c', ) diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 8fe73d59755c6146bba1d364938a6305af9e1baa..0db3d11bf84ca07f8a65f499fa1497a55940968c 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -15,4 +15,3 @@ void vSerialTask(void *pvParameters); void vPmicTask(void *pvParameters); #endif /* MODULES_H */ - diff --git a/epicardium/modules/stream.c b/epicardium/modules/stream.c new file mode 100644 index 0000000000000000000000000000000000000000..360e0357b79944c683a9017a936e613d9c3e306a --- /dev/null +++ b/epicardium/modules/stream.c @@ -0,0 +1,112 @@ +#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; +} diff --git a/epicardium/modules/stream.h b/epicardium/modules/stream.h new file mode 100644 index 0000000000000000000000000000000000000000..32c9e6e219bc7b7998babcd25236d2417d61b8c5 --- /dev/null +++ b/epicardium/modules/stream.h @@ -0,0 +1,87 @@ +#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 */