From fc9b42a891724ba18211dd2724ec88dd4673cbec Mon Sep 17 00:00:00 2001 From: Artyom Skrobov Date: Sat, 6 Mar 2021 02:20:16 -0500 Subject: [PATCH] [stm] implementation of audiopwmio Based on nrf PWMAudioOut by @jepler and stm PulseOut by @hierophect Tested on a Meowbit --- locale/circuitpython.pot | 18 +- .../stm/boards/espruino_pico/mpconfigboard.mk | 2 + .../stm/boards/espruino_wifi/mpconfigboard.mk | 4 + ports/stm/boards/pyb_nano_v2/mpconfigboard.mk | 4 + .../stm32f411ce_blackpill/mpconfigboard.mk | 4 + .../stm32f411ve_discovery/mpconfigboard.mk | 4 + ports/stm/common-hal/audiopwmio/PWMAudioOut.c | 350 ++++++++++++++++++ ports/stm/common-hal/audiopwmio/PWMAudioOut.h | 58 +++ ports/stm/common-hal/audiopwmio/__init__.c | 0 ports/stm/common-hal/digitalio/DigitalInOut.c | 7 +- ports/stm/common-hal/microcontroller/Pin.c | 9 + ports/stm/common-hal/microcontroller/Pin.h | 2 + ports/stm/mpconfigport.mk | 6 +- ports/stm/supervisor/port.c | 6 + shared-bindings/audiopwmio/PWMAudioOut.c | 3 +- 15 files changed, 466 insertions(+), 11 deletions(-) create mode 100644 ports/stm/common-hal/audiopwmio/PWMAudioOut.c create mode 100644 ports/stm/common-hal/audiopwmio/PWMAudioOut.h create mode 100644 ports/stm/common-hal/audiopwmio/__init__.c diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 2f15d70b12254..e895cb731144d 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -73,6 +73,7 @@ msgstr "" #: extmod/moductypes.c ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nrf/common-hal/pulseio/PulseIn.c +#: ports/raspberrypi/common-hal/pulseio/PulseIn.c #: ports/stm/common-hal/pulseio/PulseIn.c py/obj.c py/objstr.c #: py/objstrunicode.c msgid "%q index out of range" @@ -406,6 +407,10 @@ msgstr "" msgid "AnalogOut not supported on given pin" msgstr "" +#: ports/stm/common-hal/audiopwmio/PWMAudioOut.c +msgid "Another PWMAudioOut is already active" +msgstr "" + #: ports/atmel-samd/common-hal/pulseio/PulseOut.c #: ports/cxd56/common-hal/pulseio/PulseOut.c msgid "Another send is already active" @@ -520,6 +525,7 @@ msgid "Buffer is too small" msgstr "" #: ports/nrf/common-hal/audiopwmio/PWMAudioOut.c +#: ports/stm/common-hal/audiopwmio/PWMAudioOut.c #, c-format msgid "Buffer length %d too big. It must be less than %d" msgstr "" @@ -962,6 +968,7 @@ msgstr "" #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/esp32s2/common-hal/pulseio/PulseIn.c #: ports/nrf/common-hal/pulseio/PulseIn.c +#: ports/raspberrypi/common-hal/pulseio/PulseIn.c #: ports/stm/common-hal/pulseio/PulseIn.c #, c-format msgid "Failed to allocate RX buffer of %d bytes" @@ -975,6 +982,10 @@ msgstr "" msgid "Failed to allocate wifi scan memory" msgstr "" +#: ports/stm/common-hal/audiopwmio/PWMAudioOut.c +msgid "Failed to buffer the sample" +msgstr "" + #: ports/nrf/common-hal/_bleio/Adapter.c msgid "Failed to connect: internal error" msgstr "" @@ -1247,7 +1258,7 @@ msgstr "" msgid "Invalid format chunk size" msgstr "" -#: ports/esp32s2/common-hal/pwmio/PWMOut.c +#: ports/esp32s2/common-hal/busio/I2C.c ports/esp32s2/common-hal/pwmio/PWMOut.c msgid "Invalid frequency" msgstr "" @@ -2291,7 +2302,7 @@ msgstr "" msgid "Unsupported format" msgstr "" -#: py/moduerrno.c +#: ports/raspberrypi/common-hal/pulseio/PulseOut.c py/moduerrno.c msgid "Unsupported operation" msgstr "" @@ -3699,6 +3710,7 @@ msgstr "" #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nrf/common-hal/pulseio/PulseIn.c +#: ports/raspberrypi/common-hal/pulseio/PulseIn.c #: ports/stm/common-hal/pulseio/PulseIn.c py/objdict.c py/objlist.c py/objset.c #: shared-bindings/ps2io/Ps2.c msgid "pop from empty %q" @@ -3724,6 +3736,8 @@ msgstr "" #: ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.h #: ports/esp32s2/boards/espressif_saola_1_wroom/mpconfigboard.h #: ports/esp32s2/boards/espressif_saola_1_wrover/mpconfigboard.h +#: ports/esp32s2/boards/franzininho_wifi_wroom/mpconfigboard.h +#: ports/esp32s2/boards/franzininho_wifi_wrover/mpconfigboard.h #: ports/esp32s2/boards/lilygo_ttgo_t8_s2_st7789/mpconfigboard.h #: ports/esp32s2/boards/microdev_micro_s2/mpconfigboard.h #: ports/esp32s2/boards/muselab_nanoesp32_s2/mpconfigboard.h diff --git a/ports/stm/boards/espruino_pico/mpconfigboard.mk b/ports/stm/boards/espruino_pico/mpconfigboard.mk index d6118adf88332..91481602861b0 100644 --- a/ports/stm/boards/espruino_pico/mpconfigboard.mk +++ b/ports/stm/boards/espruino_pico/mpconfigboard.mk @@ -20,6 +20,8 @@ LD_FILE = boards/STM32F401xd_fs.ld # lto for this port, and if other stuff hasn't been added in the # meantime CIRCUITPY_ULAB = 0 +CIRCUITPY_AUDIOCORE = 0 +CIRCUITPY_AUDIOPWMIO = 0 CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FRAMEBUFFERIO = 0 diff --git a/ports/stm/boards/espruino_wifi/mpconfigboard.mk b/ports/stm/boards/espruino_wifi/mpconfigboard.mk index 9500a5f18583d..a2ceb1ae3843a 100644 --- a/ports/stm/boards/espruino_wifi/mpconfigboard.mk +++ b/ports/stm/boards/espruino_wifi/mpconfigboard.mk @@ -11,3 +11,7 @@ MCU_PACKAGE = UFQFPN48 LD_COMMON = boards/common_default.ld LD_FILE = boards/STM32F411_fs.ld + +# Too big for the flash +CIRCUITPY_AUDIOCORE = 0 +CIRCUITPY_AUDIOPWMIO = 0 diff --git a/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk b/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk index cff8eea70500f..8dfc9f0036a0d 100644 --- a/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk +++ b/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk @@ -12,3 +12,7 @@ MCU_PACKAGE = UFQFPN48 LD_COMMON = boards/common_default.ld LD_FILE = boards/STM32F411_fs.ld + +# Too big for the flash +CIRCUITPY_AUDIOCORE = 0 +CIRCUITPY_AUDIOPWMIO = 0 diff --git a/ports/stm/boards/stm32f411ce_blackpill/mpconfigboard.mk b/ports/stm/boards/stm32f411ce_blackpill/mpconfigboard.mk index 1d533e30f3118..bfb88ab0ea0dd 100644 --- a/ports/stm/boards/stm32f411ce_blackpill/mpconfigboard.mk +++ b/ports/stm/boards/stm32f411ce_blackpill/mpconfigboard.mk @@ -15,3 +15,7 @@ MCU_PACKAGE = UFQFPN48 LD_COMMON = boards/common_default.ld LD_FILE = boards/STM32F411_fs.ld + +# Too big for the flash +CIRCUITPY_AUDIOCORE = 0 +CIRCUITPY_AUDIOPWMIO = 0 diff --git a/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk b/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk index 3cb7162a4add7..3180fbd0845e0 100644 --- a/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk +++ b/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk @@ -11,3 +11,7 @@ MCU_PACKAGE = LQFP100_f4 LD_COMMON = boards/common_default.ld LD_FILE = boards/STM32F411_fs.ld + +# Too big for the flash +CIRCUITPY_AUDIOCORE = 0 +CIRCUITPY_AUDIOPWMIO = 0 diff --git a/ports/stm/common-hal/audiopwmio/PWMAudioOut.c b/ports/stm/common-hal/audiopwmio/PWMAudioOut.c new file mode 100644 index 0000000000000..69632181ff54f --- /dev/null +++ b/ports/stm/common-hal/audiopwmio/PWMAudioOut.c @@ -0,0 +1,350 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Artyom Skrobov 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. + */ +#include + +#include "py/runtime.h" +#include "common-hal/audiopwmio/PWMAudioOut.h" +#include "shared-bindings/audiopwmio/PWMAudioOut.h" + +#include "timers.h" + +// TODO: support multiple concurrently active outputs. +STATIC TIM_HandleTypeDef tim_handle; +STATIC audiopwmio_pwmaudioout_obj_t* active_audio = NULL; + +STATIC void set_pin(uint8_t channel, GPIO_PinState state) { + HAL_GPIO_WritePin(pin_port(active_audio->pin[channel]->port), + pin_mask(active_audio->pin[channel]->number), state); +} + +STATIC void toggle_pin(uint8_t channel) { + HAL_GPIO_TogglePin(pin_port(active_audio->pin[channel]->port), + pin_mask(active_audio->pin[channel]->number)); +} + +STATIC void start_timer(audiopwmio_pwmaudioout_obj_t *self) { + if (self->buffer_ptr[0] >= self->buffer_length[0]) // no more pulses + return; + + self->period = self->buffer[0][self->buffer_ptr[0]]; + if (self->pin[1] && self->period > self->buffer[1][self->buffer_ptr[1]]) + self->period = self->buffer[1][self->buffer_ptr[1]]; + + // Set the new period + tim_handle.Init.Period = self->period - 1; + HAL_TIM_Base_Init(&tim_handle); + + // TIM7 has limited HAL support, set registers manually + tim_handle.Instance->SR = 0; // Prevent the SR from triggering an interrupt + tim_handle.Instance->CR1 |= TIM_CR1_CEN; // Resume timer + tim_handle.Instance->CR1 |= TIM_CR1_URS; // Disable non-overflow interrupts + __HAL_TIM_ENABLE_IT(&tim_handle, TIM_IT_UPDATE); +} + +STATIC bool fill_buffers(audiopwmio_pwmaudioout_obj_t *self) { + // Naive PCM-to-PWM conversion + int16_t threshold = 0x666; // 0.05; TODO: make configurable + uint8_t *buffer; + uint32_t buffer_length; + audioio_get_buffer_result_t get_buffer_result; + + bool average = (self->sample_channel_count > 1) && !self->pin[1]; + bool replicate = (self->sample_channel_count == 1) && self->pin[1]; + int8_t effective_channels = average ? 1 : self->sample_channel_count; + + do { + get_buffer_result = audiosample_get_buffer(self->sample, false, 0, &buffer, &buffer_length); + if (get_buffer_result == GET_BUFFER_ERROR) + return false; + + uint32_t num_samples = buffer_length / self->bytes_per_sample / self->sample_channel_count; + int16_t *buffer16 = (int16_t*)buffer; + + while (num_samples--) { + for (int8_t channel=0; channel < effective_channels; channel++) { + int16_t val; + if (self->bytes_per_sample == 1) + val = *buffer++ << 8; + else + val = *buffer16++; + val += self->sample_offset; + + if (average) { + int16_t next; + if (self->bytes_per_sample == 1) + next = *buffer++ << 8; + else + next = *buffer16++; + next += self->sample_offset; + val += (next - val) / 2; + } + + int8_t new_pos = (val > threshold) - (val < -threshold); + if (new_pos == -self->pos[channel]) { + self->buffer[channel][self->buffer_length[channel]++] = self->len[channel]; + if (replicate) { + self->buffer[1-channel][self->buffer_length[1-channel]++] = self->len[channel]; + } + self->pos[channel] = new_pos; + self->len[channel] = 0; + } + self->len[channel]++; + } + } + } while (get_buffer_result == GET_BUFFER_MORE_DATA && + (!self->buffer_length[0] || (self->pin[1] && !self->buffer_length[1]))); + + if (get_buffer_result == GET_BUFFER_DONE) { + // It's the final countdown + for (int8_t channel=0; channel < effective_channels; channel++) { + self->buffer[channel][self->buffer_length[channel]++] = self->len[channel]; + if (replicate) { + self->buffer[1-channel][self->buffer_length[1-channel]++] = self->len[channel]; + } + } + + if (self->loop) + audiosample_reset_buffer(self->sample, false, 0); + else + self->stopping = true; + } + return true; +} + +STATIC void move_to_beginning(uint16_t *buffer, uint16_t *buffer_length, uint16_t *buffer_ptr) { + if (*buffer_ptr < *buffer_length) { + memmove(buffer, buffer + *buffer_ptr, *buffer_length - *buffer_ptr); + *buffer_length -= *buffer_ptr; + } else { + *buffer_length = 0; + } + *buffer_ptr = 0; +} + +STATIC void pwmaudioout_event_handler(void) { + // Detect TIM Update event + if (__HAL_TIM_GET_FLAG(&tim_handle, TIM_FLAG_UPDATE) != RESET) + { + if (__HAL_TIM_GET_IT_SOURCE(&tim_handle, TIM_IT_UPDATE) != RESET) + { + __HAL_TIM_CLEAR_IT(&tim_handle, TIM_IT_UPDATE); + if (!active_audio || active_audio->paused) { + __HAL_TIM_DISABLE_IT(&tim_handle, TIM_IT_UPDATE); + return; + } + + bool refill = false; + + active_audio->buffer[0][active_audio->buffer_ptr[0]] -= active_audio->period; + if (!active_audio->buffer[0][active_audio->buffer_ptr[0]]) { + toggle_pin(0); + if (++(active_audio->buffer_ptr[0]) >= active_audio->buffer_length[0]) + refill = true; + } + if (active_audio->pin[1]) { + active_audio->buffer[1][active_audio->buffer_ptr[1]] -= active_audio->period; + if (!active_audio->buffer[1][active_audio->buffer_ptr[1]]) { + toggle_pin(1); + if (++(active_audio->buffer_ptr[1]) >= active_audio->buffer_length[1]) + refill = true; + } + } + + if (refill) { + __HAL_TIM_DISABLE_IT(&tim_handle, TIM_IT_UPDATE); + + move_to_beginning(active_audio->buffer[0], &active_audio->buffer_length[0], &active_audio->buffer_ptr[0]); + if (active_audio->pin[1]) + move_to_beginning(active_audio->buffer[1], &active_audio->buffer_length[1], &active_audio->buffer_ptr[1]); + + if (active_audio->stopping || !fill_buffers(active_audio)) { + // No more audio. Turn off output and don't restart. + common_hal_audiopwmio_pwmaudioout_stop(active_audio); + return; + } + } + + // Count up to the next given value. + start_timer(active_audio); + } + } +} + +void audiopwmout_reset() { + if (active_audio) { + common_hal_audiopwmio_pwmaudioout_stop(active_audio); + } +} + +// Caller validates that pins are free. +void common_hal_audiopwmio_pwmaudioout_construct(audiopwmio_pwmaudioout_obj_t* self, + const mcu_pin_obj_t* left_channel, const mcu_pin_obj_t* right_channel, uint16_t quiescent_value) { + + // Set up the pin(s) for output + self->pin[0] = left_channel; + self->pin[1] = right_channel; + set_drive_mode(left_channel, GPIO_MODE_OUTPUT_PP); + if (right_channel) + set_drive_mode(right_channel, GPIO_MODE_OUTPUT_PP); + + self->buffer[0] = NULL; + self->buffer[1] = NULL; + + self->quiescent_value = quiescent_value; +} + +bool common_hal_audiopwmio_pwmaudioout_deinited(audiopwmio_pwmaudioout_obj_t* self) { + return !self->pin[0]; +} + +STATIC void free_buffers(audiopwmio_pwmaudioout_obj_t *self) { + m_free(self->buffer[0]); + self->buffer[0] = NULL; + m_free(self->buffer[1]); + self->buffer[1] = NULL; +} + +void common_hal_audiopwmio_pwmaudioout_deinit(audiopwmio_pwmaudioout_obj_t* self) { + if (common_hal_audiopwmio_pwmaudioout_deinited(self)) { + return; + } + common_hal_audiopwmio_pwmaudioout_stop(self); + + free_buffers(self); + + self->pin[0] = 0; + self->pin[1] = 0; +} + +void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t* self, mp_obj_t sample, bool loop) { + common_hal_audiopwmio_pwmaudioout_stop(self); + if (active_audio) { + mp_raise_RuntimeError(translate("Another PWMAudioOut is already active")); // TODO + } + self->sample = sample; + self->loop = loop; + + uint32_t sample_rate = audiosample_sample_rate(sample); + self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; + + uint32_t max_buffer_length; + uint8_t spacing; + bool single_buffer; + bool samples_signed; + audiosample_get_buffer_structure(sample, /* single channel */ false, + &single_buffer, &samples_signed, &max_buffer_length, &spacing); + self->sample_channel_count = audiosample_channel_count(sample); + self->sample_offset = (samples_signed ? 0x8000 : 0) - self->quiescent_value; + + free_buffers(self); + + if (max_buffer_length > UINT16_MAX) { + mp_raise_ValueError_varg(translate("Buffer length %d too big. It must be less than %d"), max_buffer_length, UINT16_MAX); + } + uint16_t buffer_length = (uint16_t)max_buffer_length; + self->buffer[0] = m_malloc(buffer_length * sizeof(uint16_t), false); + self->buffer_ptr[0] = self->buffer_length[0] = 0; + if (self->pin[1]) { + self->buffer[1] = m_malloc(buffer_length * sizeof(uint16_t), false); + self->buffer_ptr[1] = self->buffer_length[1] = 0; + } + + self->pos[0] = self->pos[1] = 1; // initially on + self->len[0] = self->len[1] = 0; + + audiosample_reset_buffer(self->sample, false, 0); + self->stopping = false; + self->paused = false; + if (!fill_buffers(self)) + mp_raise_RuntimeError(translate("Failed to buffer the sample")); + + // Calculate period (TODO: supersample to 1 MHz?) + TIM_TypeDef * tim_instance = stm_peripherals_find_timer(); + uint32_t source = stm_peripherals_timer_get_source_freq(tim_instance); + uint32_t prescaler = source/sample_rate; + + // Activate timer + active_audio = self; + stm_peripherals_timer_reserve(tim_instance); + stm_peripherals_timer_preinit(tim_instance, 4, pwmaudioout_event_handler); + + tim_handle.Instance = tim_instance; + tim_handle.Init.Period = 100; //immediately replaced. + tim_handle.Init.Prescaler = prescaler - 1; + tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP; + tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + + HAL_TIM_Base_Init(&tim_handle); + tim_handle.Instance->SR = 0; + + // Alternate on and off, starting with on. + set_pin(0, GPIO_PIN_SET); + if (self->pin[1]) + set_pin(1, GPIO_PIN_SET); + + // Count up to the next given value. + start_timer(self); +} + +void common_hal_audiopwmio_pwmaudioout_stop(audiopwmio_pwmaudioout_obj_t* self) { + if (active_audio != self) + return; + + // Turn off timer counter. + tim_handle.Instance->CR1 &= ~TIM_CR1_CEN; + stm_peripherals_timer_free(tim_handle.Instance); + + active_audio = NULL; + self->stopping = false; + self->paused = false; + + // Make sure pins are left low. + set_pin(0, GPIO_PIN_RESET); + if (self->pin[1]) + set_pin(1, GPIO_PIN_RESET); + + // Cannot free buffers here because we may be called from + // the interrupt handler, and the heap is not reentrant. +} + +bool common_hal_audiopwmio_pwmaudioout_get_playing(audiopwmio_pwmaudioout_obj_t* self) { + return active_audio == self; +} + +void common_hal_audiopwmio_pwmaudioout_pause(audiopwmio_pwmaudioout_obj_t* self) { + self->paused = true; +} + +void common_hal_audiopwmio_pwmaudioout_resume(audiopwmio_pwmaudioout_obj_t* self) { + self->paused = false; + if (active_audio == self) + start_timer(self); +} + +bool common_hal_audiopwmio_pwmaudioout_get_paused(audiopwmio_pwmaudioout_obj_t* self) { + return self->paused; +} diff --git a/ports/stm/common-hal/audiopwmio/PWMAudioOut.h b/ports/stm/common-hal/audiopwmio/PWMAudioOut.h new file mode 100644 index 0000000000000..a137a2f4f1ea2 --- /dev/null +++ b/ports/stm/common-hal/audiopwmio/PWMAudioOut.h @@ -0,0 +1,58 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Artyom Skrobov 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_STM_COMMON_HAL_AUDIOPWM_AUDIOOUT_H +#define MICROPY_INCLUDED_STM_COMMON_HAL_AUDIOPWM_AUDIOOUT_H + +#include "common-hal/microcontroller/Pin.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t* pin[2]; + uint16_t quiescent_value; + + uint16_t *buffer[2]; + uint16_t buffer_length[2]; + uint16_t buffer_ptr[2]; + + mp_obj_t *sample; + int16_t sample_offset; + uint8_t sample_channel_count; + uint8_t bytes_per_sample; + + // PCM-to-PWM conversion state + int8_t pos[2]; // -1 for off, +1 for on + uint16_t len[2]; + + uint16_t period; + bool stopping; + bool paused; + bool loop; +} audiopwmio_pwmaudioout_obj_t; + +void audiopwmout_reset(void); + +#endif diff --git a/ports/stm/common-hal/audiopwmio/__init__.c b/ports/stm/common-hal/audiopwmio/__init__.c new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ports/stm/common-hal/digitalio/DigitalInOut.c b/ports/stm/common-hal/digitalio/DigitalInOut.c index a676aeb155734..931223412aeff 100644 --- a/ports/stm/common-hal/digitalio/DigitalInOut.c +++ b/ports/stm/common-hal/digitalio/DigitalInOut.c @@ -117,13 +117,8 @@ bool common_hal_digitalio_digitalinout_get_value( digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( digitalio_digitalinout_obj_t *self, digitalio_drive_mode_t drive_mode) { - GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = pin_mask(self->pin->number); - GPIO_InitStruct.Mode = (drive_mode == DRIVE_MODE_OPEN_DRAIN ? + set_drive_mode(self->pin, drive_mode == DRIVE_MODE_OPEN_DRAIN ? GPIO_MODE_OUTPUT_OD : GPIO_MODE_OUTPUT_PP); - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - HAL_GPIO_Init(pin_port(self->pin->port), &GPIO_InitStruct); return DIGITALINOUT_OK; } diff --git a/ports/stm/common-hal/microcontroller/Pin.c b/ports/stm/common-hal/microcontroller/Pin.c index 37c202d86ecf0..700f1bead0e8c 100644 --- a/ports/stm/common-hal/microcontroller/Pin.c +++ b/ports/stm/common-hal/microcontroller/Pin.c @@ -194,3 +194,12 @@ void common_hal_mcu_pin_claim(const mcu_pin_obj_t* pin) { void common_hal_mcu_pin_reset_number(uint8_t pin_no) { reset_pin_number(pin_no / 16, pin_no % 16); } + +void set_drive_mode(const mcu_pin_obj_t *pin, uint32_t mode) { + GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = pin_mask(pin->number); + GPIO_InitStruct.Mode = mode; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + HAL_GPIO_Init(pin_port(pin->port), &GPIO_InitStruct); +} diff --git a/ports/stm/common-hal/microcontroller/Pin.h b/ports/stm/common-hal/microcontroller/Pin.h index f461d3813fdec..ee99de04b1135 100644 --- a/ports/stm/common-hal/microcontroller/Pin.h +++ b/ports/stm/common-hal/microcontroller/Pin.h @@ -49,4 +49,6 @@ void never_reset_pin_number(uint8_t pin_port, uint8_t pin_number); GPIO_TypeDef * pin_port(uint8_t pin_port); uint16_t pin_mask(uint8_t pin_number); +void set_drive_mode(const mcu_pin_obj_t *pin, uint32_t mode); + #endif // MICROPY_INCLUDED_STM32_COMMON_HAL_MICROCONTROLLER_PIN_H diff --git a/ports/stm/mpconfigport.mk b/ports/stm/mpconfigport.mk index 4d76d03c92bc9..a3216f26783e1 100644 --- a/ports/stm/mpconfigport.mk +++ b/ports/stm/mpconfigport.mk @@ -12,9 +12,13 @@ ifeq ($(MCU_VARIANT),$(filter $(MCU_VARIANT),STM32F405xx STM32F407xx)) endif ifeq ($(MCU_SERIES),F4) + # Audio via PWM + CIRCUITPY_AUDIOIO = 0 + CIRCUITPY_AUDIOCORE ?= 1 + CIRCUITPY_AUDIOPWMIO ?= 1 + # Not yet implemented common-hal modules: CIRCUITPY_AUDIOBUSIO ?= 0 - CIRCUITPY_AUDIOIO ?= 0 CIRCUITPY_COUNTIO ?= 0 CIRCUITPY_FREQUENCYIO ?= 0 CIRCUITPY_I2CPERIPHERAL ?= 0 diff --git a/ports/stm/supervisor/port.c b/ports/stm/supervisor/port.c index 3103a071606ec..0abce426c3913 100644 --- a/ports/stm/supervisor/port.c +++ b/ports/stm/supervisor/port.c @@ -32,6 +32,9 @@ #include "common-hal/microcontroller/Pin.h" +#ifdef CIRCUITPY_AUDIOPWMIO +#include "common-hal/audiopwmio/PWMAudioOut.h" +#endif #if CIRCUITPY_BUSIO #include "common-hal/busio/I2C.h" #include "common-hal/busio/SPI.h" @@ -229,6 +232,9 @@ void SysTick_Handler(void) { void reset_port(void) { reset_all_pins(); +#if CIRCUITPY_AUDIOPWMIO + audiopwmout_reset(); +#endif #if CIRCUITPY_BUSIO i2c_reset(); spi_reset(); diff --git a/shared-bindings/audiopwmio/PWMAudioOut.c b/shared-bindings/audiopwmio/PWMAudioOut.c index 06571fae1e6a0..543649125b6c0 100644 --- a/shared-bindings/audiopwmio/PWMAudioOut.c +++ b/shared-bindings/audiopwmio/PWMAudioOut.c @@ -155,8 +155,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiopwmio_pwmaudioout___exit___obj, //| Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. //| //| The sample itself should consist of 16 bit samples. Microcontrollers with a lower output -//| resolution will use the highest order bits to output. For example, the SAMD21 has a 10 bit -//| DAC that ignores the lowest 6 bits when playing 16 bit samples.""" +//| resolution will use the highest order bits to output.""" //| ... //| STATIC mp_obj_t audiopwmio_pwmaudioout_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {