Skip to content

Commit 9358889

Browse files
authored
Merge pull request #5196 from dhalbert/samd-audio-fixes
improve SAMD audio DMA
2 parents 6f9078c + 046372d commit 9358889

File tree

7 files changed

+126
-85
lines changed

7 files changed

+126
-85
lines changed

ports/atmel-samd/audio_dma.c

Lines changed: 106 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,27 @@
2424
* THE SOFTWARE.
2525
*/
2626

27+
#include <string.h>
28+
2729
#include "audio_dma.h"
2830
#include "samd/clocks.h"
2931
#include "samd/events.h"
3032
#include "samd/dma.h"
3133

3234
#include "shared-bindings/audiocore/RawSample.h"
3335
#include "shared-bindings/audiocore/WaveFile.h"
36+
#include "shared-bindings/microcontroller/__init__.h"
3437
#include "supervisor/background_callback.h"
3538

3639
#include "py/mpstate.h"
3740
#include "py/runtime.h"
3841

3942
#if CIRCUITPY_AUDIOIO || CIRCUITPY_AUDIOBUSIO
4043

44+
// Flag value for dma->buffer_to_load, indicating there is nothing to do.
45+
// Otherwise dma->buffer_to_load is 0 or 1.
46+
#define NO_BUFFER_TO_LOAD 0xff
47+
4148
static audio_dma_t *audio_dma_state[AUDIO_DMA_CHANNEL_COUNT];
4249

4350
// This cannot be in audio_dma_state because it's volatile.
@@ -85,70 +92,79 @@ void audio_dma_enable_channel(uint8_t channel) {
8592
dma_enable_channel(channel);
8693
}
8794

88-
void audio_dma_convert_signed(audio_dma_t *dma, uint8_t *buffer, uint32_t buffer_length,
89-
uint8_t **output_buffer, uint32_t *output_buffer_length,
95+
static void audio_dma_convert_samples(
96+
audio_dma_t *dma,
97+
uint8_t *input, uint32_t input_length,
98+
uint8_t *available_output_buffer, uint32_t available_output_buffer_length,
99+
uint8_t **output, uint32_t *output_length,
90100
uint8_t *output_spacing) {
91-
if (dma->first_buffer_free) {
92-
*output_buffer = dma->first_buffer;
93-
} else {
94-
*output_buffer = dma->second_buffer;
95-
}
96101
#pragma GCC diagnostic push
97102
#pragma GCC diagnostic ignored "-Wcast-align"
98103
if (dma->signed_to_unsigned || dma->unsigned_to_signed) {
99-
*output_buffer_length = buffer_length / dma->spacing;
104+
105+
// Must convert.
106+
// Write the conversion into the passed-in output buffer
107+
*output = available_output_buffer;
108+
*output_length = input_length / dma->spacing;
100109
*output_spacing = 1;
110+
111+
if (*output_length > available_output_buffer_length) {
112+
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
113+
}
114+
101115
uint32_t out_i = 0;
102116
if (dma->bytes_per_sample == 1) {
103-
for (uint32_t i = 0; i < buffer_length; i += dma->spacing) {
117+
for (uint32_t i = 0; i < input_length; i += dma->spacing) {
104118
if (dma->signed_to_unsigned) {
105-
((uint8_t *)*output_buffer)[out_i] = ((int8_t *)buffer)[i] + 0x80;
119+
((uint8_t *)*output)[out_i] = ((int8_t *)input)[i] + 0x80;
106120
} else {
107-
((int8_t *)*output_buffer)[out_i] = ((uint8_t *)buffer)[i] - 0x80;
121+
((int8_t *)*output)[out_i] = ((uint8_t *)input)[i] - 0x80;
108122
}
109123
out_i += 1;
110124
}
111125
} else if (dma->bytes_per_sample == 2) {
112-
for (uint32_t i = 0; i < buffer_length / 2; i += dma->spacing) {
126+
for (uint32_t i = 0; i < input_length / 2; i += dma->spacing) {
113127
if (dma->signed_to_unsigned) {
114-
((uint16_t *)*output_buffer)[out_i] = ((int16_t *)buffer)[i] + 0x8000;
128+
((uint16_t *)*output)[out_i] = ((int16_t *)input)[i] + 0x8000;
115129
} else {
116-
((int16_t *)*output_buffer)[out_i] = ((uint16_t *)buffer)[i] - 0x8000;
130+
((int16_t *)*output)[out_i] = ((uint16_t *)input)[i] - 0x8000;
117131
}
118132
out_i += 1;
119133
}
120134
}
121135
} else {
122-
*output_buffer = buffer;
123-
*output_buffer_length = buffer_length;
136+
*output = input;
137+
*output_length = input_length;
124138
*output_spacing = dma->spacing;
125139
}
126140
#pragma GCC diagnostic pop
127-
dma->first_buffer_free = !dma->first_buffer_free;
128141
}
129142

130-
void audio_dma_load_next_block(audio_dma_t *dma) {
131-
uint8_t *buffer;
132-
uint32_t buffer_length;
143+
static void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) {
144+
uint8_t *sample_buffer;
145+
uint32_t sample_buffer_length;
133146
audioio_get_buffer_result_t get_buffer_result =
134147
audiosample_get_buffer(dma->sample, dma->single_channel_output, dma->audio_channel,
135-
&buffer, &buffer_length);
148+
&sample_buffer, &sample_buffer_length);
136149

137-
DmacDescriptor *descriptor = dma->second_descriptor;
138-
if (dma->first_descriptor_free) {
139-
descriptor = dma_descriptor(dma->dma_channel);
140-
}
141-
dma->first_descriptor_free = !dma->first_descriptor_free;
150+
DmacDescriptor *descriptor = dma->descriptor[buffer_idx];
142151

143152
if (get_buffer_result == GET_BUFFER_ERROR) {
144153
audio_dma_stop(dma);
145154
return;
146155
}
147156

157+
// Use one of the allocated buffers for conversion. But if there's no conversion,
158+
// this will be set to buffer in audio_dma_convert_samples() to avoid any copying.
148159
uint8_t *output_buffer;
149160
uint32_t output_buffer_length;
150161
uint8_t output_spacing;
151-
audio_dma_convert_signed(dma, buffer, buffer_length, &output_buffer, &output_buffer_length,
162+
163+
audio_dma_convert_samples(dma, sample_buffer, sample_buffer_length,
164+
// Available output buffer: may be used or not.
165+
dma->buffer[buffer_idx], dma->buffer_length[buffer_idx],
166+
// Buffer where output was placed.
167+
&output_buffer, &output_buffer_length,
152168
&output_spacing);
153169

154170
descriptor->BTCNT.reg = output_buffer_length / dma->beat_size / output_spacing;
@@ -157,9 +173,10 @@ void audio_dma_load_next_block(audio_dma_t *dma) {
157173
if (dma->loop) {
158174
audiosample_reset_buffer(dma->sample, dma->single_channel_output, dma->audio_channel);
159175
} else {
160-
if ((output_buffer_length == 0) && dma_transfer_status(SHARED_RX_CHANNEL) & 0x3) {
176+
if (output_buffer_length == 0) {
161177
// Nothing further to read and previous buffer is finished.
162178
audio_dma_stop(dma);
179+
return;
163180
} else {
164181
// Break descriptor chain.
165182
descriptor->DESCADDR.reg = 0;
@@ -206,30 +223,33 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
206223
dma->dma_channel = dma_channel;
207224
dma->signed_to_unsigned = false;
208225
dma->unsigned_to_signed = false;
209-
dma->second_descriptor = NULL;
210226
dma->spacing = 1;
211-
dma->first_descriptor_free = true;
212227
audiosample_reset_buffer(sample, single_channel_output, audio_channel);
228+
dma->buffer_to_load = NO_BUFFER_TO_LOAD;
229+
dma->descriptor[0] = dma_descriptor(dma_channel);
230+
dma->descriptor[1] = &dma->second_descriptor;
213231

214-
bool single_buffer;
215232
bool samples_signed;
216233
uint32_t max_buffer_length;
217-
audiosample_get_buffer_structure(sample, single_channel_output, &single_buffer, &samples_signed,
234+
audiosample_get_buffer_structure(sample, single_channel_output, &dma->single_buffer, &samples_signed,
218235
&max_buffer_length, &dma->spacing);
219236
uint8_t output_spacing = dma->spacing;
220237
if (output_signed != samples_signed) {
221238
output_spacing = 1;
222239
max_buffer_length /= dma->spacing;
223240
}
224241

225-
dma->first_buffer = (uint8_t *)m_realloc(dma->first_buffer, max_buffer_length);
226-
if (dma->first_buffer == NULL) {
242+
243+
dma->buffer[0] = (uint8_t *)m_realloc(dma->buffer[0], max_buffer_length);
244+
dma->buffer_length[0] = max_buffer_length;
245+
if (dma->buffer[0] == NULL) {
227246
return AUDIO_DMA_MEMORY_ERROR;
228247
}
229-
dma->first_buffer_free = true;
230-
if (!single_buffer) {
231-
dma->second_buffer = (uint8_t *)m_realloc(dma->second_buffer, max_buffer_length);
232-
if (dma->second_buffer == NULL) {
248+
249+
if (!dma->single_buffer) {
250+
dma->buffer[1] = (uint8_t *)m_realloc(dma->buffer[1], max_buffer_length);
251+
dma->buffer_length[1] = max_buffer_length;
252+
if (dma->buffer[1] == NULL) {
233253
return AUDIO_DMA_MEMORY_ERROR;
234254
}
235255
}
@@ -238,12 +258,7 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
238258
dma->unsigned_to_signed = output_signed && !samples_signed;
239259

240260
dma->event_channel = 0xff;
241-
if (!single_buffer) {
242-
dma->second_descriptor = (DmacDescriptor *)m_malloc(sizeof(DmacDescriptor), false);
243-
if (dma->second_descriptor == NULL) {
244-
return AUDIO_DMA_MEMORY_ERROR;
245-
}
246-
261+
if (!dma->single_buffer) {
247262
// We're likely double buffering so set up the block interrupts.
248263
turn_on_event_system();
249264
dma->event_channel = find_sync_event_channel_raise();
@@ -280,25 +295,27 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
280295
int irq = EVSYS_IRQn;
281296
#endif
282297

283-
DmacDescriptor *first_descriptor = dma_descriptor(dma_channel);
284-
setup_audio_descriptor(first_descriptor, dma->beat_size, output_spacing, output_register_address);
285-
if (single_buffer) {
286-
first_descriptor->DESCADDR.reg = 0;
298+
setup_audio_descriptor(dma->descriptor[0], dma->beat_size, output_spacing, output_register_address);
299+
if (dma->single_buffer) {
300+
dma->descriptor[0]->DESCADDR.reg = 0;
287301
if (dma->loop) {
288-
first_descriptor->DESCADDR.reg = (uint32_t)first_descriptor;
302+
// The descriptor chains to itself.
303+
dma->descriptor[0]->DESCADDR.reg = (uint32_t)dma->descriptor[0];
289304
}
290305
} else {
291-
first_descriptor->DESCADDR.reg = (uint32_t)dma->second_descriptor;
292-
setup_audio_descriptor(dma->second_descriptor, dma->beat_size, output_spacing, output_register_address);
293-
dma->second_descriptor->DESCADDR.reg = (uint32_t)first_descriptor;
306+
// Set up the two descriptors to chain to each other.
307+
dma->descriptor[0]->DESCADDR.reg = (uint32_t)dma->descriptor[1];
308+
setup_audio_descriptor(dma->descriptor[1], dma->beat_size, output_spacing, output_register_address);
309+
dma->descriptor[1]->DESCADDR.reg = (uint32_t)dma->descriptor[0];
294310
}
295311

296312
// Load the first two blocks up front.
297-
audio_dma_load_next_block(dma);
298-
if (!single_buffer) {
299-
audio_dma_load_next_block(dma);
313+
audio_dma_load_next_block(dma, 0);
314+
if (!dma->single_buffer) {
315+
audio_dma_load_next_block(dma, 1);
300316
}
301317

318+
dma->playing_in_progress = true;
302319
dma_configure(dma_channel, dma_trigger_source, true);
303320
audio_dma_enable_channel(dma_channel);
304321

@@ -317,6 +334,7 @@ void audio_dma_stop(audio_dma_t *dma) {
317334
dma_free_channel(dma->dma_channel);
318335
}
319336
dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
337+
dma->playing_in_progress = false;
320338
}
321339

322340
void audio_dma_pause(audio_dma_t *dma) {
@@ -355,12 +373,7 @@ bool audio_dma_get_playing(audio_dma_t *dma) {
355373
if (dma->dma_channel >= AUDIO_DMA_CHANNEL_COUNT) {
356374
return false;
357375
}
358-
uint32_t status = dma_transfer_status(dma->dma_channel);
359-
if ((status & DMAC_CHINTFLAG_TCMPL) != 0 || (status & DMAC_CHINTFLAG_TERR) != 0) {
360-
audio_dma_stop(dma);
361-
}
362-
363-
return (status & DMAC_CHINTFLAG_TERR) == 0;
376+
return dma->playing_in_progress;
364377
}
365378

366379
// WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls
@@ -371,7 +384,16 @@ STATIC void dma_callback_fun(void *arg) {
371384
return;
372385
}
373386

374-
audio_dma_load_next_block(dma);
387+
common_hal_mcu_disable_interrupts();
388+
uint8_t buffer_to_load = dma->buffer_to_load;
389+
dma->buffer_to_load = NO_BUFFER_TO_LOAD;
390+
common_hal_mcu_enable_interrupts();
391+
392+
if (buffer_to_load == NO_BUFFER_TO_LOAD) {
393+
audio_dma_stop(dma);
394+
} else {
395+
audio_dma_load_next_block(dma, buffer_to_load);
396+
}
375397
}
376398

377399
void audio_evsys_handler(void) {
@@ -384,6 +406,28 @@ void audio_evsys_handler(void) {
384406
if (!block_done) {
385407
continue;
386408
}
409+
410+
// By the time we get here, the write-back descriptor has been set to the
411+
// current running descriptor. Fill the buffer that the next chained descriptor
412+
// will play.
413+
//
414+
// The state of the write-back descriptor was determined empirically,
415+
// The datasheet appears to imply that the descriptor that just finished would
416+
// be in the write-back descriptor. But the VALID bit is set in the write-back descriptor,
417+
// and reversing which buffer to fill produces crackly output. So the choice
418+
// of which buffer to fill here appears correct.
419+
DmacDescriptor *next_descriptor =
420+
(DmacDescriptor *)dma_write_back_descriptor(dma->dma_channel)->DESCADDR.reg;
421+
if (next_descriptor == dma->descriptor[0]) {
422+
dma->buffer_to_load = 0;
423+
} else if (next_descriptor == dma->descriptor[1]) {
424+
dma->buffer_to_load = 1;
425+
} else if (next_descriptor == NULL) {
426+
dma->buffer_to_load = NO_BUFFER_TO_LOAD;
427+
} else {
428+
continue;
429+
}
430+
387431
background_callback_add(&dma->callback, dma_callback_fun, (void *)dma);
388432
}
389433
}

ports/atmel-samd/audio_dma.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,24 @@
3535

3636
typedef struct {
3737
mp_obj_t sample;
38+
uint8_t *buffer[2];
39+
size_t buffer_length[2];
40+
DmacDescriptor *descriptor[2];
41+
DmacDescriptor second_descriptor;
42+
background_callback_t callback;
3843
uint8_t dma_channel;
3944
uint8_t event_channel;
4045
uint8_t audio_channel;
4146
uint8_t bytes_per_sample;
4247
uint8_t beat_size;
4348
uint8_t spacing;
49+
uint8_t buffer_to_load; // Index
4450
bool loop;
51+
bool single_buffer;
4552
bool single_channel_output;
4653
bool signed_to_unsigned;
4754
bool unsigned_to_signed;
48-
bool first_buffer_free;
49-
uint8_t *first_buffer;
50-
uint8_t *second_buffer;
51-
bool first_descriptor_free;
52-
DmacDescriptor *second_descriptor;
53-
background_callback_t callback;
55+
bool playing_in_progress;
5456
} audio_dma_t;
5557

5658
typedef enum {

ports/atmel-samd/boards/dynossat_edu_obc/mpconfigboard.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,11 @@
2323
// USB is always used internally so skip the pin objects for it.
2424
#define IGNORE_PIN_PA24 1
2525
#define IGNORE_PIN_PA25 1
26-
#define IGNORE_PIN_PA02 1
2726
#define IGNORE_PIN_PA13 1
2827
#define IGNORE_PIN_PA14 1
29-
#define IGNORE_PIN_PA20 1
30-
#define IGNORE_PIN_PA21 1
3128
#define IGNORE_PIN_PA27 1
32-
#define IGNORE_PIN_PB00 1
3329
#define IGNORE_PIN_PB04 1
3430
#define IGNORE_PIN_PB05 1
35-
#define IGNORE_PIN_PB16 1
3631
#define IGNORE_PIN_PB17 1
3732
#define IGNORE_PIN_PB23 1
3833
#define IGNORE_PIN_PB31 1

ports/atmel-samd/common-hal/audioio/AudioOut.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ void common_hal_audioio_audioout_construct(audioio_audioout_obj_t *self,
248248
}
249249
self->tc_index = tc_index;
250250

251-
// Use the 48mhz clocks on both the SAMD21 and 51 because we will be going much slower.
251+
// Use the 48MHz clocks on both the SAMD21 and 51 because we will be going much slower.
252252
uint8_t tc_gclk = 0;
253253
#ifdef SAM_D5X_E5X
254254
tc_gclk = 1;
@@ -469,8 +469,8 @@ bool common_hal_audioio_audioout_get_paused(audioio_audioout_obj_t *self) {
469469
}
470470

471471
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t *self) {
472-
Tc *timer = tc_insts[self->tc_index];
473-
timer->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_STOP;
472+
// Do not stop the timer here. There are occasional audible artifacts if the DMA-triggering timer
473+
// is stopped between audio plays. (Heard this only on PyPortal with one particular 32kHz sample.)
474474
audio_dma_stop(&self->left_dma);
475475
#ifdef SAM_D5X_E5X
476476
audio_dma_stop(&self->right_dma);

ports/raspberrypi/audio_dma.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ STATIC void audio_dma_convert_samples(
137137
#pragma GCC diagnostic pop
138138
}
139139

140-
// channel_idx is 0 or 1.
140+
// buffer_idx is 0 or 1.
141141
STATIC void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) {
142142
size_t dma_channel = dma->channel[buffer_idx];
143143

0 commit comments

Comments
 (0)