From 8a5bfe44a5b686b479ef0638e25fe968099f2a28 Mon Sep 17 00:00:00 2001 From: Mike Teachman Date: Fri, 16 Apr 2021 21:27:40 -0700 Subject: [PATCH] esp32,stm32: Add new machine.I2S class for I2S protocol support. This commit adds I2S protocol support for the esp32 and stm32 ports, via a new machine.I2S class. It builds on the stm32 work of blmorris, #1361. Features include: - a consistent I2S API across the esp32 and stm32 ports - I2S configurations supported: - master transmit and master receive - 16-bit and 32-bit sample sizes - mono and stereo formats - sampling frequency - 3 modes of operation: - blocking - non-blocking with callback - uasyncio - internal ring buffer size can be tuned - documentation for Pyboards and esp32-based boards - tested on the following development boards: - Pyboard D SF2W - Pyboard V1.1 - ESP32 with SPIRAM - ESP32 Signed-off-by: Mike Teachman --- docs/esp32/quickref.rst | 18 + docs/library/machine.I2S.rst | 162 +++ docs/library/machine.rst | 1 + docs/pyboard/quickref.rst | 20 + ports/esp32/machine_i2s.c | 809 +++++++++++++ ports/esp32/main.c | 1 + ports/esp32/main/CMakeLists.txt | 1 + ports/esp32/modmachine.c | 1 + ports/esp32/modmachine.h | 2 + ports/stm32/Makefile | 13 + ports/stm32/boards/PYBD_SF2/mpconfigboard.h | 5 + ports/stm32/boards/PYBV10/mpconfigboard.h | 4 + ports/stm32/boards/PYBV11/mpconfigboard.h | 4 + ports/stm32/boards/make-pins.py | 2 +- ports/stm32/dma.c | 35 + ports/stm32/dma.h | 4 + ports/stm32/machine_i2s.c | 1126 +++++++++++++++++++ ports/stm32/main.c | 4 + ports/stm32/modmachine.c | 3 + ports/stm32/modmachine.h | 2 + ports/stm32/pin_defs_stm32.h | 2 + 21 files changed, 2218 insertions(+), 1 deletion(-) create mode 100644 docs/library/machine.I2S.rst create mode 100644 ports/esp32/machine_i2s.c create mode 100644 ports/stm32/machine_i2s.c diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 77c7027c40..0b825d5208 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 `. :: + + 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 0000000000..48f03b419d --- /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 0a1c1c9530..26f99680a0 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 3dbd093043..3ea3190999 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 `. +I2S bus +------- + +See :ref:`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 0000000000..b0d02e74f6 --- /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 +#include +#include +#include +#include + +#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 ff6dd69574..b04831fff1 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 d01656e567..fb08c2759e 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 bbe7fae033..c46f8ab8d7 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 3e99e11205..7bf03b0cab 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 e94e7d72f3..4ec03c1746 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 48b3ee3eb9..ed76b3f971 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 52382f44d5..50ef3ae269 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 8e84694048..aec83d1349 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 70213de6ac..d898832f09 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 de30886917..ba09dc1707 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 5a17276a49..8bd101a611 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 0000000000..d663767bf3 --- /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 +#include +#include +#include +#include + +#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 6df7374cce..86b8fb7972 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 aee563ee88..66c696982b 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 e846208540..0e6c000a80 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 9285c190a6..d90ef1bb6d 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