summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/esp32/quickref.rst18
-rw-r--r--docs/library/machine.I2S.rst162
-rw-r--r--docs/library/machine.rst1
-rw-r--r--docs/pyboard/quickref.rst20
-rw-r--r--ports/esp32/machine_i2s.c809
-rw-r--r--ports/esp32/main.c1
-rw-r--r--ports/esp32/main/CMakeLists.txt1
-rw-r--r--ports/esp32/modmachine.c1
-rw-r--r--ports/esp32/modmachine.h2
-rw-r--r--ports/stm32/Makefile13
-rw-r--r--ports/stm32/boards/PYBD_SF2/mpconfigboard.h5
-rw-r--r--ports/stm32/boards/PYBV10/mpconfigboard.h4
-rw-r--r--ports/stm32/boards/PYBV11/mpconfigboard.h4
-rwxr-xr-xports/stm32/boards/make-pins.py2
-rw-r--r--ports/stm32/dma.c35
-rw-r--r--ports/stm32/dma.h4
-rw-r--r--ports/stm32/machine_i2s.c1126
-rw-r--r--ports/stm32/main.c4
-rw-r--r--ports/stm32/modmachine.c3
-rw-r--r--ports/stm32/modmachine.h2
-rw-r--r--ports/stm32/pin_defs_stm32.h2
21 files changed, 2218 insertions, 1 deletions
diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst
index 77c7027c4..0b825d520 100644
--- a/docs/esp32/quickref.rst
+++ b/docs/esp32/quickref.rst
@@ -385,6 +385,24 @@ has the same methods as software I2C above::
i2c = I2C(0)
i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000)
+I2S bus
+-------
+
+See :ref:`machine.I2S <machine.I2S>`. ::
+
+ from machine import I2S, Pin
+
+ i2s = I2S(0, sck=Pin(13), ws=Pin(14), sd=Pin(34), mode=I2S.TX, bits=16, format=I2S.STEREO, rate=44100, ibuf=40000) # create I2S object
+ i2s.write(buf) # write buffer of audio samples to I2S device
+
+ i2s = I2S(1, sck=Pin(33), ws=Pin(25), sd=Pin(32), mode=I2S.RX, bits=16, format=I2S.MONO, rate=22050, ibuf=40000) # create I2S object
+ i2s.readinto(buf) # fill buffer with audio samples from I2S device
+
+The I2S class is currently available as a Technical Preview. During the preview period, feedback from
+users is encouraged. Based on this feedback, the I2S class API and implementation may be changed.
+
+ESP32 has two I2S buses with id=0 and id=1
+
Real time clock (RTC)
---------------------
diff --git a/docs/library/machine.I2S.rst b/docs/library/machine.I2S.rst
new file mode 100644
index 000000000..48f03b419
--- /dev/null
+++ b/docs/library/machine.I2S.rst
@@ -0,0 +1,162 @@
+.. currentmodule:: machine
+.. _machine.I2S:
+
+class I2S -- Inter-IC Sound bus protocol
+========================================
+
+I2S is a synchronous serial protocol used to connect digital audio devices.
+At the physical level, a bus consists of 3 lines: SCK, WS, SD.
+The I2S class supports Master operation. Slave operation is not supported.
+
+The I2S class is currently available as a Technical Preview. During the preview period, feedback from
+users is encouraged. Based on this feedback, the I2S class API and implementation may be changed.
+
+I2S objects can be created and initialized using::
+
+ from machine import I2S
+ from machine import Pin
+
+ # ESP32
+ sck_pin = Pin(14) # Serial clock output
+ ws_pin = Pin(13) # Word clock output
+ sdout_pin = Pin(12) # Serial data output
+
+ or
+
+ # PyBoards
+ sck_pin = Pin("Y6") # Serial clock output
+ ws_pin = Pin("Y5") # Word clock output
+ sdout_pin = Pin("Y8") # Serial data output
+
+ audio_out = I2S(2,
+ sck=sck_pin, ws=ws_pin, sdin=sdin_pin,
+ mode=I2S.TX,
+ bits=16,
+ format=I2S.MONO,
+ rate=44100,
+ ibuf=20000)
+
+ audio_in = I2S(2,
+ sck=sck_pin, ws=ws_pin, sdin=sdin_pin,
+ mode=I2S.RX,
+ bits=32,
+ format=I2S.STEREO,
+ rate=22050,
+ ibuf=20000)
+
+3 modes of operation are supported:
+ - blocking
+ - non-blocking
+ - uasyncio
+
+blocking::
+
+ num_written = audio_out.write(buf) # blocks until buf emptied
+
+ num_read = audio_in.readinto(buf) # blocks until buf filled
+
+non-blocking::
+
+ audio_out.irq(i2s_callback) # i2s_callback is called when buf is emptied
+ num_written = audio_out.write(buf) # returns immediately
+
+ audio_in.irq(i2s_callback) # i2s_callback is called when buf is filled
+ num_read = audio_in.readinto(buf) # returns immediately
+
+uasyncio::
+
+ swriter = uasyncio.StreamWriter(audio_out)
+ swriter.write(buf)
+ await swriter.drain()
+
+ sreader = uasyncio.StreamReader(audio_in)
+ num_read = await sreader.readinto(buf)
+
+Constructor
+-----------
+
+.. class:: I2S(id, *, sck, ws, sd, mode, bits, format, rate, ibuf)
+
+ Construct an I2S object of the given id:
+
+ - ``id`` identifies a particular I2S bus.
+
+ ``id`` is board and port specific:
+
+ - PYBv1.0/v1.1: has one I2S bus with id=2.
+ - PYBD-SFxW: has two I2S buses with id=1 and id=2.
+ - ESP32: has two I2S buses with id=0 and id=1.
+
+ Keyword-only parameters that are supported on all ports:
+
+ - ``sck`` is a pin object for the serial clock line
+ - ``ws`` is a pin object for the word select line
+ - ``sd`` is a pin object for the serial data line
+ - ``mode`` specifies receive or transmit
+ - ``bits`` specifies sample size (bits), 16 or 32
+ - ``format`` specifies channel format, STEREO or MONO
+ - ``rate`` specifies audio sampling rate (samples/s)
+ - ``ibuf`` specifies internal buffer length (bytes)
+
+ For all ports, DMA runs continuously in the background and allows user applications to perform other operations while
+ sample data is transfered between the internal buffer and the I2S peripheral unit.
+ Increasing the size of the internal buffer has the potential to increase the time that user applications can perform non-I2S operations
+ before underflow (e.g. ``write`` method) or overflow (e.g. ``readinto`` method).
+
+Methods
+-------
+
+.. method:: I2S.init(sck, ...)
+
+ see Constructor for argument descriptions
+
+.. method:: I2S.deinit()
+
+ Deinitialize the I2S bus
+
+.. method:: I2S.readinto(buf)
+
+ Read audio samples into the buffer specified by ``buf``. ``buf`` must support the buffer protocol, such as bytearray or array.
+ "buf" byte ordering is little-endian. For Stereo format, left channel sample precedes right channel sample. For Mono format,
+ the left channel sample data is used.
+ Returns number of bytes read
+
+.. method:: I2S.write(buf)
+
+ Write audio samples contained in ``buf``. ``buf`` must support the buffer protocol, such as bytearray or array.
+ "buf" byte ordering is little-endian. For Stereo format, left channel sample precedes right channel sample. For Mono format,
+ the sample data is written to both the right and left channels.
+ Returns number of bytes written
+
+.. method:: I2S.irq(handler)
+
+ Set a callback. ``handler`` is called when ``buf`` is emptied (``write`` method) or becomes full (``readinto`` method).
+ Setting a callback changes the ``write`` and ``readinto`` methods to non-blocking operation.
+ ``handler`` is called in the context of the MicroPython scheduler.
+
+.. staticmethod:: I2S.shift(buf, bits, shift)
+
+ bitwise shift of all samples contained in ``buf``. ``bits`` specifies sample size in bits. ``shift`` specifies the number of bits to shift each sample.
+ Positive for left shift, negative for right shift.
+ Typically used for volume control. Each bit shift changes sample volume by 6dB.
+
+Constants
+---------
+
+.. data:: I2S.RX
+
+ for initialising the I2S bus ``mode`` to receive
+
+.. data:: I2S.TX
+
+ for initialising the I2S bus ``mode`` to transmit
+
+.. data:: I2S.STEREO
+
+ for initialising the I2S bus ``format`` to stereo
+
+.. data:: I2S.MONO
+
+ for initialising the I2S bus ``format`` to mono
+
+
diff --git a/docs/library/machine.rst b/docs/library/machine.rst
index 0a1c1c953..26f99680a 100644
--- a/docs/library/machine.rst
+++ b/docs/library/machine.rst
@@ -181,6 +181,7 @@ Classes
machine.UART.rst
machine.SPI.rst
machine.I2C.rst
+ machine.I2S.rst
machine.RTC.rst
machine.Timer.rst
machine.WDT.rst
diff --git a/docs/pyboard/quickref.rst b/docs/pyboard/quickref.rst
index 3dbd09304..3ea319099 100644
--- a/docs/pyboard/quickref.rst
+++ b/docs/pyboard/quickref.rst
@@ -219,6 +219,26 @@ eg ``I2C(1)``. Software I2C is also available by explicitly specifying the
Note: for legacy I2C support see :ref:`pyb.I2C <pyb.I2C>`.
+I2S bus
+-------
+
+See :ref:`machine.I2S <machine.I2S>`. ::
+
+ from machine import I2S, Pin
+
+ i2s = I2S(2, sck=Pin('Y6'), ws=Pin('Y5'), sd=Pin('Y8'), mode=I2S.TX, bits=16, format=I2S.STEREO, rate=44100, ibuf=40000) # create I2S object
+ i2s.write(buf) # write buffer of audio samples to I2S device
+
+ i2s = I2S(1, sck=Pin('X5'), ws=Pin('X6'), sd=Pin('Y4'), mode=I2S.RX, bits=16, format=I2S.MONO, rate=22050, ibuf=40000) # create I2S object
+ i2s.readinto(buf) # fill buffer with audio samples from I2S device
+
+The I2S class is currently available as a Technical Preview. During the preview period, feedback from
+users is encouraged. Based on this feedback, the I2S class API and implementation may be changed.
+
+PYBv1.0/v1.1 has one I2S bus with id=2.
+PYBD-SFxW has two I2S buses with id=1 and id=2.
+I2S is shared with SPI.
+
CAN bus (controller area network)
---------------------------------
diff --git a/ports/esp32/machine_i2s.c b/ports/esp32/machine_i2s.c
new file mode 100644
index 000000000..b0d02e74f
--- /dev/null
+++ b/ports/esp32/machine_i2s.c
@@ -0,0 +1,809 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Mike Teachman
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/misc.h"
+#include "py/stream.h"
+#include "py/objstr.h"
+#include "modmachine.h"
+#include "mphalport.h"
+
+#include "driver/i2s.h"
+#include "soc/i2s_reg.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "esp_task.h"
+
+// The I2S module has 3 modes of operation:
+//
+// Mode1: Blocking
+// - readinto() and write() methods block until the supplied buffer is filled (read) or emptied (write)
+// - this is the default mode of operation
+//
+// Mode2: Non-Blocking
+// - readinto() and write() methods return immediately.
+// - buffer filling and emptying happens asynchronously to the main MicroPython task
+// - a callback function is called when the supplied buffer has been filled (read) or emptied (write)
+// - non-blocking mode is enabled when a callback is set with the irq() method
+// - a FreeRTOS task is created to implement the asynchronous background operations
+// - a FreeRTOS queue is used to transfer the supplied buffer to the background task
+//
+// Mode3: Uasyncio
+// - implements the stream protocol
+// - uasyncio mode is enabled when the ioctl() function is called
+// - the I2S event queue is used to detect that I2S samples can be read or written from/to DMA memory
+//
+// The samples contained in the app buffer supplied for the readinto() and write() methods have the following convention:
+// Mono: little endian format
+// Stereo: little endian format, left channel first
+//
+// I2S terms:
+// "frame": consists of two audio samples (Left audio sample + Right audio sample)
+//
+// Misc:
+// - for Mono configuration:
+// - readinto method: samples are gathered from the L channel only
+// - write method: every sample is output to both the L and R channels
+// - for readinto method the I2S hardware is read using 8-byte frames
+// (this is standard for almost all I2S hardware, such as MEMS microphones)
+// - all sample data transfers use DMA
+
+#define I2S_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1)
+#define I2S_TASK_STACK_SIZE (2048)
+
+#define DMA_BUF_LEN_IN_I2S_FRAMES (256)
+
+// The transform buffer is used with the readinto() method to bridge the opaque DMA memory on the ESP devices
+// with the app buffer. It facilitates audio sample transformations. e.g. 32-bits samples to 16-bit samples.
+// The size of 240 bytes is an engineering optimum that balances transfer performance with an acceptable use of heap space
+#define SIZEOF_TRANSFORM_BUFFER_IN_BYTES (240)
+
+#define NUM_I2S_USER_FORMATS (4)
+#define I2S_RX_FRAME_SIZE_IN_BYTES (8)
+
+typedef enum {
+ MONO,
+ STEREO
+} format_t;
+
+typedef enum {
+ BLOCKING,
+ NON_BLOCKING,
+ UASYNCIO
+} io_mode_t;
+
+typedef enum {
+ I2S_TX_TRANSFER,
+ I2S_RX_TRANSFER,
+} direction_t;
+
+typedef struct _non_blocking_descriptor_t {
+ mp_buffer_info_t appbuf;
+ mp_obj_t callback;
+ direction_t direction;
+} non_blocking_descriptor_t;
+
+typedef struct _machine_i2s_obj_t {
+ mp_obj_base_t base;
+ i2s_port_t port;
+ mp_hal_pin_obj_t sck;
+ mp_hal_pin_obj_t ws;
+ mp_hal_pin_obj_t sd;
+ int8_t mode;
+ i2s_bits_per_sample_t bits;
+ format_t format;
+ int32_t rate;
+ int32_t ibuf;
+ mp_obj_t callback_for_non_blocking;
+ io_mode_t io_mode;
+ uint8_t transform_buffer[SIZEOF_TRANSFORM_BUFFER_IN_BYTES];
+ QueueHandle_t i2s_event_queue;
+ QueueHandle_t non_blocking_mode_queue;
+ TaskHandle_t non_blocking_mode_task;
+} machine_i2s_obj_t;
+
+STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in);
+
+// The frame map is used with the readinto() method to transform the audio sample data coming
+// from DMA memory (32-bit stereo, with the L and R channels reversed) to the format specified
+// in the I2S constructor. e.g. 16-bit mono
+STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYTES] = {
+ { 6, 7, -1, -1, -1, -1, -1, -1 }, // Mono, 16-bits
+ { 4, 5, 6, 7, -1, -1, -1, -1 }, // Mono, 32-bits
+ { 6, 7, 2, 3, -1, -1, -1, -1 }, // Stereo, 16-bits
+ { 4, 5, 6, 7, 0, 1, 2, 3 }, // Stereo, 32-bits
+};
+
+STATIC machine_i2s_obj_t *machine_i2s_obj[I2S_NUM_MAX];
+
+void machine_i2s_init0() {
+ for (i2s_port_t p = 0; p < I2S_NUM_MAX; p++) {
+ machine_i2s_obj[p] = NULL;
+ }
+}
+
+// The following function takes a sample buffer and swaps L/R channels
+//
+// Background: For 32-bit stereo, the ESP-IDF API has a L/R channel orientation that breaks
+// convention with other ESP32 channel formats
+//
+// appbuf[] = [L_0-7, L_8-15, L_16-23, L_24-31, R_0-7, R_8-15, R_16-23, R_24-31] = [Left channel, Right channel]
+// dma[] = [R_0-7, R_8-15, R_16-23, R_24-31, L_0-7, L_8-15, L_16-23, L_24-31] = [Right channel, Left channel]
+//
+// where:
+// L_0-7 is the least significant byte of the 32 bit sample in the Left channel
+// L_24-31 is the most significant byte of the 32 bit sample in the Left channel
+//
+// Example:
+//
+// appbuf[] = [0x99, 0xBB, 0x11, 0x22, 0x44, 0x55, 0xAB, 0x77] = [Left channel, Right channel]
+// dma[] = [0x44, 0x55, 0xAB, 0x77, 0x99, 0xBB, 0x11, 0x22] = [Right channel, Left channel]
+// where:
+// LEFT Channel = 0x99, 0xBB, 0x11, 0x22
+// RIGHT Channel = 0x44, 0x55, 0xAB, 0x77
+//
+// samples in appbuf are in little endian format:
+// 0x77 is the most significant byte of the 32-bit sample
+// 0x44 is the least significant byte of the 32-bit sample
+STATIC void swap_32_bit_stereo_channels(mp_buffer_info_t *bufinfo) {
+ int32_t swap_sample;
+ int32_t *sample = bufinfo->buf;
+ uint32_t num_samples = bufinfo->len / 4;
+ for (uint32_t i = 0; i < num_samples; i += 2) {
+ swap_sample = sample[i + 1];
+ sample[i + 1] = sample[i];
+ sample[i] = swap_sample;
+ }
+}
+
+STATIC int8_t get_frame_mapping_index(i2s_bits_per_sample_t bits, format_t format) {
+ if (format == MONO) {
+ if (bits == I2S_BITS_PER_SAMPLE_16BIT) {
+ return 0;
+ } else { // 32 bits
+ return 1;
+ }
+ } else { // STEREO
+ if (bits == I2S_BITS_PER_SAMPLE_16BIT) {
+ return 2;
+ } else { // 32 bits
+ return 3;
+ }
+ }
+}
+
+STATIC i2s_bits_per_sample_t get_dma_bits(uint8_t mode, i2s_bits_per_sample_t bits) {
+ if (mode == (I2S_MODE_MASTER | I2S_MODE_TX)) {
+ return bits;
+ } else { // Master Rx
+ // read 32 bit samples for I2S hardware. e.g. MEMS microphones
+ return I2S_BITS_PER_SAMPLE_32BIT;
+ }
+}
+
+STATIC i2s_channel_fmt_t get_dma_format(uint8_t mode, format_t format) {
+ if (mode == (I2S_MODE_MASTER | I2S_MODE_TX)) {
+ if (format == MONO) {
+ return I2S_CHANNEL_FMT_ONLY_LEFT;
+ } else { // STEREO
+ return I2S_CHANNEL_FMT_RIGHT_LEFT;
+ }
+ } else { // Master Rx
+ // read stereo frames for all I2S hardware
+ return I2S_CHANNEL_FMT_RIGHT_LEFT;
+ }
+}
+
+STATIC uint32_t get_dma_buf_count(uint8_t mode, i2s_bits_per_sample_t bits, format_t format, int32_t ibuf) {
+ // calculate how many DMA buffers need to be allocated
+ uint32_t dma_frame_size_in_bytes =
+ (get_dma_bits(mode, bits) / 8) * (get_dma_format(mode, format) == I2S_CHANNEL_FMT_RIGHT_LEFT ? 2: 1);
+
+ uint32_t dma_buf_count = ibuf / (DMA_BUF_LEN_IN_I2S_FRAMES * dma_frame_size_in_bytes);
+
+ return dma_buf_count;
+}
+
+STATIC uint32_t fill_appbuf_from_dma(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
+
+ // copy audio samples from DMA memory to the app buffer
+ // audio samples are read from DMA memory in chunks
+ // loop, reading and copying chunks until the app buffer is filled
+ // For uasyncio mode, the loop will make an early exit if DMA memory becomes empty
+ // Example:
+ // a MicroPython I2S object is configured for 16-bit mono (2 bytes per audio sample).
+ // For every frame coming from DMA (8 bytes), 2 bytes are "cherry picked" and
+ // copied to the supplied app buffer.
+ // Thus, for every 1 byte copied to the app buffer, 4 bytes are read from DMA memory.
+ // If a 8kB app buffer is supplied, 32kB of audio samples is read from DMA memory.
+
+ uint32_t a_index = 0;
+ uint8_t *app_p = appbuf->buf;
+ uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1);
+ uint32_t num_bytes_needed_from_dma = appbuf->len * (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes);
+ while (num_bytes_needed_from_dma) {
+ uint32_t num_bytes_requested_from_dma = MIN(sizeof(self->transform_buffer), num_bytes_needed_from_dma);
+ uint32_t num_bytes_received_from_dma = 0;
+
+ TickType_t delay;
+ if (self->io_mode == UASYNCIO) {
+ delay = 0; // stop i2s_read() operation if DMA memory becomes empty
+ } else {
+ delay = portMAX_DELAY; // block until supplied buffer is filled
+ }
+
+ // read a chunk of audio samples from DMA memory
+ check_esp_err(i2s_read(
+ self->port,
+ self->transform_buffer,
+ num_bytes_requested_from_dma,
+ &num_bytes_received_from_dma,
+ delay));
+
+ // process the transform buffer one frame at a time.
+ // copy selected bytes from the transform buffer into the user supplied appbuf.
+ // Example:
+ // a MicroPython I2S object is configured for 16-bit mono. This configuration associates to
+ // a frame map index of 0 = { 6, 7, -1, -1, -1, -1, -1, -1 } in the i2s_frame_map array
+ // This mapping indicates:
+ // appbuf[x+0] = frame[6]
+ // appbuf[x+1] = frame[7]
+ // frame bytes 0-5 are not used
+
+ uint32_t t_index = 0;
+ uint8_t f_index = get_frame_mapping_index(self->bits, self->format);
+ while (t_index < num_bytes_received_from_dma) {
+ uint8_t *transform_p = self->transform_buffer + t_index;
+
+ for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) {
+ int8_t t_to_a_mapping = i2s_frame_map[f_index][i];
+ if (t_to_a_mapping != -1) {
+ *app_p++ = transform_p[t_to_a_mapping];
+ a_index++;
+ }
+ t_index++;
+ }
+ }
+
+ num_bytes_needed_from_dma -= num_bytes_received_from_dma;
+
+ if ((self->io_mode == UASYNCIO) && (num_bytes_received_from_dma < num_bytes_requested_from_dma)) {
+ // Unable to fill the entire app buffer from DMA memory. This indicates all DMA RX buffers are empty.
+ // Clear the I2S event queue so ioctl() indicates that the I2S object cannot currently
+ // supply more audio samples
+ xQueueReset(self->i2s_event_queue);
+ break;
+ }
+ }
+
+ return a_index;
+}
+
+STATIC uint32_t copy_appbuf_to_dma(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
+ if ((self->bits == I2S_BITS_PER_SAMPLE_32BIT) && (self->format == STEREO)) {
+ swap_32_bit_stereo_channels(appbuf);
+ }
+
+ uint32_t num_bytes_written = 0;
+
+ TickType_t delay;
+ if (self->io_mode == UASYNCIO) {
+ delay = 0; // stop i2s_write() operation if DMA memory becomes full
+ } else {
+ delay = portMAX_DELAY; // block until supplied buffer is emptied
+ }
+
+ check_esp_err(i2s_write(self->port, appbuf->buf, appbuf->len, &num_bytes_written, delay));
+
+ if ((self->io_mode == UASYNCIO) && (num_bytes_written < appbuf->len)) {
+ // Unable to empty the entire app buffer into DMA memory. This indicates all DMA TX buffers are full.
+ // Clear the I2S event queue so ioctl() indicates that the I2S object cannot currently
+ // accept more audio samples
+ xQueueReset(self->i2s_event_queue);
+
+ // Undo the swap transformation as the buffer has not been completely emptied.
+ // The uasyncio stream writer will use the same buffer in a future write call.
+ if ((self->bits == I2S_BITS_PER_SAMPLE_32BIT) && (self->format == STEREO)) {
+ swap_32_bit_stereo_channels(appbuf);
+ }
+ }
+ return num_bytes_written;
+}
+
+// FreeRTOS task used for non-blocking mode
+STATIC void task_for_non_blocking_mode(void *self_in) {
+ machine_i2s_obj_t *self = (machine_i2s_obj_t *)self_in;
+
+ non_blocking_descriptor_t descriptor;
+
+ for (;;) {
+ if (xQueueReceive(self->non_blocking_mode_queue, &descriptor, portMAX_DELAY)) {
+ if (descriptor.direction == I2S_TX_TRANSFER) {
+ copy_appbuf_to_dma(self, &descriptor.appbuf);
+ } else { // RX
+ fill_appbuf_from_dma(self, &descriptor.appbuf);
+ }
+ mp_sched_schedule(descriptor.callback, MP_OBJ_FROM_PTR(self));
+ }
+ }
+}
+
+STATIC void machine_i2s_init_helper(machine_i2s_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+
+ enum {
+ ARG_sck,
+ ARG_ws,
+ ARG_sd,
+ ARG_mode,
+ ARG_bits,
+ ARG_format,
+ ARG_rate,
+ ARG_ibuf,
+ };
+
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_sd, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_format, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_ibuf, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ };
+
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ //
+ // ---- Check validity of arguments ----
+ //
+
+ // are Pins valid?
+ int8_t sck = args[ARG_sck].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sck].u_obj);
+ int8_t ws = args[ARG_ws].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_ws].u_obj);
+ int8_t sd = args[ARG_sd].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sd].u_obj);
+
+ // is Mode valid?
+ i2s_mode_t mode = args[ARG_mode].u_int;
+ if ((mode != (I2S_MODE_MASTER | I2S_MODE_RX)) &&
+ (mode != (I2S_MODE_MASTER | I2S_MODE_TX))) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid mode"));
+ }
+
+ // is Bits valid?
+ i2s_bits_per_sample_t bits = args[ARG_bits].u_int;
+ if ((bits != I2S_BITS_PER_SAMPLE_16BIT) &&
+ (bits != I2S_BITS_PER_SAMPLE_32BIT)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
+ }
+
+ // is Format valid?
+ format_t format = args[ARG_format].u_int;
+ if ((format != STEREO) &&
+ (format != MONO)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid format"));
+ }
+
+ // is Rate valid?
+ // Not checked: ESP-IDF I2S API does not indicate a valid range for sample rate
+
+ // is Ibuf valid?
+ // Not checked: ESP-IDF I2S API will return error if requested buffer size exceeds available memory
+
+ self->sck = sck;
+ self->ws = ws;
+ self->sd = sd;
+ self->mode = mode;
+ self->bits = bits;
+ self->format = format;
+ self->rate = args[ARG_rate].u_int;
+ self->ibuf = args[ARG_ibuf].u_int;
+ self->callback_for_non_blocking = MP_OBJ_NULL;
+ self->i2s_event_queue = NULL;
+ self->non_blocking_mode_queue = NULL;
+ self->non_blocking_mode_task = NULL;
+ self->io_mode = BLOCKING;
+
+ i2s_config_t i2s_config;
+ i2s_config.communication_format = I2S_COMM_FORMAT_I2S;
+ i2s_config.mode = mode;
+ i2s_config.bits_per_sample = get_dma_bits(mode, bits);
+ i2s_config.channel_format = get_dma_format(mode, format);
+ i2s_config.sample_rate = self->rate;
+ i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LOWMED;
+ i2s_config.dma_buf_count = get_dma_buf_count(mode, bits, format, self->ibuf);
+ i2s_config.dma_buf_len = DMA_BUF_LEN_IN_I2S_FRAMES;
+ i2s_config.use_apll = false;
+
+ // I2S queue size equals the number of DMA buffers
+ check_esp_err(i2s_driver_install(self->port, &i2s_config, i2s_config.dma_buf_count, &self->i2s_event_queue));
+
+ // apply low-level workaround for bug in some ESP-IDF versions that swap
+ // the left and right channels
+ // https://github.com/espressif/esp-idf/issues/6625
+ REG_SET_BIT(I2S_CONF_REG(self->port), I2S_TX_MSB_RIGHT);
+ REG_SET_BIT(I2S_CONF_REG(self->port), I2S_RX_MSB_RIGHT);
+
+ i2s_pin_config_t pin_config;
+ pin_config.bck_io_num = self->sck;
+ pin_config.ws_io_num = self->ws;
+
+ if (mode == (I2S_MODE_MASTER | I2S_MODE_RX)) {
+ pin_config.data_in_num = self->sd;
+ pin_config.data_out_num = -1;
+ } else { // TX
+ pin_config.data_in_num = -1;
+ pin_config.data_out_num = self->sd;
+ }
+
+ check_esp_err(i2s_set_pin(self->port, &pin_config));
+}
+
+STATIC void machine_i2s_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_printf(print, "I2S(id=%u,\n"
+ "sck="MP_HAL_PIN_FMT ",\n"
+ "ws="MP_HAL_PIN_FMT ",\n"
+ "sd="MP_HAL_PIN_FMT ",\n"
+ "mode=%u,\n"
+ "bits=%u, format=%u,\n"
+ "rate=%d, ibuf=%d)",
+ self->port,
+ mp_hal_pin_name(self->sck),
+ mp_hal_pin_name(self->ws),
+ mp_hal_pin_name(self->sd),
+ self->mode,
+ self->bits, self->format,
+ self->rate, self->ibuf
+ );
+}
+
+STATIC mp_obj_t machine_i2s_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) {
+ mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true);
+
+ i2s_port_t port = mp_obj_get_int(args[0]);
+ if (port < 0 || port >= I2S_NUM_MAX) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid id"));
+ }
+
+ machine_i2s_obj_t *self;
+ if (machine_i2s_obj[port] == NULL) {
+ self = m_new_obj(machine_i2s_obj_t);
+ machine_i2s_obj[port] = self;
+ self->base.type = &machine_i2s_type;
+ self->port = port;
+ } else {
+ self = machine_i2s_obj[port];
+ machine_i2s_deinit(self);
+ }
+
+ mp_map_t kw_args;
+ mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args);
+ machine_i2s_init_helper(self, n_pos_args - 1, args + 1, &kw_args);
+
+ return MP_OBJ_FROM_PTR(self);
+}
+
+STATIC mp_obj_t machine_i2s_obj_init(mp_uint_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ machine_i2s_obj_t *self = pos_args[0];
+ machine_i2s_deinit(self);
+ machine_i2s_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_init_obj, 1, machine_i2s_obj_init);
+
+STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ i2s_driver_uninstall(self->port);
+
+ if (self->non_blocking_mode_task != NULL) {
+ vTaskDelete(self->non_blocking_mode_task);
+ self->non_blocking_mode_task = NULL;
+ }
+
+ if (self->non_blocking_mode_queue != NULL) {
+ vQueueDelete(self->non_blocking_mode_queue);
+ self->non_blocking_mode_queue = NULL;
+ }
+
+ self->i2s_event_queue = NULL;
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_i2s_deinit_obj, machine_i2s_deinit);
+
+STATIC mp_obj_t machine_i2s_irq(mp_obj_t self_in, mp_obj_t handler) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ if (handler != mp_const_none && !mp_obj_is_callable(handler)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid callback"));
+ }
+
+ if (handler != mp_const_none) {
+ self->io_mode = NON_BLOCKING;
+
+ // create a queue linking the MicroPython task to a FreeRTOS task
+ // that manages the non blocking mode of operation
+ self->non_blocking_mode_queue = xQueueCreate(1, sizeof(non_blocking_descriptor_t));
+
+ // non-blocking mode requires a background FreeRTOS task
+ if (xTaskCreatePinnedToCore(
+ task_for_non_blocking_mode,
+ "i2s_non_blocking",
+ I2S_TASK_STACK_SIZE,
+ self,
+ I2S_TASK_PRIORITY,
+ (TaskHandle_t *)&self->non_blocking_mode_task,
+ MP_TASK_COREID) != pdPASS) {
+
+ mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create I2S task"));
+ }
+ } else {
+ if (self->non_blocking_mode_task != NULL) {
+ vTaskDelete(self->non_blocking_mode_task);
+ self->non_blocking_mode_task = NULL;
+ }
+
+ if (self->non_blocking_mode_queue != NULL) {
+ vQueueDelete(self->non_blocking_mode_queue);
+ self->non_blocking_mode_queue = NULL;
+ }
+
+ self->io_mode = BLOCKING;
+ }
+
+ self->callback_for_non_blocking = handler;
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_irq_obj, machine_i2s_irq);
+
+// Shift() is typically used as a volume control.
+// shift=1 increases volume by 6dB, shift=-1 decreases volume by 6dB
+STATIC mp_obj_t machine_i2s_shift(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ enum { ARG_buf, ARG_bits, ARG_shift};
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
+ { MP_QSTR_bits, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_shift, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
+ };
+
+ // parse args
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ mp_buffer_info_t bufinfo;
+ mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_RW);
+
+ int16_t *buf_16 = bufinfo.buf;
+ int32_t *buf_32 = bufinfo.buf;
+
+ uint8_t bits = args[ARG_bits].u_int;
+ int8_t shift = args[ARG_shift].u_int;
+
+ uint32_t num_audio_samples;
+ switch (bits) {
+ case 16:
+ num_audio_samples = bufinfo.len / 2;
+ break;
+
+ case 32:
+ num_audio_samples = bufinfo.len / 4;
+ break;
+
+ default:
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
+ break;
+ }
+
+ for (uint32_t i = 0; i < num_audio_samples; i++) {
+ switch (bits) {
+ case 16:
+ if (shift >= 0) {
+ buf_16[i] = buf_16[i] << shift;
+ } else {
+ buf_16[i] = buf_16[i] >> abs(shift);
+ }
+ break;
+ case 32:
+ if (shift >= 0) {
+ buf_32[i] = buf_32[i] << shift;
+ } else {
+ buf_32[i] = buf_32[i] >> abs(shift);
+ }
+ break;
+ }
+ }
+
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_shift_fun_obj, 0, machine_i2s_shift);
+STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(machine_i2s_shift_obj, MP_ROM_PTR(&machine_i2s_shift_fun_obj));
+
+STATIC const mp_rom_map_elem_t machine_i2s_locals_dict_table[] = {
+ // Methods
+ { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2s_init_obj) },
+ { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
+ { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
+ { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2s_deinit_obj) },
+ { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_i2s_irq_obj) },
+
+ // Static method
+ { MP_ROM_QSTR(MP_QSTR_shift), MP_ROM_PTR(&machine_i2s_shift_obj) },
+
+ // Constants
+ { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_RX) },
+ { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_TX) },
+ { MP_ROM_QSTR(MP_QSTR_STEREO), MP_ROM_INT(STEREO) },
+ { MP_ROM_QSTR(MP_QSTR_MONO), MP_ROM_INT(MONO) },
+};
+MP_DEFINE_CONST_DICT(machine_i2s_locals_dict, machine_i2s_locals_dict_table);
+
+STATIC mp_uint_t machine_i2s_stream_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ if (self->mode != (I2S_MODE_MASTER | I2S_MODE_RX)) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1);
+ if (size % appbuf_sample_size_in_bytes != 0) {
+ *errcode = MP_EINVAL;
+ return MP_STREAM_ERROR;
+ }
+
+ if (size == 0) {
+ return 0;
+ }
+
+ if (self->io_mode == NON_BLOCKING) {
+ non_blocking_descriptor_t descriptor;
+ descriptor.appbuf.buf = (void *)buf_in;
+ descriptor.appbuf.len = size;
+ descriptor.callback = self->callback_for_non_blocking;
+ descriptor.direction = I2S_RX_TRANSFER;
+ // send the descriptor to the task that handles non-blocking mode
+ xQueueSend(self->non_blocking_mode_queue, &descriptor, 0);
+ return size;
+ } else { // blocking or uasyncio mode
+ mp_buffer_info_t appbuf;
+ appbuf.buf = (void *)buf_in;
+ appbuf.len = size;
+ uint32_t num_bytes_read = fill_appbuf_from_dma(self, &appbuf);
+ return num_bytes_read;
+ }
+}
+
+STATIC mp_uint_t machine_i2s_stream_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ if (self->mode != (I2S_MODE_MASTER | I2S_MODE_TX)) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ if (size == 0) {
+ return 0;
+ }
+
+ if (self->io_mode == NON_BLOCKING) {
+ non_blocking_descriptor_t descriptor;
+ descriptor.appbuf.buf = (void *)buf_in;
+ descriptor.appbuf.len = size;
+ descriptor.callback = self->callback_for_non_blocking;
+ descriptor.direction = I2S_TX_TRANSFER;
+ // send the descriptor to the task that handles non-blocking mode
+ xQueueSend(self->non_blocking_mode_queue, &descriptor, 0);
+ return size;
+ } else { // blocking or uasyncio mode
+ mp_buffer_info_t appbuf;
+ appbuf.buf = (void *)buf_in;
+ appbuf.len = size;
+ uint32_t num_bytes_written = copy_appbuf_to_dma(self, &appbuf);
+ return num_bytes_written;
+ }
+}
+
+STATIC mp_uint_t machine_i2s_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_uint_t ret;
+ mp_uint_t flags = arg;
+ self->io_mode = UASYNCIO; // a call to ioctl() is an indication that uasyncio is being used
+
+ if (request == MP_STREAM_POLL) {
+ ret = 0;
+
+ if (flags & MP_STREAM_POLL_RD) {
+ if (self->mode != (I2S_MODE_MASTER | I2S_MODE_RX)) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ i2s_event_t i2s_event;
+
+ // check event queue to determine if a DMA buffer has been filled
+ // (which is an indication that at least one DMA buffer is available to be read)
+ // note: timeout = 0 so the call is non-blocking
+ if (xQueueReceive(self->i2s_event_queue, &i2s_event, 0)) {
+ if (i2s_event.type == I2S_EVENT_RX_DONE) {
+ // getting here means that at least one DMA buffer is now full
+ // indicating that audio samples can be read from the I2S object
+ ret |= MP_STREAM_POLL_RD;
+ }
+ }
+ }
+
+ if (flags & MP_STREAM_POLL_WR) {
+ if (self->mode != (I2S_MODE_MASTER | I2S_MODE_TX)) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ i2s_event_t i2s_event;
+
+ // check event queue to determine if a DMA buffer has been emptied
+ // (which is an indication that at least one DMA buffer is available to be written)
+ // note: timeout = 0 so the call is non-blocking
+ if (xQueueReceive(self->i2s_event_queue, &i2s_event, 0)) {
+ if (i2s_event.type == I2S_EVENT_TX_DONE) {
+ // getting here means that at least one DMA buffer is now empty
+ // indicating that audio samples can be written to the I2S object
+ ret |= MP_STREAM_POLL_WR;
+ }
+ }
+ }
+ } else {
+ *errcode = MP_EINVAL;
+ ret = MP_STREAM_ERROR;
+ }
+
+ return ret;
+}
+
+STATIC const mp_stream_p_t i2s_stream_p = {
+ .read = machine_i2s_stream_read,
+ .write = machine_i2s_stream_write,
+ .ioctl = machine_i2s_ioctl,
+ .is_text = false,
+};
+
+const mp_obj_type_t machine_i2s_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_I2S,
+ .print = machine_i2s_print,
+ .getiter = mp_identity_getiter,
+ .iternext = mp_stream_unbuffered_iter,
+ .protocol = &i2s_stream_p,
+ .make_new = machine_i2s_make_new,
+ .locals_dict = (mp_obj_dict_t *)&machine_i2s_locals_dict,
+};
diff --git a/ports/esp32/main.c b/ports/esp32/main.c
index ff6dd6957..b04831fff 100644
--- a/ports/esp32/main.c
+++ b/ports/esp32/main.c
@@ -138,6 +138,7 @@ soft_reset:
// initialise peripherals
machine_pins_init();
+ machine_i2s_init0();
// run boot-up scripts
pyexec_frozen_module("_boot.py");
diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt
index d01656e56..fb08c2759 100644
--- a/ports/esp32/main/CMakeLists.txt
+++ b/ports/esp32/main/CMakeLists.txt
@@ -52,6 +52,7 @@ set(MICROPY_SOURCE_PORT
${PROJECT_DIR}/machine_adc.c
${PROJECT_DIR}/machine_dac.c
${PROJECT_DIR}/machine_i2c.c
+ ${PROJECT_DIR}/machine_i2s.c
${PROJECT_DIR}/machine_pwm.c
${PROJECT_DIR}/machine_uart.c
${PROJECT_DIR}/modmachine.c
diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c
index bbe7fae03..c46f8ab8d 100644
--- a/ports/esp32/modmachine.c
+++ b/ports/esp32/modmachine.c
@@ -280,6 +280,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) },
{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hw_i2c_type) },
{ MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) },
+ { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) },
{ MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) },
{ MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) },
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hw_spi_type) },
diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h
index 3e99e1120..7bf03b0ca 100644
--- a/ports/esp32/modmachine.h
+++ b/ports/esp32/modmachine.h
@@ -18,6 +18,7 @@ extern const mp_obj_type_t machine_dac_type;
extern const mp_obj_type_t machine_pwm_type;
extern const mp_obj_type_t machine_hw_i2c_type;
extern const mp_obj_type_t machine_hw_spi_type;
+extern const mp_obj_type_t machine_i2s_type;
extern const mp_obj_type_t machine_uart_type;
extern const mp_obj_type_t machine_rtc_type;
extern const mp_obj_type_t machine_sdcard_type;
@@ -27,5 +28,6 @@ void machine_deinit(void);
void machine_pins_init(void);
void machine_pins_deinit(void);
void machine_timer_deinit_all(void);
+void machine_i2s_init0();
#endif // MICROPY_INCLUDED_ESP32_MODMACHINE_H
diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile
index e94e7d72f..4ec03c174 100644
--- a/ports/stm32/Makefile
+++ b/ports/stm32/Makefile
@@ -318,6 +318,7 @@ SRC_C += \
help.c \
machine_adc.c \
machine_i2c.c \
+ machine_i2s.c \
machine_spi.c \
machine_timer.c \
machine_uart.c \
@@ -427,6 +428,18 @@ ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 f4 f7 h7 l4))
endif
endif
+ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 f4 f7 l0))
+HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\
+ hal_i2s.c \
+ )
+endif
+
+ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4))
+HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\
+ hal_i2s_ex.c \
+ )
+endif
+
USBDEV_SRC_C += $(addprefix $(USBDEV_DIR)/,\
core/src/usbd_core.c \
core/src/usbd_ctlreq.c \
diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h
index 48b3ee3eb..ed76b3f97 100644
--- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h
+++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h
@@ -40,6 +40,7 @@
#define MICROPY_HW_ENABLE_SDCARD (1)
#define MICROPY_HW_ENABLE_MMCARD (1)
#define MICROPY_HW_ENABLE_RF_SWITCH (1)
+#define MICROPY_HW_ENABLE_I2S (1)
#define MICROPY_BOARD_EARLY_INIT board_early_init
#define MICROPY_BOARD_ENTER_STOP board_sleep(1);
@@ -146,6 +147,10 @@ extern struct _spi_bdev_t spi_bdev2;
#define MICROPY_HW_SPI3_MISO (pyb_pin_W50)
#define MICROPY_HW_SPI3_MOSI (pyb_pin_W46)
+// I2S buses
+#define MICROPY_HW_I2S1 (1)
+#define MICROPY_HW_I2S2 (1)
+
// CAN buses
#define MICROPY_HW_CAN1_NAME "X"
#define MICROPY_HW_CAN1_TX (pyb_pin_X10)
diff --git a/ports/stm32/boards/PYBV10/mpconfigboard.h b/ports/stm32/boards/PYBV10/mpconfigboard.h
index 52382f44d..50ef3ae26 100644
--- a/ports/stm32/boards/PYBV10/mpconfigboard.h
+++ b/ports/stm32/boards/PYBV10/mpconfigboard.h
@@ -11,6 +11,7 @@
#define MICROPY_HW_ENABLE_DAC (1)
#define MICROPY_HW_ENABLE_USB (1)
#define MICROPY_HW_ENABLE_SDCARD (1)
+#define MICROPY_HW_ENABLE_I2S (1)
// HSE is 8MHz
#define MICROPY_HW_CLK_PLLM (8)
@@ -64,6 +65,9 @@
#define MICROPY_HW_SPI2_MISO (pin_B14) // Y7
#define MICROPY_HW_SPI2_MOSI (pin_B15) // Y8
+// I2S buses
+#define MICROPY_HW_I2S2 (1)
+
// CAN buses
#define MICROPY_HW_CAN1_NAME "YA"
#define MICROPY_HW_CAN1_TX (pin_B9) // Y4
diff --git a/ports/stm32/boards/PYBV11/mpconfigboard.h b/ports/stm32/boards/PYBV11/mpconfigboard.h
index 8e8469404..aec83d134 100644
--- a/ports/stm32/boards/PYBV11/mpconfigboard.h
+++ b/ports/stm32/boards/PYBV11/mpconfigboard.h
@@ -11,6 +11,7 @@
#define MICROPY_HW_ENABLE_DAC (1)
#define MICROPY_HW_ENABLE_USB (1)
#define MICROPY_HW_ENABLE_SDCARD (1)
+#define MICROPY_HW_ENABLE_I2S (1)
// HSE is 12MHz
#define MICROPY_HW_CLK_PLLM (12)
@@ -64,6 +65,9 @@
#define MICROPY_HW_SPI2_MISO (pin_B14) // Y7
#define MICROPY_HW_SPI2_MOSI (pin_B15) // Y8
+// I2S buses
+#define MICROPY_HW_I2S2 (1)
+
// CAN buses
#define MICROPY_HW_CAN1_NAME "YA"
#define MICROPY_HW_CAN1_TX (pin_B9) // Y4
diff --git a/ports/stm32/boards/make-pins.py b/ports/stm32/boards/make-pins.py
index 70213de6a..d898832f0 100755
--- a/ports/stm32/boards/make-pins.py
+++ b/ports/stm32/boards/make-pins.py
@@ -22,7 +22,7 @@ SUPPORTED_FN = {
CONDITIONAL_VAR = {
"I2C": "MICROPY_HW_I2C{num}_SCL",
- "I2S": "MICROPY_HW_ENABLE_I2S{num}",
+ "I2S": "MICROPY_HW_I2S{num}",
"SPI": "MICROPY_HW_SPI{num}_SCK",
"UART": "MICROPY_HW_UART{num}_TX",
"LPUART": "MICROPY_HW_LPUART{num}_TX",
diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c
index de3088691..ba09dc170 100644
--- a/ports/stm32/dma.c
+++ b/ports/stm32/dma.c
@@ -103,6 +103,31 @@ static const DMA_InitTypeDef dma_init_struct_spi_i2c = {
#endif
};
+#if MICROPY_HW_ENABLE_I2S
+// Default parameters to dma_init() for i2s; Channel and Direction
+// vary depending on the peripheral instance so they get passed separately
+static const DMA_InitTypeDef dma_init_struct_i2s = {
+ #if defined(STM32F4) || defined(STM32F7)
+ .Channel = 0,
+ #elif defined(STM32H7) || defined(STM32L0) || defined(STM32L4)
+ .Request = 0,
+ #endif
+ .Direction = DMA_MEMORY_TO_PERIPH,
+ .PeriphInc = DMA_PINC_DISABLE,
+ .MemInc = DMA_MINC_ENABLE,
+ .PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD,
+ .MemDataAlignment = DMA_MDATAALIGN_HALFWORD,
+ .Mode = DMA_CIRCULAR,
+ .Priority = DMA_PRIORITY_LOW,
+ #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
+ .FIFOMode = DMA_FIFOMODE_DISABLE,
+ .FIFOThreshold = DMA_FIFO_THRESHOLD_FULL,
+ .MemBurst = DMA_MBURST_SINGLE,
+ .PeriphBurst = DMA_PBURST_SINGLE
+ #endif
+};
+#endif
+
#if ENABLE_SDIO && !defined(STM32H7)
// Parameters to dma_init() for SDIO tx and rx.
static const DMA_InitTypeDef dma_init_struct_sdio = {
@@ -242,6 +267,10 @@ const dma_descr_t dma_I2C_3_RX = { DMA1_Stream2, DMA_CHANNEL_3, dma_id_2, &dma
const dma_descr_t dma_I2C_2_RX = { DMA1_Stream2, DMA_CHANNEL_7, dma_id_2, &dma_init_struct_spi_i2c };
const dma_descr_t dma_SPI_2_RX = { DMA1_Stream3, DMA_CHANNEL_0, dma_id_3, &dma_init_struct_spi_i2c };
const dma_descr_t dma_SPI_2_TX = { DMA1_Stream4, DMA_CHANNEL_0, dma_id_4, &dma_init_struct_spi_i2c };
+#if MICROPY_HW_ENABLE_I2S
+const dma_descr_t dma_I2S_2_RX = { DMA1_Stream3, DMA_CHANNEL_0, dma_id_3, &dma_init_struct_i2s };
+const dma_descr_t dma_I2S_2_TX = { DMA1_Stream4, DMA_CHANNEL_0, dma_id_4, &dma_init_struct_i2s };
+#endif
const dma_descr_t dma_I2C_3_TX = { DMA1_Stream4, DMA_CHANNEL_3, dma_id_4, &dma_init_struct_spi_i2c };
#if defined(STM32F7)
const dma_descr_t dma_I2C_4_TX = { DMA1_Stream5, DMA_CHANNEL_2, dma_id_5, &dma_init_struct_spi_i2c };
@@ -266,6 +295,9 @@ const dma_descr_t dma_SDMMC_2 = { DMA2_Stream0, DMA_CHANNEL_11, dma_id_8, &dma_
const dma_descr_t dma_DCMI_0 = { DMA2_Stream1, DMA_CHANNEL_1, dma_id_9, &dma_init_struct_dcmi };
#endif
const dma_descr_t dma_SPI_1_RX = { DMA2_Stream2, DMA_CHANNEL_3, dma_id_10, &dma_init_struct_spi_i2c };
+#if MICROPY_HW_ENABLE_I2S
+const dma_descr_t dma_I2S_1_RX = { DMA2_Stream2, DMA_CHANNEL_3, dma_id_10, &dma_init_struct_i2s };
+#endif
const dma_descr_t dma_SPI_5_RX = { DMA2_Stream3, DMA_CHANNEL_2, dma_id_11, &dma_init_struct_spi_i2c };
#if ENABLE_SDIO
const dma_descr_t dma_SDIO_0 = { DMA2_Stream3, DMA_CHANNEL_4, dma_id_11, &dma_init_struct_sdio };
@@ -275,6 +307,9 @@ const dma_descr_t dma_SPI_5_TX = { DMA2_Stream4, DMA_CHANNEL_2, dma_id_12, &dma
const dma_descr_t dma_SPI_4_TX = { DMA2_Stream4, DMA_CHANNEL_5, dma_id_12, &dma_init_struct_spi_i2c };
const dma_descr_t dma_SPI_6_TX = { DMA2_Stream5, DMA_CHANNEL_1, dma_id_13, &dma_init_struct_spi_i2c };
const dma_descr_t dma_SPI_1_TX = { DMA2_Stream5, DMA_CHANNEL_3, dma_id_13, &dma_init_struct_spi_i2c };
+#if MICROPY_HW_ENABLE_I2S
+const dma_descr_t dma_I2S_1_TX = { DMA2_Stream5, DMA_CHANNEL_3, dma_id_13, &dma_init_struct_i2s };
+#endif
// #if defined(STM32F7) && defined(SDMMC2) && ENABLE_SDIO
// const dma_descr_t dma_SDMMC_2 = { DMA2_Stream5, DMA_CHANNEL_11, dma_id_13, &dma_init_struct_sdio };
// #endif
diff --git a/ports/stm32/dma.h b/ports/stm32/dma.h
index 5a17276a4..8bd101a61 100644
--- a/ports/stm32/dma.h
+++ b/ports/stm32/dma.h
@@ -57,6 +57,10 @@ extern const dma_descr_t dma_SDMMC_2;
extern const dma_descr_t dma_SPI_6_RX;
extern const dma_descr_t dma_SDIO_0;
extern const dma_descr_t dma_DCMI_0;
+extern const dma_descr_t dma_I2S_1_RX;
+extern const dma_descr_t dma_I2S_1_TX;
+extern const dma_descr_t dma_I2S_2_RX;
+extern const dma_descr_t dma_I2S_2_TX;
#elif defined(STM32L0)
diff --git a/ports/stm32/machine_i2s.c b/ports/stm32/machine_i2s.c
new file mode 100644
index 000000000..d663767bf
--- /dev/null
+++ b/ports/stm32/machine_i2s.c
@@ -0,0 +1,1126 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Bryan Morrissey
+ * Copyright (c) 2021 Mike Teachman
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/mphal.h"
+#include "py/misc.h"
+#include "py/stream.h"
+#include "py/objstr.h"
+#include "modmachine.h"
+#include "pin.h"
+#include "dma.h"
+
+#if MICROPY_HW_ENABLE_I2S
+// The I2S module has 3 modes of operation:
+//
+// Mode1: Blocking
+// - readinto() and write() methods block until the supplied buffer is filled (read) or emptied (write)
+// - this is the default mode of operation
+//
+// Mode2: Non-Blocking
+// - readinto() and write() methods return immediately
+// - buffer filling and emptying happens asynchronously to the main MicroPython task
+// - a callback function is called when the supplied buffer has been filled (read) or emptied (write)
+// - non-blocking mode is enabled when a callback is set with the irq() method
+// - the DMA callbacks (1/2 complete and complete) are used to implement the asynchronous background operations
+//
+// Mode3: Uasyncio
+// - implements the stream protocol
+// - uasyncio mode is enabled when the ioctl() function is called
+// - the state of the internal ring buffer is used to detect that I2S samples can be read or written
+//
+// The samples contained in the app buffer supplied for the readinto() and write() methods have the following convention:
+// Mono: little endian format
+// Stereo: little endian format, left channel first
+//
+// I2S terms:
+// "frame": consists of two audio samples (Left audio sample + Right audio sample)
+//
+// Misc:
+// - for Mono configuration:
+// - readinto method: samples are gathered from the L channel only
+// - write method: every sample is output to both the L and R channels
+// - for readinto method the I2S hardware is read using 8-byte frames
+// (this is standard for almost all I2S hardware, such as MEMS microphones)
+// - all 3 Modes of operation are implemented using the HAL I2S Generic Driver
+// - all sample data transfers use DMA
+// - the DMA controller is configured in Circular mode to fulfil continuous and gapless sample flows
+// - the DMA ping-pong buffer needs to be aligned to a cache line size of 32 bytes. 32 byte
+// alignment is needed to use the routines that clean and invalidate D-Cache which work on a
+// 32 byte address boundary. Not all STM32 devices have a D-Cache. Buffer alignment
+// will still happen on these devices to keep this code simple.
+
+#define MAX_I2S_STM32 (2)
+
+// DMA ping-pong buffer size was empirically determined. It is a tradeoff between:
+// 1. memory use (smaller buffer size desirable to reduce memory footprint)
+// 2. interrupt frequency (larger buffer size desirable to reduce interrupt frequency)
+// The sizeof 1/2 of the DMA buffer must be evenly divisible by the cache line size of 32 bytes.
+#define SIZEOF_DMA_BUFFER_IN_BYTES (256)
+#define SIZEOF_HALF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER_IN_BYTES / 2)
+
+// For non-blocking mode, to avoid underflow/overflow, sample data is written/read to/from the ring buffer at a rate faster
+// than the DMA transfer rate
+#define NON_BLOCKING_RATE_MULTIPLIER (4)
+#define SIZEOF_NON_BLOCKING_COPY_IN_BYTES (SIZEOF_HALF_DMA_BUFFER_IN_BYTES * NON_BLOCKING_RATE_MULTIPLIER)
+
+#define NUM_I2S_USER_FORMATS (4)
+#define I2S_RX_FRAME_SIZE_IN_BYTES (8)
+
+typedef enum {
+ MONO,
+ STEREO
+} format_t;
+
+typedef enum {
+ BLOCKING,
+ NON_BLOCKING,
+ UASYNCIO
+} io_mode_t;
+
+typedef enum {
+ TOP_HALF,
+ BOTTOM_HALF
+} ping_pong_t;
+
+typedef struct _ring_buf_t {
+ uint8_t *buffer;
+ size_t head;
+ size_t tail;
+ size_t size;
+} ring_buf_t;
+
+typedef struct _non_blocking_descriptor_t {
+ mp_buffer_info_t appbuf;
+ uint32_t index;
+ bool copy_in_progress;
+} non_blocking_descriptor_t;
+
+typedef struct _machine_i2s_obj_t {
+ mp_obj_base_t base;
+ uint8_t i2s_id;
+ mp_hal_pin_obj_t sck;
+ mp_hal_pin_obj_t ws;
+ mp_hal_pin_obj_t sd;
+ uint16_t mode;
+ int8_t bits;
+ format_t format;
+ int32_t rate;
+ int32_t ibuf;
+ mp_obj_t callback_for_non_blocking;
+ uint8_t dma_buffer[SIZEOF_DMA_BUFFER_IN_BYTES + 0x1f]; // 0x1f related to D-Cache alignment
+ uint8_t *dma_buffer_dcache_aligned;
+ ring_buf_t ring_buffer;
+ uint8_t *ring_buffer_storage;
+ non_blocking_descriptor_t non_blocking_descriptor;
+ io_mode_t io_mode;
+ I2S_HandleTypeDef hi2s;
+ DMA_HandleTypeDef hdma_tx;
+ DMA_HandleTypeDef hdma_rx;
+ const dma_descr_t *dma_descr_tx;
+ const dma_descr_t *dma_descr_rx;
+} machine_i2s_obj_t;
+
+STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in);
+
+// The frame map is used with the readinto() method to transform the audio sample data coming
+// from DMA memory (32-bit stereo) to the format specified
+// in the I2S constructor. e.g. 16-bit mono
+STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYTES] = {
+ { 0, 1, -1, -1, -1, -1, -1, -1 }, // Mono, 16-bits
+ { 2, 3, 0, 1, -1, -1, -1, -1 }, // Mono, 32-bits
+ { 0, 1, 4, 5, -1, -1, -1, -1 }, // Stereo, 16-bits
+ { 2, 3, 0, 1, 6, 7, 4, 5 }, // Stereo, 32-bits
+};
+
+STATIC machine_i2s_obj_t *machine_i2s_obj[MAX_I2S_STM32];
+
+void machine_i2s_init0() {
+ for (uint8_t i = 0; i < MAX_I2S_STM32; i++) {
+ machine_i2s_obj[i] = NULL;
+ }
+}
+
+// Ring Buffer
+// Thread safe when used with these constraints:
+// - Single Producer, Single Consumer
+// - Sequential atomic operations
+// One byte of capacity is used to detect buffer empty/full
+
+STATIC void ringbuf_init(ring_buf_t *rbuf, uint8_t *buffer, size_t size) {
+ rbuf->buffer = buffer;
+ rbuf->size = size;
+ rbuf->head = 0;
+ rbuf->tail = 0;
+}
+
+STATIC bool ringbuf_push(ring_buf_t *rbuf, uint8_t data) {
+ size_t next_tail = (rbuf->tail + 1) % rbuf->size;
+
+ if (next_tail != rbuf->head) {
+ rbuf->buffer[rbuf->tail] = data;
+ rbuf->tail = next_tail;
+ return true;
+ }
+
+ // full
+ return false;
+}
+
+STATIC bool ringbuf_pop(ring_buf_t *rbuf, uint8_t *data) {
+ if (rbuf->head == rbuf->tail) {
+ // empty
+ return false;
+ }
+
+ *data = rbuf->buffer[rbuf->head];
+ rbuf->head = (rbuf->head + 1) % rbuf->size;
+ return true;
+}
+
+STATIC bool ringbuf_is_empty(ring_buf_t *rbuf) {
+ return rbuf->head == rbuf->tail;
+}
+
+STATIC bool ringbuf_is_full(ring_buf_t *rbuf) {
+ return ((rbuf->tail + 1) % rbuf->size) == rbuf->head;
+}
+
+STATIC size_t ringbuf_available_data(ring_buf_t *rbuf) {
+ return (rbuf->tail - rbuf->head + rbuf->size) % rbuf->size;
+}
+
+STATIC size_t ringbuf_available_space(ring_buf_t *rbuf) {
+ return rbuf->size - ringbuf_available_data(rbuf) - 1;
+}
+
+// For 32-bit audio samples, the STM32 HAL API expects each 32-bit sample to be encoded
+// in an unusual byte ordering: Byte_2, Byte_3, Byte_0, Byte_1
+// where: Byte_0 is the least significant byte of the 32-bit sample
+//
+// The following function takes a buffer containing 32-bits sample values formatted as little endian
+// and performs an in-place modification into the STM32 HAL API convention
+//
+// Example:
+//
+// wav_samples[] = [L_0-7, L_8-15, L_16-23, L_24-31, R_0-7, R_8-15, R_16-23, R_24-31] = [Left channel, Right channel]
+// stm_api[] = [L_16-23, L_24-31, L_0-7, L_8-15, R_16-23, R_24-31, R_0-7, R_8-15] = [Left channel, Right channel]
+//
+// where:
+// L_0-7 is the least significant byte of the 32 bit sample in the Left channel
+// L_24-31 is the most significant byte of the 32 bit sample in the Left channel
+//
+// wav_samples[] = [0x99, 0xBB, 0x11, 0x22, 0x44, 0x55, 0xAB, 0x77] = [Left channel, Right channel]
+// stm_api[] = [0x11, 0x22, 0x99, 0xBB, 0xAB, 0x77, 0x44, 0x55] = [Left channel, Right channel]
+//
+// where:
+// LEFT Channel = 0x99, 0xBB, 0x11, 0x22
+// RIGHT Channel = 0x44, 0x55, 0xAB, 0x77
+STATIC void reformat_32_bit_samples(int32_t *sample, uint32_t num_samples) {
+ int16_t sample_ms;
+ int16_t sample_ls;
+ for (uint32_t i = 0; i < num_samples; i++) {
+ sample_ls = sample[i] & 0xFFFF;
+ sample_ms = sample[i] >> 16;
+ sample[i] = (sample_ls << 16) + sample_ms;
+ }
+}
+
+STATIC int8_t get_frame_mapping_index(int8_t bits, format_t format) {
+ if (format == MONO) {
+ if (bits == 16) {
+ return 0;
+ } else { // 32 bits
+ return 1;
+ }
+ } else { // STEREO
+ if (bits == 16) {
+ return 2;
+ } else { // 32 bits
+ return 3;
+ }
+ }
+}
+
+STATIC int8_t get_dma_bits(uint16_t mode, int8_t bits) {
+ if (mode == I2S_MODE_MASTER_TX) {
+ if (bits == 16) {
+ return I2S_DATAFORMAT_16B;
+ } else {
+ return I2S_DATAFORMAT_32B;
+ }
+ return bits;
+ } else { // Master Rx
+ // always read 32 bit words for I2S e.g. I2S MEMS microphones
+ return I2S_DATAFORMAT_32B;
+ }
+}
+
+STATIC uint32_t fill_appbuf_from_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
+
+ // copy audio samples from the ring buffer to the app buffer
+ // loop, copying samples until the app buffer is filled
+ // For uasyncio mode, the loop will make an early exit if the ring buffer becomes empty
+ // Example:
+ // a MicroPython I2S object is configured for 16-bit mono (2 bytes per audio sample).
+ // For every frame coming from the ring buffer (8 bytes), 2 bytes are "cherry picked" and
+ // copied to the supplied app buffer.
+ // Thus, for every 1 byte copied to the app buffer, 4 bytes are read from the ring buffer.
+ // If a 8kB app buffer is supplied, 32kB of audio samples is read from the ring buffer.
+
+ uint32_t num_bytes_copied_to_appbuf = 0;
+ uint8_t *app_p = (uint8_t *)appbuf->buf;
+ uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1);
+ uint32_t num_bytes_needed_from_ringbuf = appbuf->len * (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes);
+ uint8_t discard_byte;
+ while (num_bytes_needed_from_ringbuf) {
+
+ uint8_t f_index = get_frame_mapping_index(self->bits, self->format);
+
+ for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) {
+ int8_t r_to_a_mapping = i2s_frame_map[f_index][i];
+ if (r_to_a_mapping != -1) {
+ if (self->io_mode == BLOCKING) {
+ // poll the ringbuf until a sample becomes available, copy into appbuf using the mapping transform
+ while (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) {
+ ;
+ }
+ num_bytes_copied_to_appbuf++;
+ } else if (self->io_mode == UASYNCIO) {
+ if (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) {
+ // ring buffer is empty, exit
+ goto exit;
+ } else {
+ num_bytes_copied_to_appbuf++;
+ }
+ } else {
+ return 0; // should never get here (non-blocking mode does not use this function)
+ }
+ } else { // r_a_mapping == -1
+ // discard unused byte from ring buffer
+ if (self->io_mode == BLOCKING) {
+ // poll the ringbuf until a sample becomes available
+ while (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) {
+ ;
+ }
+ } else if (self->io_mode == UASYNCIO) {
+ if (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) {
+ // ring buffer is empty, exit
+ goto exit;
+ }
+ } else {
+ return 0; // should never get here (non-blocking mode does not use this function)
+ }
+ }
+ num_bytes_needed_from_ringbuf--;
+ }
+ app_p += appbuf_sample_size_in_bytes;
+ }
+exit:
+ return num_bytes_copied_to_appbuf;
+}
+
+// function is used in IRQ context
+STATIC void fill_appbuf_from_ringbuf_non_blocking(machine_i2s_obj_t *self) {
+
+ // attempt to copy a block of audio samples from the ring buffer to the supplied app buffer.
+ // audio samples will be formatted as part of the copy operation
+
+ uint32_t num_bytes_copied_to_appbuf = 0;
+ uint8_t *app_p = &(((uint8_t *)self->non_blocking_descriptor.appbuf.buf)[self->non_blocking_descriptor.index]);
+
+ uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1);
+ uint32_t num_bytes_remaining_to_copy_to_appbuf = self->non_blocking_descriptor.appbuf.len - self->non_blocking_descriptor.index;
+ uint32_t num_bytes_remaining_to_copy_from_ring_buffer = num_bytes_remaining_to_copy_to_appbuf *
+ (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes);
+ uint32_t num_bytes_needed_from_ringbuf = MIN(SIZEOF_NON_BLOCKING_COPY_IN_BYTES, num_bytes_remaining_to_copy_from_ring_buffer);
+ uint8_t discard_byte;
+ if (ringbuf_available_data(&self->ring_buffer) >= num_bytes_needed_from_ringbuf) {
+ while (num_bytes_needed_from_ringbuf) {
+
+ uint8_t f_index = get_frame_mapping_index(self->bits, self->format);
+
+ for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) {
+ int8_t r_to_a_mapping = i2s_frame_map[f_index][i];
+ if (r_to_a_mapping != -1) {
+ ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping);
+ num_bytes_copied_to_appbuf++;
+ } else { // r_a_mapping == -1
+ // discard unused byte from ring buffer
+ ringbuf_pop(&self->ring_buffer, &discard_byte);
+ }
+ num_bytes_needed_from_ringbuf--;
+ }
+ app_p += appbuf_sample_size_in_bytes;
+ }
+ self->non_blocking_descriptor.index += num_bytes_copied_to_appbuf;
+
+ if (self->non_blocking_descriptor.index >= self->non_blocking_descriptor.appbuf.len) {
+ self->non_blocking_descriptor.copy_in_progress = false;
+ mp_sched_schedule(self->callback_for_non_blocking, MP_OBJ_FROM_PTR(self));
+ }
+ }
+}
+
+STATIC uint32_t copy_appbuf_to_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
+
+ // copy audio samples from the app buffer to the ring buffer
+ // loop, reading samples until the app buffer is emptied
+ // for uasyncio mode, the loop will make an early exit if the ring buffer becomes full
+
+ uint32_t a_index = 0;
+
+ while (a_index < appbuf->len) {
+ if (self->io_mode == BLOCKING) {
+ // copy a byte to the ringbuf when space becomes available
+ while (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) {
+ ;
+ }
+ a_index++;
+ } else if (self->io_mode == UASYNCIO) {
+ if (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) {
+ // ring buffer is full, exit
+ break;
+ } else {
+ a_index++;
+ }
+ } else {
+ return 0; // should never get here (non-blocking mode does not use this function)
+ }
+ }
+
+ return a_index;
+}
+
+// function is used in IRQ context
+STATIC void copy_appbuf_to_ringbuf_non_blocking(machine_i2s_obj_t *self) {
+
+ // copy audio samples from app buffer into ring buffer
+ uint32_t num_bytes_remaining_to_copy = self->non_blocking_descriptor.appbuf.len - self->non_blocking_descriptor.index;
+ uint32_t num_bytes_to_copy = MIN(SIZEOF_NON_BLOCKING_COPY_IN_BYTES, num_bytes_remaining_to_copy);
+
+ if (ringbuf_available_space(&self->ring_buffer) >= num_bytes_to_copy) {
+ for (uint32_t i = 0; i < num_bytes_to_copy; i++) {
+ ringbuf_push(&self->ring_buffer,
+ ((uint8_t *)self->non_blocking_descriptor.appbuf.buf)[self->non_blocking_descriptor.index + i]);
+ }
+
+ self->non_blocking_descriptor.index += num_bytes_to_copy;
+ if (self->non_blocking_descriptor.index >= self->non_blocking_descriptor.appbuf.len) {
+ self->non_blocking_descriptor.copy_in_progress = false;
+ mp_sched_schedule(self->callback_for_non_blocking, MP_OBJ_FROM_PTR(self));
+ }
+ }
+}
+
+// function is used in IRQ context
+STATIC void empty_dma(machine_i2s_obj_t *self, ping_pong_t dma_ping_pong) {
+ uint16_t dma_buffer_offset = 0;
+
+ if (dma_ping_pong == TOP_HALF) {
+ dma_buffer_offset = 0;
+ } else { // BOTTOM_HALF
+ dma_buffer_offset = SIZEOF_HALF_DMA_BUFFER_IN_BYTES;
+ }
+
+ uint8_t *dma_buffer_p = &self->dma_buffer_dcache_aligned[dma_buffer_offset];
+
+ // flush and invalidate cache so the CPU reads data placed into RAM by DMA
+ MP_HAL_CLEANINVALIDATE_DCACHE(dma_buffer_p, SIZEOF_HALF_DMA_BUFFER_IN_BYTES);
+
+ // when space exists, copy samples into ring buffer
+ if (ringbuf_available_space(&self->ring_buffer) >= SIZEOF_HALF_DMA_BUFFER_IN_BYTES) {
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES; i++) {
+ ringbuf_push(&self->ring_buffer, dma_buffer_p[i]);
+ }
+ }
+}
+
+// function is used in IRQ context
+STATIC void feed_dma(machine_i2s_obj_t *self, ping_pong_t dma_ping_pong) {
+ uint16_t dma_buffer_offset = 0;
+
+ if (dma_ping_pong == TOP_HALF) {
+ dma_buffer_offset = 0;
+ } else { // BOTTOM_HALF
+ dma_buffer_offset = SIZEOF_HALF_DMA_BUFFER_IN_BYTES;
+ }
+
+ uint8_t *dma_buffer_p = &self->dma_buffer_dcache_aligned[dma_buffer_offset];
+
+ // when data exists, copy samples from ring buffer
+ if (ringbuf_available_data(&self->ring_buffer) >= SIZEOF_HALF_DMA_BUFFER_IN_BYTES) {
+
+ // copy a block of samples from the ring buffer to the dma buffer.
+ // STM32 HAL API has a stereo I2S implementation, but not mono
+ // mono format is implemented by duplicating each sample into both L and R channels.
+ if ((self->format == MONO) && (self->bits == 16)) {
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES / 4; i++) {
+ for (uint8_t b = 0; b < sizeof(uint16_t); b++) {
+ ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 4 + b]);
+ dma_buffer_p[i * 4 + b + 2] = dma_buffer_p[i * 4 + b]; // duplicated mono sample
+ }
+ }
+ } else if ((self->format == MONO) && (self->bits == 32)) {
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES / 8; i++) {
+ for (uint8_t b = 0; b < sizeof(uint32_t); b++) {
+ ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 8 + b]);
+ dma_buffer_p[i * 8 + b + 4] = dma_buffer_p[i * 8 + b]; // duplicated mono sample
+ }
+ }
+ } else { // STEREO, both 16-bit and 32-bit
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES; i++) {
+ ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i]);
+ }
+ }
+
+ // reformat 32 bit samples to match STM32 HAL API format
+ if (self->bits == 32) {
+ reformat_32_bit_samples((int32_t *)dma_buffer_p, SIZEOF_HALF_DMA_BUFFER_IN_BYTES / (sizeof(uint32_t)));
+ }
+ }
+
+ // flush cache to RAM so DMA can read the sample data
+ MP_HAL_CLEAN_DCACHE(dma_buffer_p, SIZEOF_HALF_DMA_BUFFER_IN_BYTES);
+}
+
+STATIC bool i2s_init(machine_i2s_obj_t *self) {
+
+ // init the GPIO lines
+ GPIO_InitTypeDef GPIO_InitStructure;
+ GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
+ GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
+ GPIO_InitStructure.Pull = GPIO_PULLUP;
+
+ if (self->i2s_id == 1) {
+ self->hi2s.Instance = I2S1;
+ __SPI1_CLK_ENABLE();
+ // configure DMA streams
+ if (self->mode == I2S_MODE_MASTER_RX) {
+ self->dma_descr_rx = &dma_I2S_1_RX;
+ } else {
+ self->dma_descr_tx = &dma_I2S_1_TX;
+ }
+ } else if (self->i2s_id == 2) {
+ self->hi2s.Instance = I2S2;
+ __SPI2_CLK_ENABLE();
+ // configure DMA streams
+ if (self->mode == I2S_MODE_MASTER_RX) {
+ self->dma_descr_rx = &dma_I2S_2_RX;
+ } else {
+ self->dma_descr_tx = &dma_I2S_2_TX;
+ }
+ } else {
+ // invalid id number; should not get here as i2s object should not
+ // have been created without setting a valid i2s instance number
+ return false;
+ }
+
+ // GPIO Pin initialization
+ if (self->sck != MP_OBJ_TO_PTR(MP_OBJ_NULL)) {
+ GPIO_InitStructure.Pin = self->sck->pin_mask;
+ const pin_af_obj_t *af = pin_find_af(self->sck, AF_FN_I2S, self->i2s_id);
+ GPIO_InitStructure.Alternate = (uint8_t)af->idx;
+ HAL_GPIO_Init(self->sck->gpio, &GPIO_InitStructure);
+ }
+
+ if (self->ws != MP_OBJ_TO_PTR(MP_OBJ_NULL)) {
+ GPIO_InitStructure.Pin = self->ws->pin_mask;
+ const pin_af_obj_t *af = pin_find_af(self->ws, AF_FN_I2S, self->i2s_id);
+ GPIO_InitStructure.Alternate = (uint8_t)af->idx;
+ HAL_GPIO_Init(self->ws->gpio, &GPIO_InitStructure);
+ }
+
+ if (self->sd != MP_OBJ_TO_PTR(MP_OBJ_NULL)) {
+ GPIO_InitStructure.Pin = self->sd->pin_mask;
+ const pin_af_obj_t *af = pin_find_af(self->sd, AF_FN_I2S, self->i2s_id);
+ GPIO_InitStructure.Alternate = (uint8_t)af->idx;
+ HAL_GPIO_Init(self->sd->gpio, &GPIO_InitStructure);
+ }
+
+ if (HAL_I2S_Init(&self->hi2s) == HAL_OK) {
+ // Reset and initialize Tx and Rx DMA channels
+ if (self->mode == I2S_MODE_MASTER_RX) {
+ dma_invalidate_channel(self->dma_descr_rx);
+ dma_init(&self->hdma_rx, self->dma_descr_rx, DMA_PERIPH_TO_MEMORY, &self->hi2s);
+ self->hi2s.hdmarx = &self->hdma_rx;
+ } else { // I2S_MODE_MASTER_TX
+ dma_invalidate_channel(self->dma_descr_tx);
+ dma_init(&self->hdma_tx, self->dma_descr_tx, DMA_MEMORY_TO_PERIPH, &self->hi2s);
+ self->hi2s.hdmatx = &self->hdma_tx;
+ }
+
+ __HAL_RCC_PLLI2S_ENABLE(); // start I2S clock
+
+ return true;
+ } else {
+ return false;
+ }
+
+}
+
+void HAL_I2S_ErrorCallback(I2S_HandleTypeDef *hi2s) {
+ uint32_t errorCode = HAL_I2S_GetError(hi2s);
+ printf("I2S Error = %ld\n", errorCode);
+}
+
+void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s) {
+ machine_i2s_obj_t *self;
+ if (hi2s->Instance == I2S1) {
+ self = machine_i2s_obj[0];
+ } else {
+ self = machine_i2s_obj[1];
+ }
+
+ // bottom half of buffer now filled,
+ // safe to empty the bottom half while the top half of buffer is being filled
+ empty_dma(self, BOTTOM_HALF);
+
+ // for non-blocking operation, this IRQ-based callback handles
+ // the readinto() method requests.
+ if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
+ fill_appbuf_from_ringbuf_non_blocking(self);
+ }
+}
+
+void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
+ machine_i2s_obj_t *self;
+ if (hi2s->Instance == I2S1) {
+ self = machine_i2s_obj[0];
+ } else {
+ self = machine_i2s_obj[1];
+ }
+
+ // top half of buffer now filled,
+ // safe to empty the top half while the bottom half of buffer is being filled
+ empty_dma(self, TOP_HALF);
+
+ // for non-blocking operation, this IRQ-based callback handles
+ // the readinto() method requests.
+ if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
+ fill_appbuf_from_ringbuf_non_blocking(self);
+ }
+}
+
+void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) {
+ machine_i2s_obj_t *self;
+
+ if (hi2s->Instance == I2S1) {
+ self = machine_i2s_obj[0];
+ } else {
+ self = machine_i2s_obj[1];
+ }
+
+ // for non-blocking operation, this IRQ-based callback handles
+ // the write() method requests.
+ if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
+ copy_appbuf_to_ringbuf_non_blocking(self);
+ }
+
+ // bottom half of buffer now emptied,
+ // safe to fill the bottom half while the top half of buffer is being emptied
+ feed_dma(self, BOTTOM_HALF);
+}
+
+void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
+ machine_i2s_obj_t *self;
+ if (hi2s->Instance == I2S1) {
+ self = machine_i2s_obj[0];
+ } else {
+ self = machine_i2s_obj[1];
+ }
+
+ // for non-blocking operation, this IRQ-based callback handles
+ // the write() method requests.
+ if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
+ copy_appbuf_to_ringbuf_non_blocking(self);
+ }
+
+ // top half of buffer now emptied,
+ // safe to fill the top half while the bottom half of buffer is being emptied
+ feed_dma(self, TOP_HALF);
+}
+
+STATIC void machine_i2s_init_helper(machine_i2s_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+
+ enum {
+ ARG_sck,
+ ARG_ws,
+ ARG_sd,
+ ARG_mode,
+ ARG_bits,
+ ARG_format,
+ ARG_rate,
+ ARG_ibuf,
+ };
+
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_sd, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_format, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_ibuf, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ };
+
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ memset(&self->hi2s, 0, sizeof(self->hi2s));
+
+ //
+ // ---- Check validity of arguments ----
+ //
+
+ // are I2S pin assignments valid?
+ const pin_af_obj_t *pin_af;
+
+ // is SCK valid?
+ if (mp_obj_is_type(args[ARG_sck].u_obj, &pin_type)) {
+ pin_af = pin_find_af(MP_OBJ_TO_PTR(args[ARG_sck].u_obj), AF_FN_I2S, self->i2s_id);
+ if (pin_af->type != AF_PIN_TYPE_I2S_CK) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid SCK pin"));
+ }
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("SCK not a Pin type"));
+ }
+
+ // is WS valid?
+ if (mp_obj_is_type(args[ARG_ws].u_obj, &pin_type)) {
+ pin_af = pin_find_af(MP_OBJ_TO_PTR(args[ARG_ws].u_obj), AF_FN_I2S, self->i2s_id);
+ if (pin_af->type != AF_PIN_TYPE_I2S_WS) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid WS pin"));
+ }
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("WS not a Pin type"));
+ }
+
+ // is SD valid?
+ if (mp_obj_is_type(args[ARG_sd].u_obj, &pin_type)) {
+ pin_af = pin_find_af(MP_OBJ_TO_PTR(args[ARG_sd].u_obj), AF_FN_I2S, self->i2s_id);
+ if (pin_af->type != AF_PIN_TYPE_I2S_SD) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid SD pin"));
+ }
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("SD not a Pin type"));
+ }
+
+ // is Mode valid?
+ uint16_t i2s_mode = args[ARG_mode].u_int;
+ if ((i2s_mode != (I2S_MODE_MASTER_RX)) &&
+ (i2s_mode != (I2S_MODE_MASTER_TX))) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid mode"));
+ }
+
+ // is Bits valid?
+ int8_t i2s_bits = args[ARG_bits].u_int;
+ if ((i2s_bits != 16) &&
+ (i2s_bits != 32)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
+ }
+
+ // is Format valid?
+ format_t i2s_format = args[ARG_format].u_int;
+ if ((i2s_format != MONO) &&
+ (i2s_format != STEREO)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid format"));
+ }
+
+ // is Rate valid?
+ // Not checked
+
+ // is Ibuf valid?
+ int32_t ring_buffer_len = args[ARG_ibuf].u_int;
+ if (ring_buffer_len > 0) {
+ uint8_t *buffer = m_new(uint8_t, ring_buffer_len);
+ self->ring_buffer_storage = buffer;
+ ringbuf_init(&self->ring_buffer, buffer, ring_buffer_len);
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid ibuf"));
+ }
+
+ self->sck = MP_OBJ_TO_PTR(args[ARG_sck].u_obj);
+ self->ws = MP_OBJ_TO_PTR(args[ARG_ws].u_obj);
+ self->sd = MP_OBJ_TO_PTR(args[ARG_sd].u_obj);
+ self->mode = i2s_mode;
+ self->bits = i2s_bits;
+ self->format = i2s_format;
+ self->rate = args[ARG_rate].u_int;
+ self->ibuf = ring_buffer_len;
+ self->callback_for_non_blocking = MP_OBJ_NULL;
+ self->non_blocking_descriptor.copy_in_progress = false;
+ self->io_mode = BLOCKING;
+
+ I2S_InitTypeDef *init = &self->hi2s.Init;
+ init->Mode = i2s_mode;
+ init->Standard = I2S_STANDARD_PHILIPS;
+ init->DataFormat = get_dma_bits(self->mode, self->bits);
+ init->MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
+ init->AudioFreq = args[ARG_rate].u_int;
+ init->CPOL = I2S_CPOL_LOW;
+ init->ClockSource = I2S_CLOCK_PLL;
+
+ // init the I2S bus
+ if (!i2s_init(self)) {
+ mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("I2S init failed"));
+ }
+
+ // start DMA. DMA is configured to run continuously, using a circular buffer configuration
+ uint32_t number_of_samples = 0;
+ if (init->DataFormat == I2S_DATAFORMAT_16B) {
+ number_of_samples = SIZEOF_DMA_BUFFER_IN_BYTES / sizeof(uint16_t);
+ } else { // 32 bits
+ number_of_samples = SIZEOF_DMA_BUFFER_IN_BYTES / sizeof(uint32_t);
+ }
+
+ HAL_StatusTypeDef status;
+ if (self->mode == I2S_MODE_MASTER_TX) {
+ status = HAL_I2S_Transmit_DMA(&self->hi2s, (void *)self->dma_buffer_dcache_aligned, number_of_samples);
+ } else { // RX
+ status = HAL_I2S_Receive_DMA(&self->hi2s, (void *)self->dma_buffer_dcache_aligned, number_of_samples);
+ }
+
+ if (status != HAL_OK) {
+ mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("DMA init failed"));
+ }
+}
+
+STATIC void machine_i2s_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_printf(print, "I2S(id=%u,\n"
+ "sck="MP_HAL_PIN_FMT ",\n"
+ "ws="MP_HAL_PIN_FMT ",\n"
+ "sd="MP_HAL_PIN_FMT ",\n"
+ "mode=%u,\n"
+ "bits=%u, format=%u,\n"
+ "rate=%d, ibuf=%d)",
+ self->i2s_id,
+ mp_hal_pin_name(self->sck),
+ mp_hal_pin_name(self->ws),
+ mp_hal_pin_name(self->sd),
+ self->mode,
+ self->bits, self->format,
+ self->rate, self->ibuf
+ );
+}
+
+STATIC mp_obj_t machine_i2s_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) {
+ mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true);
+ uint8_t i2s_id = mp_obj_get_int(args[0]);
+ uint8_t i2s_id_zero_base = 0;
+
+ if (0) {
+ #ifdef MICROPY_HW_I2S1
+ } else if (i2s_id == 1) {
+ i2s_id_zero_base = 0;
+ #endif
+ #ifdef MICROPY_HW_I2S2
+ } else if (i2s_id == 2) {
+ i2s_id_zero_base = 1;
+ #endif
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid id"));
+ }
+
+ machine_i2s_obj_t *self;
+ if (machine_i2s_obj[i2s_id_zero_base] == NULL) {
+ self = m_new_obj(machine_i2s_obj_t);
+ machine_i2s_obj[i2s_id_zero_base] = self;
+ self->base.type = &machine_i2s_type;
+ self->i2s_id = i2s_id;
+ } else {
+ self = machine_i2s_obj[i2s_id_zero_base];
+ machine_i2s_deinit(MP_OBJ_FROM_PTR(self));
+ }
+
+ // align DMA buffer start to the cache line size (32 bytes)
+ self->dma_buffer_dcache_aligned = (uint8_t *)((uint32_t)(self->dma_buffer + 0x1f) & ~0x1f);
+
+ mp_map_t kw_args;
+ mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args);
+ machine_i2s_init_helper(self, n_pos_args - 1, args + 1, &kw_args);
+
+ return MP_OBJ_FROM_PTR(self);
+}
+
+STATIC mp_obj_t machine_i2s_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
+ machine_i2s_deinit(MP_OBJ_FROM_PTR(self));
+ machine_i2s_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_init_obj, 1, machine_i2s_init);
+
+STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in) {
+
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ dma_deinit(self->dma_descr_tx);
+ dma_deinit(self->dma_descr_rx);
+ HAL_I2S_DeInit(&self->hi2s);
+
+ if (self->hi2s.Instance == I2S1) {
+ __SPI1_FORCE_RESET();
+ __SPI1_RELEASE_RESET();
+ __SPI1_CLK_DISABLE();
+ } else if (self->hi2s.Instance == I2S2) {
+ __SPI2_FORCE_RESET();
+ __SPI2_RELEASE_RESET();
+ __SPI2_CLK_DISABLE();
+ }
+
+ m_free(self->ring_buffer_storage);
+
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_i2s_deinit_obj, machine_i2s_deinit);
+
+STATIC mp_obj_t machine_i2s_irq(mp_obj_t self_in, mp_obj_t handler) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ if (handler != mp_const_none && !mp_obj_is_callable(handler)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid callback"));
+ }
+
+ if (handler != mp_const_none) {
+ self->io_mode = NON_BLOCKING;
+ } else {
+ self->io_mode = BLOCKING;
+ }
+
+ self->callback_for_non_blocking = handler;
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_irq_obj, machine_i2s_irq);
+
+// Shift() is typically used as a volume control.
+// shift=1 increases volume by 6dB, shift=-1 decreases volume by 6dB
+STATIC mp_obj_t machine_i2s_shift(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ enum { ARG_buf, ARG_bits, ARG_shift};
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_bits, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_shift, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
+ };
+
+ // parse args
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ mp_buffer_info_t bufinfo;
+ mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_RW);
+
+ int16_t *buf_16 = bufinfo.buf;
+ int32_t *buf_32 = bufinfo.buf;
+
+ uint8_t bits = args[ARG_bits].u_int;
+ int8_t shift = args[ARG_shift].u_int;
+
+ uint32_t num_audio_samples;
+ switch (bits) {
+ case 16:
+ num_audio_samples = bufinfo.len / sizeof(uint16_t);
+ break;
+
+ case 32:
+ num_audio_samples = bufinfo.len / sizeof(uint32_t);
+ break;
+
+ default:
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
+ break;
+ }
+
+ for (uint32_t i = 0; i < num_audio_samples; i++) {
+ switch (bits) {
+ case 16:
+ if (shift >= 0) {
+ buf_16[i] = buf_16[i] << shift;
+ } else {
+ buf_16[i] = buf_16[i] >> abs(shift);
+ }
+ break;
+ case 32:
+ if (shift >= 0) {
+ buf_32[i] = buf_32[i] << shift;
+ } else {
+ buf_32[i] = buf_32[i] >> abs(shift);
+ }
+ break;
+ }
+ }
+
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_shift_fun_obj, 0, machine_i2s_shift);
+STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(machine_i2s_shift_obj, MP_ROM_PTR(&machine_i2s_shift_fun_obj));
+
+STATIC const mp_rom_map_elem_t machine_i2s_locals_dict_table[] = {
+ // Methods
+ { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2s_init_obj) },
+ { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
+ { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
+ { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2s_deinit_obj) },
+ { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_i2s_irq_obj) },
+
+ // Static method
+ { MP_ROM_QSTR(MP_QSTR_shift), MP_ROM_PTR(&machine_i2s_shift_obj) },
+
+ // Constants
+ { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_INT(I2S_MODE_MASTER_RX) },
+ { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_INT(I2S_MODE_MASTER_TX) },
+ { MP_ROM_QSTR(MP_QSTR_STEREO), MP_ROM_INT(STEREO) },
+ { MP_ROM_QSTR(MP_QSTR_MONO), MP_ROM_INT(MONO) },
+};
+MP_DEFINE_CONST_DICT(machine_i2s_locals_dict, machine_i2s_locals_dict_table);
+
+STATIC mp_uint_t machine_i2s_stream_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ if (self->mode != I2S_MODE_MASTER_RX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1);
+ if (size % appbuf_sample_size_in_bytes != 0) {
+ *errcode = MP_EINVAL;
+ return MP_STREAM_ERROR;
+ }
+
+ if (size == 0) {
+ return 0;
+ }
+
+ if (self->io_mode == NON_BLOCKING) {
+ self->non_blocking_descriptor.appbuf.buf = (void *)buf_in;
+ self->non_blocking_descriptor.appbuf.len = size;
+ self->non_blocking_descriptor.index = 0;
+ self->non_blocking_descriptor.copy_in_progress = true;
+ return size;
+ } else { // blocking or uasyncio mode
+ mp_buffer_info_t appbuf;
+ appbuf.buf = (void *)buf_in;
+ appbuf.len = size;
+ uint32_t num_bytes_read = fill_appbuf_from_ringbuf(self, &appbuf);
+ return num_bytes_read;
+ }
+}
+
+STATIC mp_uint_t machine_i2s_stream_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ if (self->mode != I2S_MODE_MASTER_TX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ if (size == 0) {
+ return 0;
+ }
+
+ if (self->io_mode == NON_BLOCKING) {
+ self->non_blocking_descriptor.appbuf.buf = (void *)buf_in;
+ self->non_blocking_descriptor.appbuf.len = size;
+ self->non_blocking_descriptor.index = 0;
+ self->non_blocking_descriptor.copy_in_progress = true;
+ return size;
+ } else { // blocking or uasyncio mode
+ mp_buffer_info_t appbuf;
+ appbuf.buf = (void *)buf_in;
+ appbuf.len = size;
+ uint32_t num_bytes_written = copy_appbuf_to_ringbuf(self, &appbuf);
+ return num_bytes_written;
+ }
+}
+
+STATIC mp_uint_t machine_i2s_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_uint_t ret;
+ uintptr_t flags = arg;
+ self->io_mode = UASYNCIO; // a call to ioctl() is an indication that uasyncio is being used
+
+ if (request == MP_STREAM_POLL) {
+ ret = 0;
+
+ if (flags & MP_STREAM_POLL_RD) {
+ if (self->mode != I2S_MODE_MASTER_RX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ if (!ringbuf_is_empty(&self->ring_buffer)) {
+ ret |= MP_STREAM_POLL_RD;
+ }
+ }
+
+ if (flags & MP_STREAM_POLL_WR) {
+ if (self->mode != I2S_MODE_MASTER_TX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ if (!ringbuf_is_full(&self->ring_buffer)) {
+ ret |= MP_STREAM_POLL_WR;
+ }
+ }
+ } else {
+ *errcode = MP_EINVAL;
+ ret = MP_STREAM_ERROR;
+ }
+
+ return ret;
+}
+
+STATIC const mp_stream_p_t i2s_stream_p = {
+ .read = machine_i2s_stream_read,
+ .write = machine_i2s_stream_write,
+ .ioctl = machine_i2s_ioctl,
+ .is_text = false,
+};
+
+const mp_obj_type_t machine_i2s_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_I2S,
+ .print = machine_i2s_print,
+ .getiter = mp_identity_getiter,
+ .iternext = mp_stream_unbuffered_iter,
+ .protocol = &i2s_stream_p,
+ .make_new = machine_i2s_make_new,
+ .locals_dict = (mp_obj_dict_t *)&machine_i2s_locals_dict,
+};
+
+#endif // MICROPY_HW_ENABLE_I2S
+
diff --git a/ports/stm32/main.c b/ports/stm32/main.c
index 6df7374cc..86b8fb797 100644
--- a/ports/stm32/main.c
+++ b/ports/stm32/main.c
@@ -527,6 +527,10 @@ soft_reset:
pyb_usb_init0();
#endif
+ #if MICROPY_HW_ENABLE_I2S
+ machine_i2s_init0();
+ #endif
+
// Initialise the local flash filesystem.
// Create it if needed, mount in on /flash, and set it as current dir.
bool mounted_flash = false;
diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c
index aee563ee8..66c696982 100644
--- a/ports/stm32/modmachine.c
+++ b/ports/stm32/modmachine.c
@@ -431,6 +431,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) },
{ MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) },
#endif
+ #if MICROPY_HW_ENABLE_I2S
+ { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) },
+ #endif
{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) },
{ MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&pyb_wdt_type) },
{ MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) },
diff --git a/ports/stm32/modmachine.h b/ports/stm32/modmachine.h
index e84620854..0e6c000a8 100644
--- a/ports/stm32/modmachine.h
+++ b/ports/stm32/modmachine.h
@@ -31,9 +31,11 @@
extern const mp_obj_type_t machine_adc_type;
extern const mp_obj_type_t machine_timer_type;
extern const mp_obj_type_t machine_hard_i2c_type;
+extern const mp_obj_type_t machine_i2s_type;
void machine_init(void);
void machine_deinit(void);
+void machine_i2s_init0();
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_info_obj);
MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj);
diff --git a/ports/stm32/pin_defs_stm32.h b/ports/stm32/pin_defs_stm32.h
index 9285c190a..d90ef1bb6 100644
--- a/ports/stm32/pin_defs_stm32.h
+++ b/ports/stm32/pin_defs_stm32.h
@@ -109,6 +109,7 @@ enum {
// some #defines to massage things. Also I2S and SPI share the same
// peripheral.
+#define GPIO_AF5_I2S1 GPIO_AF5_SPI1
#define GPIO_AF5_I2S2 GPIO_AF5_SPI2
#define GPIO_AF5_I2S3 GPIO_AF5_I2S3ext
#define GPIO_AF6_I2S2 GPIO_AF6_I2S2ext
@@ -116,6 +117,7 @@ enum {
#define GPIO_AF7_I2S2 GPIO_AF7_SPI2
#define GPIO_AF7_I2S3 GPIO_AF7_I2S3ext
+#define I2S1 SPI1
#define I2S2 SPI2
#define I2S3 SPI3