Skip to content

Commit cc5d177

Browse files
authored
Add MCLK support for I2S, optimize clocks for jitter-free playback (#1555)
Fixes #1065
1 parent 0f437e4 commit cc5d177

File tree

7 files changed

+238
-8
lines changed

7 files changed

+238
-8
lines changed

docs/i2s.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ bool setDATA(pin_size_t pin)
4040
Sets the DOUT or DIN pin of the I2S device. Any pin may be used.
4141
Call before ``I2S::begin()``
4242

43+
bool setMCLK(pin_size_t pin)
44+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45+
Sets the MCLK pin of the I2S device and enables MCLK output. Any pin may be used.
46+
Call before ``I2S::begin()``
47+
48+
bool setMCLKmult(int mult)
49+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50+
Sets the sample rate to MCLK multiplier value. Only multiples of 64 are valid.
51+
Call before ``I2S::begin()``
52+
4353
bool setBitsPerSample(int bits)
4454
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4555
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
5868
already running. May be called after ``I2S::begin()`` to change the
5969
sample rate on-the-fly.
6070

71+
bool setSysClk(int samplerate)
72+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
73+
Changes the PICO system clock to optimise for the desired samplerate.
74+
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.
75+
Note that using ``setSysClk()`` may affect the timing of other sysclk-dependent functions.
76+
Should be called before any I2S functions and any other sysclk dependent initialisations.
77+
6178
bool setLSBJFormat()
6279
~~~~~~~~~~~~~~~~~~~~
6380
Enables LSB-J format for I2S output. In this mode the MSB comes out at the
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
This example demonstrates I2S output with the optional MCLK signal.
3+
Note: System clock sppeds used here may not be compatible with all other libraries
4+
requiring specific sys_clks, particularly Pico PIO USB.
5+
6+
Original 17 November 2016
7+
by Sandeep Mistry
8+
modified for RP2040 by Earle F. Philhower, III <[email protected]>
9+
10+
bool setBCLK(pin_size_t pin);
11+
- This assigns two adjacent pins - the pin after this one (one greater)
12+
is the WS (word select) signal, which toggles before the sample for
13+
each channel is sent
14+
15+
bool setDATA(pin_size_t pin);
16+
- Sets the DOUT pin, can be any valid GPIO pin
17+
18+
19+
modified for MCLK by Richard Palmer June 2023
20+
*/
21+
22+
#include <I2S.h>
23+
24+
// Create the I2S port using a PIO state machine
25+
I2S i2s(OUTPUT);
26+
27+
// GPIO pin numbers
28+
#define pDOUT 19
29+
#define pBCLK 20
30+
#define pWS (pBCLK+1)
31+
#define pMCLK 22 // optional MCLK pin
32+
33+
const int frequency = 440; // frequency of square wave in Hz
34+
const int sampleRate = 16000; // minimum for many i2s DACs
35+
const int bitsPerSample = 16;
36+
const int amplitude = 1 << (bitsPerSample - 2); // amplitude of square wave = 1/2 of maximum
37+
38+
#define MCLK_MUL 256 // depends on audio hardware. Suits common hardware.
39+
40+
const int halfWavelength = sampleRate / (2 * frequency); // half wavelength of square wave
41+
42+
int16_t sample = amplitude; // current sample value
43+
int count = 0;
44+
45+
void setup() {
46+
pinMode(LED_BUILTIN, OUTPUT);
47+
digitalWrite(LED_BUILTIN, 1);
48+
49+
// set the system clock to a compatible value to the samplerate
50+
i2s.setSysClk(sampleRate); // best to do this before starting anything clock-dependent
51+
52+
Serial.begin(115200);
53+
while (!Serial) {
54+
delay(10);
55+
}
56+
Serial.println("I2S with MCLK - square wave");
57+
58+
i2s.setBCLK(pBCLK);
59+
i2s.setDATA(pDOUT);
60+
i2s.setMCLK(pMCLK); // must be run before setFrequency() and i2s.begin()
61+
i2s.setMCLKmult(MCLK_MUL); // also before i2s.begin()
62+
i2s.setBitsPerSample(16);
63+
64+
// start I2S at the sample rate with 16-bits per sample
65+
if (!i2s.begin(sampleRate)) {
66+
Serial.println("Failed to initialize I2S!");
67+
while (100); // do nothing
68+
}
69+
70+
}
71+
72+
void loop() {
73+
if (count % halfWavelength == 0) {
74+
// invert the sample every half wavelength count multiple to generate square wave
75+
sample = -1 * sample;
76+
}
77+
78+
// write the same sample twice, once for left and once for the right channel
79+
i2s.write(sample);
80+
i2s.write(sample);
81+
82+
// increment the counter for the next sample
83+
count++;
84+
}

libraries/I2S/keywords.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ end KEYWORD2
1616

1717
setBCLK KEYWORD2
1818
setDATA KEYWORD2
19+
setMCLK KEYWORD2
1920
setBitsPerSample KEYWORD2
2021
setFrequency KEYWORD2
2122
setBuffers KEYWORD2
2223
setLSBJFormat KEYWORD2
2324
swapClocks KEYWORD2
25+
setMCLKmult KEYWORD2
26+
setSysClk KEYWORD2
2427

2528
read8 KEYWORD2
2629
read16 KEYWORD2

libraries/I2S/src/I2S.cpp

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <Arduino.h>
2222
#include "I2S.h"
2323
#include "pio_i2s.pio.h"
24+
#include <pico/stdlib.h>
2425

2526

2627
I2S::I2S(PinMode direction) {
@@ -30,6 +31,8 @@ I2S::I2S(PinMode direction) {
3031
_isOutput = direction == OUTPUT;
3132
_pinBCLK = 26;
3233
_pinDOUT = 28;
34+
_pinMCLK = 25;
35+
_MCLKenabled = false;
3336
#ifdef PIN_I2S_BCLK
3437
_pinBCLK = PIN_I2S_BCLK;
3538
#endif
@@ -53,6 +56,7 @@ I2S::I2S(PinMode direction) {
5356
_silenceSample = 0;
5457
_isLSBJ = false;
5558
_swapClocks = false;
59+
_multMCLK = 256;
5660
}
5761

5862
I2S::~I2S() {
@@ -67,6 +71,14 @@ bool I2S::setBCLK(pin_size_t pin) {
6771
return true;
6872
}
6973

74+
75+
bool I2S::setMCLK(pin_size_t pin) {
76+
if (_running || (pin > 28)) {
77+
return false;
78+
}
79+
_pinMCLK = pin;
80+
return true;
81+
}
7082
bool I2S::setDATA(pin_size_t pin) {
7183
if (_running || (pin > 29)) {
7284
return false;
@@ -96,12 +108,41 @@ bool I2S::setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample)
96108
bool I2S::setFrequency(int newFreq) {
97109
_freq = newFreq;
98110
if (_running) {
99-
float bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
100-
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
111+
if (_MCLKenabled) {
112+
int bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
113+
pio_sm_set_clkdiv_int_frac(_pio, _sm, clock_get_hz(clk_sys) / bitClk, 0);
114+
} else {
115+
float bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
116+
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
117+
}
101118
}
102119
return true;
103120
}
104121

122+
bool I2S::setSysClk(int samplerate) { // optimise sys_clk for desired samplerate
123+
if (samplerate % 11025 == 0) {
124+
set_sys_clock_khz(I2SSYSCLK_44_1, false); // 147.6 unsuccessful - no I2S no USB
125+
return true;
126+
}
127+
if (samplerate % 8000 == 0) {
128+
set_sys_clock_khz(I2SSYSCLK_8, false);
129+
return true;
130+
}
131+
return false;
132+
}
133+
134+
bool I2S::setMCLKmult(int mult) {
135+
if (_running || !_isOutput) {
136+
return false;
137+
}
138+
if ((mult % 64) == 0) {
139+
_MCLKenabled = true;
140+
_multMCLK = mult;
141+
return true;
142+
}
143+
return false;
144+
}
145+
105146
bool I2S::setLSBJFormat() {
106147
if (_running || !_isOutput) {
107148
return false;
@@ -136,6 +177,16 @@ void I2S::onReceive(void(*fn)(void)) {
136177
}
137178
}
138179

180+
void I2S::MCLKbegin() {
181+
int off = 0;
182+
_i2sMCLK = new PIOProgram(&pio_i2s_mclk_program);
183+
_i2sMCLK->prepare(&_pioMCLK, &_smMCLK, &off); // not sure how to use the same PIO
184+
pio_i2s_MCLK_program_init(_pioMCLK, _smMCLK, off, _pinMCLK);
185+
int mClk = _multMCLK * _freq * 2.0 /* edges per clock */;
186+
pio_sm_set_clkdiv_int_frac(_pioMCLK, _smMCLK, clock_get_hz(clk_sys) / mClk, 0);
187+
pio_sm_set_enabled(_pioMCLK, _smMCLK, true);
188+
}
189+
139190
bool I2S::begin() {
140191
_running = true;
141192
_hasPeeked = false;
@@ -162,6 +213,9 @@ bool I2S::begin() {
162213
pio_i2s_in_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
163214
}
164215
setFrequency(_freq);
216+
if (_MCLKenabled) {
217+
MCLKbegin();
218+
}
165219
if (_bps == 8) {
166220
uint8_t a = _silenceSample & 0xff;
167221
_silenceSample = (a << 24) | (a << 16) | (a << 8) | a;
@@ -189,6 +243,11 @@ bool I2S::begin() {
189243

190244
void I2S::end() {
191245
if (_running) {
246+
if (_MCLKenabled) {
247+
pio_sm_set_enabled(_pioMCLK, _smMCLK, false);
248+
delete _i2sMCLK;
249+
_i2sMCLK = nullptr;
250+
}
192251
pio_sm_set_enabled(_pio, _sm, false);
193252
_running = false;
194253
delete _arb;
@@ -204,8 +263,13 @@ int I2S::available() {
204263
} else {
205264
auto avail = _arb->available();
206265
avail *= 4; // 4 samples per 32-bits
207-
if (_bps < 24 && !_isOutput) {
208-
avail += _isHolding / 8;
266+
if (_bps < 24) {
267+
if (_isOutput) {
268+
// 16- and 8-bit can have holding bytes available
269+
avail += (32 - _isHolding) / 8;
270+
} else {
271+
avail += _isHolding / 8;
272+
}
209273
}
210274
return avail;
211275
}

libraries/I2S/src/I2S.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ class I2S : public Stream {
3030

3131
bool setBCLK(pin_size_t pin);
3232
bool setDATA(pin_size_t pin);
33+
bool setMCLK(pin_size_t pin);
3334
bool setBitsPerSample(int bps);
3435
bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0);
3536
bool setFrequency(int newFreq);
3637
bool setLSBJFormat();
3738
bool swapClocks();
39+
bool setMCLKmult(int mult);
40+
bool setSysClk(int samplerate);
3841

3942
bool begin(long sampleRate) {
4043
setFrequency(sampleRate);
@@ -110,14 +113,17 @@ class I2S : public Stream {
110113
private:
111114
pin_size_t _pinBCLK;
112115
pin_size_t _pinDOUT;
116+
pin_size_t _pinMCLK;
113117
int _bps;
114118
int _freq;
119+
int _multMCLK;
115120
size_t _buffers;
116121
size_t _bufferWords;
117122
int32_t _silenceSample;
118123
bool _isLSBJ;
119124
bool _isOutput;
120125
bool _swapClocks;
126+
bool _MCLKenabled;
121127

122128
bool _running;
123129

@@ -132,9 +138,14 @@ class I2S : public Stream {
132138
int _isHolding = 0;
133139

134140
void (*_cb)();
141+
void MCLKbegin();
135142

136143
AudioBufferManager *_arb;
137144
PIOProgram *_i2s;
138-
PIO _pio;
139-
int _sm;
145+
PIOProgram *_i2sMCLK;
146+
PIO _pio, _pioMCLK;
147+
int _sm, _smMCLK;
148+
149+
const int I2SSYSCLK_44_1 = 135600; // 44.1, 88.2 kHz sample rates
150+
const int I2SSYSCLK_8 = 147600; // 8k, 16, 32, 48, 96, 192 kHz
140151
};

libraries/I2S/src/pio_i2s.pio

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@
1919
; License along with this library; if not, write to the Free Software
2020
; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2121

22-
22+
.program pio_i2s_mclk
23+
; PIO SM clock is MCLK x 2
24+
; MCLK/BCLK sync is maintained by ensuring IN/OUT PIO SM clock dividers
25+
; are x128, 384, 256, 512 or 768 multiples of MCLK SM divider as required.
26+
27+
loop_mclk:
28+
set pins, 1 ; toggle mclk
29+
set pins, 0
30+
; Loop back to beginning...
31+
2332
.program pio_i2s_out
2433
.side_set 2 ; 0 = bclk, 1=wclk
2534

@@ -157,6 +166,14 @@ right:
157166

158167
% c-sdk {
159168

169+
static inline void pio_i2s_MCLK_program_init(PIO pio, uint sm, uint offset, uint MCLK_pin) {
170+
pio_gpio_init(pio, MCLK_pin);
171+
pio_sm_set_consecutive_pindirs(pio, sm, MCLK_pin, 1, true);
172+
pio_sm_config sm_config = pio_i2s_mclk_program_get_default_config(offset);
173+
sm_config_set_set_pins(&sm_config, MCLK_pin, 1);
174+
pio_sm_init(pio, sm, offset, &sm_config);
175+
}
176+
160177
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) {
161178
pio_gpio_init(pio, data_pin);
162179
pio_gpio_init(pio, clock_pin_base);

0 commit comments

Comments
 (0)