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