From e9daaa3589db23a44a979ed7e98e5db09592d8cd Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 30 Aug 2023 08:28:34 -0700 Subject: [PATCH] Add TDM support to I2S (#1673) Fixes #1066 Implements a simple TDM mode for the I2S output object. --- docs/i2s.rst | 9 ++++ libraries/I2S/examples/TDM/TDM.ino | 49 ++++++++++++++++++ libraries/I2S/keywords.txt | 2 + libraries/I2S/src/I2S.cpp | 30 +++++++++-- libraries/I2S/src/I2S.h | 4 ++ libraries/I2S/src/pio_i2s.pio | 59 +++++++++++++++++++++ libraries/I2S/src/pio_i2s.pio.h | 83 ++++++++++++++++++++++++++++++ 7 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 libraries/I2S/examples/TDM/TDM.ino diff --git a/docs/i2s.rst b/docs/i2s.rst index 22b7dc2..2aadcf1 100644 --- a/docs/i2s.rst +++ b/docs/i2s.rst @@ -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 diff --git a/libraries/I2S/examples/TDM/TDM.ino b/libraries/I2S/examples/TDM/TDM.ino new file mode 100644 index 0000000..91c9edf --- /dev/null +++ b/libraries/I2S/examples/TDM/TDM.ino @@ -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 +*/ + +#include + +// 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++; + } +} diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt index 3f8260a..1b1f91b 100644 --- a/libraries/I2S/keywords.txt +++ b/libraries/I2S/keywords.txt @@ -21,6 +21,8 @@ setBitsPerSample KEYWORD2 setFrequency KEYWORD2 setBuffers KEYWORD2 setLSBJFormat KEYWORD2 +setTDMFormat KEYWORD2 +setTDMChannels KEYWORD2 swapClocks KEYWORD2 setMCLKmult KEYWORD2 setSysClk KEYWORD2 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index d30bdd9..a9323fb 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -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); diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 44ba569..b8bca22 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -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; diff --git a/libraries/I2S/src/pio_i2s.pio b/libraries/I2S/src/pio_i2s.pio index 5d5be86..c4bedf3 100644 --- a/libraries/I2S/src/pio_i2s.pio +++ b/libraries/I2S/src/pio_i2s.pio @@ -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); diff --git a/libraries/I2S/src/pio_i2s.pio.h b/libraries/I2S/src/pio_i2s.pio.h index a93bb2f..9400c24 100644 --- a/libraries/I2S/src/pio_i2s.pio.h +++ b/libraries/I2S/src/pio_i2s.pio.h @@ -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 +