audiocore: Support swapping dclk and lrclk pins for I2S

Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
2025-09-28 18:29:35 +02:00
parent da90228ab5
commit c9150eb21a
8 changed files with 57 additions and 15 deletions

View File

@@ -40,7 +40,7 @@ void __time_critical_func(core1_main)(void)
{
uint32_t ret = 0;
bool running = true, playing = false;
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base)) {
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base, shared_context.sideset_dclk_first)) {
ret = MP_EIO;
goto out;
}

View File

@@ -31,6 +31,7 @@ struct audiocore_shared_context {
// Set by module.c before core1 is launched and then never changed, can be read without lock
int out_pin, sideset_base, samplerate;
bool sideset_dclk_first;
// Must hold lock. The indices 0..MP3_BUFFER_PREAREA-1 may only be read and written on core1 (no
// lock needed) The buffer is aligned to, and MP3_BUFFER_PREAREA is a multiple of, the machine

View File

@@ -8,11 +8,10 @@ from utils import get_pin_index
class Audiocore:
def __init__(self, din, dclk, lrclk):
assert get_pin_index(lrclk) == get_pin_index(dclk)+1 # TODO: Support different pin arrangements
# PIO requires sideset pins to be adjacent
assert get_pin_index(lrclk) == get_pin_index(dclk)+1 or get_pin_index(lrclk) == get_pin_index(dclk)-1
self.notify = ThreadSafeFlag()
self.pin = din
self.sideset = dclk
self._audiocore = _audiocore.Audiocore(self.pin, self.sideset, self._interrupt)
self._audiocore = _audiocore.Audiocore(din, dclk, lrclk, self._interrupt)
def deinit(self):
self._audiocore.deinit()

View File

@@ -113,17 +113,18 @@ void i2s_stop(void)
pio_sm_clear_fifos(audiocore_pio, i2s_context.pio_sm);
}
bool i2s_init(int out_pin, int sideset_base)
bool i2s_init(int out_pin, int sideset_base, bool dclk_first)
{
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0][0]) * AUDIO_BUFS * I2S_DMA_BUF_SIZE);
if (!pio_can_add_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program))
const pio_program_t *program = dclk_first ? &i2s_max98357_program : &i2s_max98357_lrclk_program;
if (!pio_can_add_program(audiocore_pio, program))
return false;
i2s_context.pio_sm = pio_claim_unused_sm(audiocore_pio, false);
i2s_context.out_pin = out_pin;
i2s_context.sideset_base = sideset_base;
if (i2s_context.pio_sm == -1)
return false;
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program);
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, program);
i2s_context.dma_ch = dma_claim_unused_channel(false);
if (i2s_context.dma_ch == -1)

View File

@@ -8,7 +8,7 @@
#define I2S_DMA_BUF_SIZE (1152)
bool i2s_init(int out_pin, int sideset_base);
bool i2s_init(int out_pin, int sideset_base, bool dclk_first);
void i2s_deinit(void);
void i2s_play(int samplerate);

View File

@@ -10,7 +10,7 @@
.lang_opt python autopull = True
// data - DOUT
// sideset - 2-BCLK, 1-LRCLK
// sideset - 2-LRCLK, 1-BCLK
set x,15 side 0
nop side 1
@@ -33,6 +33,38 @@ right_loop:
set x, 14 side 1
.wrap
.program i2s_max98357_lrclk
.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 2
startup_loop:
nop side 1
jmp x-- startup_loop side 3
nop side 0
set x, 14 side 2
left_loop:
.wrap_target
out pins, 1 side 0
jmp x-- left_loop side 2
out pins, 1 side 1
set x, 14 side 3
right_loop:
out pins, 1 side 1
jmp x-- right_loop side 3
out pins, 1 side 0
set x, 14 side 2
.wrap
% c-sdk {
#include "hardware/clocks.h"

View File

@@ -170,10 +170,11 @@ static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_set_volume_obj, audiocore_set_volume)
*/
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, ARG_handler };
enum { ARG_pin, ARG_dclk, ARG_lrclk, ARG_handler };
static const mp_arg_t allowed_args[] = {
{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_dclk, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_lrclk, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
};
if (initialized)
@@ -188,7 +189,8 @@ static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_ob
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_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 sideset_pin = mp_hal_get_pin_obj(args[ARG_sideset].u_obj);
const mp_hal_pin_obj_t dclk_pin = mp_hal_get_pin_obj(args[ARG_dclk].u_obj);
const mp_hal_pin_obj_t lrclk_pin = mp_hal_get_pin_obj(args[ARG_lrclk].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;
@@ -203,7 +205,14 @@ static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_ob
memset(shared_context.mp3_buffer, 0, MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE);
multicore_reset_core1();
shared_context.out_pin = pin;
shared_context.sideset_base = sideset_pin;
// PIO requires sideset pins to be adjacent, but we support both dclk first and lrclk first
if (lrclk_pin == dclk_pin + 1) {
shared_context.sideset_base = dclk_pin;
shared_context.sideset_dclk_first = true;
} else {
shared_context.sideset_base = lrclk_pin;
shared_context.sideset_dclk_first = false;
}
initialized = true;
multicore_launch_core1(&core1_main);
uint32_t result = get_fifo_read_value_blocking(obj);

View File

@@ -15,7 +15,7 @@ static unsigned multicore_fifo_push_last;
static unsigned (*multicore_fifo_pop_blocking_cb)(void);
bool i2s_init(int out_pin, int sideset_base)
bool i2s_init(int out_pin, int sideset_base, bool dclk_first)
{
TEST_ASSERT_FALSE(i2s_initialized);
if (i2s_init_return)