Merge pull request #5079 from dhalbert/debug-audio
Fix various RP2040 and SAMD audio issues
This commit is contained in:
commit
ebf0901558
|
@ -493,6 +493,10 @@ msgstr ""
|
|||
msgid "Attempted heap allocation when VM not running."
|
||||
msgstr ""
|
||||
|
||||
#: ports/raspberrypi/audio_dma.c
|
||||
msgid "Audio conversion not implemented"
|
||||
msgstr ""
|
||||
|
||||
#: shared-bindings/wifi/Radio.c
|
||||
msgid "AuthMode.OPEN is not used with password"
|
||||
msgstr ""
|
||||
|
@ -1217,6 +1221,10 @@ msgstr ""
|
|||
msgid "Insufficient encryption"
|
||||
msgstr ""
|
||||
|
||||
#: ports/raspberrypi/audio_dma.c
|
||||
msgid "Internal audio buffer too small"
|
||||
msgstr ""
|
||||
|
||||
#: ports/stm/common-hal/busio/UART.c
|
||||
msgid "Internal define error"
|
||||
msgstr ""
|
||||
|
|
|
@ -157,7 +157,13 @@ void audio_dma_load_next_block(audio_dma_t *dma) {
|
|||
if (dma->loop) {
|
||||
audiosample_reset_buffer(dma->sample, dma->single_channel_output, dma->audio_channel);
|
||||
} else {
|
||||
descriptor->DESCADDR.reg = 0;
|
||||
if ((output_buffer_length == 0) && dma_transfer_status(SHARED_RX_CHANNEL) & 0x3) {
|
||||
// Nothing further to read and previous buffer is finished.
|
||||
audio_dma_stop(dma);
|
||||
} else {
|
||||
// Break descriptor chain.
|
||||
descriptor->DESCADDR.reg = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
descriptor->BTCTRL.bit.VALID = true;
|
||||
|
@ -214,21 +220,23 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
if (output_signed != samples_signed) {
|
||||
output_spacing = 1;
|
||||
max_buffer_length /= dma->spacing;
|
||||
dma->first_buffer = (uint8_t *)m_realloc(dma->first_buffer, max_buffer_length);
|
||||
if (dma->first_buffer == NULL) {
|
||||
}
|
||||
|
||||
dma->first_buffer = (uint8_t *)m_realloc(dma->first_buffer, max_buffer_length);
|
||||
if (dma->first_buffer == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
dma->first_buffer_free = true;
|
||||
if (!single_buffer) {
|
||||
dma->second_buffer = (uint8_t *)m_realloc(dma->second_buffer, max_buffer_length);
|
||||
if (dma->second_buffer == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
dma->first_buffer_free = true;
|
||||
if (!single_buffer) {
|
||||
dma->second_buffer = (uint8_t *)m_realloc(dma->second_buffer, max_buffer_length);
|
||||
if (dma->second_buffer == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
}
|
||||
dma->signed_to_unsigned = !output_signed && samples_signed;
|
||||
dma->unsigned_to_signed = output_signed && !samples_signed;
|
||||
}
|
||||
|
||||
dma->signed_to_unsigned = !output_signed && samples_signed;
|
||||
dma->unsigned_to_signed = output_signed && !samples_signed;
|
||||
|
||||
dma->event_channel = 0xff;
|
||||
if (!single_buffer) {
|
||||
dma->second_descriptor = (DmacDescriptor *)m_malloc(sizeof(DmacDescriptor), false);
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
void i2sout_reset(void) {
|
||||
// Make sure the I2S peripheral is running so we can see if the resources we need are free.
|
||||
#ifdef SAM_D5X_E5X
|
||||
// Connect the clock units to the 2mhz clock. It can't disable without it.
|
||||
// Connect the clock units to the 2MHz clock. It can't disable without it.
|
||||
connect_gclk_to_peripheral(5, I2S_GCLK_ID_0);
|
||||
connect_gclk_to_peripheral(5, I2S_GCLK_ID_1);
|
||||
#endif
|
||||
|
@ -83,7 +83,7 @@ void i2sout_reset(void) {
|
|||
|
||||
// Make sure the I2S peripheral is running so we can see if the resources we need are free.
|
||||
#ifdef SAM_D5X_E5X
|
||||
// Connect the clock units to the 2mhz clock by default. They can't reset without it.
|
||||
// Connect the clock units to the 2MHz clock by default. They can't reset without it.
|
||||
disconnect_gclk_from_peripheral(5, I2S_GCLK_ID_0);
|
||||
disconnect_gclk_from_peripheral(5, I2S_GCLK_ID_1);
|
||||
|
||||
|
@ -222,7 +222,6 @@ void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t *self) {
|
|||
reset_pin_number(self->word_select->number);
|
||||
self->word_select = NULL;
|
||||
reset_pin_number(self->data->number);
|
||||
self->data = NULL;
|
||||
}
|
||||
|
||||
void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self,
|
||||
|
@ -288,7 +287,7 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self,
|
|||
I2S->TXCTRL.reg = serctrl;
|
||||
#endif
|
||||
|
||||
// The DFLL is always a 48mhz clock
|
||||
// The DFLL is always a 48MHz clock
|
||||
enable_clock_generator(self->gclk, CLOCK_48MHZ, divisor);
|
||||
connect_gclk_to_peripheral(self->gclk, I2S_GCLK_ID_0 + self->clock_unit);
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "shared-bindings/audiocore/RawSample.h"
|
||||
#include "shared-bindings/audiocore/WaveFile.h"
|
||||
#include "shared-bindings/microcontroller/__init__.h"
|
||||
#include "supervisor/background_callback.h"
|
||||
|
||||
#include "py/mpstate.h"
|
||||
|
@ -37,10 +38,8 @@
|
|||
|
||||
#if CIRCUITPY_AUDIOPWMIO || CIRCUITPY_AUDIOBUSIO
|
||||
|
||||
#define AUDIO_DMA_CHANNEL_COUNT NUM_DMA_CHANNELS
|
||||
|
||||
void audio_dma_reset(void) {
|
||||
for (size_t channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) {
|
||||
for (size_t channel = 0; channel < NUM_DMA_CHANNELS; channel++) {
|
||||
if (MP_STATE_PORT(playing_audio)[channel] == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
@ -49,116 +48,152 @@ void audio_dma_reset(void) {
|
|||
}
|
||||
}
|
||||
|
||||
void audio_dma_convert_signed(audio_dma_t *dma, uint8_t *buffer, uint32_t buffer_length,
|
||||
uint8_t **output_buffer, uint32_t *output_buffer_length) {
|
||||
if (dma->first_buffer_free) {
|
||||
*output_buffer = dma->first_buffer;
|
||||
} else {
|
||||
*output_buffer = dma->second_buffer;
|
||||
}
|
||||
|
||||
STATIC void audio_dma_convert_samples(
|
||||
audio_dma_t *dma,
|
||||
uint8_t *input, uint32_t input_length,
|
||||
uint8_t *available_output_buffer, uint32_t available_output_buffer_length,
|
||||
uint8_t **output, uint32_t *output_length) {
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-align"
|
||||
|
||||
// Check whether a conversion is necessary
|
||||
if (dma->signed_to_unsigned ||
|
||||
dma->unsigned_to_signed ||
|
||||
dma->sample_spacing > 1 ||
|
||||
(dma->sample_resolution != dma->output_resolution)) {
|
||||
*output_buffer_length = buffer_length / dma->sample_spacing;
|
||||
|
||||
// Must convert.
|
||||
// Write the conversion into the passed-in output buffer
|
||||
*output = available_output_buffer;
|
||||
*output_length = input_length / dma->sample_spacing;
|
||||
|
||||
if (*output_length > available_output_buffer_length) {
|
||||
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
|
||||
}
|
||||
|
||||
uint32_t out_i = 0;
|
||||
if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
|
||||
// reading bytes, writing 16-bit words, so output buffer will be bigger.
|
||||
|
||||
*output_length = *output_length * 2;
|
||||
if (*output_length > available_output_buffer_length) {
|
||||
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
|
||||
}
|
||||
|
||||
size_t shift = dma->output_resolution - dma->sample_resolution;
|
||||
|
||||
for (uint32_t i = 0; i < buffer_length; i += dma->sample_spacing) {
|
||||
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
|
||||
if (dma->signed_to_unsigned) {
|
||||
((uint16_t *)*output_buffer)[out_i] = ((uint16_t)((int8_t *)buffer)[i] + 0x80) << shift;
|
||||
((uint16_t *)*output)[out_i] = ((uint16_t)((int8_t *)input)[i] + 0x80) << shift;
|
||||
} else if (dma->unsigned_to_signed) {
|
||||
((int16_t *)*output_buffer)[out_i] = ((int16_t)((uint8_t *)buffer)[i] - 0x80) << shift;
|
||||
((int16_t *)*output)[out_i] = ((int16_t)((uint8_t *)input)[i] - 0x80) << shift;
|
||||
} else {
|
||||
((uint16_t *)*output_buffer)[out_i] = ((uint16_t)((uint8_t *)buffer)[i]) << shift;
|
||||
((uint16_t *)*output)[out_i] = ((uint16_t)((uint8_t *)input)[i]) << shift;
|
||||
}
|
||||
out_i += 1;
|
||||
}
|
||||
} else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) {
|
||||
for (uint32_t i = 0; i < buffer_length; i += dma->sample_spacing) {
|
||||
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
|
||||
if (dma->signed_to_unsigned) {
|
||||
((uint8_t *)*output_buffer)[out_i] = ((int8_t *)buffer)[i] + 0x80;
|
||||
((uint8_t *)*output)[out_i] = ((int8_t *)input)[i] + 0x80;
|
||||
} else if (dma->unsigned_to_signed) {
|
||||
((int8_t *)*output_buffer)[out_i] = ((uint8_t *)buffer)[i] - 0x80;
|
||||
((int8_t *)*output)[out_i] = ((uint8_t *)input)[i] - 0x80;
|
||||
} else {
|
||||
((uint8_t *)*output_buffer)[out_i] = ((uint8_t *)buffer)[i];
|
||||
((uint8_t *)*output)[out_i] = ((uint8_t *)input)[i];
|
||||
}
|
||||
out_i += 1;
|
||||
}
|
||||
} else if (dma->sample_resolution > 8 && dma->output_resolution > 8) {
|
||||
size_t shift = 16 - dma->output_resolution;
|
||||
for (uint32_t i = 0; i < buffer_length / 2; i += dma->sample_spacing) {
|
||||
for (uint32_t i = 0; i < input_length / 2; i += dma->sample_spacing) {
|
||||
if (dma->signed_to_unsigned) {
|
||||
((uint16_t *)*output_buffer)[out_i] = ((int16_t *)buffer)[i] + 0x8000;
|
||||
((uint16_t *)*output)[out_i] = ((int16_t *)input)[i] + 0x8000;
|
||||
} else if (dma->unsigned_to_signed) {
|
||||
((int16_t *)*output_buffer)[out_i] = ((uint16_t *)buffer)[i] - 0x8000;
|
||||
((int16_t *)*output)[out_i] = ((uint16_t *)input)[i] - 0x8000;
|
||||
} else {
|
||||
((uint16_t *)*output_buffer)[out_i] = ((uint16_t *)buffer)[i];
|
||||
((uint16_t *)*output)[out_i] = ((uint16_t *)input)[i];
|
||||
}
|
||||
if (dma->output_resolution < 16) {
|
||||
if (dma->output_signed) {
|
||||
((int16_t *)*output_buffer)[out_i] = ((int16_t *)*output_buffer)[out_i] >> shift;
|
||||
((int16_t *)*output)[out_i] = ((int16_t *)*output)[out_i] >> shift;
|
||||
} else {
|
||||
((uint16_t *)*output_buffer)[out_i] = ((uint16_t *)*output_buffer)[out_i] >> shift;
|
||||
((uint16_t *)*output)[out_i] = ((uint16_t *)*output)[out_i] >> shift;
|
||||
}
|
||||
}
|
||||
out_i += 1;
|
||||
}
|
||||
} else {
|
||||
// (dma->sample_resolution > 8 && dma->output_resolution <= 8)
|
||||
// Not currently used, but might be in the future.
|
||||
mp_raise_RuntimeError(translate("Audio conversion not implemented"));
|
||||
}
|
||||
} else {
|
||||
*output_buffer = buffer;
|
||||
*output_buffer_length = buffer_length;
|
||||
// No conversion necessary. Designate the input buffer as the output buffer.
|
||||
*output = input;
|
||||
*output_length = input_length;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
dma->first_buffer_free = !dma->first_buffer_free;
|
||||
}
|
||||
|
||||
void audio_dma_load_next_block(audio_dma_t *dma) {
|
||||
uint8_t dma_channel = dma->channel[1];
|
||||
if (dma->first_channel_free) {
|
||||
dma_channel = dma->channel[0];
|
||||
}
|
||||
dma->first_channel_free = !dma->first_channel_free;
|
||||
// channel_idx is 0 or 1.
|
||||
STATIC void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) {
|
||||
size_t dma_channel = dma->channel[buffer_idx];
|
||||
|
||||
uint8_t *output_buffer;
|
||||
uint32_t output_buffer_length;
|
||||
audioio_get_buffer_result_t get_buffer_result;
|
||||
uint8_t *buffer;
|
||||
uint32_t buffer_length;
|
||||
uint8_t *sample_buffer;
|
||||
uint32_t sample_buffer_length;
|
||||
get_buffer_result = audiosample_get_buffer(dma->sample,
|
||||
dma->single_channel_output, dma->audio_channel, &buffer, &buffer_length);
|
||||
dma->single_channel_output, dma->audio_channel, &sample_buffer, &sample_buffer_length);
|
||||
|
||||
if (get_buffer_result == GET_BUFFER_ERROR) {
|
||||
audio_dma_stop(dma);
|
||||
return;
|
||||
}
|
||||
|
||||
audio_dma_convert_signed(dma, buffer, buffer_length, &output_buffer, &output_buffer_length);
|
||||
// Convert the sample format resolution and signedness, as necessary.
|
||||
// The input sample buffer is what was read from a file, Mixer, or a raw sample buffer.
|
||||
// The output buffer is one of the DMA buffers (passed in), or if no conversion was done,
|
||||
// the original sample buffer (to save copying).
|
||||
|
||||
// If we don't have an output buffer, save the pointer to first_buffer for use in the single
|
||||
// buffer special case.
|
||||
if (dma->first_buffer == NULL) {
|
||||
dma->first_buffer = output_buffer;
|
||||
}
|
||||
// audio_dma_convert_samples() will write the converted samples into the given output
|
||||
// buffer if necessary. If no conversion was needed, it will return the sample buffer
|
||||
// as the output buffer.
|
||||
uint8_t *output_buffer;
|
||||
uint32_t output_buffer_length;
|
||||
|
||||
audio_dma_convert_samples(dma, sample_buffer, sample_buffer_length,
|
||||
dma->buffer[buffer_idx], dma->buffer_length[buffer_idx],
|
||||
&output_buffer, &output_buffer_length);
|
||||
|
||||
dma_channel_set_trans_count(dma_channel, output_buffer_length / dma->output_size, false /* trigger */);
|
||||
dma_channel_set_read_addr(dma_channel, output_buffer, false /* trigger */);
|
||||
dma_channel_set_trans_count(dma_channel, output_buffer_length / dma->output_size, false /* trigger */);
|
||||
|
||||
if (get_buffer_result == GET_BUFFER_DONE) {
|
||||
if (dma->loop) {
|
||||
audiosample_reset_buffer(dma->sample, dma->single_channel_output, dma->audio_channel);
|
||||
} else {
|
||||
// Set channel trigger to ourselves so we don't keep going.
|
||||
dma_channel_hw_t *c = &dma_hw->ch[dma_channel];
|
||||
c->al1_ctrl = (c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
|
||||
c->al1_ctrl =
|
||||
(c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) |
|
||||
(dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
|
||||
|
||||
if (output_buffer_length == 0 &&
|
||||
!dma_channel_is_busy(dma->channel[0]) &&
|
||||
!dma_channel_is_busy(dma->channel[1])) {
|
||||
// No data has been read, and both DMA channels have now finished, so it's safe to stop.
|
||||
audio_dma_stop(dma);
|
||||
dma->playing_in_progress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Playback should be shutdown before calling this.
|
||||
audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
||||
audio_dma_result audio_dma_setup_playback(
|
||||
audio_dma_t *dma,
|
||||
mp_obj_t sample,
|
||||
bool loop,
|
||||
bool single_channel_output,
|
||||
|
@ -167,6 +202,7 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
uint8_t output_resolution,
|
||||
uint32_t output_register_address,
|
||||
uint8_t dma_trigger_source) {
|
||||
|
||||
// Use two DMA channels to play because the DMA can't wrap to itself without the
|
||||
// buffer being power of two aligned.
|
||||
int dma_channel_0_maybe = dma_claim_unused_channel(false);
|
||||
|
@ -191,12 +227,15 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
dma->unsigned_to_signed = false;
|
||||
dma->output_signed = output_signed;
|
||||
dma->sample_spacing = 1;
|
||||
dma->first_channel_free = true;
|
||||
dma->output_resolution = output_resolution;
|
||||
dma->sample_resolution = audiosample_bits_per_sample(sample);
|
||||
dma->output_register_address = output_register_address;
|
||||
|
||||
audiosample_reset_buffer(sample, single_channel_output, audio_channel);
|
||||
|
||||
bool single_buffer;
|
||||
|
||||
bool single_buffer; // True if data fits in one single buffer.
|
||||
|
||||
bool samples_signed;
|
||||
uint32_t max_buffer_length;
|
||||
audiosample_get_buffer_structure(sample, single_channel_output, &single_buffer, &samples_signed,
|
||||
|
@ -210,22 +249,25 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
dma->sample_spacing > 1 ||
|
||||
(dma->sample_resolution != dma->output_resolution)) {
|
||||
max_buffer_length /= dma->sample_spacing;
|
||||
dma->first_buffer = (uint8_t *)m_realloc(dma->first_buffer, max_buffer_length);
|
||||
if (dma->first_buffer == NULL) {
|
||||
}
|
||||
|
||||
dma->buffer[0] = (uint8_t *)m_realloc(dma->buffer[0], max_buffer_length);
|
||||
dma->buffer_length[0] = max_buffer_length;
|
||||
if (dma->buffer[0] == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
|
||||
if (!single_buffer) {
|
||||
dma->buffer[1] = (uint8_t *)m_realloc(dma->buffer[1], max_buffer_length);
|
||||
dma->buffer_length[1] = max_buffer_length;
|
||||
if (dma->buffer[1] == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
|
||||
dma->first_buffer_free = true;
|
||||
if (!single_buffer) {
|
||||
dma->second_buffer = (uint8_t *)m_realloc(dma->second_buffer, max_buffer_length);
|
||||
if (dma->second_buffer == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
}
|
||||
dma->signed_to_unsigned = !output_signed && samples_signed;
|
||||
dma->unsigned_to_signed = output_signed && !samples_signed;
|
||||
}
|
||||
|
||||
dma->signed_to_unsigned = !output_signed && samples_signed;
|
||||
dma->unsigned_to_signed = output_signed && !samples_signed;
|
||||
|
||||
if (output_resolution > 8) {
|
||||
dma->output_size = 2;
|
||||
} else {
|
||||
|
@ -248,9 +290,11 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
channel_config_set_dreq(&c, dma_trigger_source);
|
||||
channel_config_set_read_increment(&c, true);
|
||||
channel_config_set_write_increment(&c, false);
|
||||
|
||||
// Chain to the other channel by default.
|
||||
channel_config_set_chain_to(&c, dma->channel[(i + 1) % 2]);
|
||||
dma_channel_set_config(dma->channel[i], &c, false /* trigger */);
|
||||
|
||||
dma_channel_set_write_addr(dma->channel[i], (void *)output_register_address, false /* trigger */);
|
||||
}
|
||||
|
||||
|
@ -260,9 +304,9 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
MP_STATE_PORT(playing_audio)[dma->channel[1]] = dma;
|
||||
|
||||
// Load the first two blocks up front.
|
||||
audio_dma_load_next_block(dma);
|
||||
audio_dma_load_next_block(dma, 0);
|
||||
if (!single_buffer) {
|
||||
audio_dma_load_next_block(dma);
|
||||
audio_dma_load_next_block(dma, 1);
|
||||
}
|
||||
|
||||
// Special case the DMA for a single buffer. It's commonly used for a single wave length of sound
|
||||
|
@ -279,16 +323,17 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
channel_config_set_chain_to(&c, dma->channel[1]); // Chain to ourselves so we stop.
|
||||
dma_channel_configure(dma->channel[1], &c,
|
||||
&dma_hw->ch[dma->channel[0]].al3_read_addr_trig, // write address
|
||||
&dma->first_buffer, // read address
|
||||
&dma->buffer[0], // read address
|
||||
1, // transaction count
|
||||
false); // trigger
|
||||
} else {
|
||||
// Enable our DMA channels on DMA0 to the CPU. This will wake us up when
|
||||
// Enable our DMA channels on DMA_IRQ_0 to the CPU. This will wake us up when
|
||||
// we're WFI.
|
||||
dma_hw->inte0 |= (1 << dma->channel[0]) | (1 << dma->channel[1]);
|
||||
irq_set_mask_enabled(1 << DMA_IRQ_0, true);
|
||||
}
|
||||
|
||||
dma->playing_in_progress = true;
|
||||
dma_channel_start(dma->channel[0]);
|
||||
|
||||
return AUDIO_DMA_OK;
|
||||
|
@ -296,7 +341,14 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
|
|||
|
||||
void audio_dma_stop(audio_dma_t *dma) {
|
||||
// Disable our interrupts.
|
||||
dma_hw->inte0 &= ~((1 << dma->channel[0]) | (1 << dma->channel[1]));
|
||||
uint32_t channel_mask = 0;
|
||||
if (dma->channel[0] < NUM_DMA_CHANNELS) {
|
||||
channel_mask |= 1 << dma->channel[0];
|
||||
}
|
||||
if (dma->channel[1] < NUM_DMA_CHANNELS) {
|
||||
channel_mask |= 1 << dma->channel[1];
|
||||
}
|
||||
dma_hw->inte0 &= ~channel_mask;
|
||||
irq_set_mask_enabled(1 << DMA_IRQ_0, false);
|
||||
|
||||
// Run any remaining audio tasks because we remove ourselves from
|
||||
|
@ -305,6 +357,10 @@ void audio_dma_stop(audio_dma_t *dma) {
|
|||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
size_t channel = dma->channel[i];
|
||||
if (channel == NUM_DMA_CHANNELS) {
|
||||
// Channel not in use.
|
||||
continue;
|
||||
}
|
||||
|
||||
dma_channel_config c = dma_channel_get_default_config(dma->channel[i]);
|
||||
channel_config_set_enable(&c, false);
|
||||
|
@ -313,6 +369,7 @@ void audio_dma_stop(audio_dma_t *dma) {
|
|||
if (dma_channel_is_busy(channel)) {
|
||||
dma_channel_abort(channel);
|
||||
}
|
||||
|
||||
dma_channel_set_read_addr(channel, NULL, false /* trigger */);
|
||||
dma_channel_set_write_addr(channel, NULL, false /* trigger */);
|
||||
dma_channel_set_trans_count(channel, 0, false /* trigger */);
|
||||
|
@ -320,6 +377,7 @@ void audio_dma_stop(audio_dma_t *dma) {
|
|||
MP_STATE_PORT(playing_audio)[channel] = NULL;
|
||||
dma->channel[i] = NUM_DMA_CHANNELS;
|
||||
}
|
||||
dma->playing_in_progress = false;
|
||||
|
||||
// Hold onto our buffers.
|
||||
}
|
||||
|
@ -344,7 +402,7 @@ void audio_dma_resume(audio_dma_t *dma) {
|
|||
}
|
||||
|
||||
bool audio_dma_get_paused(audio_dma_t *dma) {
|
||||
if (dma->channel[0] >= AUDIO_DMA_CHANNEL_COUNT) {
|
||||
if (dma->channel[0] >= NUM_DMA_CHANNELS) {
|
||||
return false;
|
||||
}
|
||||
uint32_t control = dma_hw->ch[dma->channel[0]].ctrl_trig;
|
||||
|
@ -353,30 +411,26 @@ bool audio_dma_get_paused(audio_dma_t *dma) {
|
|||
}
|
||||
|
||||
void audio_dma_init(audio_dma_t *dma) {
|
||||
dma->first_buffer = NULL;
|
||||
dma->second_buffer = NULL;
|
||||
dma->buffer[0] = NULL;
|
||||
dma->buffer[1] = NULL;
|
||||
|
||||
dma->channel[0] = NUM_DMA_CHANNELS;
|
||||
dma->channel[1] = NUM_DMA_CHANNELS;
|
||||
}
|
||||
|
||||
void audio_dma_deinit(audio_dma_t *dma) {
|
||||
m_free(dma->first_buffer);
|
||||
dma->first_buffer = NULL;
|
||||
m_free(dma->buffer[0]);
|
||||
dma->buffer[0] = NULL;
|
||||
|
||||
m_free(dma->second_buffer);
|
||||
dma->second_buffer = NULL;
|
||||
m_free(dma->buffer[1]);
|
||||
dma->buffer[1] = NULL;
|
||||
}
|
||||
|
||||
bool audio_dma_get_playing(audio_dma_t *dma) {
|
||||
if (dma->channel[0] == NUM_DMA_CHANNELS) {
|
||||
return false;
|
||||
}
|
||||
if (!dma_channel_is_busy(dma->channel[0]) &&
|
||||
!dma_channel_is_busy(dma->channel[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return dma->playing_in_progress;
|
||||
}
|
||||
|
||||
// WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls
|
||||
|
@ -389,7 +443,25 @@ STATIC void dma_callback_fun(void *arg) {
|
|||
return;
|
||||
}
|
||||
|
||||
audio_dma_load_next_block(dma);
|
||||
common_hal_mcu_disable_interrupts();
|
||||
uint32_t channels_to_load_mask = dma->channels_to_load_mask;
|
||||
dma->channels_to_load_mask = 0;
|
||||
common_hal_mcu_enable_interrupts();
|
||||
|
||||
// Load the blocks for the requested channels.
|
||||
uint32_t channel = 0;
|
||||
while (channels_to_load_mask) {
|
||||
if (channels_to_load_mask & 1) {
|
||||
if (dma->channel[0] == channel) {
|
||||
audio_dma_load_next_block(dma, 0);
|
||||
}
|
||||
if (dma->channel[1] == channel) {
|
||||
audio_dma_load_next_block(dma, 1);
|
||||
}
|
||||
}
|
||||
channels_to_load_mask >>= 1;
|
||||
channel++;
|
||||
}
|
||||
}
|
||||
|
||||
void isr_dma_0(void) {
|
||||
|
@ -397,6 +469,8 @@ void isr_dma_0(void) {
|
|||
uint32_t mask = 1 << i;
|
||||
if ((dma_hw->intr & mask) != 0 && MP_STATE_PORT(playing_audio)[i] != NULL) {
|
||||
audio_dma_t *dma = MP_STATE_PORT(playing_audio)[i];
|
||||
// Record all channels whose DMA has completed; they need loading.
|
||||
dma->channels_to_load_mask |= mask;
|
||||
background_callback_add(&dma->callback, dma_callback_fun, (void *)dma);
|
||||
dma_hw->ints0 = mask;
|
||||
}
|
||||
|
|
|
@ -43,12 +43,13 @@ typedef struct {
|
|||
bool signed_to_unsigned;
|
||||
bool unsigned_to_signed;
|
||||
bool output_signed;
|
||||
bool first_channel_free;
|
||||
bool first_buffer_free;
|
||||
bool playing_in_progress;
|
||||
uint8_t output_resolution; // in bits
|
||||
uint8_t sample_resolution; // in bits
|
||||
uint8_t *first_buffer;
|
||||
uint8_t *second_buffer;
|
||||
uint8_t *buffer[2];
|
||||
size_t buffer_length[2];
|
||||
uint32_t channels_to_load_mask;
|
||||
uint32_t output_register_address;
|
||||
background_callback_t callback;
|
||||
} audio_dma_t;
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ void board_init(void) {
|
|||
&pin_GPIO24, // Command or data
|
||||
&pin_GPIO22, // Chip select
|
||||
&pin_GPIO23, // Reset
|
||||
1000000, // Baudrate
|
||||
10000000, // Baudrate
|
||||
0, // Polarity
|
||||
0); // Phase
|
||||
|
||||
|
|
|
@ -117,7 +117,8 @@ void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t *self,
|
|||
}
|
||||
|
||||
// Use the state machine to manage pins.
|
||||
common_hal_rp2pio_statemachine_construct(&self->state_machine,
|
||||
common_hal_rp2pio_statemachine_construct(
|
||||
&self->state_machine,
|
||||
program, program_len,
|
||||
44100 * 32 * 6, // Clock at 44.1 khz to warm the DAC up.
|
||||
NULL, 0,
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
#include "shared-bindings/microcontroller/__init__.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "shared-bindings/microcontroller/Processor.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "supervisor/shared/translate.h"
|
||||
|
||||
#include "src/rp2040/hardware_structs/include/hardware/structs/dma.h"
|
||||
|
@ -158,27 +157,6 @@ void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t *self,
|
|||
tx_register += self->left_pwm.channel * sizeof(uint16_t);
|
||||
}
|
||||
|
||||
audio_dma_result result = audio_dma_setup_playback(
|
||||
&self->dma,
|
||||
sample,
|
||||
loop,
|
||||
false, // single channel
|
||||
0, // audio channel
|
||||
false, // output signed
|
||||
BITS_PER_SAMPLE,
|
||||
(uint32_t)tx_register, // output register: PWM cc register
|
||||
0x3b + pacing_timer); // data request line
|
||||
|
||||
if (result == AUDIO_DMA_DMA_BUSY) {
|
||||
common_hal_audiopwmio_pwmaudioout_stop(self);
|
||||
mp_raise_RuntimeError(translate("No DMA channel found"));
|
||||
}
|
||||
if (result == AUDIO_DMA_MEMORY_ERROR) {
|
||||
common_hal_audiopwmio_pwmaudioout_stop(self);
|
||||
mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion"));
|
||||
}
|
||||
|
||||
// OK! We got all of the resources we need and dma is ready.
|
||||
self->pacing_timer = pacing_timer;
|
||||
|
||||
// Playback with two independent clocks. One is the sample rate which
|
||||
|
@ -215,6 +193,27 @@ void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t *self,
|
|||
}
|
||||
|
||||
dma_hw->timer[pacing_timer] = best_numerator << 16 | best_denominator;
|
||||
|
||||
audio_dma_result result = audio_dma_setup_playback(
|
||||
&self->dma,
|
||||
sample,
|
||||
loop,
|
||||
false, // single channel
|
||||
0, // audio channel
|
||||
false, // output signed
|
||||
BITS_PER_SAMPLE,
|
||||
(uint32_t)tx_register, // output register: PWM cc register
|
||||
0x3b + pacing_timer); // data request line
|
||||
|
||||
if (result == AUDIO_DMA_DMA_BUSY) {
|
||||
common_hal_audiopwmio_pwmaudioout_stop(self);
|
||||
mp_raise_RuntimeError(translate("No DMA channel found"));
|
||||
}
|
||||
if (result == AUDIO_DMA_MEMORY_ERROR) {
|
||||
common_hal_audiopwmio_pwmaudioout_stop(self);
|
||||
mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion"));
|
||||
}
|
||||
// OK! We got all of the resources we need and dma is ready.
|
||||
}
|
||||
|
||||
void common_hal_audiopwmio_pwmaudioout_stop(audiopwmio_pwmaudioout_obj_t *self) {
|
||||
|
@ -226,9 +225,11 @@ void common_hal_audiopwmio_pwmaudioout_stop(audiopwmio_pwmaudioout_obj_t *self)
|
|||
audio_dma_stop(&self->dma);
|
||||
|
||||
// Set to quiescent level.
|
||||
pwm_hw->slice[self->left_pwm.slice].cc = self->quiescent_value;
|
||||
common_hal_pwmio_pwmout_set_duty_cycle(&self->left_pwm, self->quiescent_value);
|
||||
pwmio_pwmout_set_top(&self->left_pwm, PWM_TOP);
|
||||
if (self->stereo) {
|
||||
pwm_hw->slice[self->right_pwm.slice].cc = self->quiescent_value;
|
||||
common_hal_pwmio_pwmout_set_duty_cycle(&self->right_pwm, self->quiescent_value);
|
||||
pwmio_pwmout_set_top(&self->right_pwm, PWM_TOP);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ audioio_get_buffer_result_t audioio_wavefile_get_buffer(audioio_wavefile_obj_t *
|
|||
}
|
||||
|
||||
if (need_more_data) {
|
||||
uint16_t num_bytes_to_load = self->len;
|
||||
uint32_t num_bytes_to_load = self->len;
|
||||
if (num_bytes_to_load > self->bytes_remaining) {
|
||||
num_bytes_to_load = self->bytes_remaining;
|
||||
}
|
||||
|
|
|
@ -77,8 +77,6 @@ void common_hal_keypad_keymatrix_construct(keypad_keymatrix_obj_t *self, mp_uint
|
|||
|
||||
// Add self to the list of active keypad scanners.
|
||||
keypad_register_scanner((keypad_scanner_obj_t *)self);
|
||||
|
||||
supervisor_enable_tick();
|
||||
}
|
||||
|
||||
void common_hal_keypad_keymatrix_deinit(keypad_keymatrix_obj_t *self) {
|
||||
|
|
|
@ -63,8 +63,6 @@ void common_hal_keypad_keys_construct(keypad_keys_obj_t *self, mp_uint_t num_pin
|
|||
|
||||
// Add self to the list of active keypad scanners.
|
||||
keypad_register_scanner((keypad_scanner_obj_t *)self);
|
||||
|
||||
supervisor_enable_tick();
|
||||
}
|
||||
|
||||
void common_hal_keypad_keys_deinit(keypad_keys_obj_t *self) {
|
||||
|
|
|
@ -71,8 +71,6 @@ void common_hal_keypad_shiftregisterkeys_construct(keypad_shiftregisterkeys_obj_
|
|||
|
||||
// Add self to the list of active keypad scanners.
|
||||
keypad_register_scanner((keypad_scanner_obj_t *)self);
|
||||
|
||||
supervisor_enable_tick();
|
||||
}
|
||||
|
||||
void common_hal_keypad_shiftregisterkeys_deinit(keypad_shiftregisterkeys_obj_t *self) {
|
||||
|
|
|
@ -57,12 +57,9 @@ void keypad_tick(void) {
|
|||
}
|
||||
|
||||
void keypad_reset(void) {
|
||||
if (MP_STATE_VM(keypad_scanners_linked_list)) {
|
||||
supervisor_disable_tick();
|
||||
while (MP_STATE_VM(keypad_scanners_linked_list)) {
|
||||
keypad_deregister_scanner(MP_STATE_VM(keypad_scanners_linked_list));
|
||||
}
|
||||
|
||||
MP_STATE_VM(keypad_scanners_linked_list) = NULL;
|
||||
keypad_scanners_linked_list_lock = false;
|
||||
}
|
||||
|
||||
// Register a Keys, KeyMatrix, etc. that will be scanned in the background
|
||||
|
@ -71,10 +68,16 @@ void keypad_register_scanner(keypad_scanner_obj_t *scanner) {
|
|||
scanner->next = MP_STATE_VM(keypad_scanners_linked_list);
|
||||
MP_STATE_VM(keypad_scanners_linked_list) = scanner;
|
||||
supervisor_release_lock(&keypad_scanners_linked_list_lock);
|
||||
|
||||
// One more request for ticks.
|
||||
supervisor_enable_tick();
|
||||
}
|
||||
|
||||
// Remove scanner from the list of active scanners.
|
||||
void keypad_deregister_scanner(keypad_scanner_obj_t *scanner) {
|
||||
// One less request for ticks.
|
||||
supervisor_disable_tick();
|
||||
|
||||
supervisor_acquire_lock(&keypad_scanners_linked_list_lock);
|
||||
if (MP_STATE_VM(keypad_scanners_linked_list) == scanner) {
|
||||
// Scanner is at the front; splice it out.
|
||||
|
|
Loading…
Reference in New Issue