From e724bc1c4eb1224aa043a1489b29ec3daaa832aa Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Thu, 14 Jun 2018 18:47:40 -0400 Subject: [PATCH] Fix playing audio from SD card --- extmod/vfs_fat_diskio.c | 26 +++++++++++++++++---- main.c | 8 ++++--- ports/atmel-samd/Makefile | 6 +++-- ports/atmel-samd/audio_dma.c | 36 ++++++++++++++++++++++------ shared-module/audioio/RawSample.c | 12 +++++----- shared-module/audioio/RawSample.h | 12 ++++++---- shared-module/audioio/WaveFile.c | 39 ++++++++++++++++++++----------- shared-module/audioio/WaveFile.h | 12 ++++++---- shared-module/audioio/__init__.h | 36 ++++++++++++++++++++++++++++ 9 files changed, 142 insertions(+), 45 deletions(-) create mode 100644 shared-module/audioio/__init__.h diff --git a/extmod/vfs_fat_diskio.c b/extmod/vfs_fat_diskio.c index 3b9d86037e..43c390a110 100644 --- a/extmod/vfs_fat_diskio.c +++ b/extmod/vfs_fat_diskio.c @@ -131,8 +131,17 @@ DRESULT disk_read ( } else { vfs->readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector); vfs->readblocks[3] = mp_obj_new_bytearray_by_ref(count * SECSIZE(&vfs->fatfs), buff); - mp_call_method_n_kw(2, 0, vfs->readblocks); - // TODO handle error return + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t ret = mp_call_method_n_kw(2, 0, vfs->readblocks); + nlr_pop(); + if (mp_obj_get_int(ret) != 0) { + return RES_ERROR; + } + } else { + // Exception thrown by readblocks or something it calls. + return RES_ERROR; + } } return RES_OK; @@ -167,8 +176,17 @@ DRESULT disk_write ( } else { vfs->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector); vfs->writeblocks[3] = mp_obj_new_bytearray_by_ref(count * SECSIZE(&vfs->fatfs), (void*)buff); - mp_call_method_n_kw(2, 0, vfs->writeblocks); - // TODO handle error return + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t ret = mp_call_method_n_kw(2, 0, vfs->writeblocks); + nlr_pop(); + if (mp_obj_get_int(ret) != 0) { + return RES_ERROR; + } + } else { + // Exception thrown by writeblocks or something it calls. + return RES_ERROR; + } } return RES_OK; diff --git a/main.c b/main.c index fb1a9e90fb..cbc0b093c3 100644 --- a/main.c +++ b/main.c @@ -233,7 +233,7 @@ bool start_mp(safe_mode_t safe_mode) { } } -void run_boot_py(safe_mode_t safe_mode) { +void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) { // If not in safe mode, run boot before initing USB and capture output in a // file. if (filesystem_present() && safe_mode == NO_SAFE_MODE && MP_STATE_VM(vfs_mount_table) != NULL) { @@ -258,10 +258,12 @@ void run_boot_py(safe_mode_t safe_mode) { // This saves wear and tear on the flash and also prevents filesystem damage if power is lost // during the write, which may happen due to bobbling the power connector or weak power. + static const size_t NUM_CHARS_TO_COMPARE = 160; if (!have_boot_py && f_open(fs, boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_READ) == FR_OK) { - char file_contents[512]; + + char file_contents[NUM_CHARS_TO_COMPARE]; UINT chars_read = 0; - f_read(boot_output_file, file_contents, 512, &chars_read); + f_read(boot_output_file, file_contents, NUM_CHARS_TO_COMPARE, &chars_read); f_close(boot_output_file); skip_boot_output = // + 2 accounts for \r\n. diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile index 1a406c1d01..8d454a4f60 100644 --- a/ports/atmel-samd/Makefile +++ b/ports/atmel-samd/Makefile @@ -102,8 +102,10 @@ endif ifeq ($(DEBUG), 1) # Turn on Python modules useful for debugging (e.g. uheap, ustack). CFLAGS += -ggdb -## CFLAGS += -flto - CFLAGS += -fno-inline -fno-ipa-sra + # You may want to disable -flto if it interferes with debugging. + CFLAGS += -flto + # You may want to enable these flags to make setting breakpoints easier. +## CFLAGS += -fno-inline -fno-ipa-sra ifeq ($(CHIP_FAMILY), samd21) CFLAGS += -DENABLE_MICRO_TRACE_BUFFER endif diff --git a/ports/atmel-samd/audio_dma.c b/ports/atmel-samd/audio_dma.c index 5a572e9d1f..4ac6079e90 100644 --- a/ports/atmel-samd/audio_dma.c +++ b/ports/atmel-samd/audio_dma.c @@ -34,6 +34,11 @@ #include "py/mpstate.h" +static audio_dma_t* audio_dma_state[AUDIO_DMA_CHANNEL_COUNT]; + +// This cannot be in audio_dma_state because it's volatile. +static volatile bool audio_dma_pending[AUDIO_DMA_CHANNEL_COUNT]; + uint32_t audiosample_sample_rate(mp_obj_t sample_obj) { if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); @@ -70,7 +75,7 @@ uint8_t audiosample_channel_count(mp_obj_t sample_obj) { return 1; } -void audiosample_reset_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t audio_channel) { +static void audiosample_reset_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t audio_channel) { if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); audioio_rawsample_reset_buffer(sample, single_channel, audio_channel); @@ -81,7 +86,10 @@ void audiosample_reset_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t } } -bool audiosample_get_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t channel, uint8_t** buffer, uint32_t* buffer_length) { +static audioio_get_buffer_result_t audiosample_get_buffer(mp_obj_t sample_obj, + bool single_channel, + uint8_t channel, + uint8_t** buffer, uint32_t* buffer_length) { if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); return audioio_rawsample_get_buffer(sample, single_channel, channel, buffer, buffer_length); @@ -90,7 +98,7 @@ bool audiosample_get_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t ch audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj); return audioio_wavefile_get_buffer(file, single_channel, channel, buffer, buffer_length); } - return true; + return GET_BUFFER_DONE; } static void audiosample_get_buffer_structure(mp_obj_t sample_obj, bool single_channel, @@ -117,7 +125,6 @@ uint8_t find_free_audio_dma_channel(void) { return channel; } -audio_dma_t* audio_dma_state[AUDIO_DMA_CHANNEL_COUNT]; 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, uint8_t* output_spacing) { @@ -163,8 +170,9 @@ void audio_dma_convert_signed(audio_dma_t* dma, uint8_t* buffer, uint32_t buffer void audio_dma_load_next_block(audio_dma_t* dma) { uint8_t* buffer; uint32_t buffer_length; - bool last_buffer = audiosample_get_buffer(dma->sample, dma->single_channel, dma->audio_channel, - &buffer, &buffer_length); + audioio_get_buffer_result_t get_buffer_result = + audiosample_get_buffer(dma->sample, dma->single_channel, dma->audio_channel, + &buffer, &buffer_length); DmacDescriptor* descriptor = dma->second_descriptor; if (dma->first_descriptor_free) { @@ -172,6 +180,11 @@ void audio_dma_load_next_block(audio_dma_t* dma) { } dma->first_descriptor_free = !dma->first_descriptor_free; + if (get_buffer_result == GET_BUFFER_ERROR) { + audio_dma_stop(dma); + return; + } + uint8_t* output_buffer; uint32_t output_buffer_length; uint8_t output_spacing; @@ -180,7 +193,7 @@ void audio_dma_load_next_block(audio_dma_t* dma) { descriptor->BTCNT.reg = output_buffer_length / dma->beat_size / output_spacing; descriptor->SRCADDR.reg = ((uint32_t) output_buffer) + output_buffer_length; - if (last_buffer) { + if (get_buffer_result == GET_BUFFER_DONE) { if (dma->loop) { audiosample_reset_buffer(dma->sample, dma->single_channel, dma->audio_channel); } else { @@ -347,6 +360,7 @@ void audio_dma_init(audio_dma_t* dma) { void audio_dma_reset(void) { for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { audio_dma_state[i] = NULL; + audio_dma_pending[i] = false; dma_disable_channel(i); dma_descriptor(i)->BTCTRL.bit.VALID = false; MP_STATE_PORT(playing_audio)[i] = NULL; @@ -367,8 +381,12 @@ bool audio_dma_get_playing(audio_dma_t* dma) { // WARN(tannewt): DO NOT print from here. Printing calls background tasks such as this and causes a // stack overflow. + void audio_dma_background(void) { for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { + if (audio_dma_pending[i]) { + continue; + } audio_dma_t* dma = audio_dma_state[i]; if (dma == NULL) { continue; @@ -379,6 +397,10 @@ void audio_dma_background(void) { continue; } + // audio_dma_load_next_block() can call Python code, which can call audio_dma_background() + // recursively at the next background processing time. So disallow recursive calls to here. + audio_dma_pending[i] = true; audio_dma_load_next_block(dma); + audio_dma_pending[i] = false; } } diff --git a/shared-module/audioio/RawSample.c b/shared-module/audioio/RawSample.c index e7059bf5a4..6422e346b7 100644 --- a/shared-module/audioio/RawSample.c +++ b/shared-module/audioio/RawSample.c @@ -66,18 +66,18 @@ void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t* self, uint8_t channel) { } -bool audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self, - bool single_channel, - uint8_t channel, - uint8_t** buffer, - uint32_t* buffer_length) { +audioio_get_buffer_result_t audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self, + bool single_channel, + uint8_t channel, + uint8_t** buffer, + uint32_t* buffer_length) { *buffer_length = self->len; if (single_channel) { *buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8); } else { *buffer = self->buffer; } - return true; + return GET_BUFFER_DONE; } void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t* self, bool single_channel, diff --git a/shared-module/audioio/RawSample.h b/shared-module/audioio/RawSample.h index 1604a1725f..fe5283db45 100644 --- a/shared-module/audioio/RawSample.h +++ b/shared-module/audioio/RawSample.h @@ -29,6 +29,8 @@ #include "py/obj.h" +#include "shared-module/audioio/__init__.h" + typedef struct { mp_obj_base_t base; uint8_t* buffer; @@ -45,11 +47,11 @@ typedef struct { void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t* self, bool single_channel, uint8_t channel); -bool audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self, - bool single_channel, - uint8_t channel, - uint8_t** buffer, - uint32_t* buffer_length); // length in bytes +audioio_get_buffer_result_t audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self, + bool single_channel, + uint8_t channel, + uint8_t** buffer, + uint32_t* buffer_length); // length in bytes void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t* self, bool single_channel, bool* single_buffer, bool* samples_signed, uint32_t* max_buffer_length, uint8_t* spacing); diff --git a/shared-module/audioio/WaveFile.c b/shared-module/audioio/WaveFile.c index 99283937ca..7247db137a 100644 --- a/shared-module/audioio/WaveFile.c +++ b/shared-module/audioio/WaveFile.c @@ -29,6 +29,7 @@ #include #include +#include "py/mperrno.h" #include "py/runtime.h" #include "shared-module/audioio/WaveFile.h" @@ -50,20 +51,26 @@ void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t* self, uint8_t chunk_header[16]; f_rewind(&self->file->fp); UINT bytes_read; - f_read(&self->file->fp, chunk_header, 16, &bytes_read); + if (f_read(&self->file->fp, chunk_header, 16, &bytes_read) != FR_OK) { + mp_raise_OSError(MP_EIO); + } if (bytes_read != 16 || memcmp(chunk_header, "RIFF", 4) != 0 || memcmp(chunk_header + 8, "WAVEfmt ", 8) != 0) { mp_raise_ValueError("Invalid wave file"); } uint32_t format_size; - f_read(&self->file->fp, &format_size, 4, &bytes_read); + if (f_read(&self->file->fp, &format_size, 4, &bytes_read) != FR_OK) { + mp_raise_OSError(MP_EIO); + } if (bytes_read != 4 || format_size > sizeof(struct wave_format_chunk)) { mp_raise_ValueError("Invalid format chunk size"); } struct wave_format_chunk format; - f_read(&self->file->fp, &format, format_size, &bytes_read); + if (f_read(&self->file->fp, &format, format_size, &bytes_read) != FR_OK) { + mp_raise_OSError(MP_EIO); + } if (bytes_read != format_size) { } @@ -83,14 +90,18 @@ void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t* self, // TODO(tannewt): Skip any extra chunks that occur before the data section. uint8_t data_tag[4]; - f_read(&self->file->fp, &data_tag, 4, &bytes_read); + if (f_read(&self->file->fp, &data_tag, 4, &bytes_read) != FR_OK) { + mp_raise_OSError(MP_EIO); + } if (bytes_read != 4 || memcmp((uint8_t *) data_tag, "data", 4) != 0) { mp_raise_ValueError("Data chunk must follow fmt chunk"); } uint32_t data_length; - f_read(&self->file->fp, &data_length, 4, &bytes_read); + if (f_read(&self->file->fp, &data_length, 4, &bytes_read) != FR_OK) { + mp_raise_OSError(MP_EIO); + } if (bytes_read != 4) { mp_raise_ValueError("Invalid file"); } @@ -152,11 +163,11 @@ void audioio_wavefile_reset_buffer(audioio_wavefile_obj_t* self, self->right_read_count = 0; } -bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self, - bool single_channel, - uint8_t channel, - uint8_t** buffer, - uint32_t* buffer_length) { +audioio_get_buffer_result_t audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self, + bool single_channel, + uint8_t channel, + uint8_t** buffer, + uint32_t* buffer_length) { if (!single_channel) { channel = 0; } @@ -171,7 +182,7 @@ bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self, if (self->bytes_remaining == 0 && need_more_data) { *buffer = NULL; *buffer_length = 0; - return true; + return GET_BUFFER_DONE; } if (need_more_data) { @@ -185,7 +196,9 @@ bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self, } else { *buffer = self->buffer; } - f_read(&self->file->fp, *buffer, num_bytes_to_load, &length_read); + if (f_read(&self->file->fp, *buffer, num_bytes_to_load, &length_read) != FR_OK) { + return GET_BUFFER_ERROR; + } *buffer_length = length_read; if (self->buffer_index % 2 == 1) { self->second_buffer_length = length_read; @@ -213,7 +226,7 @@ bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self, *buffer = *buffer + self->bits_per_sample / 8; } - return self->bytes_remaining == 0; + return self->bytes_remaining == 0 ? GET_BUFFER_DONE : GET_BUFFER_MORE_DATA; } void audioio_wavefile_get_buffer_structure(audioio_wavefile_obj_t* self, bool single_channel, diff --git a/shared-module/audioio/WaveFile.h b/shared-module/audioio/WaveFile.h index 9698d3c93e..75a46c03b1 100644 --- a/shared-module/audioio/WaveFile.h +++ b/shared-module/audioio/WaveFile.h @@ -29,6 +29,8 @@ #include "py/obj.h" +#include "shared-module/audioio/__init__.h" + typedef struct { mp_obj_base_t base; uint8_t* buffer; @@ -56,11 +58,11 @@ typedef struct { void audioio_wavefile_reset_buffer(audioio_wavefile_obj_t* self, bool single_channel, uint8_t channel); -bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self, - bool single_channel, - uint8_t channel, - uint8_t** buffer, - uint32_t* buffer_length); // length in bytes +audioio_get_buffer_result_t audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self, + bool single_channel, + uint8_t channel, + uint8_t** buffer, + uint32_t* buffer_length); // length in bytes void audioio_wavefile_get_buffer_structure(audioio_wavefile_obj_t* self, bool single_channel, bool* single_buffer, bool* samples_signed, uint32_t* max_buffer_length, uint8_t* spacing); diff --git a/shared-module/audioio/__init__.h b/shared-module/audioio/__init__.h new file mode 100644 index 0000000000..2491beb124 --- /dev/null +++ b/shared-module/audioio/__init__.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Dan Halbert for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO__INIT__H +#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO__INIT__H + +typedef enum { + GET_BUFFER_DONE, // No more data to read + GET_BUFFER_MORE_DATA, // More data to read. + GET_BUFFER_ERROR, // Error while reading data. +} audioio_get_buffer_result_t; + +#endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO__INIT__H