From 50fc90bc5f74dbd7c01e53b02650127ca408f0ae Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 7 May 2018 17:47:29 -0700 Subject: [PATCH] Add pause/resume control to AudioOut and I2SOut Fixes #808 --- ports/atmel-samd/audio_dma.c | 21 ++++++- ports/atmel-samd/audio_dma.h | 3 + .../atmel-samd/common-hal/audiobusio/I2SOut.c | 20 +++++++ .../atmel-samd/common-hal/audioio/AudioOut.c | 26 +++++++++ ports/atmel-samd/shared_dma.c | 31 ++++++++++ ports/atmel-samd/shared_dma.h | 2 + shared-bindings/audiobusio/I2SOut.c | 53 +++++++++++++++++ shared-bindings/audiobusio/I2SOut.h | 3 + shared-bindings/audioio/AudioOut.c | 57 ++++++++++++++++++- shared-bindings/audioio/AudioOut.h | 3 + 10 files changed, 215 insertions(+), 4 deletions(-) diff --git a/ports/atmel-samd/audio_dma.c b/ports/atmel-samd/audio_dma.c index b468acf51e..e8dbdc61d6 100644 --- a/ports/atmel-samd/audio_dma.c +++ b/ports/atmel-samd/audio_dma.c @@ -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 diff --git a/ports/atmel-samd/audio_dma.h b/ports/atmel-samd/audio_dma.h index fd2e6a153e..6734adb645 100644 --- a/ports/atmel-samd/audio_dma.h +++ b/ports/atmel-samd/audio_dma.h @@ -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); diff --git a/ports/atmel-samd/common-hal/audiobusio/I2SOut.c b/ports/atmel-samd/common-hal/audiobusio/I2SOut.c index 65dae5dcbd..f4a8eb52e8 100644 --- a/ports/atmel-samd/common-hal/audiobusio/I2SOut.c +++ b/ports/atmel-samd/common-hal/audiobusio/I2SOut.c @@ -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); diff --git a/ports/atmel-samd/common-hal/audioio/AudioOut.c b/ports/atmel-samd/common-hal/audioio/AudioOut.c index 3d03bfd67a..6971c020b4 100644 --- a/ports/atmel-samd/common-hal/audioio/AudioOut.c +++ b/ports/atmel-samd/common-hal/audioio/AudioOut.c @@ -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; diff --git a/ports/atmel-samd/shared_dma.c b/ports/atmel-samd/shared_dma.c index a7c27a46eb..edc2d0f1a0 100644 --- a/ports/atmel-samd/shared_dma.c +++ b/ports/atmel-samd/shared_dma.c @@ -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(); diff --git a/ports/atmel-samd/shared_dma.h b/ports/atmel-samd/shared_dma.h index 5f51d404fb..03e5b276c8 100644 --- a/ports/atmel-samd/shared_dma.h +++ b/ports/atmel-samd/shared_dma.h @@ -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); diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c index 6233bb51a2..b177d353cd 100644 --- a/shared-bindings/audiobusio/I2SOut.c +++ b/shared-bindings/audiobusio/I2SOut.c @@ -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); diff --git a/shared-bindings/audiobusio/I2SOut.h b/shared-bindings/audiobusio/I2SOut.h index 930044054f..edf4ecfa0b 100644 --- a/shared-bindings/audiobusio/I2SOut.h +++ b/shared-bindings/audiobusio/I2SOut.h @@ -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 diff --git a/shared-bindings/audioio/AudioOut.c b/shared-bindings/audioio/AudioOut.c index 189a217d49..9d79bfbb52 100644 --- a/shared-bindings/audioio/AudioOut.c +++ b/shared-bindings/audioio/AudioOut.c @@ -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); diff --git a/shared-bindings/audioio/AudioOut.h b/shared-bindings/audioio/AudioOut.h index 55958af799..751473605d 100644 --- a/shared-bindings/audioio/AudioOut.h +++ b/shared-bindings/audioio/AudioOut.h @@ -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