Add TDM support to I2S (#1673)

Fixes #1066

Implements a simple TDM mode for the I2S output object.
This commit is contained in:
Earle F. Philhower, III 2023-08-30 08:28:34 -07:00 committed by GitHub
parent 1393811525
commit e9daaa3589
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 231 additions and 5 deletions

View file

@ -81,6 +81,15 @@ Enables LSB-J format for I2S output. In this mode the MSB comes out at the
same time as the LRCLK changes, and not the normal 1-cycle delay. Useful for
DAC chips like the PT8211.
bool setTDMFormat()
~~~~~~~~~~~~~~~~~~~
Enabled TDM formatted multi-channel output. Be sure to set the number of channels to
the expected value (8 normally) and the bits per sample to 32.
bool setTDMChannels(int channels)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets the number of TDM channels between frame syncs. Generally should be set to 8.
bool swapClocks()
~~~~~~~~~~~~~~~~~
Certain boards are hardwired with the WCLK before the BCLK, instead of the normal

View file

@ -0,0 +1,49 @@
/*
This example just sends out TDM data on 8 channels with their value
equal to the channel number.
Released to the public domain by Earle F. Philhower, III <earlephilhower@yahoo.com>
*/
#include <I2S.h>
// Create the I2S port using a PIO state machine
I2S i2s(OUTPUT);
// GPIO pin numbers
#define pBCLK 20
#define pWS (pBCLK+1)
#define pDOUT 22
const int sampleRate = 1000;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, 1);
Serial.begin(115200);
Serial.println("I2S TDM test");
i2s.setBCLK(pBCLK);
i2s.setDATA(pDOUT);
i2s.setBitsPerSample(32);
i2s.setTDMFormat();
i2s.setTDMChannels(8);
// start I2S at the sample rate with 16-bits per sample
if (!i2s.begin(sampleRate)) {
Serial.println("Failed to initialize I2S!");
while (1); // do nothing
}
}
void loop() {
uint32_t x = 0;
while (1) {
i2s.write(x & 0x07);
if (!(x % 10000)) {
Serial.println(x);
}
x++;
}
}

View file

@ -21,6 +21,8 @@ setBitsPerSample KEYWORD2
setFrequency KEYWORD2
setBuffers KEYWORD2
setLSBJFormat KEYWORD2
setTDMFormat KEYWORD2
setTDMChannels KEYWORD2
swapClocks KEYWORD2
setMCLKmult KEYWORD2
setSysClk KEYWORD2

View file

@ -55,6 +55,8 @@ I2S::I2S(PinMode direction) {
_bufferWords = 0;
_silenceSample = 0;
_isLSBJ = false;
_isTDM = false;
_tdmChannels = 8;
_swapClocks = false;
_multMCLK = 256;
}
@ -109,10 +111,10 @@ bool I2S::setFrequency(int newFreq) {
_freq = newFreq;
if (_running) {
if (_MCLKenabled) {
int bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
int bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * 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 * 2.0 /* channels */ * 2.0 /* edges per clock */;
float bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * 2.0 /* edges per clock */;
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
}
}
@ -151,6 +153,22 @@ bool I2S::setLSBJFormat() {
return true;
}
bool I2S::setTDMFormat() {
if (_running || !_isOutput) {
return false;
}
_isTDM = true;
return true;
}
bool I2S::setTDMChannels(int channels) {
if (_running || !_isOutput) {
return false;
}
_tdmChannels = channels;
return true;
}
bool I2S::swapClocks() {
if (_running || !_isOutput) {
return false;
@ -193,9 +211,9 @@ bool I2S::begin() {
_isHolding = 0;
int off = 0;
if (!_swapClocks) {
_i2s = new PIOProgram(_isOutput ? (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program) : &pio_i2s_in_program);
_i2s = new PIOProgram(_isOutput ? (_isTDM ? &pio_tdm_out_program : (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program)) : &pio_i2s_in_program);
} else {
_i2s = new PIOProgram(_isOutput ? (_isLSBJ ? &pio_lsbj_out_swap_program : &pio_i2s_out_swap_program) : &pio_i2s_in_swap_program);
_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);
}
if (!_i2s->prepare(&_pio, &_sm, &off)) {
_running = false;
@ -204,7 +222,9 @@ bool I2S::begin() {
return false;
}
if (_isOutput) {
if (_isLSBJ) {
if (_isTDM) {
pio_tdm_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks, _tdmChannels);
} 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);

View file

@ -35,6 +35,8 @@ public:
bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0);
bool setFrequency(int newFreq);
bool setLSBJFormat();
bool setTDMFormat();
bool setTDMChannels(int channels);
bool swapClocks();
bool setMCLKmult(int mult);
bool setSysClk(int samplerate);
@ -121,6 +123,8 @@ private:
size_t _bufferWords;
int32_t _silenceSample;
bool _isLSBJ;
bool _isTDM;
int _tdmChannels;
bool _isOutput;
bool _swapClocks;
bool _MCLKenabled;

View file

@ -72,6 +72,38 @@ right:
; Loop back to beginning...
.program pio_tdm_out
.side_set 2 ; 0 = bclk, 1 = wclk
; The C code should place (number of bits - 1) in Y and update SHIFTCTRL
; to be 32 (as per the TDM specs)
; +----- WCLK
; |+---- BCLK
mov x, y side 0b11
bitloop:
out pins, 1 side 0b00
jmp x-- bitloop side 0b01
lastbit:
out pins, 1 side 0b10
; Loop back to the beginning
.program pio_tdm_out_swap
.side_set 2 ; 0 = wclk, 1 = bclk
; The C code should place (number of bits - 1) in Y and update SHIFTCTRL
; to be 32 (as per the TDM specs)
; +----- BCLK
; |+---- WCLK
mov x, y side 0b11
bitloop:
out pins, 1 side 0b00
jmp x-- bitloop side 0b10
lastbit:
out pins, 1 side 0b01
; Loop back to the beginning
.program pio_lsbj_out
.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
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
}
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);
pio_gpio_init(pio, clock_pin_base + 1);
pio_sm_config sm_config = swap ? pio_tdm_out_swap_program_get_default_config(offset) : pio_tdm_out_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, data_pin, 1);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, true, 32);
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
// Can't set constant > 31, so push and pop/mov
pio_sm_put_blocking(pio, sm, bits * channels - 2);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_mov(pio_y, pio_osr));
// 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
pio_sm_exec(pio, sm, pio_encode_out(pio_osr, 32));
}
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) {
pio_gpio_init(pio, data_pin);
pio_gpio_init(pio, clock_pin_base);

View file

@ -106,6 +106,68 @@ static inline pio_sm_config pio_i2s_out_swap_program_get_default_config(uint off
}
#endif
// ----------- //
// pio_tdm_out //
// ----------- //
#define pio_tdm_out_wrap_target 0
#define pio_tdm_out_wrap 3
static const uint16_t pio_tdm_out_program_instructions[] = {
// .wrap_target
0xb822, // 0: mov x, y side 3
0x6001, // 1: out pins, 1 side 0
0x0841, // 2: jmp x--, 1 side 1
0x7001, // 3: out pins, 1 side 2
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pio_tdm_out_program = {
.instructions = pio_tdm_out_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config pio_tdm_out_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pio_tdm_out_wrap_target, offset + pio_tdm_out_wrap);
sm_config_set_sideset(&c, 2, false, false);
return c;
}
#endif
// ---------------- //
// pio_tdm_out_swap //
// ---------------- //
#define pio_tdm_out_swap_wrap_target 0
#define pio_tdm_out_swap_wrap 3
static const uint16_t pio_tdm_out_swap_program_instructions[] = {
// .wrap_target
0xb822, // 0: mov x, y side 3
0x6001, // 1: out pins, 1 side 0
0x1041, // 2: jmp x--, 1 side 2
0x6801, // 3: out pins, 1 side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pio_tdm_out_swap_program = {
.instructions = pio_tdm_out_swap_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config pio_tdm_out_swap_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pio_tdm_out_swap_wrap_target, offset + pio_tdm_out_swap_wrap);
sm_config_set_sideset(&c, 2, false, false);
return c;
}
#endif
// ------------ //
// pio_lsbj_out //
// ------------ //
@ -267,6 +329,26 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
pio_sm_set_pins(pio, sm, 0); // clear pins
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
}
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);
pio_gpio_init(pio, clock_pin_base + 1);
pio_sm_config sm_config = swap ? pio_tdm_out_swap_program_get_default_config(offset) : pio_tdm_out_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, data_pin, 1);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, true, 32);
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
// Can't set constant > 31, so push and pop/mov
pio_sm_put_blocking(pio, sm, bits * channels - 2);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_mov(pio_y, pio_osr));
// 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
pio_sm_exec(pio, sm, pio_encode_out(pio_osr, 32));
}
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) {
pio_gpio_init(pio, data_pin);
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
}
#endif