audiocore: Add async support

Rename the exising audiocore C module to _audiocore and create a new
Micropython wrapper module audiocore. This makes it easier to implement
async methods.
Add interrupt support to _audiocore that notifies core0 whenever data
has been consumed from the MP3 bitstream buffer.
Use this interrupt and an asyncio.ThreadSafeFlag to implement
audiocore.async_put which will play back the provided buffer, allowing
other async tasks to run while waiting for space in the bitstream
buffer.
This commit is contained in:
2025-03-15 22:43:22 +01:00
parent cc2bf8a84b
commit 931571bd0a
10 changed files with 144 additions and 30 deletions

View File

@@ -33,6 +33,7 @@ function(make_unity_test )
target_link_libraries(${MAKE_UNITY_TEST_NAME} PUBLIC unity::framework) target_link_libraries(${MAKE_UNITY_TEST_NAME} PUBLIC unity::framework)
target_include_directories(${MAKE_UNITY_TEST_NAME} target_include_directories(${MAKE_UNITY_TEST_NAME}
PRIVATE ${MAKE_UNITY_TEST_INCLUDES}) PRIVATE ${MAKE_UNITY_TEST_INCLUDES})
target_compile_definitions(${MAKE_UNITY_TEST_NAME} PRIVATE UNIT_TESTING)
add_test(NAME ${MAKE_UNITY_TEST_NAME} add_test(NAME ${MAKE_UNITY_TEST_NAME}
COMMAND sh -c "$<TARGET_FILE:${MAKE_UNITY_TEST_NAME}> > ${PROJECT_BINARY_DIR}/reports/${MAKE_UNITY_TEST_NAME}.testresults" COMMAND sh -c "$<TARGET_FILE:${MAKE_UNITY_TEST_NAME}> > ${PROJECT_BINARY_DIR}/reports/${MAKE_UNITY_TEST_NAME}.testresults"

View File

@@ -11,3 +11,5 @@ require("aiorepl")
module("mfrc522.py", "../../lib/micropython-mfrc522/") module("mfrc522.py", "../../lib/micropython-mfrc522/")
module("microdot.py", "../../lib/microdot/src/microdot/") module("microdot.py", "../../lib/microdot/src/microdot/")
module("audiocore.py", "../../src/audiocore")

View File

@@ -14,6 +14,26 @@ void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t
} }
} }
static void __time_critical_func(send_fifo_response)(uint32_t data)
{
multicore_fifo_push_blocking(AUDIOCORE_FIFO_DATA_FLAG | data);
}
#ifndef UNIT_TESTING
static void __time_critical_func(send_consume_notify)(void)
{
if (multicore_fifo_wready())
sio_hw->fifo_wr = 0;
}
#else
// Don't do optimization with raw HW access in unit test environment
static void send_consume_notify(void)
{
if (multicore_fifo_wready())
multicore_fifo_push_blocking(0);
}
#endif
void __time_critical_func(core1_main)(void) void __time_critical_func(core1_main)(void)
{ {
uint32_t ret = 0; uint32_t ret = 0;
@@ -27,7 +47,7 @@ void __time_critical_func(core1_main)(void)
goto out_i2s; goto out_i2s;
} }
multicore_fifo_push_blocking(0); send_fifo_response(0);
uint32_t current_volume = AUDIOCORE_MAX_VOLUME >> 4; uint32_t current_volume = AUDIOCORE_MAX_VOLUME >> 4;
bool flushing = false; bool flushing = false;
while (running) { while (running) {
@@ -42,14 +62,18 @@ void __time_critical_func(core1_main)(void)
case AUDIOCORE_CMD_SET_VOLUME: { case AUDIOCORE_CMD_SET_VOLUME: {
const uint32_t new_volume = multicore_fifo_pop_blocking(); const uint32_t new_volume = multicore_fifo_pop_blocking();
if (new_volume > AUDIOCORE_MAX_VOLUME) { if (new_volume > AUDIOCORE_MAX_VOLUME) {
multicore_fifo_push_blocking(1); send_fifo_response(1);
} else { } else {
current_volume = new_volume; current_volume = new_volume;
multicore_fifo_push_blocking(0); send_fifo_response(0);
} }
} break; } break;
case AUDIOCORE_CMD_FLUSH: case AUDIOCORE_CMD_FLUSH:
flushing = true; if (playing) {
flushing = true;
} else {
send_fifo_response(0);
}
break; break;
default: default:
break; break;
@@ -65,6 +89,7 @@ void __time_critical_func(core1_main)(void)
} }
volume_adjust((int16_t *)buf, 2304, current_volume); volume_adjust((int16_t *)buf, 2304, current_volume);
i2s_commit_buf(buf); i2s_commit_buf(buf);
send_consume_notify();
continue; continue;
} }
/* mp3_decode returned false: not enough data in buffer */ /* mp3_decode returned false: not enough data in buffer */
@@ -73,7 +98,9 @@ void __time_critical_func(core1_main)(void)
i2s_stop(); i2s_stop();
playing = false; playing = false;
flushing = false; flushing = false;
multicore_fifo_push_blocking(0); send_fifo_response(0);
} else {
send_consume_notify();
} }
} }
@@ -84,5 +111,5 @@ void __time_critical_func(core1_main)(void)
out_i2s: out_i2s:
i2s_deinit(); i2s_deinit();
out: out:
multicore_fifo_push_blocking(ret); send_fifo_response(ret);
} }

View File

@@ -79,6 +79,10 @@ void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t
void core1_main(void); void core1_main(void);
// For data sent from core1 to core0, signals that this is a return value from some function call
// Otherwise it it just a trigger to wake core0 and the data can be discarded.
#define AUDIOCORE_FIFO_DATA_FLAG 0x80000000
// SHUTDOWN - no arguments - return 0 // SHUTDOWN - no arguments - return 0
#define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de #define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de

View File

@@ -0,0 +1,37 @@
import _audiocore
from asyncio import ThreadSafeFlag
class Audiocore:
def __init__(self, pin, sideset):
self.notify = ThreadSafeFlag()
self._audiocore = _audiocore.Audiocore(pin, sideset, self._interrupt)
def __del__(self):
self._audiocore.deinit()
def _interrupt(self, _):
self.notify.set()
def flush(self):
self._audiocore.flush()
def set_volume(self, volume):
self._audiocore.set_volume(volume)
def put(self, buffer, blocking=False):
pos = 0
while True:
(copied, buf_space, underruns) = self._audiocore.put(buffer[pos:])
pos += copied
if pos >= len(buffer) or not blocking:
return (pos, buf_space, underruns)
async def async_put(self, buffer):
pos = 0
while True:
(copied, buf_space, underruns) = self._audiocore.put(buffer[pos:])
pos += copied
if pos >= len(buffer):
return (pos, buf_space, underruns)
await self.notify.wait()

View File

@@ -102,6 +102,7 @@ void i2s_stop(void)
} while (have_data); } while (have_data);
const long flags = save_and_disable_interrupts(); const long flags = save_and_disable_interrupts();
i2s_context.playback_active = false; i2s_context.playback_active = false;
shared_context.underruns = 0;
restore_interrupts(flags); restore_interrupts(flags);
// Workaround rp2040 E13 // Workaround rp2040 E13
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false); dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);

View File

@@ -6,6 +6,7 @@
// Include MicroPython API. // Include MicroPython API.
#include "py/mperrno.h" #include "py/mperrno.h"
#include "py/runtime.h" #include "py/runtime.h"
#include "shared/runtime/mpirq.h"
// This module is RP2 specific // This module is RP2 specific
#include "mphalport.h" #include "mphalport.h"
@@ -18,9 +19,41 @@ static bool initialized = false;
struct audiocore_obj { struct audiocore_obj {
mp_obj_base_t base; mp_obj_base_t base;
mp_irq_obj_t *irq_obj;
uint32_t fifo_read_value;
}; };
const mp_obj_type_t audiocore_type; const mp_obj_type_t audiocore_type;
static struct audiocore_obj *the_audiocore_obj = NULL;
static void __time_critical_func(fifo_isr)(void)
{
if (!multicore_fifo_rvalid())
return;
const uint32_t val = sio_hw->fifo_rd;
if (!the_audiocore_obj)
return;
if (val & AUDIOCORE_FIFO_DATA_FLAG)
the_audiocore_obj->fifo_read_value = val;
if (the_audiocore_obj->irq_obj)
mp_irq_handler(the_audiocore_obj->irq_obj);
}
static const mp_irq_methods_t audiocore_irq_methods = {};
static uint32_t get_fifo_read_value_blocking(struct audiocore_obj *obj)
{
while (true) {
const long flags = save_and_disable_interrupts();
const uint32_t value = obj->fifo_read_value;
obj->fifo_read_value = 0;
restore_interrupts(flags);
if (value & AUDIOCORE_FIFO_DATA_FLAG)
return value & ~AUDIOCORE_FIFO_DATA_FLAG;
__wfi();
}
}
/* /*
* audiocore.Context.deinit(self) * audiocore.Context.deinit(self)
* *
@@ -30,8 +63,10 @@ static mp_obj_t audiocore_deinit(mp_obj_t self_in)
{ {
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in); struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN); multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN);
multicore_fifo_pop_blocking(); get_fifo_read_value_blocking(self);
(void)self; irq_set_enabled(SIO_IRQ_PROC0, false);
irq_remove_handler(SIO_IRQ_PROC0, &fifo_isr);
the_audiocore_obj = NULL;
initialized = false; initialized = false;
return mp_const_none; return mp_const_none;
} }
@@ -86,10 +121,9 @@ static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_put_obj, audiocore_put);
static mp_obj_t audiocore_flush(mp_obj_t self_in) static mp_obj_t audiocore_flush(mp_obj_t self_in)
{ {
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in); struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
(void)self;
multicore_fifo_push_blocking(AUDIOCORE_CMD_FLUSH); multicore_fifo_push_blocking(AUDIOCORE_CMD_FLUSH);
__sev(); __sev();
multicore_fifo_pop_blocking(); get_fifo_read_value_blocking(self);
return mp_const_none; return mp_const_none;
} }
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_flush_obj, audiocore_flush); static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_flush_obj, audiocore_flush);
@@ -105,14 +139,13 @@ static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_flush_obj, audiocore_flush);
static mp_obj_t audiocore_set_volume(mp_obj_t self_in, mp_obj_t volume_obj) static mp_obj_t audiocore_set_volume(mp_obj_t self_in, mp_obj_t volume_obj)
{ {
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in); struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
(void)self;
const int volume = mp_obj_get_int(volume_obj); const int volume = mp_obj_get_int(volume_obj);
if (volume < 0 || volume > 255) if (volume < 0 || volume > 255)
mp_raise_ValueError("volume out of range"); mp_raise_ValueError("volume out of range");
multicore_fifo_push_blocking(AUDIOCORE_CMD_SET_VOLUME); multicore_fifo_push_blocking(AUDIOCORE_CMD_SET_VOLUME);
multicore_fifo_push_blocking(AUDIOCORE_MAX_VOLUME * volume / 255); multicore_fifo_push_blocking(AUDIOCORE_MAX_VOLUME * volume / 255);
__sev(); __sev();
const uint32_t ret = multicore_fifo_pop_blocking(); const uint32_t ret = get_fifo_read_value_blocking(self);
if (ret != 0) if (ret != 0)
mp_raise_OSError(MP_EINVAL); mp_raise_OSError(MP_EINVAL);
return mp_const_none; return mp_const_none;
@@ -132,10 +165,11 @@ static uint32_t __scratch_y("core1_stack") core1_stack[1024];
*/ */
static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
{ {
enum { ARG_pin, ARG_sideset }; enum { ARG_pin, ARG_sideset, ARG_handler };
static const mp_arg_t allowed_args[] = { static const mp_arg_t allowed_args[] = {
{MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, {MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_sideset, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, {MP_QSTR_sideset, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
}; };
if (initialized) if (initialized)
mp_raise_OSError(MP_EBUSY); mp_raise_OSError(MP_EBUSY);
@@ -150,6 +184,16 @@ static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_ob
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
const mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[ARG_pin].u_obj); const mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[ARG_pin].u_obj);
const mp_hal_pin_obj_t sideset_pin = mp_hal_get_pin_obj(args[ARG_sideset].u_obj); const mp_hal_pin_obj_t sideset_pin = mp_hal_get_pin_obj(args[ARG_sideset].u_obj);
if (args[ARG_handler].u_obj != MP_OBJ_NULL) {
obj->irq_obj = mp_irq_new(&audiocore_irq_methods, MP_OBJ_FROM_PTR(obj));
obj->irq_obj->handler = args[ARG_handler].u_obj;
obj->irq_obj->ishard = false;
} else {
obj->irq_obj = NULL;
}
the_audiocore_obj = obj;
irq_set_exclusive_handler(SIO_IRQ_PROC0, &fifo_isr);
irq_set_enabled(SIO_IRQ_PROC0, true);
shared_context.mp3_buffer_write = shared_context.mp3_buffer_read = shared_context.underruns = 0; shared_context.mp3_buffer_write = shared_context.mp3_buffer_read = shared_context.underruns = 0;
memset(shared_context.mp3_buffer, 0, MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE); memset(shared_context.mp3_buffer, 0, MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE);
multicore_reset_core1(); multicore_reset_core1();
@@ -157,9 +201,12 @@ static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_ob
shared_context.sideset_base = sideset_pin; shared_context.sideset_base = sideset_pin;
initialized = true; initialized = true;
multicore_launch_core1_with_stack(&core1_main, core1_stack, sizeof(core1_stack)); multicore_launch_core1_with_stack(&core1_main, core1_stack, sizeof(core1_stack));
uint32_t result = multicore_fifo_pop_blocking(); uint32_t result = get_fifo_read_value_blocking(obj);
if (result != 0) { if (result != 0) {
multicore_reset_core1(); multicore_reset_core1();
irq_set_enabled(SIO_IRQ_PROC0, false);
irq_remove_handler(SIO_IRQ_PROC0, &fifo_isr);
the_audiocore_obj = NULL;
initialized = false; initialized = false;
mp_raise_OSError(result); mp_raise_OSError(result);
} }
@@ -187,7 +234,7 @@ MP_DEFINE_CONST_OBJ_TYPE(audiocore_type, MP_QSTR_Audiocore, MP_TYPE_FLAG_NONE, l
make_new, &audiocore_make_new); make_new, &audiocore_make_new);
static const mp_rom_map_elem_t audiocore_module_globals_table[] = { static const mp_rom_map_elem_t audiocore_module_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore)}, {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__audiocore)},
{MP_ROM_QSTR(MP_QSTR_Audiocore), MP_ROM_PTR(&audiocore_type)}, {MP_ROM_QSTR(MP_QSTR_Audiocore), MP_ROM_PTR(&audiocore_type)},
}; };
static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table); static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table);
@@ -196,4 +243,4 @@ const mp_obj_module_t audiocore_cmodule = {
.base = {&mp_type_module}, .base = {&mp_type_module},
.globals = (mp_obj_dict_t *)&audiocore_module_globals, .globals = (mp_obj_dict_t *)&audiocore_module_globals,
}; };
MP_REGISTER_MODULE(MP_QSTR_audiocore, audiocore_cmodule); MP_REGISTER_MODULE(MP_QSTR__audiocore, audiocore_cmodule);

View File

@@ -55,17 +55,18 @@ unsigned multicore_fifo_pop_blocking(void)
} }
bool multicore_fifo_rvalid(void) { return multicore_fifo_pop_blocking_cb; } bool multicore_fifo_rvalid(void) { return multicore_fifo_pop_blocking_cb; }
bool multicore_fifo_wready(void) { return true; }
void test_audiocore_handles_i2sinit_failure(void) void test_audiocore_handles_i2sinit_failure(void)
{ {
i2s_init_return = false; i2s_init_return = false;
core1_main(); core1_main();
TEST_ASSERT_EQUAL(multicore_fifo_push_last, MP_EIO); TEST_ASSERT_EQUAL(AUDIOCORE_FIFO_DATA_FLAG | MP_EIO, multicore_fifo_push_last);
} }
unsigned audiocore_init_deinit_pop_cb(void) unsigned audiocore_init_deinit_pop_cb(void)
{ {
TEST_ASSERT_EQUAL(0, multicore_fifo_push_last); TEST_ASSERT_EQUAL(AUDIOCORE_FIFO_DATA_FLAG | 0, multicore_fifo_push_last);
TEST_ASSERT_TRUE(i2s_initialized); TEST_ASSERT_TRUE(i2s_initialized);
return AUDIOCORE_CMD_SHUTDOWN; return AUDIOCORE_CMD_SHUTDOWN;
} }
@@ -76,7 +77,7 @@ void test_audiocore_init_deinit(void)
i2s_init_return = true; i2s_init_return = true;
core1_main(); core1_main();
TEST_ASSERT_EQUAL(0, multicore_fifo_push_last); TEST_ASSERT_EQUAL(AUDIOCORE_FIFO_DATA_FLAG | 0, multicore_fifo_push_last);
TEST_ASSERT_FALSE(i2s_initialized); TEST_ASSERT_FALSE(i2s_initialized);
} }

View File

@@ -43,21 +43,13 @@ async def rainbow(np, period=10):
async def play_mp3(audiocore, mp3file): async def play_mp3(audiocore, mp3file):
_, avail, _ = audioctx.put(b'')
known_underruns = 0 known_underruns = 0
while True: while True:
data = mp3file.read(avail) data = mp3file.read(4096)
if avail > 0 and len(data) == 0: if len(data) == 0:
# End of file # End of file
break break
pos = 0 _, _, underruns = await audioctx.async_put(data)
while pos < len(data):
pushed, avail, underruns = audioctx.put(data[pos:])
if pushed == 0:
await asyncio.sleep_ms(0)
else:
await asyncio.sleep_ms(0)
pos += pushed
if underruns > known_underruns: if underruns > known_underruns:
print(f"{underruns:x}") print(f"{underruns:x}")
known_underruns = underruns known_underruns = underruns
@@ -66,6 +58,7 @@ async def play_mp3(audiocore, mp3file):
async def play_mp3s(audiocore, mp3files): async def play_mp3s(audiocore, mp3files):
audiocore.set_volume(64)
for name in mp3files: for name in mp3files:
print(b'Playing ' + name) print(b'Playing ' + name)
with open(name, "rb") as testfile: with open(name, "rb") as testfile:

View File

@@ -11,6 +11,7 @@ typedef struct spin_lock spin_lock_t;
void multicore_fifo_push_blocking(unsigned val); void multicore_fifo_push_blocking(unsigned val);
unsigned multicore_fifo_pop_blocking(void); unsigned multicore_fifo_pop_blocking(void);
bool multicore_fifo_rvalid(void); bool multicore_fifo_rvalid(void);
bool multicore_fifo_wready(void);
#define __time_critical_func(x) x #define __time_critical_func(x) x
#define __wfe() #define __wfe()