From e92b5ac81616b050162843e47b124727df474887 Mon Sep 17 00:00:00 2001 From: R Date: Tue, 15 Jul 2025 20:01:41 +0100 Subject: [PATCH 1/3] pbio/drv/sound/sound_ev3: Create stub sound driver --- bricks/_common/sources.mk | 1 + lib/pbio/drv/sound/sound_ev3.c | 22 ++++++++++++++++++++++ lib/pbio/platform/ev3/pbdrvconfig.h | 3 +++ pybricks/hubs/pb_type_ev3brick.c | 3 +++ 4 files changed, 29 insertions(+) create mode 100644 lib/pbio/drv/sound/sound_ev3.c diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index 81a19f53c..40abbea47 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -175,6 +175,7 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ drv/reset/reset_stm32.c \ drv/reset/reset_ev3.c \ drv/resistor_ladder/resistor_ladder.c \ + drv/sound/sound_ev3.c \ drv/sound/sound_nxt.c \ drv/sound/sound_stm32_hal_dac.c \ drv/stack/stack_embedded.c \ diff --git a/lib/pbio/drv/sound/sound_ev3.c b/lib/pbio/drv/sound/sound_ev3.c new file mode 100644 index 000000000..441ab58d9 --- /dev/null +++ b/lib/pbio/drv/sound/sound_ev3.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +#include + +#if PBDRV_CONFIG_SOUND_EV3 + +#include + +void pbdrv_sound_stop() { + +} + +void pbdrv_sound_start(const uint16_t *data, uint32_t length, uint32_t sample_rate) { + +} + +void pbdrv_sound_init() { + +} + +#endif // PBDRV_CONFIG_SOUND_EV3 diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index 9ef46f313..2aa29f852 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -71,6 +71,9 @@ #define PBDRV_CONFIG_RESET (1) #define PBDRV_CONFIG_RESET_EV3 (1) +#define PBDRV_CONFIG_SOUND (1) +#define PBDRV_CONFIG_SOUND_EV3 (1) + #define PBDRV_CONFIG_UART (1) #define PBDRV_CONFIG_UART_DEBUG_FIRST_PORT (1) #define PBDRV_CONFIG_UART_EV3 (1) diff --git a/pybricks/hubs/pb_type_ev3brick.c b/pybricks/hubs/pb_type_ev3brick.c index 33feeb4d8..bf8778d12 100644 --- a/pybricks/hubs/pb_type_ev3brick.c +++ b/pybricks/hubs/pb_type_ev3brick.c @@ -20,6 +20,7 @@ typedef struct _hubs_EV3Brick_obj_t { mp_obj_t buttons; mp_obj_t light; mp_obj_t screen; + mp_obj_t speaker; mp_obj_t system; } hubs_EV3Brick_obj_t; @@ -53,6 +54,7 @@ static mp_obj_t hubs_EV3Brick_make_new(const mp_obj_type_t *type, size_t n_args, self->buttons = pb_type_Keypad_obj_new(pb_type_ev3brick_button_pressed); self->light = common_ColorLight_internal_obj_new(pbsys_status_light_main); self->screen = pb_type_Image_display_obj_new(); + self->speaker = mp_call_function_0(MP_OBJ_FROM_PTR(&pb_type_Speaker)); self->system = MP_OBJ_FROM_PTR(&pb_type_System); return MP_OBJ_FROM_PTR(self); @@ -63,6 +65,7 @@ static const pb_attr_dict_entry_t hubs_EV3Brick_attr_dict[] = { PB_DEFINE_CONST_ATTR_RO(MP_QSTR_buttons, hubs_EV3Brick_obj_t, buttons), PB_DEFINE_CONST_ATTR_RO(MP_QSTR_light, hubs_EV3Brick_obj_t, light), PB_DEFINE_CONST_ATTR_RO(MP_QSTR_screen, hubs_EV3Brick_obj_t, screen), + PB_DEFINE_CONST_ATTR_RO(MP_QSTR_speaker, hubs_EV3Brick_obj_t, speaker), PB_DEFINE_CONST_ATTR_RO(MP_QSTR_system, hubs_EV3Brick_obj_t, system), PB_ATTR_DICT_SENTINEL }; From 0747186565ab2abec938593c980507365f337d99 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 16 Jul 2025 01:52:06 +0100 Subject: [PATCH 2/3] pbio/drv/sound/sound_ev3: Set up hardware and IRQ handler --- lib/pbio/drv/sound/sound_ev3.c | 79 +++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/lib/pbio/drv/sound/sound_ev3.c b/lib/pbio/drv/sound/sound_ev3.c index 441ab58d9..7262a1c81 100644 --- a/lib/pbio/drv/sound/sound_ev3.c +++ b/lib/pbio/drv/sound/sound_ev3.c @@ -7,8 +7,48 @@ #include -void pbdrv_sound_stop() { +#include +#include +#include +#include +#include + +#include "../drv/gpio/gpio_ev3.h" + +// Audio amplifier enable +static const pbdrv_gpio_t pin_sound_en = PBDRV_GPIO_EV3_PIN(13, 3, 0, 6, 15); +// Audio output pin +#define SYSCFG_PINMUX3_PINMUX3_7_4_GPIO0_0 0 +static const pbdrv_gpio_t pin_audio = PBDRV_GPIO_EV3_PIN(3, 7, 4, 0, 0); +// This hardware is not capable of producing 16 bits per sample +// at an acceptable sampling rate. As a trade-off, use 12 bits per sample +// giving a sampling rate of 150 MHz / 2**12 ~= 36 ksps +// +// Unlike other audio drivers, this one runs at a fixed sample rate. +// This is because the AM1808 has relatively few possible clock division ratios, +// and we do not want the usable bit depth to vary as the sample rate changes. +static const unsigned N_BITS_PER_SAMPLE = 12; + +static void sound_isr() { + EHRPWMETIntClear(SOC_EHRPWM_0_REGS); + IntSystemStatusClear(SYS_INT_EHRPWM0); + + // TODO: Load samples +} + +void pbdrv_sound_stop() { + // Turn speaker amplifier off + pbdrv_gpio_out_low(&pin_sound_en); + // Clean up counter + HWREGH(SOC_EHRPWM_0_REGS + EHRPWM_TBCTL) |= EHRPWM_TBCTL_CTRMODE_STOPFREEZE; + EHRPWMWriteTBCount(SOC_EHRPWM_0_REGS, 0); + EHRPWMETIntDisable(SOC_EHRPWM_0_REGS); + EHRPWMETIntClear(SOC_EHRPWM_0_REGS); + // Disable shadowing and set the count to 0 + EHRPWMLoadCMPB(SOC_EHRPWM_0_REGS, 0, true, 0, true); + // Re-enable shadowing + EHRPWMLoadCMPB(SOC_EHRPWM_0_REGS, 0, false, EHRPWM_CMPCTL_LOADBMODE_TBCTRPRD, true); } void pbdrv_sound_start(const uint16_t *data, uint32_t length, uint32_t sample_rate) { @@ -16,7 +56,44 @@ void pbdrv_sound_start(const uint16_t *data, uint32_t length, uint32_t sample_ra } void pbdrv_sound_init() { + // Turn on EPWM + PSCModuleControl(SOC_PSC_1_REGS, HW_PSC_EHRPWM, PSC_POWERDOMAIN_ALWAYS_ON, PSC_MDCTL_NEXT_ENABLE); + + // The stop function performs various initializations + pbdrv_sound_stop(); + + // Set up settings which will stay consistent throughout + EHRPWMTimebaseClkConfig(SOC_EHRPWM_0_REGS, SOC_EHRPWM_0_MODULE_FREQ, SOC_EHRPWM_0_MODULE_FREQ); + // Set the period to go up to max of nbits + HWREGH(SOC_EHRPWM_0_REGS + EHRPWM_TBCTL) |= EHRPWM_TBCTL_PRDLD; + HWREGH(SOC_EHRPWM_0_REGS + EHRPWM_TBPRD) = (1 << N_BITS_PER_SAMPLE) - 1; + // Pulse goes high @ t=0 + // Pulse goes low @ t=CMPB + EHRPWMConfigureAQActionOnB( + SOC_EHRPWM_0_REGS, + EHRPWM_AQCTLB_ZRO_EPWMXBHIGH, + EHRPWM_AQCTLB_PRD_DONOTHING, + EHRPWM_AQCTLB_CAU_DONOTHING, + EHRPWM_AQCTLB_CAD_DONOTHING, + EHRPWM_AQCTLB_CBU_EPWMXBLOW, + EHRPWM_AQCTLB_CBD_DONOTHING, + EHRPWM_AQSFRC_ACTSFB_DONOTHING + ); + // Disable unused features + EHRPWMDBOutput(SOC_EHRPWM_0_REGS, EHRPWM_DBCTL_OUT_MODE_BYPASS); + EHRPWMChopperDisable(SOC_EHRPWM_0_REGS); + EHRPWMTZTripEventDisable(SOC_EHRPWM_0_REGS, false); + EHRPWMTZTripEventDisable(SOC_EHRPWM_0_REGS, true); + + // Interrupts + IntRegister(SYS_INT_EHRPWM0, sound_isr); + IntChannelSet(SYS_INT_EHRPWM0, 1); + IntSystemEnable(SYS_INT_EHRPWM0); + EHRPWMETIntSourceSelect(SOC_EHRPWM_0_REGS, EHRPWM_ETSEL_INTSEL_TBCTREQUPRD); + EHRPWMETIntPrescale(SOC_EHRPWM_0_REGS, EHRPWM_ETPS_INTPRD_FIRSTEVENT); + // Configure IO pin mode + pbdrv_gpio_alt(&pin_audio, SYSCFG_PINMUX3_PINMUX3_7_4_EPWM0B); } #endif // PBDRV_CONFIG_SOUND_EV3 From db1119ae9d0cab042aca1a5d9c21549f28a77d78 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 16 Jul 2025 01:54:44 +0100 Subject: [PATCH 3/3] pbio/drv/sound/sound_ev3: Implement minimum-viable audio playback --- lib/pbio/drv/sound/sound_ev3.c | 44 +++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/pbio/drv/sound/sound_ev3.c b/lib/pbio/drv/sound/sound_ev3.c index 7262a1c81..02c08b0cd 100644 --- a/lib/pbio/drv/sound/sound_ev3.c +++ b/lib/pbio/drv/sound/sound_ev3.c @@ -30,11 +30,32 @@ static const pbdrv_gpio_t pin_audio = PBDRV_GPIO_EV3_PIN(3, 7, 4, 0, 0); // and we do not want the usable bit depth to vary as the sample rate changes. static const unsigned N_BITS_PER_SAMPLE = 12; +static uint64_t sample_timing_accum_numerator; +static uint32_t sample_idx; + +static const uint16_t *playing_data; +static uint32_t playing_data_len; +static uint32_t playing_sample_rate; + static void sound_isr() { EHRPWMETIntClear(SOC_EHRPWM_0_REGS); IntSystemStatusClear(SYS_INT_EHRPWM0); - // TODO: Load samples + // Convert the hardware sample index to a desired data sample index + // (using a naive ratio, rearranged to be computable using integers) + // TODO: Use a real DSP resampling algorithm + sample_timing_accum_numerator += (uint64_t)playing_sample_rate * (1ull << N_BITS_PER_SAMPLE); + if (sample_timing_accum_numerator >= SOC_EHRPWM_0_MODULE_FREQ) { + sample_timing_accum_numerator -= SOC_EHRPWM_0_MODULE_FREQ; + sample_idx++; + } + if (sample_idx == playing_data_len) { + sample_idx = 0; + } + + uint16_t sample = playing_data[sample_idx]; + // TODO: Dither the quantization error + HWREGH(SOC_EHRPWM_0_REGS + EHRPWM_CMPB) = sample >> (16 - N_BITS_PER_SAMPLE); } void pbdrv_sound_stop() { @@ -52,7 +73,28 @@ void pbdrv_sound_stop() { } void pbdrv_sound_start(const uint16_t *data, uint32_t length, uint32_t sample_rate) { + // Stop any currently-playing sounds + pbdrv_sound_stop(); + if (length == 0) { + return; + } + + __asm__ volatile ("" ::: "memory"); + playing_data = data; + playing_data_len = length; + playing_sample_rate = sample_rate; + sample_idx = 0; + sample_timing_accum_numerator = 0; + __asm__ volatile ("" ::: "memory"); + + // Set the first sample + HWREGH(SOC_EHRPWM_0_REGS + EHRPWM_CMPB) = data[0] >> (16 - N_BITS_PER_SAMPLE); + + // Enable all the sound generation + pbdrv_gpio_out_high(&pin_sound_en); + EHRPWMETIntEnable(SOC_EHRPWM_0_REGS); + HWREGH(SOC_EHRPWM_0_REGS + EHRPWM_TBCTL) = (HWREGH(SOC_EHRPWM_0_REGS + EHRPWM_TBCTL) & ~EHRPWM_TBCTL_CTRMODE) | EHRPWM_TBCTL_CTRMODE_UP; } void pbdrv_sound_init() {