Skip to content

Commit 41ee45c

Browse files
Add I2S slave output mode (#3125)
* Add I2S slave output mode Uses an external BCLK and LRCLK to control timing of the I2S data output. Useful for synchronizing to an external device or for higher precision clocking than the internal PLL can provide. * On DMA underflow, ensure 0s continue to be sent Don't block on a PULL, just send the X register out (will be set to 0 by the setup code).
1 parent 8208b1f commit 41ee45c

File tree

6 files changed

+231
-14
lines changed

6 files changed

+231
-14
lines changed

docs/i2s.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ Creates a bi-directional I2S input and output port. Needs to be
3636
connected up to the desired pins (see below) and started before
3737
any input or output can happen.
3838

39+
bool setSlave()
40+
~~~~~~~~~~~~~~~
41+
Enables slave mode. BCLK and LRCLK are inputs and used to control the
42+
timing of the DOUT output. Only normal I2S output mode is supported in
43+
slave mode.
44+
3945
bool setBCLK(pin_size_t pin)
4046
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4147
Sets the BCLK pin of the I2S device. The LRCLK/word clock will be ``pin + 1``

libraries/I2S/keywords.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ setTDMChannels KEYWORD2
2828
swapClocks KEYWORD2
2929
setMCLKmult KEYWORD2
3030
setSysClk KEYWORD2
31+
setSlave KEYWORD2
3132

3233
read8 KEYWORD2
3334
read16 KEYWORD2

libraries/I2S/src/I2S.cpp

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ I2S::I2S(PinMode direction, pin_size_t bclk, pin_size_t data, pin_size_t mclk, p
6565
_tdmChannels = 8;
6666
_swapClocks = false;
6767
_multMCLK = 256;
68+
_isSlave = false;
6869
}
6970

7071
I2S::~I2S() {
@@ -87,6 +88,14 @@ bool I2S::setMCLK(pin_size_t pin) {
8788
return true;
8889
}
8990

91+
bool I2S::setSlave() {
92+
if (_running) {
93+
return false;
94+
}
95+
_isSlave = true;
96+
return true;
97+
}
98+
9099
bool I2S::setDATA(pin_size_t pin) {
91100
if (_running || (pin >= __GPIOCNT) || (_isOutput && _isInput)) {
92101
return false;
@@ -136,12 +145,16 @@ bool I2S::setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample)
136145
bool I2S::setFrequency(int newFreq) {
137146
_freq = newFreq;
138147
if (_running) {
139-
if (_MCLKenabled) {
140-
int bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */;
141-
pio_sm_set_clkdiv_int_frac(_pio, _sm, clock_get_hz(clk_sys) / bitClk, 0);
148+
if (_isSlave) {
149+
pio_sm_set_clkdiv_int_frac(_pio, _sm, 1, 0);
142150
} else {
143-
float bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */;
144-
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
151+
if (_MCLKenabled) {
152+
int bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */;
153+
pio_sm_set_clkdiv_int_frac(_pio, _sm, clock_get_hz(clk_sys) / bitClk, 0);
154+
} else {
155+
float bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * (_isInput && _isOutput ? 4.0 : 2.0) /* edges per clock */;
156+
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
157+
}
145158
}
146159
}
147160
return true;
@@ -255,7 +268,11 @@ bool I2S::begin() {
255268
_isHolding = 0;
256269
int off = 0;
257270
if (!_swapClocks) {
258-
_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);
271+
if (!_isSlave) {
272+
_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);
273+
} else {
274+
_i2s = new PIOProgram(_bps > 16 ? &pio_i2s_out_slave_32_program : &pio_i2s_out_slave_16_program);
275+
}
259276
} else {
260277
_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);
261278
}
@@ -288,7 +305,11 @@ bool I2S::begin() {
288305
} else if (_isLSBJ) {
289306
pio_lsbj_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
290307
} else {
291-
pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
308+
if (_isSlave) {
309+
pio_i2s_out_slave_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
310+
} else {
311+
pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
312+
}
292313
}
293314
} else {
294315
pio_i2s_in_program_init(_pio, _sm, off, _pinDIN, _pinBCLK, _bps, _swapClocks);

libraries/I2S/src/I2S.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class I2S : public Stream, public AudioOutputBase {
3434
bool setDOUT(pin_size_t pin);
3535
bool setDIN(pin_size_t pin);
3636
bool setMCLK(pin_size_t pin);
37+
bool setSlave();
3738
virtual bool setBitsPerSample(int bps) override;
3839
virtual bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0) override;
3940
virtual bool setFrequency(int newFreq) override;
@@ -155,6 +156,7 @@ class I2S : public Stream, public AudioOutputBase {
155156
bool _isOutput;
156157
bool _swapClocks;
157158
bool _MCLKenabled;
159+
bool _isSlave;
158160

159161
bool _running;
160162

libraries/I2S/src/pio_i2s.pio

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,61 @@ right1:
5050
out pins, 1 side 0b00 ; Last bit of right also has WCLK change
5151
; Loop back to beginning...
5252

53+
.program pio_i2s_out_slave_16
54+
; IN 0 = bclk
55+
; IN 1 = lrclk, also EXECCTRL_JMP_PIN
56+
; OUT 0 = DOUT
57+
58+
waitfirstbit:
59+
wait 0 pin 0
60+
wait 1 pin 0
61+
jmp pin waitfirstbit ; LRCLK1 == right channel, so if high keep waiting
62+
63+
.wrap_target
64+
pull noblock
65+
sendleft:
66+
wait 0 pin 0 ; Falling edge start sending
67+
out pins, 1 ; Shift out a bit
68+
wait 1 pin 0 ; Receiver should grab data here
69+
jmp pin sendright ; LRCLK == 1 => right channel
70+
jmp sendleft
71+
72+
sendright:
73+
wait 0 pin 0 ; Falling edge start sending
74+
out pins, 1 ; Shift out a bit
75+
wait 1 pin 0 ; Receiver should grab data here
76+
jmp pin sendright ; LRCLK == 1 => right channel
77+
.wrap
78+
79+
.program pio_i2s_out_slave_32
80+
; IN 0 = bclk
81+
; IN 1 = lrclk, also EXECCTRL_JMP_PIN
82+
; OUT 0 = DOUT
83+
84+
waitfirstbit:
85+
wait 0 pin 0
86+
wait 1 pin 0
87+
jmp pin waitfirstbit ; LRCLK1 == right channel, so if high keep waiting
88+
89+
.wrap_target
90+
pull noblock
91+
sendleft:
92+
wait 0 pin 0 ; Falling edge start sending
93+
out pins, 1 ; Shift out a bit
94+
wait 1 pin 0 ; Receiver should grab data here
95+
jmp pin sendright ; LRCLK == 1 => right channel
96+
jmp sendleft
97+
98+
pull noblock
99+
sendright:
100+
wait 0 pin 0 ; Falling edge start sending
101+
out pins, 1 ; Shift out a bit
102+
wait 1 pin 0 ; Receiver should grab data here
103+
jmp pin sendright ; LRCLK == 1 => right channel
104+
.wrap
105+
106+
107+
53108
.program pio_i2s_out_swap
54109
.side_set 2 ; 0 = wclk, 1=bclk
55110

@@ -61,7 +116,7 @@ right1:
61116
mov x, y side 0b10
62117
left1:
63118
out pins, 1 side 0b00
64-
jmp x--, left1 side 0b10
119+
jmp x--, left1 side 0b10
65120
out pins, 1 side 0b01 ; Last bit of left has WCLK change per I2S spec
66121

67122
mov x, y side 0b11
@@ -308,9 +363,6 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
308363

309364
pio_sm_init(pio, sm, offset, &sm_config);
310365

311-
//uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
312-
//pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
313-
//pio_sm_set_pins(pio, sm, 0); // clear pins
314366
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
315367
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, true);
316368
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
319371
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
320372
}
321373

374+
375+
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) {
376+
pio_gpio_init(pio, data_pin);
377+
pio_gpio_init(pio, clock_pin_base);
378+
pio_gpio_init(pio, clock_pin_base + 1);
379+
380+
// 16-bits does a pull every L+R frame. 24/32 bits do a pull every L or R side
381+
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);
382+
383+
sm_config_set_out_pins(&sm_config, data_pin, 1);
384+
385+
sm_config_set_in_pins(&sm_config, clock_pin_base);
386+
sm_config_set_in_pin_count(&sm_config, 2); // BLCK and LRCLK
387+
sm_config_set_jmp_pin(&sm_config, clock_pin_base + 1);
388+
389+
sm_config_set_out_shift(&sm_config, false, false, (bits <= 16) ? 2 * bits : bits);
390+
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
391+
392+
pio_sm_init(pio, sm, offset, &sm_config);
393+
394+
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
395+
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, false);
396+
pio_sm_set_out_pins(pio, sm, data_pin, 1);
397+
pio_sm_set_in_pins(pio, sm, clock_pin_base);
398+
399+
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 0));
400+
}
401+
402+
403+
322404
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) {
323405
pio_gpio_init(pio, data_pin);
324406
pio_gpio_init(pio, clock_pin_base);

libraries/I2S/src/pio_i2s.pio.h

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,95 @@ static inline pio_sm_config pio_i2s_out_program_get_default_config(uint offset)
8181
}
8282
#endif
8383

84+
// -------------------- //
85+
// pio_i2s_out_slave_16 //
86+
// -------------------- //
87+
88+
#define pio_i2s_out_slave_16_wrap_target 3
89+
#define pio_i2s_out_slave_16_wrap 12
90+
#define pio_i2s_out_slave_16_pio_version 0
91+
92+
static const uint16_t pio_i2s_out_slave_16_program_instructions[] = {
93+
0x2020, // 0: wait 0 pin, 0
94+
0x20a0, // 1: wait 1 pin, 0
95+
0x00c0, // 2: jmp pin, 0
96+
// .wrap_target
97+
0x8080, // 3: pull noblock
98+
0x2020, // 4: wait 0 pin, 0
99+
0x6001, // 5: out pins, 1
100+
0x20a0, // 6: wait 1 pin, 0
101+
0x00c9, // 7: jmp pin, 9
102+
0x0004, // 8: jmp 4
103+
0x2020, // 9: wait 0 pin, 0
104+
0x6001, // 10: out pins, 1
105+
0x20a0, // 11: wait 1 pin, 0
106+
0x00c9, // 12: jmp pin, 9
107+
// .wrap
108+
};
109+
110+
#if !PICO_NO_HARDWARE
111+
static const struct pio_program pio_i2s_out_slave_16_program = {
112+
.instructions = pio_i2s_out_slave_16_program_instructions,
113+
.length = 13,
114+
.origin = -1,
115+
.pio_version = pio_i2s_out_slave_16_pio_version,
116+
#if PICO_PIO_VERSION > 0
117+
.used_gpio_ranges = 0x0
118+
#endif
119+
};
120+
121+
static inline pio_sm_config pio_i2s_out_slave_16_program_get_default_config(uint offset) {
122+
pio_sm_config c = pio_get_default_sm_config();
123+
sm_config_set_wrap(&c, offset + pio_i2s_out_slave_16_wrap_target, offset + pio_i2s_out_slave_16_wrap);
124+
return c;
125+
}
126+
#endif
127+
128+
// -------------------- //
129+
// pio_i2s_out_slave_32 //
130+
// -------------------- //
131+
132+
#define pio_i2s_out_slave_32_wrap_target 3
133+
#define pio_i2s_out_slave_32_wrap 13
134+
#define pio_i2s_out_slave_32_pio_version 0
135+
136+
static const uint16_t pio_i2s_out_slave_32_program_instructions[] = {
137+
0x2020, // 0: wait 0 pin, 0
138+
0x20a0, // 1: wait 1 pin, 0
139+
0x00c0, // 2: jmp pin, 0
140+
// .wrap_target
141+
0x8080, // 3: pull noblock
142+
0x2020, // 4: wait 0 pin, 0
143+
0x6001, // 5: out pins, 1
144+
0x20a0, // 6: wait 1 pin, 0
145+
0x00ca, // 7: jmp pin, 10
146+
0x0004, // 8: jmp 4
147+
0x8080, // 9: pull noblock
148+
0x2020, // 10: wait 0 pin, 0
149+
0x6001, // 11: out pins, 1
150+
0x20a0, // 12: wait 1 pin, 0
151+
0x00ca, // 13: jmp pin, 10
152+
// .wrap
153+
};
154+
155+
#if !PICO_NO_HARDWARE
156+
static const struct pio_program pio_i2s_out_slave_32_program = {
157+
.instructions = pio_i2s_out_slave_32_program_instructions,
158+
.length = 14,
159+
.origin = -1,
160+
.pio_version = pio_i2s_out_slave_32_pio_version,
161+
#if PICO_PIO_VERSION > 0
162+
.used_gpio_ranges = 0x0
163+
#endif
164+
};
165+
166+
static inline pio_sm_config pio_i2s_out_slave_32_program_get_default_config(uint offset) {
167+
pio_sm_config c = pio_get_default_sm_config();
168+
sm_config_set_wrap(&c, offset + pio_i2s_out_slave_32_wrap_target, offset + pio_i2s_out_slave_32_wrap);
169+
return c;
170+
}
171+
#endif
172+
84173
// ---------------- //
85174
// pio_i2s_out_swap //
86175
// ---------------- //
@@ -535,15 +624,31 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
535624
sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits);
536625
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
537626
pio_sm_init(pio, sm, offset, &sm_config);
538-
//uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
539-
//pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
540-
//pio_sm_set_pins(pio, sm, 0); // clear pins
541627
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
542628
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, true);
543629
pio_sm_set_set_pins(pio, sm, data_pin, 1);
544630
pio_sm_set_set_pins(pio, sm, clock_pin_base, 2);
545631
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
546632
}
633+
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) {
634+
pio_gpio_init(pio, data_pin);
635+
pio_gpio_init(pio, clock_pin_base);
636+
pio_gpio_init(pio, clock_pin_base + 1);
637+
// 16-bits does a pull every L+R frame. 24/32 bits do a pull every L or R side
638+
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);
639+
sm_config_set_out_pins(&sm_config, data_pin, 1);
640+
sm_config_set_in_pins(&sm_config, clock_pin_base);
641+
sm_config_set_in_pin_count(&sm_config, 2); // BLCK and LRCLK
642+
sm_config_set_jmp_pin(&sm_config, clock_pin_base + 1);
643+
sm_config_set_out_shift(&sm_config, false, false, (bits <= 16) ? 2 * bits : bits);
644+
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
645+
pio_sm_init(pio, sm, offset, &sm_config);
646+
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
647+
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, false);
648+
pio_sm_set_out_pins(pio, sm, data_pin, 1);
649+
pio_sm_set_in_pins(pio, sm, clock_pin_base);
650+
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 0));
651+
}
547652
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) {
548653
pio_gpio_init(pio, data_pin);
549654
pio_gpio_init(pio, clock_pin_base);

0 commit comments

Comments
 (0)