Add pause/resume control to AudioOut and I2SOut

Fixes #808
This commit is contained in:
Scott Shawcroft 2018-05-07 17:47:29 -07:00
parent d3a5d40374
commit 50fc90bc5f
10 changed files with 215 additions and 4 deletions

View File

@ -323,6 +323,23 @@ void audio_dma_stop(audio_dma_t* dma) {
dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
}
void audio_dma_pause(audio_dma_t* dma) {
dma_suspend_channel(dma->dma_channel);
}
void audio_dma_resume(audio_dma_t* dma) {
dma_resume_channel(dma->dma_channel);
}
bool audio_dma_get_paused(audio_dma_t* dma) {
if (dma->dma_channel >= AUDIO_DMA_CHANNEL_COUNT) {
return false;
}
uint32_t status = dma_transfer_status(dma->dma_channel);
return (status & DMAC_CHINTFLAG_SUSP) != 0;
}
void audio_dma_init(audio_dma_t* dma) {
dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
}
@ -341,11 +358,11 @@ bool audio_dma_get_playing(audio_dma_t* dma) {
return false;
}
uint32_t status = dma_transfer_status(dma->dma_channel);
if ((status & DMAC_CHINTFLAG_TCMPL) != 0) {
if ((status & DMAC_CHINTFLAG_TCMPL) != 0 || (status & DMAC_CHINTFLAG_TERR) != 0) {
audio_dma_stop(dma);
}
return status == 0;
return (status & DMAC_CHINTFLAG_TERR) == 0;
}
// WARN(tannewt): DO NOT print from here. Printing calls background tasks such as this and causes a

View File

@ -85,6 +85,9 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t* dma,
uint8_t dma_trigger_source);
void audio_dma_stop(audio_dma_t* dma);
bool audio_dma_get_playing(audio_dma_t* dma);
void audio_dma_pause(audio_dma_t* dma);
void audio_dma_resume(audio_dma_t* dma);
bool audio_dma_get_paused(audio_dma_t* dma);
void audio_dma_background(void);

View File

@ -324,6 +324,26 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self,
self->playing = true;
}
void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self) {
audio_dma_pause(&self->dma);
}
void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self) {
// Clear any overrun/underrun errors
#ifdef SAMD21
I2S->INTFLAG.reg = I2S_INTFLAG_TXUR0 << self->serializer;
#endif
#ifdef SAMD51
I2S->INTFLAG.reg = I2S_INTFLAG_TXUR0 | I2S_INTFLAG_TXUR1;
#endif
audio_dma_resume(&self->dma);
}
bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self) {
return audio_dma_get_paused(&self->dma);
}
void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) {
audio_dma_stop(&self->dma);

View File

@ -327,6 +327,32 @@ void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self,
self->playing = true;
}
void common_hal_audioio_audioout_pause(audioio_audioout_obj_t* self) {
audio_dma_pause(&self->left_dma);
#ifdef SAMD51
audio_dma_pause(&self->right_dma);
#endif
}
void common_hal_audioio_audioout_resume(audioio_audioout_obj_t* self) {
// Clear any overrun/underrun errors
#ifdef SAMD21
DAC->INTFLAG.reg = DAC_INTFLAG_UNDERRUN;
#endif
#ifdef SAMD51
DAC->INTFLAG.reg = DAC_INTFLAG_UNDERRUN0 | DAC_INTFLAG_UNDERRUN1;
#endif
audio_dma_resume(&self->left_dma);
#ifdef SAMD51
audio_dma_resume(&self->right_dma);
#endif
}
bool common_hal_audioio_audioout_get_paused(audioio_audioout_obj_t* self) {
return audio_dma_get_paused(&self->left_dma);
}
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self) {
Tc* timer = tc_insts[self->tc_index];
timer->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_STOP;

View File

@ -128,6 +128,37 @@ void dma_disable_channel(uint8_t channel_number) {
#endif
}
void dma_suspend_channel(uint8_t channel_number) {
#ifdef SAMD21
common_hal_mcu_disable_interrupts();
/** Select the DMA channel and clear software trigger */
DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
DMAC->CHCTRLB.bit.CMD = DMAC_CHCTRLB_CMD_SUSPEND_Val;
common_hal_mcu_enable_interrupts();
#endif
#ifdef SAMD51
DmacChannel* channel = &DMAC->Channel[channel_number];
channel->CHCTRLB.reg = DMAC_CHCTRLB_CMD_SUSPEND;
#endif
}
void dma_resume_channel(uint8_t channel_number) {
#ifdef SAMD21
common_hal_mcu_disable_interrupts();
/** Select the DMA channel and clear software trigger */
DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
DMAC->CHCTRLB.bit.CMD = DMAC_CHCTRLB_CMD_RESUME_Val;
DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_SUSP;
common_hal_mcu_enable_interrupts();
#endif
#ifdef SAMD51
DmacChannel* channel = &DMAC->Channel[channel_number];
channel->CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;
#endif
}
bool dma_channel_enabled(uint8_t channel_number) {
#ifdef SAMD21
common_hal_mcu_disable_interrupts();

View File

@ -56,6 +56,8 @@ int32_t sercom_dma_transfer(Sercom* sercom, const uint8_t* buffer_out, uint8_t*
void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event);
void dma_enable_channel(uint8_t channel_number);
void dma_disable_channel(uint8_t channel_number);
void dma_suspend_channel(uint8_t channel_number);
void dma_resume_channel(uint8_t channel_number);
bool dma_channel_enabled(uint8_t channel_number);
uint8_t dma_transfer_status(uint8_t channel_number);
DmacDescriptor* dma_descriptor(uint8_t channel_number);

View File

@ -212,6 +212,56 @@ const mp_obj_property_t audiobusio_i2sout_playing_obj = {
(mp_obj_t)&mp_const_none_obj},
};
//| .. method:: pause()
//|
//| Stops playback temporarily while remembering the position. Use `resume` to resume playback.
//|
STATIC mp_obj_t audiobusio_i2sout_obj_pause(mp_obj_t self_in) {
audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
if (!common_hal_audiobusio_i2sout_get_playing(self)) {
mp_raise_RuntimeError("No sample playing cannot pause");
}
common_hal_audiobusio_i2sout_pause(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_pause_obj, audiobusio_i2sout_obj_pause);
//| .. method:: resume()
//|
//| Resumes sample playback after :py:func:`pause`.
//|
STATIC mp_obj_t audiobusio_i2sout_obj_resume(mp_obj_t self_in) {
audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
if (!common_hal_audiobusio_i2sout_get_paused(self)) {
mp_raise_RuntimeError("No paused sample");
}
common_hal_audiobusio_i2sout_resume(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_resume_obj, audiobusio_i2sout_obj_resume);
//| .. attribute:: paused
//|
//| True when playback is paused. (read-only)
//|
STATIC mp_obj_t audiobusio_i2sout_obj_get_paused(mp_obj_t self_in) {
audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
return mp_obj_new_bool(common_hal_audiobusio_i2sout_get_paused(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_get_paused_obj, audiobusio_i2sout_obj_get_paused);
const mp_obj_property_t audiobusio_i2sout_paused_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&audiobusio_i2sout_get_paused_obj,
(mp_obj_t)&mp_const_none_obj,
(mp_obj_t)&mp_const_none_obj},
};
STATIC const mp_rom_map_elem_t audiobusio_i2sout_locals_dict_table[] = {
// Methods
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiobusio_i2sout_deinit_obj) },
@ -219,9 +269,12 @@ STATIC const mp_rom_map_elem_t audiobusio_i2sout_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiobusio_i2sout___exit___obj) },
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiobusio_i2sout_play_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiobusio_i2sout_stop_obj) },
{ MP_ROM_QSTR(MP_QSTR_pause), MP_ROM_PTR(&audiobusio_i2sout_pause_obj) },
{ MP_ROM_QSTR(MP_QSTR_resume), MP_ROM_PTR(&audiobusio_i2sout_resume_obj) },
// Properties
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiobusio_i2sout_playing_obj) },
{ MP_ROM_QSTR(MP_QSTR_paused), MP_ROM_PTR(&audiobusio_i2sout_paused_obj) },
};
STATIC MP_DEFINE_CONST_DICT(audiobusio_i2sout_locals_dict, audiobusio_i2sout_locals_dict_table);

View File

@ -41,5 +41,8 @@ bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self);
void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, mp_obj_t sample, bool loop);
void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self);
bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self);
void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self);
void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self);
bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOBUSIO_I2SOUT_H

View File

@ -181,7 +181,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(audioio_audioout_play_obj, 1, audioio_audioout_obj_pl
//| .. method:: stop()
//|
//| Stops playback.
//| Stops playback and resets to the start of the sample.
//|
STATIC mp_obj_t audioio_audioout_obj_stop(mp_obj_t self_in) {
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
@ -193,7 +193,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_stop_obj, audioio_audioout_obj_stop);
//| .. attribute:: playing
//|
//| True when an audio sample is being output. (read-only)
//| True when an audio sample is being output even if `paused`. (read-only)
//|
STATIC mp_obj_t audioio_audioout_obj_get_playing(mp_obj_t self_in) {
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
@ -209,6 +209,56 @@ const mp_obj_property_t audioio_audioout_playing_obj = {
(mp_obj_t)&mp_const_none_obj},
};
//| .. method:: pause()
//|
//| Stops playback temporarily while remembering the position. Use `resume` to resume playback.
//|
STATIC mp_obj_t audioio_audioout_obj_pause(mp_obj_t self_in) {
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
if (!common_hal_audioio_audioout_get_playing(self)) {
mp_raise_RuntimeError("No sample playing cannot pause");
}
common_hal_audioio_audioout_pause(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_pause_obj, audioio_audioout_obj_pause);
//| .. method:: resume()
//|
//| Resumes sample playback after :py:func:`pause`.
//|
STATIC mp_obj_t audioio_audioout_obj_resume(mp_obj_t self_in) {
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
if (!common_hal_audioio_audioout_get_paused(self)) {
mp_raise_RuntimeError("No paused sample");
}
common_hal_audioio_audioout_resume(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_resume_obj, audioio_audioout_obj_resume);
//| .. attribute:: paused
//|
//| True when playback is paused. (read-only)
//|
STATIC mp_obj_t audioio_audioout_obj_get_paused(mp_obj_t self_in) {
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
return mp_obj_new_bool(common_hal_audioio_audioout_get_paused(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_get_paused_obj, audioio_audioout_obj_get_paused);
const mp_obj_property_t audioio_audioout_paused_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&audioio_audioout_get_paused_obj,
(mp_obj_t)&mp_const_none_obj,
(mp_obj_t)&mp_const_none_obj},
};
STATIC const mp_rom_map_elem_t audioio_audioout_locals_dict_table[] = {
// Methods
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_audioout_deinit_obj) },
@ -216,9 +266,12 @@ STATIC const mp_rom_map_elem_t audioio_audioout_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioio_audioout___exit___obj) },
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audioio_audioout_play_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audioio_audioout_stop_obj) },
{ MP_ROM_QSTR(MP_QSTR_pause), MP_ROM_PTR(&audioio_audioout_pause_obj) },
{ MP_ROM_QSTR(MP_QSTR_resume), MP_ROM_PTR(&audioio_audioout_resume_obj) },
// Properties
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audioio_audioout_playing_obj) },
{ MP_ROM_QSTR(MP_QSTR_paused), MP_ROM_PTR(&audioio_audioout_paused_obj) },
};
STATIC MP_DEFINE_CONST_DICT(audioio_audioout_locals_dict, audioio_audioout_locals_dict_table);

View File

@ -42,5 +42,8 @@ bool common_hal_audioio_audioout_deinited(audioio_audioout_obj_t* self);
void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self, mp_obj_t sample, bool loop);
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self);
bool common_hal_audioio_audioout_get_playing(audioio_audioout_obj_t* self);
void common_hal_audioio_audioout_pause(audioio_audioout_obj_t* self);
void common_hal_audioio_audioout_resume(audioio_audioout_obj_t* self);
bool common_hal_audioio_audioout_get_paused(audioio_audioout_obj_t* self);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_AUDIOOUT_H