Skip to content

Commit e9daaa3

Browse files
Add TDM support to I2S (#1673)
Fixes #1066 Implements a simple TDM mode for the I2S output object.
1 parent 1393811 commit e9daaa3

File tree

7 files changed

+231
-5
lines changed

7 files changed

+231
-5
lines changed

docs/i2s.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ Enables LSB-J format for I2S output. In this mode the MSB comes out at the
8181
same time as the LRCLK changes, and not the normal 1-cycle delay. Useful for
8282
DAC chips like the PT8211.
8383

84+
bool setTDMFormat()
85+
~~~~~~~~~~~~~~~~~~~
86+
Enabled TDM formatted multi-channel output. Be sure to set the number of channels to
87+
the expected value (8 normally) and the bits per sample to 32.
88+
89+
bool setTDMChannels(int channels)
90+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
91+
Sets the number of TDM channels between frame syncs. Generally should be set to 8.
92+
8493
bool swapClocks()
8594
~~~~~~~~~~~~~~~~~
8695
Certain boards are hardwired with the WCLK before the BCLK, instead of the normal

libraries/I2S/examples/TDM/TDM.ino

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
This example just sends out TDM data on 8 channels with their value
3+
equal to the channel number.
4+
Released to the public domain by Earle F. Philhower, III <[email protected]>
5+
*/
6+
7+
#include <I2S.h>
8+
9+
// Create the I2S port using a PIO state machine
10+
I2S i2s(OUTPUT);
11+
12+
// GPIO pin numbers
13+
#define pBCLK 20
14+
#define pWS (pBCLK+1)
15+
#define pDOUT 22
16+
17+
const int sampleRate = 1000;
18+
19+
void setup() {
20+
pinMode(LED_BUILTIN, OUTPUT);
21+
digitalWrite(LED_BUILTIN, 1);
22+
23+
Serial.begin(115200);
24+
Serial.println("I2S TDM test");
25+
26+
i2s.setBCLK(pBCLK);
27+
i2s.setDATA(pDOUT);
28+
i2s.setBitsPerSample(32);
29+
i2s.setTDMFormat();
30+
i2s.setTDMChannels(8);
31+
32+
// start I2S at the sample rate with 16-bits per sample
33+
if (!i2s.begin(sampleRate)) {
34+
Serial.println("Failed to initialize I2S!");
35+
while (1); // do nothing
36+
}
37+
38+
}
39+
40+
void loop() {
41+
uint32_t x = 0;
42+
while (1) {
43+
i2s.write(x & 0x07);
44+
if (!(x % 10000)) {
45+
Serial.println(x);
46+
}
47+
x++;
48+
}
49+
}

libraries/I2S/keywords.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ setBitsPerSample KEYWORD2
2121
setFrequency KEYWORD2
2222
setBuffers KEYWORD2
2323
setLSBJFormat KEYWORD2
24+
setTDMFormat KEYWORD2
25+
setTDMChannels KEYWORD2
2426
swapClocks KEYWORD2
2527
setMCLKmult KEYWORD2
2628
setSysClk KEYWORD2

libraries/I2S/src/I2S.cpp

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ I2S::I2S(PinMode direction) {
5555
_bufferWords = 0;
5656
_silenceSample = 0;
5757
_isLSBJ = false;
58+
_isTDM = false;
59+
_tdmChannels = 8;
5860
_swapClocks = false;
5961
_multMCLK = 256;
6062
}
@@ -109,10 +111,10 @@ bool I2S::setFrequency(int newFreq) {
109111
_freq = newFreq;
110112
if (_running) {
111113
if (_MCLKenabled) {
112-
int bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
114+
int bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * 2.0 /* edges per clock */;
113115
pio_sm_set_clkdiv_int_frac(_pio, _sm, clock_get_hz(clk_sys) / bitClk, 0);
114116
} else {
115-
float bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
117+
float bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * 2.0 /* edges per clock */;
116118
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
117119
}
118120
}
@@ -151,6 +153,22 @@ bool I2S::setLSBJFormat() {
151153
return true;
152154
}
153155

156+
bool I2S::setTDMFormat() {
157+
if (_running || !_isOutput) {
158+
return false;
159+
}
160+
_isTDM = true;
161+
return true;
162+
}
163+
164+
bool I2S::setTDMChannels(int channels) {
165+
if (_running || !_isOutput) {
166+
return false;
167+
}
168+
_tdmChannels = channels;
169+
return true;
170+
}
171+
154172
bool I2S::swapClocks() {
155173
if (_running || !_isOutput) {
156174
return false;
@@ -193,9 +211,9 @@ bool I2S::begin() {
193211
_isHolding = 0;
194212
int off = 0;
195213
if (!_swapClocks) {
196-
_i2s = new PIOProgram(_isOutput ? (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program) : &pio_i2s_in_program);
214+
_i2s = new PIOProgram(_isOutput ? (_isTDM ? &pio_tdm_out_program : (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program)) : &pio_i2s_in_program);
197215
} else {
198-
_i2s = new PIOProgram(_isOutput ? (_isLSBJ ? &pio_lsbj_out_swap_program : &pio_i2s_out_swap_program) : &pio_i2s_in_swap_program);
216+
_i2s = new PIOProgram(_isOutput ? (_isTDM ? &pio_tdm_out_swap_program : (_isLSBJ ? &pio_lsbj_out_swap_program : &pio_i2s_out_swap_program)) : &pio_i2s_in_swap_program);
199217
}
200218
if (!_i2s->prepare(&_pio, &_sm, &off)) {
201219
_running = false;
@@ -204,7 +222,9 @@ bool I2S::begin() {
204222
return false;
205223
}
206224
if (_isOutput) {
207-
if (_isLSBJ) {
225+
if (_isTDM) {
226+
pio_tdm_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks, _tdmChannels);
227+
} else if (_isLSBJ) {
208228
pio_lsbj_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
209229
} else {
210230
pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);

libraries/I2S/src/I2S.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class I2S : public Stream {
3535
bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0);
3636
bool setFrequency(int newFreq);
3737
bool setLSBJFormat();
38+
bool setTDMFormat();
39+
bool setTDMChannels(int channels);
3840
bool swapClocks();
3941
bool setMCLKmult(int mult);
4042
bool setSysClk(int samplerate);
@@ -121,6 +123,8 @@ class I2S : public Stream {
121123
size_t _bufferWords;
122124
int32_t _silenceSample;
123125
bool _isLSBJ;
126+
bool _isTDM;
127+
int _tdmChannels;
124128
bool _isOutput;
125129
bool _swapClocks;
126130
bool _MCLKenabled;

libraries/I2S/src/pio_i2s.pio

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,38 @@ right:
7272
; Loop back to beginning...
7373

7474

75+
.program pio_tdm_out
76+
.side_set 2 ; 0 = bclk, 1 = wclk
77+
; The C code should place (number of bits - 1) in Y and update SHIFTCTRL
78+
; to be 32 (as per the TDM specs)
79+
; +----- WCLK
80+
; |+---- BCLK
81+
mov x, y side 0b11
82+
bitloop:
83+
out pins, 1 side 0b00
84+
jmp x-- bitloop side 0b01
85+
86+
lastbit:
87+
out pins, 1 side 0b10
88+
; Loop back to the beginning
89+
90+
91+
.program pio_tdm_out_swap
92+
.side_set 2 ; 0 = wclk, 1 = bclk
93+
; The C code should place (number of bits - 1) in Y and update SHIFTCTRL
94+
; to be 32 (as per the TDM specs)
95+
; +----- BCLK
96+
; |+---- WCLK
97+
mov x, y side 0b11
98+
bitloop:
99+
out pins, 1 side 0b00
100+
jmp x-- bitloop side 0b10
101+
102+
lastbit:
103+
out pins, 1 side 0b01
104+
; Loop back to the beginning
105+
106+
75107

76108
.program pio_lsbj_out
77109
.side_set 2 ; 0 = bclk, 1=wclk
@@ -195,6 +227,33 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
195227
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
196228
}
197229

230+
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) {
231+
pio_gpio_init(pio, data_pin);
232+
pio_gpio_init(pio, clock_pin_base);
233+
pio_gpio_init(pio, clock_pin_base + 1);
234+
235+
pio_sm_config sm_config = swap ? pio_tdm_out_swap_program_get_default_config(offset) : pio_tdm_out_program_get_default_config(offset);
236+
237+
sm_config_set_out_pins(&sm_config, data_pin, 1);
238+
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
239+
sm_config_set_out_shift(&sm_config, false, true, 32);
240+
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
241+
242+
pio_sm_init(pio, sm, offset, &sm_config);
243+
244+
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
245+
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
246+
pio_sm_set_pins(pio, sm, 0); // clear pins
247+
248+
// Can't set constant > 31, so push and pop/mov
249+
pio_sm_put_blocking(pio, sm, bits * channels - 2);
250+
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
251+
pio_sm_exec(pio, sm, pio_encode_mov(pio_y, pio_osr));
252+
// Need to make OSR believe there's nothing left to shift out, or the 1st word will be the count we just passed in, not a sample
253+
pio_sm_exec(pio, sm, pio_encode_out(pio_osr, 32));
254+
}
255+
256+
198257
static inline void pio_lsbj_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) {
199258
pio_gpio_init(pio, data_pin);
200259
pio_gpio_init(pio, clock_pin_base);

libraries/I2S/src/pio_i2s.pio.h

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,68 @@ static inline pio_sm_config pio_i2s_out_swap_program_get_default_config(uint off
106106
}
107107
#endif
108108

109+
// ----------- //
110+
// pio_tdm_out //
111+
// ----------- //
112+
113+
#define pio_tdm_out_wrap_target 0
114+
#define pio_tdm_out_wrap 3
115+
116+
static const uint16_t pio_tdm_out_program_instructions[] = {
117+
// .wrap_target
118+
0xb822, // 0: mov x, y side 3
119+
0x6001, // 1: out pins, 1 side 0
120+
0x0841, // 2: jmp x--, 1 side 1
121+
0x7001, // 3: out pins, 1 side 2
122+
// .wrap
123+
};
124+
125+
#if !PICO_NO_HARDWARE
126+
static const struct pio_program pio_tdm_out_program = {
127+
.instructions = pio_tdm_out_program_instructions,
128+
.length = 4,
129+
.origin = -1,
130+
};
131+
132+
static inline pio_sm_config pio_tdm_out_program_get_default_config(uint offset) {
133+
pio_sm_config c = pio_get_default_sm_config();
134+
sm_config_set_wrap(&c, offset + pio_tdm_out_wrap_target, offset + pio_tdm_out_wrap);
135+
sm_config_set_sideset(&c, 2, false, false);
136+
return c;
137+
}
138+
#endif
139+
140+
// ---------------- //
141+
// pio_tdm_out_swap //
142+
// ---------------- //
143+
144+
#define pio_tdm_out_swap_wrap_target 0
145+
#define pio_tdm_out_swap_wrap 3
146+
147+
static const uint16_t pio_tdm_out_swap_program_instructions[] = {
148+
// .wrap_target
149+
0xb822, // 0: mov x, y side 3
150+
0x6001, // 1: out pins, 1 side 0
151+
0x1041, // 2: jmp x--, 1 side 2
152+
0x6801, // 3: out pins, 1 side 1
153+
// .wrap
154+
};
155+
156+
#if !PICO_NO_HARDWARE
157+
static const struct pio_program pio_tdm_out_swap_program = {
158+
.instructions = pio_tdm_out_swap_program_instructions,
159+
.length = 4,
160+
.origin = -1,
161+
};
162+
163+
static inline pio_sm_config pio_tdm_out_swap_program_get_default_config(uint offset) {
164+
pio_sm_config c = pio_get_default_sm_config();
165+
sm_config_set_wrap(&c, offset + pio_tdm_out_swap_wrap_target, offset + pio_tdm_out_swap_wrap);
166+
sm_config_set_sideset(&c, 2, false, false);
167+
return c;
168+
}
169+
#endif
170+
109171
// ------------ //
110172
// pio_lsbj_out //
111173
// ------------ //
@@ -267,6 +329,26 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
267329
pio_sm_set_pins(pio, sm, 0); // clear pins
268330
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
269331
}
332+
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) {
333+
pio_gpio_init(pio, data_pin);
334+
pio_gpio_init(pio, clock_pin_base);
335+
pio_gpio_init(pio, clock_pin_base + 1);
336+
pio_sm_config sm_config = swap ? pio_tdm_out_swap_program_get_default_config(offset) : pio_tdm_out_program_get_default_config(offset);
337+
sm_config_set_out_pins(&sm_config, data_pin, 1);
338+
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
339+
sm_config_set_out_shift(&sm_config, false, true, 32);
340+
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
341+
pio_sm_init(pio, sm, offset, &sm_config);
342+
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
343+
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
344+
pio_sm_set_pins(pio, sm, 0); // clear pins
345+
// Can't set constant > 31, so push and pop/mov
346+
pio_sm_put_blocking(pio, sm, bits * channels - 2);
347+
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
348+
pio_sm_exec(pio, sm, pio_encode_mov(pio_y, pio_osr));
349+
// Need to make OSR believe there's nothing left to shift out, or the 1st word will be the count we just passed in, not a sample
350+
pio_sm_exec(pio, sm, pio_encode_out(pio_osr, 32));
351+
}
270352
static inline void pio_lsbj_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) {
271353
pio_gpio_init(pio, data_pin);
272354
pio_gpio_init(pio, clock_pin_base);
@@ -299,3 +381,4 @@ static inline void pio_i2s_in_program_init(PIO pio, uint sm, uint offset, uint d
299381
}
300382

301383
#endif
384+

0 commit comments

Comments
 (0)