From d9c7fd3e239544f4f51bff1ebab802b823d142ac Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Mon, 10 Feb 2025 11:15:29 +0100 Subject: [PATCH] Split creation and configuration. --- lib/i2s.toit | 392 ++++++++---------- .../propagation/type_primitive_i2s.cc | 1 + src/primitive.h | 3 +- src/resources/i2s_esp32.cc | 260 ++++++------ tests/hw/esp32/i2s-shared.toit | 14 +- tests/hw/esp32/i2s-test.toit | 18 +- 6 files changed, 342 insertions(+), 346 deletions(-) diff --git a/lib/i2s.toit b/lib/i2s.toit index ef92b0eb3..5fefe81f7 100644 --- a/lib/i2s.toit +++ b/lib/i2s.toit @@ -59,14 +59,14 @@ main: // "channel" instead of "bus". channel := i2s.Bus --master=false - --no-start --tx=tx --sck=sck --ws=ws + + channel.configure --sample-rate=44100 --bits-per-sample=16 - // Since we didn't start the bus, we can preload data. start-index := 0 while true: preloaded := channel.preload SOME-DATA[start-index..] @@ -88,9 +88,6 @@ I2S Serial communication Bus, primarily used to emit sound but has a wide range of usages. */ class Bus: - i2s_ := ? - state_/ResourceState_ ::= ? - /** Philips format. @@ -306,194 +303,116 @@ class Bus: */ static SLOTS-MONO-RIGHT ::= 5 + i2s_ := ? + state_/ResourceState_ ::= ? + + mclk_/gpio.Pin? + sck_/gpio.Pin? + ws_/gpio.Pin? + tx_/gpio.Pin? + rx_/gpio.Pin? + + invert-mclk_/bool + invert-sck_/bool + invert-ws_/bool + + is-master/bool /** - Constructs an I2S channel as input. + Constructs an I2S channel. - For typical I2S setups, the $rx pin, a clock ($sck), and a word-select ($ws) + For typical I2S setups, the $rx/$tx pin, a clock ($sck), and a word-select ($ws) pins are required. The master clock ($mclk) is optional. If $master is true, then I2S peripheral runs as master. As master, the $sck, $ws, and $mclk pins are outputs. As slave, they are inputs. - The $sample-rate is the rate at which samples are written. - The $bits-per-sample is the width of each sample. It can be either 8, 16, 24, or 32. - For 8 and 24 bits see the note on the ESP32 below. - If a $mclk pin is provide, then the master clock is emitted/read from that pin. Some ESP variants have restrictions on which pins can be used as output. Some variants don't support the master clock as input. Note that the $mclk can be an output, even if the bus is in slave mode. - The $mclk-multiplier is the multiplier of the $sample-rate to be used for the - master clock. It should be one of the 128, 256, 384, 512, 576, 768, 1024, - or 1152. If none is given, it defaults to 384 for 24 bits per sample and - 256 otherwise. If the bits-per-sample is 24 bits, then the multiplier must - be a multiple of 3. - The $mclk-multiplier is mostly revelant if $mclk is provided, but can also be - used to allow slower sample-rates: a higher multiplier allows for a slower - frequency. - If the $mclk-external-frequency is set to a value and $mclk was provided, then - the master clock is read from the $mclk pin. This is only supported on some ESP32 - variants. The $mclk-external-frequency value must be higher than the clock - frequency (sample-rate * bits-per-sample * 2). - - If the $start flag is true (the default) then the bus is started immediately. - Set the flag to false if you want to $preload data before starting the bus. - - The $slots must be one of $SLOTS-STEREO-BOTH, $SLOTS-MONO-LEFT, $SLOTS-MONO-RIGHT. - one slot - the bus is in stereo mode. In stereo mode, - the left and right slots are interleaved in the data stream. The - - The $format must be one of $FORMAT-MSB, $FORMAT-PHILIPS, $FORMAT-PCM-SHORT. The $invert-sck, $invert-ws, $invert-mclk flags can be used to invert the signals. - - # Esp32 - On the ESP32, the buffer needs to be padded for 8 and 24 bits samples. That - is, for $bits-per-sample equal to 8 each sample should be 16 bits, where - only the highest 8 bits are used. For 24 bits, each sample should be 32 - bits, where only the highest 24 bits are used. The same is true when data - is read from the I2S bus. */ constructor --master/bool - --rx/gpio.Pin - --sck/gpio.Pin?=null - --ws/gpio.Pin?=null --mclk/gpio.Pin?=null - --invert-sck/bool=false - --invert-ws/bool=false + --ws/gpio.Pin? + --sck/gpio.Pin? + --tx/gpio.Pin?=null + --rx/gpio.Pin?=null --invert-mclk/bool=false - --mclk-external-frequency/int?=null - --sample-rate/int - --bits-per-sample/int - --mclk-multiplier/int?=null - --format/int=FORMAT-PHILIPS - --slots=SLOTS-STEREO-BOTH - --start/bool=true: - if slots != SLOTS-STEREO-BOTH and slots != SLOTS-MONO-LEFT and slots != SLOTS-MONO-RIGHT: + --invert-ws/bool=false + --invert-sck/bool=false: + if not tx and not rx: + // We could support this, but it is not clear what the use case would be. throw "INVALID_ARGUMENT" - return Bus.private_ - --master=master - --rx=rx - --tx=null - --sck=sck - --ws=ws - --mclk=mclk - --invert-sck=invert-sck - --invert-ws=invert-ws - --invert-mclk=invert-mclk - --mclk-external-frequency=mclk-external-frequency - --sample-rate=sample-rate - --bits-per-sample=bits-per-sample - --mclk-multiplier=mclk-multiplier - --format=format - --slots=slots - --start=start - /** - Variant of $(constructor --master --rx --sample-rate --bits-per-sample) - that creates a bus for output. + is-master = master - The $slots must be one of $SLOTS-STEREO-BOTH, $SLOTS-STEREO-LEFT, $SLOTS-STEREO-RIGHT, - $SLOTS-MONO-BOTH, $SLOTS-MONO-LEFT, or $SLOTS-MONO-RIGHT. - */ - constructor - --master/bool - --tx/gpio.Pin - --sck/gpio.Pin?=null - --ws/gpio.Pin?=null - --mclk/gpio.Pin?=null - --invert-sck/bool=false - --invert-ws/bool=false - --invert-mclk/bool=false - --mclk-external-frequency/int?=null - --sample-rate/int - --bits-per-sample/int - --mclk-multiplier/int?=null - --format/int=FORMAT-PHILIPS - --slots/int=SLOTS-STEREO-BOTH - --start/bool=true: - return Bus.private_ - --master=master - --rx=null - --tx=tx - --sck=sck - --ws=ws - --mclk=mclk - --invert-sck=invert-sck - --invert-ws=invert-ws - --invert-mclk=invert-mclk - --sample-rate=sample-rate - --bits-per-sample=bits-per-sample - --mclk-multiplier=mclk-multiplier - --mclk-external-frequency=mclk-external-frequency - --format=format - --slots=slots - --start=start + sck_ = sck + ws_ = ws + tx_ = tx + rx_ = rx + mclk_ = mclk + + invert-mclk_ = invert-mclk + invert-sck_ = invert-sck + invert-ws_ = invert-ws + + tx-pin := tx ? tx.num : -1 + rx-pin := rx ? rx.num : -1 + mclk-pin := mclk ? mclk.num : -1 + sck-pin := sck ? sck.num : -1 + ws-pin := ws ? ws.num : -1 + + i2s_ = i2s-create_ + resource-group_ + tx-pin + rx-pin + master + state_ = ResourceState_ resource-group_ i2s_ /** - Variant of $(constructor --master --rx --sample-rate --bits-per-sample) - that creates a bus for input and output. + Configures the channel. + + A channel can only be configured when it is not running ($start). + + The $sample-rate is the rate at which samples are written. + The $bits-per-sample is the width of each sample. It can be either 8, 16, 24, or 32. + For 8 and 24 bits see the note on the ESP32 below. + + The $mclk-multiplier is the multiplier of the $sample-rate to be used for the + master clock. It should be one of the 128, 256, 384, 512, 576, 768, 1024, + or 1152. If none is given, it defaults to 384 for 24 bits per sample and + 256 otherwise. If the bits-per-sample is 24 bits, then the multiplier must + be a multiple of 3. + The $mclk-multiplier is mostly revelant if a mclk pin was provided, but can + also be used to allow slower sample-rates: a higher multiplier allows for + a slower frequency. + If the $mclk-external-frequency is set to a value and a mclk pin was provided, then + the master clock is read from the mclk pin. This is only supported on some ESP32 + variants. The $mclk-external-frequency value must be higher than the clock + frequency (sample-rate * bits-per-sample * 2). - In this configuration the $sck, $ws, and $mclk pins are shared between the - input and output. + The $slots-in must be one of $SLOTS-STEREO-BOTH, $SLOTS-MONO-LEFT, + $SLOTS-MONO-RIGHT. + The $slots-out must be one of $SLOTS-STEREO-BOTH, $SLOTS-STEREO-LEFT + (data is stereo, but only emit the left channel), + $SLOTS-STEREO-RIGHT, $SLOTS-MONO-BOTH (data is mono, and should be sent to + left and right), $SLOTS-MONO-LEFT, or $SLOTS-MONO-RIGHT. + + The $format must be one of $FORMAT-MSB, $FORMAT-PHILIPS, $FORMAT-PCM-SHORT. */ - constructor.duplex - --master/bool - --rx/gpio.Pin - --tx/gpio.Pin - --sck/gpio.Pin?=null - --ws/gpio.Pin?=null - --mclk/gpio.Pin?=null - --invert-sck/bool=false - --invert-ws/bool=false - --invert-mclk/bool=false + configure --mclk-external-frequency/int?=null - --sample-rate/int - --bits-per-sample/int --mclk-multiplier/int?=null - --format/int=FORMAT-PHILIPS - --stereo/bool=true - --slots/int=SLOTS-STEREO-BOTH - --start/bool=true: - return Bus.private_ - --master=master - --rx=rx - --tx=tx - --sck=sck - --ws=ws - --mclk=mclk - --invert-sck=invert-sck - --invert-ws=invert-ws - --invert-mclk=invert-mclk - --sample-rate=sample-rate - --bits-per-sample=bits-per-sample - --mclk-multiplier=mclk-multiplier - --mclk-external-frequency=mclk-external-frequency - --format=format - --slots=slots - --start=start - - constructor.private_ - --master/bool - --sck/gpio.Pin? - --ws/gpio.Pin? - --tx/gpio.Pin? - --rx/gpio.Pin? - --mclk/gpio.Pin? - --invert-sck/bool - --invert-ws/bool - --invert-mclk/bool - --mclk-external-frequency/int? --sample-rate/int --bits-per-sample/int - --mclk-multiplier/int? - --format/int - --slots/int - --start/bool: + --format/int=FORMAT-PHILIPS + --slots-in/int + --slots-out/int: if mclk-multiplier: if bits-per-sample == 24 and mclk-multiplier % 3 != 0: throw "INVALID_ARGUMENT" if mclk-multiplier != 128 and mclk-multiplier != 256 and mclk-multiplier != 384 @@ -504,51 +423,70 @@ class Bus: mclk-multiplier = bits-per-sample == 24 ? 384 : 256 if bits-per-sample != 8 and bits-per-sample != 16 and bits-per-sample != 24 and bits-per-sample != 32: throw "INVALID_ARGUMENT" - if not tx and not rx: - // We could support this, but it is not clear what the use case would be. + if slots-in != SLOTS-STEREO-BOTH and slots-in != SLOTS-MONO-LEFT and slots-in != SLOTS-MONO-RIGHT: throw "INVALID_ARGUMENT" - if slots != SLOTS-STEREO-BOTH and slots != SLOTS-STEREO-LEFT and slots != SLOTS-STEREO-RIGHT - and slots != SLOTS-MONO-BOTH and slots != SLOTS-MONO-LEFT and slots != SLOTS-MONO-RIGHT: + if slots-out != SLOTS-STEREO-BOTH and slots-out != SLOTS-STEREO-LEFT and slots-out != SLOTS-STEREO-RIGHT + and slots-out != SLOTS-MONO-BOTH and slots-out != SLOTS-MONO-LEFT and slots-out != SLOTS-MONO-RIGHT: throw "INVALID_ARGUMENT" if format != FORMAT-PHILIPS and format != FORMAT-MSB and format != FORMAT-PCM-SHORT: throw "INVALID_ARGUMENT" - rx-pin := rx ? rx.num : -1 - tx-pin := tx ? tx.num : -1 - sck-pin := sck ? sck.num : -1 - ws-pin := ws ? ws.num : -1 - mclk-pin := mclk ? mclk.num : -1 - if sck-pin != -1 and invert-sck: sck-pin |= 0x1_0000 - if ws-pin != -1 and invert-ws: ws-pin |= 0x1_0000 - if mclk-pin != -1 and invert-mclk: mclk-pin |= 0x1_0000 - if mclk-pin != -1 and mclk-external-frequency: + tx-pin := tx_ ? tx_.num : -1 + rx-pin := rx_ ? rx_.num : -1 + mclk-pin := mclk_ ? mclk_.num : -1 + sck-pin := sck_ ? sck_.num : -1 + ws-pin := ws_ ? ws_.num : -1 + if mclk-pin != -1 and invert-mclk_: mclk-pin |= 0x1_0000 + if sck-pin != -1 and invert-sck_: sck-pin |= 0x1_0000 + if ws-pin != -1 and invert-ws_: ws-pin |= 0x1_0000 + + if mclk-external-frequency: if mclk-external-frequency < sample-rate * bits-per-sample * 2: throw "INVALID_ARGUMENT" - if not mclk-external-frequency: mclk-external-frequency = -1 - i2s_ = i2s-create_ - resource-group_ - sck-pin - ws-pin - tx-pin - rx-pin - mclk-pin + else: + mclk-external-frequency = -1 + + i2s-configure_ + i2s_ sample-rate bits-per-sample - master mclk-multiplier - format - slots mclk-external-frequency - state_ = ResourceState_ resource-group_ i2s_ + format + slots-in + slots-out + tx-pin + rx-pin + mclk-pin + sck-pin + ws-pin - if start: this.start + /** + Variant of $(configure --slots-in --slots-out --sample-rate --bits-per-sample) that + sets both slots to the same value. + */ + configure + --mclk-external-frequency/int?=null + --mclk-multiplier/int?=null + --sample-rate/int + --bits-per-sample/int + --format/int=FORMAT-PHILIPS + --slots/int=SLOTS-STEREO-BOTH: + configure + --mclk-external-frequency=mclk-external-frequency + --mclk-multiplier=mclk-multiplier + --sample-rate=sample-rate + --bits-per-sample=bits-per-sample + --format=format + --slots-in=slots + --slots-out=slots /** Deprecated. - - $is-master has been renamed to '--master' and is now mandatory. - $use-apll is no longer supported. - $buffer-size is no longer supported. + $is-master has been renamed to '--master' and is now mandatory. + $use-apll is no longer supported. + $buffer-size is no longer supported. + The bus must be constructed, configured, and started manually. */ constructor --sck/gpio.Pin?=null @@ -562,23 +500,25 @@ class Bus: --mclk-multiplier/int=256 --use-apll/bool=false --buffer-size/int=-1: - return Bus.private_ + bus := Bus --master=is-master --sck=sck --ws=ws --tx=tx --rx=rx --mclk=mclk - --invert-sck=false - --invert-ws=false - --invert-mclk=false - --mclk-external-frequency=null - --sample-rate=sample-rate - --bits-per-sample=bits-per-sample - --mclk-multiplier=mclk-multiplier - --slots=SLOTS-STEREO-BOTH - --format=FORMAT-PHILIPS - --start=true + needs-close := true + try: + bus.configure + --sample-rate=sample-rate + --bits-per-sample=bits-per-sample + --mclk-multiplier=mclk-multiplier + --slots=SLOTS-STEREO-BOTH + bus.start + needs-close = false + return bus + finally: + if needs-close: bus.close /** Number of encountered errors. @@ -613,8 +553,6 @@ class Bus: Stops the bus. It's rare that you need to stop the bus. Usually, you just close it. - - The bus must be started. */ stop -> none: if not i2s_: throw "CLOSED" @@ -637,6 +575,12 @@ class Bus: Writes bytes to the I2S bus. This method blocks until all data has been written. + + # Esp32 + On the ESP32 (but not its variants), the buffer needs to be padded for + 8 and 24 bits samples. That is, for 8 bits, samples should be provided + in 16-bit blocks and only the highest 8 bits are used. For 24 bits, + each sample should be 32 bits, where only the highest 24 bits are used. */ write bytes/ByteArray -> int: if not i2s_: throw "CLOSED" @@ -653,6 +597,8 @@ class Bus: This method blocks until some data has been written. Returns the number of bytes written. + + See $write for ESP32-specific notes. */ try-write bytes/ByteArray -> int: while true: @@ -669,9 +615,16 @@ class Bus: state := state_.wait-for-state WRITE-STATE_ | ERROR-STATE_ /** - Read bytes from the I2S bus. + Reads bytes from the I2S bus. This methods blocks until data is available. + + # Esp32 + On the ESP32 (but not its variants), the buffer is padded for + 8 and 24 bits samples. That is, for 8 bits, samples are provided + in 16-bit blocks and only the highest 8 bits are used. For 24 bits, + each sample is given as 32 bits, where only the highest 24 bits are + used. */ read -> ByteArray?: result := ByteArray 496 @@ -682,9 +635,11 @@ class Bus: return result[..count] /** - Read bytes from the I2S bus to a buffer. + Reads bytes from the I2S bus to a buffer. This methods blocks until data is available. + + See $read for ESP32-specific notes. */ read buffer/ByteArray -> int?: while true: @@ -698,7 +653,7 @@ class Bus: state_.clear-state READ-STATE_ | ERROR-STATE_ /** - Close the I2S bus and releases resources associated to it. + Closes the I2S bus and releases resources associated to it. */ close: if not i2s_: return @@ -719,19 +674,26 @@ i2s-init_: i2s-create_ resource-group - sck-pin - ws-pin tx-pin rx-pin - mclk-pin + is-master: + #primitive.i2s.create + +i2s-configure_ + i2s_ sample-rate bits-per-sample - is-master mclk-multiplier + mclk-external-frequency format - slots - mclk-external-frequency: - #primitive.i2s.create + slots-in + slots-out + tx-pin + rx-pin + mclk-pin + sck-pin + ws-pin: + #primitive.i2s.configure i2s-start_ i2s: #primitive.i2s.start diff --git a/src/compiler/propagation/type_primitive_i2s.cc b/src/compiler/propagation/type_primitive_i2s.cc index 1a86e656f..3f6ca75b4 100644 --- a/src/compiler/propagation/type_primitive_i2s.cc +++ b/src/compiler/propagation/type_primitive_i2s.cc @@ -22,6 +22,7 @@ MODULE_TYPES(i2s, MODULE_I2S) TYPE_PRIMITIVE_ANY(init) TYPE_PRIMITIVE_ANY(create) +TYPE_PRIMITIVE_ANY(configure) TYPE_PRIMITIVE_ANY(start) TYPE_PRIMITIVE_ANY(stop) TYPE_PRIMITIVE_ANY(preload) diff --git a/src/primitive.h b/src/primitive.h index 753312866..a7a7a3456 100644 --- a/src/primitive.h +++ b/src/primitive.h @@ -425,7 +425,8 @@ namespace toit { #define MODULE_I2S(PRIMITIVE) \ PRIMITIVE(init, 0) \ - PRIMITIVE(create, 13) \ + PRIMITIVE(create, 4) \ + PRIMITIVE(configure, 13) \ PRIMITIVE(start, 1) \ PRIMITIVE(stop, 1) \ PRIMITIVE(preload, 2) \ diff --git a/src/resources/i2s_esp32.cc b/src/resources/i2s_esp32.cc index 45a05ae25..edb8171ef 100644 --- a/src/resources/i2s_esp32.cc +++ b/src/resources/i2s_esp32.cc @@ -58,6 +58,7 @@ class I2sResourceGroup : public ResourceGroup { class I2sResource: public EventQueueResource { public: enum State { + UNITIALIZED, STOPPED, STARTED, }; @@ -126,7 +127,7 @@ class I2sResource: public EventQueueResource { i2s_chan_handle_t rx_handle_; mutable spinlock_t spinlock_; word pending_event_ = 0; - State state_ = STOPPED; + State state_ = UNITIALIZED; int errors_ = 0; }; @@ -191,52 +192,12 @@ IRAM_ATTR static bool channel_error_handler(i2s_chan_handle_t handle, PRIMITIVE(create) { ARGS(I2sResourceGroup, group, - int, sck_pin, - int, ws_pin, int, tx_pin, int, rx_pin, - int, mclk_pin, - uint32, sample_rate, - int, bits_per_sample, - bool, is_master, - int, toit_mclk_multiplier, - int, format, - int, slots, - int, external_frequency); + bool, is_master); esp_err_t err; bool handed_to_resource = false; - // if (mclk_multiplier != 128 && mclk_multiplier != 256 && mclk_multiplier != 384) FAIL(INVALID_ARGUMENT); - if (bits_per_sample != 8 && bits_per_sample != 16 && bits_per_sample != 24 && bits_per_sample != 32) FAIL(INVALID_ARGUMENT); - - i2s_mclk_multiple_t mclk_multiple; - switch (toit_mclk_multiplier) { - case 128: mclk_multiple = I2S_MCLK_MULTIPLE_128; break; - case 256: mclk_multiple = I2S_MCLK_MULTIPLE_256; break; - case 384: mclk_multiple = I2S_MCLK_MULTIPLE_384; break; - case 512: mclk_multiple = I2S_MCLK_MULTIPLE_512; break; - case 576: mclk_multiple = I2S_MCLK_MULTIPLE_576; break; - case 768: mclk_multiple = I2S_MCLK_MULTIPLE_768; break; - case 1024:mclk_multiple = I2S_MCLK_MULTIPLE_1024; break; - case 1152:mclk_multiple = I2S_MCLK_MULTIPLE_1152; break; - default: FAIL(INVALID_ARGUMENT); - } - - if (format < 0 || format > 2) FAIL(INVALID_ARGUMENT); - if (slots < 0 || slots > 5) FAIL(INVALID_ARGUMENT); - - bool sck_inv = (sck_pin & 0x10000) != 0; - sck_pin &= ~0x10000; - bool ws_inv = (ws_pin & 0x10000) != 0; - ws_pin &= ~0x10000; - bool mclk_inv = (mclk_pin & 0x10000) != 0; - mclk_pin &= ~0x10000; - bool mclk_is_input = external_frequency > 0; - -#ifndef SOC_I2S_HW_VERSION_2 - if (mclk_is_input) FAIL(INVALID_ARGUMENT); -#endif - ByteArray* proxy = process->object_heap()->allocate_proxy(); if (proxy == null) FAIL(ALLOCATION_FAILED); @@ -278,80 +239,6 @@ PRIMITIVE(create) { Defer del_tx_channel { [&] { if (!handed_to_resource && tx_handle != null) i2s_del_channel(tx_handle); } }; Defer del_rx_channel { [&] { if (!handed_to_resource && rx_handle != null) i2s_del_channel(rx_handle); } }; - i2s_data_bit_width_t bit_width; - switch (bits_per_sample) { - case 8: bit_width = I2S_DATA_BIT_WIDTH_8BIT; break; - case 16: bit_width = I2S_DATA_BIT_WIDTH_16BIT; break; - case 24: bit_width = I2S_DATA_BIT_WIDTH_24BIT; break; - case 32: bit_width = I2S_DATA_BIT_WIDTH_32BIT; break; - default: UNREACHABLE(); - } - - i2s_slot_mode_t mono_or_stereo = slots < 3 ? I2S_SLOT_MODE_STEREO : I2S_SLOT_MODE_MONO; - - i2s_std_config_t std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate), - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bit_width, mono_or_stereo), - .gpio_cfg = { - .mclk = mclk_pin >= 0 ? static_cast(mclk_pin) : I2S_GPIO_UNUSED, - .bclk = sck_pin >= 0 ? static_cast(sck_pin): I2S_GPIO_UNUSED, - .ws = ws_pin >= 0 ? static_cast(ws_pin): I2S_GPIO_UNUSED, - .dout = tx_pin >= 0 ? static_cast(tx_pin): I2S_GPIO_UNUSED, - .din = rx_pin >= 0 ? static_cast(rx_pin): I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = mclk_inv, - .bclk_inv = sck_inv, - .ws_inv = ws_inv, - }, - }, - }; - -#ifdef SOC_I2S_HW_VERSION_2 - if (mclk_is_input) std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_EXTERNAL; - std_cfg.clk_cfg.ext_clk_freq_hz = static_cast(external_frequency); -#endif - std_cfg.clk_cfg.mclk_multiple = mclk_multiple; - - switch (format) { - case 0: // Philips. - // Already configured. - break; - - case 1: // MSB - std_cfg.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(bit_width, mono_or_stereo); - break; - - case 2: // PCM-Short. - std_cfg.slot_cfg = I2S_STD_PCM_SLOT_DEFAULT_CONFIG(bit_width, mono_or_stereo); - break; - - default: UNREACHABLE(); - } - - switch (slots) { - case 0: // Stereo both. - case 3: // Mono both. - std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH; - break; - case 1: // Stereo left. - case 4: // Mono left. - std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT; - break; - case 2: // Stereo right. - case 5: // Mono right. - std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT; - break; - } - - if (tx_handle != null) { - err = i2s_channel_init_std_mode(tx_handle, &std_cfg); - if (err != ESP_OK) return Primitive::os_error(err, process); - } - if (rx_handle != null) { - err = i2s_channel_init_std_mode(rx_handle, &std_cfg); - if (err != ESP_OK) return Primitive::os_error(err, process); - } - bool successful_return = false; I2sResource* resource = new (resource_memory) I2sResource(group, tx_handle, rx_handle, queue); @@ -390,6 +277,143 @@ PRIMITIVE(create) { return proxy; } +PRIMITIVE(configure) { + ARGS(I2sResource, resource, + uint32, sample_rate, + int, bits_per_sample, + int, toit_mclk_multiplier, + int, external_frequency, + int, format, + int, slots_in, + int, slots_out, + int, tx_pin, + int, rx_pin, + int, mclk_pin, + int, sck_pin, + int, ws_pin); + esp_err_t err; + + auto state = resource->state(); + if (state == I2sResource::STARTED) FAIL(INVALID_STATE); + + if (bits_per_sample != 8 && bits_per_sample != 16 && bits_per_sample != 24 && bits_per_sample != 32) FAIL(INVALID_ARGUMENT); + + i2s_mclk_multiple_t mclk_multiple; + switch (toit_mclk_multiplier) { + case 128: mclk_multiple = I2S_MCLK_MULTIPLE_128; break; + case 256: mclk_multiple = I2S_MCLK_MULTIPLE_256; break; + case 384: mclk_multiple = I2S_MCLK_MULTIPLE_384; break; + case 512: mclk_multiple = I2S_MCLK_MULTIPLE_512; break; + case 576: mclk_multiple = I2S_MCLK_MULTIPLE_576; break; + case 768: mclk_multiple = I2S_MCLK_MULTIPLE_768; break; + case 1024:mclk_multiple = I2S_MCLK_MULTIPLE_1024; break; + case 1152:mclk_multiple = I2S_MCLK_MULTIPLE_1152; break; + default: FAIL(INVALID_ARGUMENT); + } + + if (format < 0 || format > 2) FAIL(INVALID_ARGUMENT); + if (slots_in != 0 && slots_in != 4 && slots_in != 5) FAIL(INVALID_ARGUMENT); + if (slots_out < 0 || slots_out > 5) FAIL(INVALID_ARGUMENT); + + bool sck_inv = (sck_pin & 0x10000) != 0; + sck_pin &= ~0x10000; + bool ws_inv = (ws_pin & 0x10000) != 0; + ws_pin &= ~0x10000; + bool mclk_inv = (mclk_pin & 0x10000) != 0; + mclk_pin &= ~0x10000; + bool mclk_is_input = external_frequency > 0; +#ifndef SOC_I2S_HW_VERSION_2 + if (mclk_is_input) FAIL(INVALID_ARGUMENT); +#endif + + i2s_data_bit_width_t bit_width; + switch (bits_per_sample) { + case 8: bit_width = I2S_DATA_BIT_WIDTH_8BIT; break; + case 16: bit_width = I2S_DATA_BIT_WIDTH_16BIT; break; + case 24: bit_width = I2S_DATA_BIT_WIDTH_24BIT; break; + case 32: bit_width = I2S_DATA_BIT_WIDTH_32BIT; break; + default: UNREACHABLE(); + } + + for (int i = 0; i < 2; i++) { + i2s_chan_handle_t handle = i == 0 ? resource->tx_handle() : resource->rx_handle(); + if (handle == null) continue; + + int slots = i == 0 ? slots_out : slots_in; + + i2s_slot_mode_t mono_or_stereo = slots < 3 ? I2S_SLOT_MODE_STEREO : I2S_SLOT_MODE_MONO; + + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate), + // The slot-cfg might be overridden later. + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bit_width, mono_or_stereo), + .gpio_cfg = { + .mclk = mclk_pin >= 0 ? static_cast(mclk_pin) : I2S_GPIO_UNUSED, + .bclk = sck_pin >= 0 ? static_cast(sck_pin): I2S_GPIO_UNUSED, + .ws = ws_pin >= 0 ? static_cast(ws_pin): I2S_GPIO_UNUSED, + .dout = tx_pin >= 0 ? static_cast(tx_pin): I2S_GPIO_UNUSED, + .din = rx_pin >= 0 ? static_cast(rx_pin): I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = mclk_inv, + .bclk_inv = sck_inv, + .ws_inv = ws_inv, + }, + }, + }; + +#ifdef SOC_I2S_HW_VERSION_2 + if (mclk_is_input) std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_EXTERNAL; + std_cfg.clk_cfg.ext_clk_freq_hz = static_cast(external_frequency); +#endif + std_cfg.clk_cfg.mclk_multiple = mclk_multiple; + + switch (format) { + case 0: // Philips. + break; + + case 1: // MSB + std_cfg.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(bit_width, mono_or_stereo); + break; + + case 2: // PCM-Short. + std_cfg.slot_cfg = I2S_STD_PCM_SLOT_DEFAULT_CONFIG(bit_width, mono_or_stereo); + break; + + default: UNREACHABLE(); + } + + switch (slots) { + case 0: // Stereo both. + case 3: // Mono both. + std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH; + break; + case 1: // Stereo left. + case 4: // Mono left. + std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT; + break; + case 2: // Stereo right. + case 5: // Mono right. + std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT; + break; + } + + if (state == I2sResource::UNITIALIZED) { + err = i2s_channel_init_std_mode(handle, &std_cfg); + if (err != ESP_OK) return Primitive::os_error(err, process); + } else { + err = i2s_channel_reconfig_std_clock(handle, &std_cfg.clk_cfg); + if (err != ESP_OK) return Primitive::os_error(err, process); + err = i2s_channel_reconfig_std_slot(handle, &std_cfg.slot_cfg); + if (err != ESP_OK) return Primitive::os_error(err, process); + err = i2s_channel_reconfig_std_gpio(handle, &std_cfg.gpio_cfg); + if (err != ESP_OK) return Primitive::os_error(err, process); + } + } + resource->set_state(I2sResource::STOPPED); + + return process->null_object(); +} + PRIMITIVE(start) { ARGS(I2sResource, resource); if (resource->state() != I2sResource::STOPPED) FAIL(INVALID_STATE); @@ -414,7 +438,9 @@ PRIMITIVE(start) { PRIMITIVE(stop) { ARGS(I2sResource, resource); - if (resource->state() != I2sResource::STARTED) FAIL(INVALID_STATE); + if (resource->state() != I2sResource::STARTED) { + return process->null_object(); + } esp_err_t err; auto tx_handle = resource->tx_handle(); diff --git a/tests/hw/esp32/i2s-shared.toit b/tests/hw/esp32/i2s-shared.toit index cb1700b2b..4e17f2619 100644 --- a/tests/hw/esp32/i2s-shared.toit +++ b/tests/hw/esp32/i2s-shared.toit @@ -147,16 +147,17 @@ test args/List if is-writer: channel = i2s.Bus --master=master + --mclk=use-mclk ? mclk : null --tx=data --sck=clk --ws=ws + + channel.configure --sample-rate=sample-rate --bits-per-sample=data-size --format=format - --mclk=use-mclk ? mclk : null --mclk-multiplier=use-mclk ? mclk-multiplier : null --mclk-external-frequency=use-mclk ? mclk-frequency : null - --start=(not is-fast-test) --slots=stereo-out if is-fast-test: @@ -164,7 +165,8 @@ test args/List while true: preloaded := generator.do: channel.preload it if preloaded == 0: break - channel.start + + channel.start printed-done := false while true: @@ -183,17 +185,21 @@ test args/List else: channel = i2s.Bus --master=master + --mclk=use-mclk ? mclk : null --rx=data --sck=clk --ws=ws + + channel.configure --sample-rate=sample-rate --bits-per-sample=data-size --format=format - --mclk=use-mclk ? mclk : null --mclk-multiplier=use-mclk ? mclk-multiplier : null --mclk-external-frequency=use-mclk ? mclk-frequency : null --slots=stereo-in + channel.start + required-size := is-fast-test ? FAST-DATA-SIZE : SLOW-DATA-SIZE diff --git a/tests/hw/esp32/i2s-test.toit b/tests/hw/esp32/i2s-test.toit index 4e3d10245..7387e5e77 100644 --- a/tests/hw/esp32/i2s-test.toit +++ b/tests/hw/esp32/i2s-test.toit @@ -103,18 +103,18 @@ test-basics: --tx=data1 --sck=clk1 --ws=ws1 + out.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start in = i2s.Bus --master --rx=data2 --sck=clk2 --ws=ws2 + in.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start generator = VerifyingDataGenerator sample-size --needs-synchronization @@ -135,18 +135,18 @@ test-basics: --tx=data1 --sck=clk1 --ws=ws1 + out.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start in = i2s.Bus --master --rx=data2 --sck=clk2 --ws=ws2 + in.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start generator = VerifyingDataGenerator sample-size --allow-leading-0-samples @@ -165,22 +165,22 @@ test-basics: // Same but switch master. out = i2s.Bus - --master=true + --master --tx=data1 --sck=clk1 --ws=ws1 + out.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start in = i2s.Bus --master=false --rx=data2 --sck=clk2 --ws=ws2 + in.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start generator = VerifyingDataGenerator sample-size --allow-missing-first-samples @@ -204,9 +204,9 @@ test-basics: --rx=data2 --sck=clk1 --ws=ws1 + in_out.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start generator = VerifyingDataGenerator sample-size --allow-missing-first-samples @@ -228,9 +228,9 @@ test-basics: --rx=data2 --sck=clk1 --ws=ws1 + in_out.configure --sample-rate=SAMPLE_RATE --bits-per-sample=sample-size - --no-start generator = VerifyingDataGenerator sample-size --needs-synchronization