diff --git a/docs/i2s.rst b/docs/i2s.rst index ab76834f6..22b7dc20f 100644 --- a/docs/i2s.rst +++ b/docs/i2s.rst @@ -40,6 +40,16 @@ bool setDATA(pin_size_t pin) Sets the DOUT or DIN pin of the I2S device. Any pin may be used. Call before ``I2S::begin()`` +bool setMCLK(pin_size_t pin) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sets the MCLK pin of the I2S device and enables MCLK output. Any pin may be used. +Call before ``I2S::begin()`` + +bool setMCLKmult(int mult) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sets the sample rate to MCLK multiplier value. Only multiples of 64 are valid. +Call before ``I2S::begin()`` + bool setBitsPerSample(int bits) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Specify how many bits per audio sample to read or write. Note that @@ -58,6 +68,13 @@ Sets the word clock frequency, but does not start the I2S device if not already running. May be called after ``I2S::begin()`` to change the sample rate on-the-fly. +bool setSysClk(int samplerate) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Changes the PICO system clock to optimise for the desired samplerate. +The clock changes to 147.6 MHz for samplerates that are a multiple of 8 kHz, and 135.6 MHz for multiples of 11.025 kHz. +Note that using ``setSysClk()`` may affect the timing of other sysclk-dependent functions. +Should be called before any I2S functions and any other sysclk dependent initialisations. + bool setLSBJFormat() ~~~~~~~~~~~~~~~~~~~~ Enables LSB-J format for I2S output. In this mode the MSB comes out at the diff --git a/libraries/I2S/examples/SquareWaveMCLK/SquareWaveMCLK.ino b/libraries/I2S/examples/SquareWaveMCLK/SquareWaveMCLK.ino new file mode 100644 index 000000000..f7274fe17 --- /dev/null +++ b/libraries/I2S/examples/SquareWaveMCLK/SquareWaveMCLK.ino @@ -0,0 +1,84 @@ +/* + This example demonstrates I2S output with the optional MCLK signal. + Note: System clock sppeds used here may not be compatible with all other libraries + requiring specific sys_clks, particularly Pico PIO USB. + + Original 17 November 2016 + by Sandeep Mistry + modified for RP2040 by Earle F. Philhower, III + + bool setBCLK(pin_size_t pin); + - This assigns two adjacent pins - the pin after this one (one greater) + is the WS (word select) signal, which toggles before the sample for + each channel is sent + + bool setDATA(pin_size_t pin); + - Sets the DOUT pin, can be any valid GPIO pin + + + modified for MCLK by Richard Palmer June 2023 +*/ + +#include + +// Create the I2S port using a PIO state machine +I2S i2s(OUTPUT); + +// GPIO pin numbers +#define pDOUT 19 +#define pBCLK 20 +#define pWS (pBCLK+1) +#define pMCLK 22 // optional MCLK pin + +const int frequency = 440; // frequency of square wave in Hz +const int sampleRate = 16000; // minimum for many i2s DACs +const int bitsPerSample = 16; +const int amplitude = 1 << (bitsPerSample - 2); // amplitude of square wave = 1/2 of maximum + +#define MCLK_MUL 256 // depends on audio hardware. Suits common hardware. + +const int halfWavelength = sampleRate / (2 * frequency); // half wavelength of square wave + +int16_t sample = amplitude; // current sample value +int count = 0; + +void setup() { + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, 1); + + // set the system clock to a compatible value to the samplerate + i2s.setSysClk(sampleRate); // best to do this before starting anything clock-dependent + + Serial.begin(115200); + while (!Serial) { + delay(10); + } + Serial.println("I2S with MCLK - square wave"); + + i2s.setBCLK(pBCLK); + i2s.setDATA(pDOUT); + i2s.setMCLK(pMCLK); // must be run before setFrequency() and i2s.begin() + i2s.setMCLKmult(MCLK_MUL); // also before i2s.begin() + i2s.setBitsPerSample(16); + + // start I2S at the sample rate with 16-bits per sample + if (!i2s.begin(sampleRate)) { + Serial.println("Failed to initialize I2S!"); + while (100); // do nothing + } + +} + +void loop() { + if (count % halfWavelength == 0) { + // invert the sample every half wavelength count multiple to generate square wave + sample = -1 * sample; + } + + // write the same sample twice, once for left and once for the right channel + i2s.write(sample); + i2s.write(sample); + + // increment the counter for the next sample + count++; +} diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt index d6c4e9433..3f8260abe 100644 --- a/libraries/I2S/keywords.txt +++ b/libraries/I2S/keywords.txt @@ -16,11 +16,14 @@ end KEYWORD2 setBCLK KEYWORD2 setDATA KEYWORD2 +setMCLK KEYWORD2 setBitsPerSample KEYWORD2 setFrequency KEYWORD2 setBuffers KEYWORD2 setLSBJFormat KEYWORD2 swapClocks KEYWORD2 +setMCLKmult KEYWORD2 +setSysClk KEYWORD2 read8 KEYWORD2 read16 KEYWORD2 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 91576c92b..90efa85f3 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -21,6 +21,7 @@ #include #include "I2S.h" #include "pio_i2s.pio.h" +#include I2S::I2S(PinMode direction) { @@ -30,6 +31,8 @@ I2S::I2S(PinMode direction) { _isOutput = direction == OUTPUT; _pinBCLK = 26; _pinDOUT = 28; + _pinMCLK = 25; + _MCLKenabled = false; #ifdef PIN_I2S_BCLK _pinBCLK = PIN_I2S_BCLK; #endif @@ -53,6 +56,7 @@ I2S::I2S(PinMode direction) { _silenceSample = 0; _isLSBJ = false; _swapClocks = false; + _multMCLK = 256; } I2S::~I2S() { @@ -67,6 +71,14 @@ bool I2S::setBCLK(pin_size_t pin) { return true; } + +bool I2S::setMCLK(pin_size_t pin) { + if (_running || (pin > 28)) { + return false; + } + _pinMCLK = pin; + return true; +} bool I2S::setDATA(pin_size_t pin) { if (_running || (pin > 29)) { return false; @@ -96,12 +108,41 @@ bool I2S::setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample) bool I2S::setFrequency(int newFreq) { _freq = newFreq; if (_running) { - float bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */; - pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk); + if (_MCLKenabled) { + int bitClk = _freq * _bps * 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 */; + pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk); + } } return true; } +bool I2S::setSysClk(int samplerate) { // optimise sys_clk for desired samplerate + if (samplerate % 11025 == 0) { + set_sys_clock_khz(I2SSYSCLK_44_1, false); // 147.6 unsuccessful - no I2S no USB + return true; + } + if (samplerate % 8000 == 0) { + set_sys_clock_khz(I2SSYSCLK_8, false); + return true; + } + return false; +} + +bool I2S::setMCLKmult(int mult) { + if (_running || !_isOutput) { + return false; + } + if ((mult % 64) == 0) { + _MCLKenabled = true; + _multMCLK = mult; + return true; + } + return false; +} + bool I2S::setLSBJFormat() { if (_running || !_isOutput) { return false; @@ -136,6 +177,16 @@ void I2S::onReceive(void(*fn)(void)) { } } +void I2S::MCLKbegin() { + int off = 0; + _i2sMCLK = new PIOProgram(&pio_i2s_mclk_program); + _i2sMCLK->prepare(&_pioMCLK, &_smMCLK, &off); // not sure how to use the same PIO + pio_i2s_MCLK_program_init(_pioMCLK, _smMCLK, off, _pinMCLK); + int mClk = _multMCLK * _freq * 2.0 /* edges per clock */; + pio_sm_set_clkdiv_int_frac(_pioMCLK, _smMCLK, clock_get_hz(clk_sys) / mClk, 0); + pio_sm_set_enabled(_pioMCLK, _smMCLK, true); +} + bool I2S::begin() { _running = true; _hasPeeked = false; @@ -162,6 +213,9 @@ bool I2S::begin() { pio_i2s_in_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks); } setFrequency(_freq); + if (_MCLKenabled) { + MCLKbegin(); + } if (_bps == 8) { uint8_t a = _silenceSample & 0xff; _silenceSample = (a << 24) | (a << 16) | (a << 8) | a; @@ -189,6 +243,11 @@ bool I2S::begin() { void I2S::end() { if (_running) { + if (_MCLKenabled) { + pio_sm_set_enabled(_pioMCLK, _smMCLK, false); + delete _i2sMCLK; + _i2sMCLK = nullptr; + } pio_sm_set_enabled(_pio, _sm, false); _running = false; delete _arb; @@ -204,8 +263,13 @@ int I2S::available() { } else { auto avail = _arb->available(); avail *= 4; // 4 samples per 32-bits - if (_bps < 24 && !_isOutput) { - avail += _isHolding / 8; + if (_bps < 24) { + if (_isOutput) { + // 16- and 8-bit can have holding bytes available + avail += (32 - _isHolding) / 8; + } else { + avail += _isHolding / 8; + } } return avail; } diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index c2820bb23..a2f137fa7 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -30,11 +30,14 @@ class I2S : public Stream { bool setBCLK(pin_size_t pin); bool setDATA(pin_size_t pin); + bool setMCLK(pin_size_t pin); bool setBitsPerSample(int bps); bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0); bool setFrequency(int newFreq); bool setLSBJFormat(); bool swapClocks(); + bool setMCLKmult(int mult); + bool setSysClk(int samplerate); bool begin(long sampleRate) { setFrequency(sampleRate); @@ -110,14 +113,17 @@ class I2S : public Stream { private: pin_size_t _pinBCLK; pin_size_t _pinDOUT; + pin_size_t _pinMCLK; int _bps; int _freq; + int _multMCLK; size_t _buffers; size_t _bufferWords; int32_t _silenceSample; bool _isLSBJ; bool _isOutput; bool _swapClocks; + bool _MCLKenabled; bool _running; @@ -132,9 +138,14 @@ class I2S : public Stream { int _isHolding = 0; void (*_cb)(); + void MCLKbegin(); AudioBufferManager *_arb; PIOProgram *_i2s; - PIO _pio; - int _sm; + PIOProgram *_i2sMCLK; + PIO _pio, _pioMCLK; + int _sm, _smMCLK; + + const int I2SSYSCLK_44_1 = 135600; // 44.1, 88.2 kHz sample rates + const int I2SSYSCLK_8 = 147600; // 8k, 16, 32, 48, 96, 192 kHz }; diff --git a/libraries/I2S/src/pio_i2s.pio b/libraries/I2S/src/pio_i2s.pio index 646d94b8a..5d5be8682 100644 --- a/libraries/I2S/src/pio_i2s.pio +++ b/libraries/I2S/src/pio_i2s.pio @@ -19,7 +19,16 @@ ; License along with this library; if not, write to the Free Software ; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - +.program pio_i2s_mclk +; PIO SM clock is MCLK x 2 +; MCLK/BCLK sync is maintained by ensuring IN/OUT PIO SM clock dividers +; are x128, 384, 256, 512 or 768 multiples of MCLK SM divider as required. + +loop_mclk: + set pins, 1 ; toggle mclk + set pins, 0 + ; Loop back to beginning... + .program pio_i2s_out .side_set 2 ; 0 = bclk, 1=wclk @@ -157,6 +166,14 @@ right: % c-sdk { +static inline void pio_i2s_MCLK_program_init(PIO pio, uint sm, uint offset, uint MCLK_pin) { + pio_gpio_init(pio, MCLK_pin); + pio_sm_set_consecutive_pindirs(pio, sm, MCLK_pin, 1, true); + pio_sm_config sm_config = pio_i2s_mclk_program_get_default_config(offset); + sm_config_set_set_pins(&sm_config, MCLK_pin, 1); + pio_sm_init(pio, sm, offset, &sm_config); +} + static inline void pio_i2s_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 c094e5f56..a93bb2f65 100644 --- a/libraries/I2S/src/pio_i2s.pio.h +++ b/libraries/I2S/src/pio_i2s.pio.h @@ -8,6 +8,34 @@ #include "hardware/pio.h" #endif +// ------------ // +// pio_i2s_mclk // +// ------------ // + +#define pio_i2s_mclk_wrap_target 0 +#define pio_i2s_mclk_wrap 1 + +static const uint16_t pio_i2s_mclk_program_instructions[] = { + // .wrap_target + 0xe001, // 0: set pins, 1 + 0xe000, // 1: set pins, 0 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program pio_i2s_mclk_program = { + .instructions = pio_i2s_mclk_program_instructions, + .length = 2, + .origin = -1, +}; + +static inline pio_sm_config pio_i2s_mclk_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + pio_i2s_mclk_wrap_target, offset + pio_i2s_mclk_wrap); + return c; +} +#endif + // ----------- // // pio_i2s_out // // ----------- // @@ -217,6 +245,13 @@ static inline pio_sm_config pio_i2s_in_swap_program_get_default_config(uint offs return c; } +static inline void pio_i2s_MCLK_program_init(PIO pio, uint sm, uint offset, uint MCLK_pin) { + pio_gpio_init(pio, MCLK_pin); + pio_sm_set_consecutive_pindirs(pio, sm, MCLK_pin, 1, true); + pio_sm_config sm_config = pio_i2s_mclk_program_get_default_config(offset); + sm_config_set_set_pins(&sm_config, MCLK_pin, 1); + pio_sm_init(pio, sm, offset, &sm_config); +} static inline void pio_i2s_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); @@ -264,4 +299,3 @@ static inline void pio_i2s_in_program_init(PIO pio, uint sm, uint offset, uint d } #endif -