You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by je...@apache.org on 2020/04/15 05:55:05 UTC

[mynewt-core] 01/03: hw/drivers/i2s: Add I2S API

This is an automated email from the ASF dual-hosted git repository.

jerzy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git

commit bfe7d7f7527e656d9f4b448f15c14d71051fbc7f
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Fri Mar 27 10:24:53 2020 +0100

    hw/drivers/i2s: Add I2S API
    
    This adds API that can be used to stream
    data through I2S interface.
---
 hw/drivers/i2s/README.md                | 224 ++++++++++++++++++++++++++
 hw/drivers/i2s/include/i2s/i2s.h        | 270 ++++++++++++++++++++++++++++++++
 hw/drivers/i2s/include/i2s/i2s_driver.h |  43 +++++
 hw/drivers/i2s/pkg.yml                  |  27 ++++
 4 files changed, 564 insertions(+)

diff --git a/hw/drivers/i2s/README.md b/hw/drivers/i2s/README.md
new file mode 100644
index 0000000..492234e
--- /dev/null
+++ b/hw/drivers/i2s/README.md
@@ -0,0 +1,224 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+-->
+
+# I2S interface (Inter-IC Sound)
+
+# Overview
+
+Inter-IC Sound is interface for sending digital audio data.
+
+Data send to and received from I2S device is grouped in sample buffers.
+When user code wants to send audio data to I2S device (speaker), it first
+request buffer from I2S device, fills it with samples and send it back to the
+device. When data is transmitted out of the system to external IC, buffer
+become available to the user core and can be obtained again.
+
+For input devices like digital microphone, correctly configured i2s device has
+several buffers that are filled with incoming data.
+User code requests sample buffer and gets buffers filled with data, user code
+can process this data and must return buffer back to the i2s device.
+
+General flow is to get buffers from i2s device fill them for outgoing transmission
+(or interpret samples for incoming transmission) and send buffer back to the device. 
+
+# API
+
+#### Device creation
+
+```i2s_create(i2s, "name", cfg)```
+
+Function creates I2S device with specified name and configuration.
+Note that cfg argument is not defined in **api.h** but by driver package that must be present in build.
+#### Device access
+
+`i2s_open("dev_name", timout, client)`
+
+Opens I2S interface. It can be input (microphone) or output (speaker) interface.
+
+`i2s_close()`
+
+Closes I2S interface, opened with `i2s_open()`
+
+#### Start/stop device operation
+
+`i2s_start()`
+
+Starts I2S device operation, for input, device starts collecting samples to internal buffers.
+For output, samples are streamed out of device if they are already sent to device with `i2s_buffer_put()`.
+* NOTE: this function must be called explicitly for input i2s device.
+
+`i2s_stop()`
+
+Stops sending or receiving samples. 
+
+#### High level read and write
+It is possible to use simple blocking functions to read from I2S input device or write to I2S output device.
+
+`i2s_write(i2s, data, size)`
+
+This function write user provided data to the output I2S device. It returns number of bytes written.
+Return value will in most cases will be less then requested since data is written in internal buffer size chunks.
+
+`i2s_read(i2s, data_buffer, size)`
+Read data from microphone to user provided buffer. Function returns actual number of bytes read. Return value
+may be less then size because it will most likely be truncated to internal buffer size. 
+
+#### Sample buffers
+
+I2S software device needs at least 2 buffers for seamless sample streaming.
+
+`i2s_buffer_get()`
+
+Returns a buffer with samples taken from the I2S input device.
+For output device it will return a buffer that should be filled with samples by user code and then passed back to the device.
+
+`i2s_buffer_put()`
+
+Sends sample to output I2S device. For input device it simply returns buffer so it can be reused for next
+incoming samples.
+
+- For output device user code gets a buffers from the i2s device with `i2s_buffer_get()`, then fills the buffer with
+new samples, then sends the buffer with samples back to the device using `i2s_buffer_put()`.
+- For input device after user code starts sampling operation calling `i2s_start()` it must wait for collected samples.
+Simplest way to wait is to call `i2s_buffer_get(i2s, OS_WAIT_FOREVER)` that will block till samples are available
+and returns buffer full of data.
+Once application does something with the samples it should immediately call `i2s_buffer_put()` to return buffer back
+to the driver so next samples can be collected.
+- It is possible to have I2S created without internal buffers. In that case user code can create buffers and pass
+them to the driver using `i2s_buffer_put()`. Such buffers would then be available when processed by the driver and
+could be taken back with `i2s_buffer_get()`. If user provided callback function `sample_buffer_ready_cb` is called
+from interrupt context and return value other then 0, buffer will not be put in internal queue and can not be obtained
+with `i2s_buffer_get()`.
+
+Buffer pool can be created by `I2S_BUFFER_POOL_DEF()` macro
+
+
+Definition ```I2S_BUFFER_POOL_DEF(my_pool, 2, 512);``` would create following structure in memory:
+
+```
+
+my_pool_buffers: +----------------------------+
+                 | i2s_sample_buffer[0]       |<---------+
+                 |               sample_data  |------+   |
+                 + - - - - - - - - - - - - - -+      |   |
+                 | i2s_sample_buffer[1]       |      |   |
+            +----|               sample_data  |      |   |
+            |    +----------------------------+      |   |
+            |    | buffer0[0]                 |<-----+   |
+            |    | ....                       |          |
+            |    | buffer0[511]               |          |
+            |    +----------------------------|          |
+            +--->| buffer1[0]                 |          |
+                 | ....                       |          |
+                 | buffer1[511]               |          |
+                 +----------------------------|          |
+                                                         |
+my_pool:         +----------------------------+          |
+                 | buf_size: 512              |          |
+                 | buf_count: 2               |          |
+                 | buffers:                   |----------+
+                 +----------------------------+
+```
+# Usage examples
+
+#####1. Simple code that streams data to output I2S using own task
+
+````c
+int total_data_to_write = ....
+uint8_t *data_ptr = ....
+
+struct i2s *spkr;
+int data_written;
+
+spekr = i2s_open("speaker", OS_WAIT_FOREVER, NULL);
+
+while (total_data_to_write) {
+    data_written = i2s_write(spkr, data_ptr, total_data_to_write);
+    total_data_to_write -= data_written;
+    data_ptr += total_data_to_write;
+}
+i2s_close(spkr);
+````
+#####2. Blocking read from microphone example
+````c
+int total_data_to_read = ....
+uint8_t *data_ptr = ....
+
+struct i2s *mic;
+int data_read;
+
+spekr = i2s_open("mic", OS_WAIT_FOREVER, NULL);
+
+while (total_data_to_read) {
+    data_read = i2s_read(mic, data_ptr, total_data_to_read);
+    total_data_to_read -= data_read;
+    data_ptr += data_read;
+}
+i2s_close(spkr);
+````
+#####3. Reading without dedicated task
+Example shows how to read I2S data stream without blocking calls.
+````c
+
+struct i2s mic;
+
+void
+buffer_ready_event_cb(struct os_event *ev)
+{
+    struct i2s_sample_buffer *buffer;
+
+    /* This function is called so there is buffer ready. i2s_buffer_get() could be called
+     * with timeout 0 */
+    buffer = i2s_buffer_get(mic, OS_WAIT_FOREVER);
+    /* Handle microphone data */
+    ...
+    /* Microphone samples processed, send buffer back to i2s device */
+    i2s_buffer_put(mic, buffer);
+}
+
+struct os_event buffer_ready_event = {
+    .ev_cb = buffer_ready_event_cb,
+};
+
+/* Function called from interrupt telling client that new buffer with samples is available */
+static int
+more_data(struct i2s *i2s, struct i2s_sample_buffer *sample_buffer)
+{
+    /* Handle incoming data in main task queue */
+    os_eventq_put(os_eventq_dflt_get(), &buffer_ready_event);
+
+    return 0;
+}
+
+static void state_changed(struct i2s *i2s, enum i2s_state state) {}
+
+struct i2s_client client = {
+    .sample_buffer_ready_cb = more_data,
+    .state_changed_cb = state_changed,
+};
+
+void start_microphone(void)
+{
+    mic = i2s_open("mic", OS_WAIT_FOREVER, client);
+    /* I2S input device is not started when device is opened */
+    i2s_start(mic);
+}
+````
\ No newline at end of file
diff --git a/hw/drivers/i2s/include/i2s/i2s.h b/hw/drivers/i2s/include/i2s/i2s.h
new file mode 100644
index 0000000..aefc1f5
--- /dev/null
+++ b/hw/drivers/i2s/include/i2s/i2s.h
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef _HW_DRIVERS_I2S_H
+#define _HW_DRIVERS_I2S_H
+
+#include <stdint.h>
+#include <os/os_dev.h>
+
+/*
+ * I2S API does not specify this structure. It is used by i2s_create() and is defined
+ * by specific driver.
+ */
+struct i2s_cfg;
+
+/**
+ * Buffer for passing data between user code and i2s driver
+ */
+struct i2s_sample_buffer {
+    /* Internal use */
+    STAILQ_ENTRY(i2s_sample_buffer) next_buffer;
+    /** Actual sample data pointer */
+    void *sample_data;
+    /**
+     * Number of samples that buffer can hold
+     * This value is used for input I2S by driver.
+     */
+    uint32_t capacity;
+    /**
+     * Actual number of samples in buffer.
+     * This value is used when buffer is filled with samples.
+     * For output i2s user code fills this value.
+     * For input i2s driver fills this value.
+     */
+    uint32_t sample_count;
+};
+
+struct i2s_buffer_pool {
+    uint16_t buffer_size;
+    uint16_t buffer_count;
+    struct i2s_sample_buffer *buffers;
+};
+
+/**
+ * I2S buffer pool definition.
+ * @param name   pool name to be used,
+ * @param count  number of buffers to initialize
+ * @param size   single buffer size in bytes
+ */
+#define I2S_BUFFER_POOL_DEF(name, count, size) \
+    static uint8_t _Alignas(struct i2s_buffer_pool) name ## _buffers[(sizeof(struct i2s_sample_buffer) + size) * \
+                                                                     count]; \
+    struct i2s_buffer_pool name = { \
+        .buffer_size = size, \
+        .buffer_count = count, \
+        .buffers = (struct i2s_sample_buffer *)name ## _buffers \
+    }
+#define I2S_BUFFER_POOL(name) ((struct i2s_buffer_pool *)(&name))
+
+enum i2s_state {
+    I2S_STATE_STOPPED,
+    I2S_STATE_OUT_OF_BUFFERS,
+    I2S_STATE_RUNNING,
+};
+
+enum i2s_direction {
+    I2S_INVALID,
+    I2S_OUT,
+    I2S_IN,
+    I2S_OUT_IN,
+};
+
+/**
+ * I2S device
+ */
+struct i2s {
+    struct os_dev dev;
+    void *driver_data;
+    struct i2s_buffer_pool *buffer_pool;
+
+    /* Internal queues not for user code */
+    STAILQ_HEAD(, i2s_sample_buffer) user_queue;
+    STAILQ_HEAD(, i2s_sample_buffer) driver_queue;
+    /* Semaphore holding number of elements in user queue */
+    struct os_sem user_queue_buffer_count;
+
+    struct i2s_client *client;
+    /* Samples per second. */
+    uint32_t sample_rate;
+    uint8_t sample_size_in_bytes;
+    enum i2s_direction direction;
+    enum i2s_state state;
+};
+
+#define I2S_OK                  0
+#define I2S_ERR_NO_BUFFER       -1
+#define I2S_ERR_INTERNAL        -2
+
+/**
+ * Function that will be called from interrupt after sample_buffer if processed by I2S driver.
+ * For output I2S it will be called when last sample was sent out.
+ * For input I2S it will be called when whole buffer is filled with samples.
+ *
+ * @return 0 - buffer should be queued in i2s device for further usage.
+ *         1 - buffer will not be stored in i2s. This is useful when buffers are provided for
+ *             data playback from FLASH and once they are processed sent out they are not be used
+ *             any more. That way output I2S device may work without any RAM buffer.
+ */
+typedef int (*i2s_sample_buffer_ready_t)(struct i2s *i2s, struct i2s_sample_buffer *sample_buffer);
+
+/**
+ * Function will be called (possibly from interrupt context) when driver state changes.
+ * For practical reason it can be useful to know when I2S stream was stopped because
+ * it run out of buffers (I2S_STATE_OUT_OF_BUFFERS).
+ */
+typedef void (*i2s_state_change_t)(struct i2s *i2s, enum i2s_state state);
+
+/**
+ * Client interface.
+ */
+struct i2s_client {
+    /** Requested sample rate */
+    uint32_t sample_rate;
+    /** Function called when I2S state changes */
+    i2s_state_change_t state_changed_cb;
+    /** Function called when buffer is ready and i2s_buffer_get() will succeed */
+    i2s_sample_buffer_ready_t sample_buffer_ready_cb;
+};
+
+/**
+ * Creates I2S device with given name and configuration.
+ *
+ * @param i2s   device to crate
+ * @param name  name of the i2s device that can be used in i2s_open()
+ * @param cfg   device specific configuration
+ *
+ * @return OS_OK when device was created successfully, non zero on failure.
+ */
+int i2s_create(struct i2s *i2s, const char *name, const struct i2s_cfg *cfg);
+
+/**
+ * Open i2s device
+ *
+ * @param name        device name to open
+ * @param timeout     timeout for open
+ * @param client      structure with user callbacks, can be NULL
+ *
+ * @return pointer ot i2s device on success, NULL on failure
+ */
+struct i2s *i2s_open(const char *name, uint32_t timeout, struct i2s_client *client);
+
+/**
+ * Convenience function for closing i2s device
+ *
+ * @param i2s        device to close
+ *
+ * @return 0 on success, non zero on failure
+ */
+int i2s_close(struct i2s *i2s);
+
+/**
+ * Start i2s device operation
+ *
+ * For i2s input device, it will start filling buffers that were queued.
+ *
+ * @param i2s   device to start
+ *
+ * @return 0 on success, I2S_STATE_OUT_OF_BUFFERS is device is paused due to missing buffers
+ */
+int i2s_start(struct i2s *i2s);
+
+/**
+ * Stop i2s device operation
+ *
+ * @param i2s   device to stop
+ *
+ * @return 0 on success, other values in the future
+ */
+int i2s_stop(struct i2s *i2s);
+
+/**
+ * High level function to write samples to I2S device.
+ * Function is intended to be used from tasks. It will block if sample buffers are
+ * not ready yet.
+ *
+ * @param i2s                 device to send samples to
+ * @param samples             sample buffer
+ * @param sample_buffer_size  size of sample buffers in bytes
+ *
+ * @return number of bytes consumed, it may be less then sample_buffer_size
+ */
+int i2s_write(struct i2s *i2s, void *samples, uint32_t sample_buffer_size);
+
+/**
+ * High level function to read samples from I2S device.
+ * Function is intended to be used from tasks. It will block if sample buffers are
+ * not ready yet.
+ *
+ * @param i2s                 device to read samples from
+ * @param samples             buffer to be filled with samples
+ * @param sample_buffer_size  size of sample buffers in bytes
+ *
+ * @return number of bytes read, it may be less then sample_buffer_size.
+ */
+int i2s_read(struct i2s *i2s, void *samples, uint32_t sample_buffer_size);
+
+/**
+ * Return number of buffers that can be acquired by application without blocking
+ *
+ * @param i2s   device to check number of buffers from
+ *
+ * @return number of buffers that can be get without blocking, negative value on error
+ */
+int i2s_available_buffers(struct i2s *i2s);
+
+/**
+ * Dequeue buffer from I2S
+ *
+ * For output I2S (speaker), function will return buffer that user code will
+ * fill with samples and then pass to same i2s to be played with i2s_buffer_put.
+ *
+ * For input I2S (microphone), function will block and receive samples that were
+ * recorded. Once samples are processed user code should enqueue buffer to receive
+ * more samples.
+ *
+ * @param i2s       interface to use (speaker of microphone)
+ * @param timeout   time to wait for buffer to be ready
+ *
+ * @return pointer  to sample buffer with data (for microphone), with no data (for
+ *                  speaker). NULL if there was buffer available in specified time.
+ */
+struct i2s_sample_buffer *i2s_buffer_get(struct i2s *i2s, os_time_t timeout);
+
+/**
+ * Add/return buffer to I2S
+ *
+ * For output I2S buffer contains sample that should be sent to I2S device.
+ * For input I2S buffer will be filled with incoming data
+ * samples.
+ *
+ * @param i2s     interface to use
+ * @param buffer buffer with samples to be played for output I2S,
+ *                buffer for incoming samples for input I2S.
+ * @return 0 on success, non 0 on failure
+ */
+int i2s_buffer_put(struct i2s *i2s, struct i2s_sample_buffer *buffer);
+
+static inline uint32_t
+i2s_get_sample_rate(struct i2s *i2s)
+{
+    return i2s->sample_rate;
+}
+
+#endif /* _HW_DRIVERS_I2S_H */
diff --git a/hw/drivers/i2s/include/i2s/i2s_driver.h b/hw/drivers/i2s/include/i2s/i2s_driver.h
new file mode 100644
index 0000000..e20ab2d
--- /dev/null
+++ b/hw/drivers/i2s/include/i2s/i2s_driver.h
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef _HW_DRIVERS_I2S_DRIVER_H
+#define _HW_DRIVERS_I2S_DRIVER_H
+
+struct i2s;
+struct i2s_cfg;
+struct i2s_sample_buffer;
+
+/* Functions to be implemented by driver */
+
+int i2s_create(struct i2s *i2s, const char *name, const struct i2s_cfg *cfg);
+
+int i2s_driver_start(struct i2s *i2s);
+int i2s_driver_stop(struct i2s *i2s);
+void i2s_driver_buffer_queued(struct i2s *i2s);
+int i2s_driver_suspend(struct i2s *i2s, os_time_t timeout, int arg);
+int i2s_driver_resume(struct i2s *i2s);
+
+/* Functions to be called by driver code */
+int i2s_init(struct i2s *i2s, struct i2s_buffer_pool *pool);
+void i2s_driver_state_changed(struct i2s *i2s, enum i2s_state);
+void i2s_driver_buffer_put(struct i2s *i2s, struct i2s_sample_buffer *buffer);
+struct i2s_sample_buffer *i2s_driver_buffer_get(struct i2s *i2s);
+
+#endif /* _HW_DRIVERS_I2S_DRIVER_H */
diff --git a/hw/drivers/i2s/pkg.yml b/hw/drivers/i2s/pkg.yml
new file mode 100644
index 0000000..01b6ad6
--- /dev/null
+++ b/hw/drivers/i2s/pkg.yml
@@ -0,0 +1,27 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+pkg.name: hw/drivers/i2s
+pkg.description: I2S device insterace
+pkg.author: "Apache Mynewt <de...@mynewt.apache.org>"
+pkg.homepage: "http://mynewt.apache.org/"
+pkg.keywords:
+pkg.deps:
+pkg.req_apis:
+    - I2S_HW_IMPL