diff --git a/docs/i2s.rst b/docs/i2s.rst index bec76dd55..d73ef85e1 100644 --- a/docs/i2s.rst +++ b/docs/i2s.rst @@ -36,6 +36,12 @@ Creates a bi-directional I2S input and output port. Needs to be connected up to the desired pins (see below) and started before any input or output can happen. +bool setSlave() +~~~~~~~~~~~~~~~ +Enables slave mode. BCLK and LRCLK are inputs and used to control the +timing of the DOUT output. Only normal I2S output mode is supported in +slave mode. + bool setBCLK(pin_size_t pin) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sets the BCLK pin of the I2S device. The LRCLK/word clock will be ``pin + 1`` diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt index 44f2e8d6e..5579b56ee 100644 --- a/libraries/I2S/keywords.txt +++ b/libraries/I2S/keywords.txt @@ -28,6 +28,7 @@ setTDMChannels KEYWORD2 swapClocks KEYWORD2 setMCLKmult KEYWORD2 setSysClk KEYWORD2 +setSlave KEYWORD2 read8 KEYWORD2 read16 KEYWORD2 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index fdba6dc88..545d08d32 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -65,6 +65,7 @@ I2S::I2S(PinMode direction, pin_size_t bclk, pin_size_t data, pin_size_t mclk, p _tdmChannels = 8; _swapClocks = false; _multMCLK = 256; + _isSlave = false; } I2S::~I2S() { @@ -87,6 +88,14 @@ bool I2S::setMCLK(pin_size_t pin) { return true; } +bool I2S::setSlave() { + if (_running) { + return false; + } + _isSlave = true; + return true; +} + bool I2S::setDATA(pin_size_t pin) { if (_running || (pin >= __GPIOCNT) || (_isOutput && _isInput)) { return false; @@ -136,12 +145,16 @@ bool I2S::setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample) bool I2S::setFrequency(int newFreq) { _freq = newFreq; if (_running) { - if (_MCLKenabled) { - int bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */; - pio_sm_set_clkdiv_int_frac(_pio, _sm, clock_get_hz(clk_sys) / bitClk, 0); + if (_isSlave) { + pio_sm_set_clkdiv_int_frac(_pio, _sm, 1, 0); } else { - float bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */; - pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk); + if (_MCLKenabled) { + int bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */; + pio_sm_set_clkdiv_int_frac(_pio, _sm, clock_get_hz(clk_sys) / bitClk, 0); + } else { + float bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */; + pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk); + } } } return true; @@ -255,7 +268,11 @@ bool I2S::begin() { _isHolding = 0; int off = 0; if (!_swapClocks) { - _i2s = new PIOProgram(_isOutput ? (_isInput ? (_isTDM ? &pio_tdm_inout_program : &pio_i2s_inout_program) : (_isTDM ? &pio_tdm_out_program : (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program))) : &pio_i2s_in_program); + if (!_isSlave) { + _i2s = new PIOProgram(_isOutput ? (_isInput ? (_isTDM ? &pio_tdm_inout_program : &pio_i2s_inout_program) : (_isTDM ? &pio_tdm_out_program : (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program))) : &pio_i2s_in_program); + } else { + _i2s = new PIOProgram(_bps > 16 ? &pio_i2s_out_slave_32_program : &pio_i2s_out_slave_16_program); + } } else { _i2s = new PIOProgram(_isOutput ? (_isInput ? (_isTDM ? &pio_tdm_inout_swap_program : &pio_i2s_inout_swap_program) : (_isTDM ? &pio_tdm_out_swap_program : (_isLSBJ ? &pio_lsbj_out_swap_program : &pio_i2s_out_swap_program))) : &pio_i2s_in_swap_program); } @@ -288,7 +305,11 @@ bool I2S::begin() { } else if (_isLSBJ) { pio_lsbj_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks); } else { - pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks); + if (_isSlave) { + pio_i2s_out_slave_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks); + } else { + pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks); + } } } else { pio_i2s_in_program_init(_pio, _sm, off, _pinDIN, _pinBCLK, _bps, _swapClocks); diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index dad45faca..1b184700d 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -34,6 +34,7 @@ class I2S : public Stream, public AudioOutputBase { bool setDOUT(pin_size_t pin); bool setDIN(pin_size_t pin); bool setMCLK(pin_size_t pin); + bool setSlave(); virtual bool setBitsPerSample(int bps) override; virtual bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0) override; virtual bool setFrequency(int newFreq) override; @@ -155,6 +156,7 @@ class I2S : public Stream, public AudioOutputBase { bool _isOutput; bool _swapClocks; bool _MCLKenabled; + bool _isSlave; bool _running; diff --git a/libraries/I2S/src/pio_i2s.pio b/libraries/I2S/src/pio_i2s.pio index 38f71d2b3..f0e82ee73 100644 --- a/libraries/I2S/src/pio_i2s.pio +++ b/libraries/I2S/src/pio_i2s.pio @@ -50,6 +50,61 @@ right1: out pins, 1 side 0b00 ; Last bit of right also has WCLK change ; Loop back to beginning... +.program pio_i2s_out_slave_16 +; IN 0 = bclk +; IN 1 = lrclk, also EXECCTRL_JMP_PIN +; OUT 0 = DOUT + +waitfirstbit: + wait 0 pin 0 + wait 1 pin 0 + jmp pin waitfirstbit ; LRCLK1 == right channel, so if high keep waiting + +.wrap_target + pull noblock +sendleft: + wait 0 pin 0 ; Falling edge start sending + out pins, 1 ; Shift out a bit + wait 1 pin 0 ; Receiver should grab data here + jmp pin sendright ; LRCLK == 1 => right channel + jmp sendleft + +sendright: + wait 0 pin 0 ; Falling edge start sending + out pins, 1 ; Shift out a bit + wait 1 pin 0 ; Receiver should grab data here + jmp pin sendright ; LRCLK == 1 => right channel +.wrap + +.program pio_i2s_out_slave_32 +; IN 0 = bclk +; IN 1 = lrclk, also EXECCTRL_JMP_PIN +; OUT 0 = DOUT + +waitfirstbit: + wait 0 pin 0 + wait 1 pin 0 + jmp pin waitfirstbit ; LRCLK1 == right channel, so if high keep waiting + +.wrap_target + pull noblock +sendleft: + wait 0 pin 0 ; Falling edge start sending + out pins, 1 ; Shift out a bit + wait 1 pin 0 ; Receiver should grab data here + jmp pin sendright ; LRCLK == 1 => right channel + jmp sendleft + + pull noblock +sendright: + wait 0 pin 0 ; Falling edge start sending + out pins, 1 ; Shift out a bit + wait 1 pin 0 ; Receiver should grab data here + jmp pin sendright ; LRCLK == 1 => right channel +.wrap + + + .program pio_i2s_out_swap .side_set 2 ; 0 = wclk, 1=bclk @@ -61,7 +116,7 @@ right1: mov x, y side 0b10 left1: out pins, 1 side 0b00 - jmp x--, left1 side 0b10 + jmp x--, left1 side 0b10 out pins, 1 side 0b01 ; Last bit of left has WCLK change per I2S spec mov x, y side 0b11 @@ -308,9 +363,6 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint pio_sm_init(pio, sm, offset, &sm_config); - //uint pin_mask = (1u << data_pin) | (3u << clock_pin_base); - //pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); - //pio_sm_set_pins(pio, sm, 0); // clear pins pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true); pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, true); pio_sm_set_set_pins(pio, sm, data_pin, 1); @@ -319,6 +371,36 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2)); } + +static inline void pio_i2s_out_slave_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) { + pio_gpio_init(pio, data_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + + // 16-bits does a pull every L+R frame. 24/32 bits do a pull every L or R side + pio_sm_config sm_config = bits > 16 ? pio_i2s_out_slave_32_program_get_default_config(offset) : pio_i2s_out_slave_16_program_get_default_config(offset); //TBD swap ? pio_i2s_out_swap_program_get_default_config(offset) : pio_i2s_out_program_get_default_config(offset); + + sm_config_set_out_pins(&sm_config, data_pin, 1); + + sm_config_set_in_pins(&sm_config, clock_pin_base); + sm_config_set_in_pin_count(&sm_config, 2); // BLCK and LRCLK + sm_config_set_jmp_pin(&sm_config, clock_pin_base + 1); + + sm_config_set_out_shift(&sm_config, false, false, (bits <= 16) ? 2 * bits : bits); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); + + pio_sm_init(pio, sm, offset, &sm_config); + + pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, false); + pio_sm_set_out_pins(pio, sm, data_pin, 1); + pio_sm_set_in_pins(pio, sm, clock_pin_base); + + pio_sm_exec(pio, sm, pio_encode_set(pio_x, 0)); +} + + + static inline void pio_tdm_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap, uint channels) { pio_gpio_init(pio, data_pin); pio_gpio_init(pio, clock_pin_base); diff --git a/libraries/I2S/src/pio_i2s.pio.h b/libraries/I2S/src/pio_i2s.pio.h index 0a9d2d33e..a1a750ffe 100644 --- a/libraries/I2S/src/pio_i2s.pio.h +++ b/libraries/I2S/src/pio_i2s.pio.h @@ -81,6 +81,95 @@ static inline pio_sm_config pio_i2s_out_program_get_default_config(uint offset) } #endif +// -------------------- // +// pio_i2s_out_slave_16 // +// -------------------- // + +#define pio_i2s_out_slave_16_wrap_target 3 +#define pio_i2s_out_slave_16_wrap 12 +#define pio_i2s_out_slave_16_pio_version 0 + +static const uint16_t pio_i2s_out_slave_16_program_instructions[] = { + 0x2020, // 0: wait 0 pin, 0 + 0x20a0, // 1: wait 1 pin, 0 + 0x00c0, // 2: jmp pin, 0 + // .wrap_target + 0x8080, // 3: pull noblock + 0x2020, // 4: wait 0 pin, 0 + 0x6001, // 5: out pins, 1 + 0x20a0, // 6: wait 1 pin, 0 + 0x00c9, // 7: jmp pin, 9 + 0x0004, // 8: jmp 4 + 0x2020, // 9: wait 0 pin, 0 + 0x6001, // 10: out pins, 1 + 0x20a0, // 11: wait 1 pin, 0 + 0x00c9, // 12: jmp pin, 9 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program pio_i2s_out_slave_16_program = { + .instructions = pio_i2s_out_slave_16_program_instructions, + .length = 13, + .origin = -1, + .pio_version = pio_i2s_out_slave_16_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config pio_i2s_out_slave_16_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + pio_i2s_out_slave_16_wrap_target, offset + pio_i2s_out_slave_16_wrap); + return c; +} +#endif + +// -------------------- // +// pio_i2s_out_slave_32 // +// -------------------- // + +#define pio_i2s_out_slave_32_wrap_target 3 +#define pio_i2s_out_slave_32_wrap 13 +#define pio_i2s_out_slave_32_pio_version 0 + +static const uint16_t pio_i2s_out_slave_32_program_instructions[] = { + 0x2020, // 0: wait 0 pin, 0 + 0x20a0, // 1: wait 1 pin, 0 + 0x00c0, // 2: jmp pin, 0 + // .wrap_target + 0x8080, // 3: pull noblock + 0x2020, // 4: wait 0 pin, 0 + 0x6001, // 5: out pins, 1 + 0x20a0, // 6: wait 1 pin, 0 + 0x00ca, // 7: jmp pin, 10 + 0x0004, // 8: jmp 4 + 0x8080, // 9: pull noblock + 0x2020, // 10: wait 0 pin, 0 + 0x6001, // 11: out pins, 1 + 0x20a0, // 12: wait 1 pin, 0 + 0x00ca, // 13: jmp pin, 10 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program pio_i2s_out_slave_32_program = { + .instructions = pio_i2s_out_slave_32_program_instructions, + .length = 14, + .origin = -1, + .pio_version = pio_i2s_out_slave_32_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config pio_i2s_out_slave_32_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + pio_i2s_out_slave_32_wrap_target, offset + pio_i2s_out_slave_32_wrap); + return c; +} +#endif + // ---------------- // // pio_i2s_out_swap // // ---------------- // @@ -535,15 +624,31 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits); sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); pio_sm_init(pio, sm, offset, &sm_config); - //uint pin_mask = (1u << data_pin) | (3u << clock_pin_base); - //pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); - //pio_sm_set_pins(pio, sm, 0); // clear pins pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true); pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, true); pio_sm_set_set_pins(pio, sm, data_pin, 1); pio_sm_set_set_pins(pio, sm, clock_pin_base, 2); pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2)); } +static inline void pio_i2s_out_slave_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) { + pio_gpio_init(pio, data_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + // 16-bits does a pull every L+R frame. 24/32 bits do a pull every L or R side + pio_sm_config sm_config = bits > 16 ? pio_i2s_out_slave_32_program_get_default_config(offset) : pio_i2s_out_slave_16_program_get_default_config(offset); //TBD swap ? pio_i2s_out_swap_program_get_default_config(offset) : pio_i2s_out_program_get_default_config(offset); + sm_config_set_out_pins(&sm_config, data_pin, 1); + sm_config_set_in_pins(&sm_config, clock_pin_base); + sm_config_set_in_pin_count(&sm_config, 2); // BLCK and LRCLK + sm_config_set_jmp_pin(&sm_config, clock_pin_base + 1); + sm_config_set_out_shift(&sm_config, false, false, (bits <= 16) ? 2 * bits : bits); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); + pio_sm_init(pio, sm, offset, &sm_config); + pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, false); + pio_sm_set_out_pins(pio, sm, data_pin, 1); + pio_sm_set_in_pins(pio, sm, clock_pin_base); + pio_sm_exec(pio, sm, pio_encode_set(pio_x, 0)); +} static inline void pio_tdm_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap, uint channels) { pio_gpio_init(pio, data_pin); pio_gpio_init(pio, clock_pin_base);