Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/i2s.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand Down
1 change: 1 addition & 0 deletions libraries/I2S/keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ setTDMChannels KEYWORD2
swapClocks KEYWORD2
setMCLKmult KEYWORD2
setSysClk KEYWORD2
setSlave KEYWORD2

read8 KEYWORD2
read16 KEYWORD2
Expand Down
35 changes: 28 additions & 7 deletions libraries/I2S/src/I2S.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions libraries/I2S/src/I2S.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -155,6 +156,7 @@ class I2S : public Stream, public AudioOutputBase {
bool _isOutput;
bool _swapClocks;
bool _MCLKenabled;
bool _isSlave;

bool _running;

Expand Down
90 changes: 86 additions & 4 deletions libraries/I2S/src/pio_i2s.pio
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
111 changes: 108 additions & 3 deletions libraries/I2S/src/pio_i2s.pio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 //
// ---------------- //
Expand Down Expand Up @@ -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);
Expand Down
Loading