8000 nrf: Add i2s audio output · adafruit/circuitpython@f38ee42 · GitHub
[go: up one dir, main page]

Skip to content

Commit f38ee42

Browse files
committed
nrf: Add i2s audio output
Testing performed: I used a Particle Xenon with a HDA1334 I2S DAC. I played a variety of mono 16-bit samples at 11025 and 22050Hz nominal bit rates. With this setup, all the 11025Hz samples sound good. I tested play, pause, and loop functionality. During some runs with 22050Hz samples, there were glitches. However, these may have only occurred during runs where I had set breakpoints and watchpoints in gdb. I also tested with a MAX98357A I2S amplifier. On this device, everything sounded "scratchy". I was powering it from 5V and the 5V rail seemed steady, so I don't have an explanation for this. However, I haven't tried it with a SAMD board.
1 parent bd7b03f commit f38ee42

File tree

4 files changed

+297
-9
lines changed

4 files changed

+297
-9
lines changed

ports/nrf/background.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
#include "shared-module/displayio/__init__.h"
3434
#endif
3535

36+
#if CIRCUITPY_AUDIOBUSIO
37+
#include "common-hal/audiobusio/I2SOut.h"
38+
#endif
39+
3640
#if CIRCUITPY_AUDIOPWMIO
3741
#include "common-hal/audiopwmio/PWMAudioOut.h"
3842
#endif
@@ -54,6 +58,10 @@ void run_background_tasks(void) {
5458
#if CIRCUITPY_AUDIOPWMIO
5559
audiopwmout_background();
5660
#endif
61+
#if CIRCUITPY_AUDIOBUSIO
62+
i2s_background();
63+
#endif
64+
5765

5866
#if CIRCUITPY_DISPLAYIO
5967
displayio_background();

ports/nrf/common-hal/audiobusio/I2SOut.c

Lines changed: 253 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,47 +24,291 @@
2424
* THE SOFTWARE.
2525
*/
2626

27+
#include <math.h>
28+
#include <string.h>
29+
2730
#include "common-hal/microcontroller/Pin.h"
2831
#include "common-hal/audiobusio/I2SOut.h"
32+
#include "shared-bindings/audiobusio/I2SOut.h"
33+
#include "shared-module/audiocore/__init__.h"
2934

3035
#include "py/obj.h"
3136
#include "py/runtime.h"
3237

38+
static audiobusio_i2sout_obj_t *instance;
39+
40+
struct { int16_t l, r; } static_sample16 = {0x8000, 0x8000};
41+
struct { uint8_t l1, r1, l2, r2; } static_sample8 = {0x80, 0x80, 0x80, 0x80};
42+
43+
struct frequency_info { uint32_t RATIO; uint32_t MCKFREQ; int sample_rate; float abserr; };
44+
struct ratio_info { uint32_t RATIO; int16_t divisor; bool can_16bit; };
45+
struct ratio_info ratios[] = {
46+
{ I2S_CONFIG_RATIO_RATIO_32X, 32, true },
47+
{ I2S_CONFIG_RATIO_RATIO_48X, 48, false },
48+
{ I2S_CONFIG_RATIO_RATIO_64X, 64, true },
49+
{ I2S_CONFIG_RATIO_RATIO_96X, 96, true },
50+
{ I2S_CONFIG_RATIO_RATIO_128X, 128, true },
51+
{ I2S_CONFIG_RATIO_RATIO_192X, 192, true },
52+
{ I2S_CONFIG_RATIO_RATIO_256X, 256, true },
53+
{ I2S_CONFIG_RATIO_RATIO_384X, 384, true },
54+
{ I2S_CONFIG_RATIO_RATIO_512X, 512, true },
55+
};
56+
57+
struct mclk_info { uint32_t MCKFREQ; int divisor; };
58+
struct mclk_info mclks[] = {
59+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV8, 8 },
60+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV10, 10 },
61+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV11, 11 },
62+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV15, 15 },
63+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV16, 16 },
64+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV21, 21 },
65+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV23, 23 },
66+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV31, 31 },
67+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV42, 42 },
68+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV63, 63 },
69+
{ I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV125, 125 },
70+
};
71+
72+
static void calculate_ratio_info(uint32_t target_sample_rate, struct frequency_info *info,
73+
int ratio_index, int mclk_index) {
74+
info->RATIO = ratios[ratio_index].RATIO;
75+
info->MCKFREQ = mclks[mclk_index].MCKFREQ;
76+
info->sample_rate = 32000000
77+
/ ratios[ratio_index].divisor / mclks[mclk_index].divisor;
78+
info->abserr = fabsf(1.0f * target_sample_rate - info->sample_rate)
79+
/ target_sample_rate;
80+
}
81+
82+
void choose_i2s_clocking(audiobusio_i2sout_obj_t *self, uint32_t sample_rate) {
83+
struct frequency_info best = {0, 0, 0, 1.0};
84+
for (size_t ri=0; ri<sizeof(ratios) / sizeof(ratios[0]); ri++) {
85+
if (NRF_I2S->CONFIG.SWIDTH == I2S_CONFIG_SWIDTH_SWIDTH_16Bit
86+
&& !ratios[ri].can_16bit) {
87+
continue;
88+
}
89+
90+
for (size_t mi=0; mi<sizeof(mclks) / sizeof(mclks[0]); mi++) {
91+
struct frequency_info info = {0, 0, 1.0};
92+
calculate_ratio_info(sample_rate, &info, ri, mi);
93+
if (info.abserr < best.abserr) {
94+
best = info;
95+
}
96+
#ifdef DEBUG_CLOCKING
97+
mp_printf(&mp_plat_print,
98+
"RATIO=%3d MCKFREQ=%08x rate=%d abserr=%.4f\n",
99+
info.RATIO, info.MCKFREQ, info.sample_rate,
100+
(double)info.abserr);
101+
#endif
102+
}
103+
}
104+
NRF_I2S->CONFIG.RATIO = best.RATIO;
105+
NRF_I2S->CONFIG.MCKFREQ = best.MCKFREQ;
106+
self->sample_rate = best.sample_rate;
107+
}
108+
109+
static void i2s_buffer_fill(audiobusio_i2sout_obj_t* self) {
110+
void *buffer = self->buffers[self->next_buffer];
111+
NRF_I2S->TXD.PTR = (uintptr_t)buffer;
112+
self->next_buffer = !self->next_buffer;
113+
size_t bytesleft = self->buffer_length;
114+
115+
if (self->paused || self->stopping) {
116+
if (self->stopping) {
117+
NRF_I2S->TASKS_STOP = 1;
118+
self->playing = false;
119+
}
120+
stopping: ;
121+
uint32_t *bp = (uint32_t*)buffer;
122+
uint32_t *be = (uint32_t*)(buffer + bytesleft);
123+
for (; bp != be; )
124+
*bp++ = self->hold_value;
125+
return;
126+
}
127+
128+
while (bytesleft) {
129+
if (self->sample_data == self->sample_end) {
130+
uint32_t sample_buffer_length;
131+
audioio_get_buffer_result_t get_buffer_result =
132+
audiosample_get_buffer(self->sample, false, 0,
133+
&self->sample_data, &sample_buffer_length);
134+
self->sample_end = self->sample_data + sample_buffer_length;
135+
if (get_buffer_result == GET_BUFFER_DONE) {
136+
if (self->loop) {
137+
audiosample_reset_buffer(self->sample, false, 0);
138+
} else {
139+
self->stopping = true;
140+
goto stopping;
141+
}
142+
}
143+
}
144+
uint16_t bytecount = MIN(bytesleft, (size_t)(self->sample_end - self->sample_data));
145+
if (self->samples_signed) {
146+
memcpy(buffer, self->sample_data, bytecount);
147+
} else if (self->bytes_per_sample == 2) {
148+
uint16_t *bp = (uint16_t*)buffer;
149+
uint16_t *be = (uint16_t*)(buffer + bytecount);
150+
uint16_t *sp = (uint16_t*)self->sample_data;
151+
for (; bp != be; bp++) {
152+
*bp++ = *sp++ + 0x8000;
153+
}
154+
} else {
155+
uint8_t *bp = (uint8_t*)buffer;
156+
uint8_t *be = (uint8_t*)(buffer + bytecount);
157+
uint8_t *sp = (uint8_t*)self->sample_data;
158+
for (; bp != be; bp++) {
159+
*bp++ = *sp++ + 0x80;
160+
}
161+
}
162+
buffer += bytecount;
163+
self->sample_data += bytecount;
164+
bytesleft -= bytecount;
165+
}
166+
if (self->bytes_per_sample == 1 && self->channel_count == 1) {
167+
self->hold_value = 0x01010101 * *(uint8_t*)(buffer-1);
168+
} else if (self->bytes_per_sample == 2 && self->channel_count == 2) {
169+
self->hold_value = *(uint32_t*)(buffer-4);
170+
} else {
171+
self->hold_value = 0x00010001 * *(uint16_t*)(buffer-2);
172+
}
173+
}
174+
33175
void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self,
34176
const mcu_pin_obj_t* bit_clock, const mcu_pin_obj_t* word_select,
35177
const mcu_pin_obj_t* data, bool left_justified) {
36-
mp_raise_NotImplementedError(NULL);
178+
if (instance)
179+
mp_raise_RuntimeError(translate("Device in use"));
180+
instance = self;
181+
182+
claim_pin(bit_clock);
183+
claim_pin(word_select);
184+
claim_pin(data);
185+
186+
NRF_I2S->PSEL.SCK = self->bit_clock_pin_number = bit_clock->number;
187+
NRF_I2S->PSEL.LRCK = self->word_select_pin_number = word_select->number;
188+
NRF_I2S->PSEL.SDOUT = self->data_pin_number = data->number;
189+
190+
NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_Master;
191+
NRF_I2S->CONFIG.RXEN = I2S_CONFIG_RXEN_RXEN_Disabled;
192+
NRF_I2S->CONFIG.TXEN = I2S_CONFIG_TXEN_TXEN_Enabled;
193+
NRF_I2S->CONFIG.MCKEN = I2S_CONFIG_MCKEN_MCKEN_Enabled;
194+
NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_16Bit;
195+
196+
NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_Left;
197+
NRF_I2S->CONFIG.FORMAT = left_justified ? I2S_CONFIG_FORMAT_FORMAT_Aligned
198+
: I2S_CONFIG_FORMAT_FORMAT_I2S;
37199
}
38200

39201
bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) {
40-
mp_raise_NotImplementedError(NULL);
202+
return self->data_pin_number == 0xff;
41203
}
42204

43205
void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) {
44-
mp_raise_NotImplementedError(NULL);
206+
if (common_hal_audiobusio_i2sout_deinited(self)) {
207+
return;
208+
}
209+
reset_pin_number(self->bit_clock_pin_number);
210+
self->bit_clock_pin_number = 0xff;
211+
reset_pin_number(self->word_select_pin_number);
212+
self->word_select_pin_number = 0xff;
213+
reset_pin_number(self->data_pin_number);
214+
self->data_pin_number = 0xff;
215+
instance = NULL;
45216
}
46217

47218
void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self,
48219
mp_obj_t sample, bool loop) {
49-
mp_raise_NotImplementedError(NULL);
220+
if (common_hal_audiobusio_i2sout_get_playing(self)) {
221+
common_hal_audiobusio_i2sout_stop(self);
222+
}
223+
224+
self->sample = sample;
225+
self->loop = loop;
226+
uint32_t sample_rate = audiosample_sample_rate(sample);
227+
self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8;
228+
229+
uint32_t max_buffer_length;
230+
bool single_buffer, samples_signed;
231+
audiosample_get_buffer_structure(sample, /* single channel */ false,
232+
&single_buffer, &samples_signed, &max_buffer_length,
233+
&self->channel_count);
234+
self->single_buffer = single_buffer;
235+
self->samples_signed = samples_signed;
236+
237+
choose_i2s_clocking(self, sample_rate);
238+
/* Allocate buffers based on a maximum duration */
239+
enum { buffer_length_ms = 8 };
240+
self->buffer_length = MAX(
241+
2*max_buffer_length,
242+
sample_rate * buffer_length_ms * self->bytes_per_sample
243+
* self->channel_count / 1000);
244+
self->buffer_length = (self->buffer_length + 3) & ~3;
245+
self->buffers[0] = m_malloc(self->buffer_length, false);
246+
self->buffers[1] = m_malloc(self->buffer_length, false);
247+
248+
249+
audiosample_reset_buffer(self->sample, false, 0);
250+
251+
self->next_buffer = 0;
252+
self->sample_data = self->sample_end = 0;
253+
self->playing = true;
254+
self->paused = false;
255+
self->stopping = false;
256+
i2s_buffer_fill(self);
257+
258+
NRF_I2S->CONFIG.CHANNELS = self->channel_count == 1 ? I2S_CONFIG_CHANNELS_CHANNELS_Left : I2S_CONFIG_CHANNELS_CHANNELS_Stereo;
259+
260+
261+
NRF_I2S->RXTXD.MAXCNT = self->buffer_length / 4;
262+
NRF_I2S->ENABLE = I2S_ENABLE_ENABLE_Enabled;
263+
264+
NRF_I2S->TASKS_START = 1;
265+
266+
i2s_background();
50267
}
51268

52269
void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self) {
53-
mp_raise_NotImplementedError(NULL);
270+
self->paused = true;
54271
}
55272

56273
void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self) {
57-
mp_raise_NotImplementedError(NULL);
274+
self->paused = false;
58275
}
59276

60277
bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self) {
61-
mp_raise_NotImplementedError(NULL);
278+
return self->paused;
62279
}
63280

64281
void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) {
65-
mp_raise_NotImplementedError(NULL);
282+
NRF_I2S->TASKS_STOP = 1;
283+
self->stopping = true;
66284
}
67285

68286
bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) {
69-
mp_raise_NotImplementedError(NULL);
287+
if (NRF_I2S->EVENTS_STOPPED) {
288+
self->playing = false;
289+
NRF_I2S->EVENTS_STOPPED = 0;
290+
}
291+
return self->playing;
292+
}
293+
294+
void i2s_background(void) {
295+
if (NRF_I2S->EVENTS_TXPTRUPD) {
296+
NRF_I2S->EVENTS_TXPTRUPD = 0;
297+
if (instance) {
298+
i2s_buffer_fill(instance);
299+
} else {
300+
NRF_I2S->TASKS_STOP = 1;
301+
}
302+
}
303+
}
304+
305+
void i2s_reset(void) {
306+
NRF_I2S->TASKS_STOP = 1;
307+
NRF_I2S->ENABLE = I2S_ENABLE_ENABLE_Disabled;
308+
NRF_I2S->PSEL.MCK = 0xFFFFFFFF;
309+
NRF_I2S->PSEL.SCK = 0xFFFFFFFF;
310+
NRF_I2S->PSEL.LRCK = 0xFFFFFFFF;
311+
NRF_I2S->PSEL.SDOUT = 0xFFFFFFFF;
312+
NRF_I2S->PSEL.SDIN = 0xFFFFFFFF;
313+
instance = NULL;
70314
}

ports/nrf/common-hal/audiobusio/I2SOut.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,33 @@
3131

3232
typedef struct {
3333
mp_obj_base_t base;
34+
35+
mp_obj_t *sample;
36+
uint8_t *buffers[2];
37+
uint8_t *sample_data, *sample_end;
38+
39+
uint16_t buffer_length;
40+
uint16_t sample_rate;
41+
uint32_t hold_value;
42+
43+
uint8_t next_buffer;
44+
uint8_t bit_clock_pin_number;
45+
uint8_t word_select_pin_number;
46+
uint8_t data_pin_number;
47+
48+
uint8_t channel_count;
49+
uint8_t bytes_per_sample;
50+
51+
bool left_justified : 1;
52+
bool playing : 1;
53+
bool stopping : 1;
54+
bool paused : 1;
55+
bool loop : 1;
56+
bool samples_signed : 1;
57+
bool single_buffer : 1;
3458
} audiobusio_i2sout_obj_t;
3559

60+
void i2s_reset(void);
61+
void i2s_background(void);
62+
3663
#endif // MICROPY_INCLUDED_NRF_COMMON_HAL_AUDIOBUSIO_I2SOUT_H

ports/nrf/supervisor/port.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050

5151
#include "shared-bindings/rtc/__init__.h"
5252

53+
#ifdef CIRCUITPY_AUDIOBUSIO
54+
#include "common-hal/audiobusio/I2SOut.h"
55+
#endif
56+
5357
#ifdef CIRCUITPY_AUDIOPWMIO
5458
#include "common-hal/audiopwmio/PWMAudioOut.h"
5559
#endif
@@ -98,10 +102,15 @@ void reset_port(void) {
98102
spi_reset();
99103
uart_reset();
100104

105+
#ifdef CIRCUITPY_AUDIOBUSIO
106+
i2s_reset();
107+
#endif
108+
101109
#ifdef CIRCUITPY_AUDIOPWMIO
102110
audiopwmout_reset();
103111
#endif
104112

113+
105114
#if CIRCUITPY_PULSEIO
106115
pwmout_reset();
107116
pulseout_reset();

0 commit comments

Comments
 (0)
0