Add audiocore module and I2S audio driver

This is the skeleton of the code that will run on core1 to perform the
audio decoding and output. In this initial commit, the module
infrastructure, starting code on core1, and the I2S audio driver with
DMA are implemented.

Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
2024-05-30 10:26:58 +02:00
parent 30a41de31a
commit a4028b11e8
9 changed files with 406 additions and 1 deletions

View File

@@ -7,7 +7,8 @@ set -eu
( cd lib/micropython
make -C mpy-cross -j $(nproc)
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR=$TOPDIR/boards/RPI_PICO_W clean
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR=$TOPDIR/boards/RPI_PICO_W -j $(nproc)
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR=$TOPDIR/boards/RPI_PICO_W \
USER_C_MODULES=$TOPDIR/src/audiocore/micropython.cmake -j $(nproc)
)
echo "Output in lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/firmware.uf2"

View File

@@ -7,5 +7,7 @@ while [ ! -e "$DEVICEPATH" ] ; do sleep 1; echo 'Waiting for RP2...'; done
set -eu
while [ ! -e "$DEVICEPATH" ] ; do sleep 1; echo 'Waiting for RP2...'; done
udisksctl mount -b "$DEVICEPATH"
cp "$IMAGEPATH" "$(findmnt "$DEVICEPATH" -n -o TARGET)"

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
#include "audiocore.h"
#include "i2s.h"
#include "py/mperrno.h"
void core1_main(void) {
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base, shared_context.samplerate))
multicore_fifo_push_blocking(MP_EIO);
multicore_fifo_push_blocking(0);
uint32_t cmd;
while ((cmd = multicore_fifo_pop_blocking()) != AUDIOCORE_CMD_SHUTDOWN) {
switch (cmd) {
default:
break;
}
}
i2s_deinit();
multicore_fifo_push_blocking(0);
}

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
#pragma once
#include <hardware/sync.h>
#include <string.h>
/* Access rules
* audiocore processing runs on core 1 and, unless stated otherwise, all variables may only be accessed from core 1.
* Take care of interrupt safety where needed.
* The micropython interface lives in module.c and is invoked from the micropython runtime on core 0.
* Micropython objects may only be handled in that context
* The audiocore_shared_context struct defined below is used for communication between the cores.
*/
#define AUDIO_BUFFER_SIZE 2048
// Context shared between the micropython runtime on core0 and the audio task on core1
// All access must hold "lock" unless otherwise noted
struct audiocore_shared_context {
spin_lock_t *lock;
int out_pin, sideset_base, samplerate; // Set by module.c before core1 is launched and then never changed, can be read without lock
uint32_t audio_buffer[AUDIO_BUFFER_SIZE];
int audio_buffer_write, audio_buffer_read;
int underruns;
};
extern struct audiocore_shared_context shared_context;
static inline unsigned audiocore_get_audio_buffer_space(void)
{
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
return AUDIO_BUFFER_SIZE - 1 - (shared_context.audio_buffer_write - shared_context.audio_buffer_read);
else
return shared_context.audio_buffer_read - shared_context.audio_buffer_write - 1;
}
static inline unsigned audiocore_get_audio_buffer_avail(void)
{
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
return shared_context.audio_buffer_write - shared_context.audio_buffer_read;
else
return AUDIO_BUFFER_SIZE - (shared_context.audio_buffer_read - shared_context.audio_buffer_write);
}
static inline void audiocore_audio_buffer_put(const uint32_t *restrict src, const size_t len)
{
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_write;
memcpy(shared_context.audio_buffer + shared_context.audio_buffer_write, src, 4 * ((end_samples >= len) ? len : end_samples));
if (end_samples < len) {
memcpy(shared_context.audio_buffer, src + end_samples, 4 * (len - end_samples));
shared_context.audio_buffer_write = len - end_samples;
} else {
shared_context.audio_buffer_write += len;
}
shared_context.audio_buffer_write %= AUDIO_BUFFER_SIZE;
}
static inline void audiocore_audio_buffer_get(uint32_t *restrict dst, const size_t len)
{
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_read;
memcpy(dst, shared_context.audio_buffer + shared_context.audio_buffer_read,
4 * ((end_samples >= len) ? len : end_samples));
if (end_samples < len) {
memcpy(dst + end_samples, shared_context.audio_buffer, 4 * (len - end_samples));
shared_context.audio_buffer_read = len - end_samples;
} else {
shared_context.audio_buffer_read += len;
}
shared_context.audio_buffer_read %= AUDIO_BUFFER_SIZE;
}
void core1_main(void);
// SHUTDOWN - no arguments - return 0
#define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de

View File

@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
#include "i2s_max98357.pio.h"
#include "audiocore.h"
#include <hardware/dma.h>
#include <hardware/sync.h>
#include <string.h>
#define audiocore_pio pio1
#define I2S_DMA_BUF_SIZE 256
struct i2s_context {
unsigned pio_program_offset;
int pio_sm;
int dma_ch;
dma_channel_config dma_config;
uint32_t dma_buf[I2S_DMA_BUF_SIZE];
};
static struct i2s_context i2s_context;
static void dma_isr(void)
{
if (!dma_channel_get_irq1_status(i2s_context.dma_ch))
return;
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
const uint32_t flags = spin_lock_blocking(shared_context.lock);
if (audiocore_get_audio_buffer_avail() >= I2S_DMA_BUF_SIZE) {
audiocore_audio_buffer_get(i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
spin_unlock(shared_context.lock, flags);
} else {
++shared_context.underruns;
spin_unlock(shared_context.lock, flags);
memset(i2s_context.dma_buf, 0, sizeof(uint32_t) * I2S_DMA_BUF_SIZE);
}
dma_channel_transfer_from_buffer_now(i2s_context.dma_ch, i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
}
static void setup_dma_config(void)
{
i2s_context.dma_config = dma_channel_get_default_config(i2s_context.dma_ch);
channel_config_set_dreq(&i2s_context.dma_config, pio_get_dreq(pio1, i2s_context.pio_sm, true));
}
bool i2s_init(int out_pin, int sideset_base, int samplerate)
{
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0]) * I2S_DMA_BUF_SIZE);
if (!pio_can_add_program(audiocore_pio, &i2s_max98357_program))
return false;
i2s_context.pio_sm = pio_claim_unused_sm(audiocore_pio, false);
if (i2s_context.pio_sm == -1)
return false;
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, &i2s_max98357_program);
i2s_max98357_program_init(audiocore_pio, i2s_context.pio_sm, i2s_context.pio_program_offset, out_pin, sideset_base, samplerate);
i2s_context.dma_ch = dma_claim_unused_channel(false);
if (i2s_context.dma_ch == -1)
goto out_dma_claim;
setup_dma_config();
irq_set_exclusive_handler(DMA_IRQ_1, &dma_isr);
dma_channel_set_irq1_enabled (i2s_context.dma_ch, true);
irq_set_enabled(DMA_IRQ_1, true);
dma_channel_configure(i2s_context.dma_ch, &i2s_context.dma_config, &audiocore_pio->txf[i2s_context.pio_sm],
i2s_context.dma_buf, I2S_DMA_BUF_SIZE, true);
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, true);
return true;
out_dma_claim:
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
return false;
}
void i2s_deinit(void)
{
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, false);
dma_channel_set_irq1_enabled (i2s_context.dma_ch, false);
dma_channel_unclaim(i2s_context.dma_ch);
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
}

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
#pragma once
#include <stdbool.h>
bool i2s_init(int out_pin, int sideset_base, int samplerate);
void i2s_deinit(void);

View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
.program i2s_max98357
.side_set 2
.lang_opt python sideset_init = pico.PIO.OUT_LOW
.lang_opt python out_init = pico.PIO.OUT_LOW
.lang_opt python out_shiftdir = pcio.PIO.SHIFT_LEFT
.lang_opt python autopull = True
// data - DOUT
// sideset - 2-BCLK, 1-LRCLK
set x,15 side 0
nop side 1
startup_loop:
nop side 2
jmp x-- startup_loop side 3
nop side 0
set x, 14 side 1
left_loop:
.wrap_target
out pins, 1 side 0
jmp x-- left_loop side 1
out pins, 1 side 2
set x, 14 side 3
right_loop:
out pins, 1 side 2
jmp x-- right_loop side 3
out pins, 1 side 0
set x, 14 side 1
.wrap
% c-sdk {
#include "hardware/clocks.h"
static inline void i2s_max98357_program_init(PIO pio, uint sm, uint offset, uint pin, uint sideset, uint samplerate) {
pio_gpio_init(pio, pin);
pio_gpio_init(pio, sideset);
pio_gpio_init(pio, sideset+1);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_set_consecutive_pindirs(pio, sm, sideset, 2, true);
pio_sm_config c = i2s_max98357_program_get_default_config(offset);
sm_config_set_out_pins(&c, pin, 1);
sm_config_set_sideset_pins(&c, sideset);
sm_config_set_out_shift(&c, false, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
const unsigned i2s_freq = samplerate * 2 * 16 * 2;
const float div = clock_get_hz(clk_sys) / (float)i2s_freq;
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
//pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@@ -0,0 +1,19 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
add_library(usermod_audiocore INTERFACE)
pico_generate_pio_header(usermod_audiocore ${CMAKE_CURRENT_LIST_DIR}/i2s_max98357.pio)
target_sources(usermod_audiocore INTERFACE
${CMAKE_CURRENT_LIST_DIR}/audiocore.c
${CMAKE_CURRENT_LIST_DIR}/module.c
${CMAKE_CURRENT_LIST_DIR}/i2s.c
${CMAKE_CURRENT_BINARY_DIR}/i2s_max98357.pio.h
)
target_include_directories(usermod_audiocore INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(usermod INTERFACE usermod_audiocore)

View File

@@ -0,0 +1,125 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
#include "audiocore.h"
// Include MicroPython API.
#include "py/runtime.h"
#include "py/mperrno.h"
// This module is RP2 specific
#include "mphalport.h"
#include <string.h>
struct audiocore_shared_context shared_context = {.lock = NULL};
static bool initialized = false;
struct audiocore_Context_obj {
mp_obj_base_t base;
};
static mp_obj_t audiocore_Context_deinit(mp_obj_t self_in) {
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN);
multicore_fifo_pop_blocking();
(void)self;
initialized = false;
mp_printf(MP_PYTHON_PRINTER, "Free audiocore Context\n");
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_Context_deinit_obj, audiocore_Context_deinit);
static mp_obj_t audiocore_Context_put(mp_obj_t self_in, mp_obj_t buffer) {
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
(void)self;
mp_buffer_info_t bufinfo;
if (!mp_get_buffer(buffer, &bufinfo, MP_BUFFER_READ))
mp_raise_ValueError("not a read buffer");
if (bufinfo.typecode != 'I')
mp_raise_ValueError("unsupported buffer type");
unsigned to_copy = bufinfo.len / 4;
const uint32_t flags = spin_lock_blocking(shared_context.lock);
const unsigned buf_space = audiocore_get_audio_buffer_space();
if (to_copy > buf_space)
to_copy = buf_space;
if (to_copy > 0) {
audiocore_audio_buffer_put(bufinfo.buf, to_copy);
}
const unsigned underruns = shared_context.underruns;
spin_unlock(shared_context.lock, flags);
mp_obj_t items[] = {mp_obj_new_int(to_copy),
mp_obj_new_int(buf_space),
mp_obj_new_int(underruns),};
return mp_obj_new_tuple(3, items);
//return mp_obj_new_int(to_copy);
}
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_Context_put_obj, audiocore_Context_put);
static const mp_rom_map_elem_t audiocore_Context_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiocore_Context_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiocore_Context_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR_put), MP_ROM_PTR(&audiocore_Context_put_obj) },
};
static MP_DEFINE_CONST_DICT(audiocore_Context_locals_dict, audiocore_Context_locals_dict_table);
const mp_obj_type_t audiocore_Context_type;
MP_DEFINE_CONST_OBJ_TYPE(
audiocore_Context_type,
MP_QSTR_Context,
MP_TYPE_FLAG_NONE,
locals_dict, &audiocore_Context_locals_dict
);
static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t samplerate_obj) {
if (initialized)
mp_raise_OSError(MP_EBUSY);
if (!shared_context.lock) {
// initialize shared context lock on first init
int lock = spin_lock_claim_unused(false);
if (lock == -1)
mp_raise_OSError(MP_ENOMEM);
shared_context.lock = spin_lock_init(lock);
}
shared_context.audio_buffer_write = shared_context.audio_buffer_read = shared_context.underruns = 0;
memset(shared_context.audio_buffer, 0, AUDIO_BUFFER_SIZE*4);
multicore_reset_core1();
struct audiocore_Context_obj *context = m_malloc_with_finaliser(sizeof(struct audiocore_Context_obj));
context->base.type = &audiocore_Context_type;
mp_hal_pin_obj_t pin = pin_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(pin_obj);
if (pin == -1)
mp_raise_ValueError("Invalid out pin");
mp_hal_pin_obj_t sideset_pin = sideset_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(sideset_obj);
if (sideset_pin == -1)
mp_raise_ValueError("Invalid sideset base pin");
int samplerate = mp_obj_get_int(samplerate_obj);
shared_context.out_pin = pin;
shared_context.sideset_base = sideset_pin;
shared_context.samplerate = samplerate;
initialized = true;
multicore_launch_core1(&core1_main);
uint32_t result = multicore_fifo_pop_blocking();
if (result != 0) {
multicore_reset_core1();
initialized = false;
mp_raise_OSError(result);
}
return MP_OBJ_FROM_PTR(context);
}
static MP_DEFINE_CONST_FUN_OBJ_3(audiocore_init_obj, audiocore_init);
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_init), MP_ROM_PTR(&audiocore_init_obj) },
{ MP_ROM_QSTR(MP_QSTR_Context), MP_ROM_PTR(&audiocore_Context_type) },
};
static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table);
const mp_obj_module_t audiocore_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&audiocore_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_audiocore, audiocore_cmodule);