Add TDM support to I2S (#1673)
Fixes #1066 Implements a simple TDM mode for the I2S output object.
This commit is contained in:
parent
1393811525
commit
e9daaa3589
7 changed files with 231 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
49
libraries/I2S/examples/TDM/TDM.ino
Normal file
49
libraries/I2S/examples/TDM/TDM.ino
Normal 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++;
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,8 @@ setBitsPerSample KEYWORD2
|
|||
setFrequency KEYWORD2
|
||||
setBuffers KEYWORD2
|
||||
setLSBJFormat KEYWORD2
|
||||
setTDMFormat KEYWORD2
|
||||
setTDMChannels KEYWORD2
|
||||
swapClocks KEYWORD2
|
||||
setMCLKmult KEYWORD2
|
||||
setSysClk KEYWORD2
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue