diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 7b0020aa18..3bec8b622b 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1535,6 +1535,11 @@ msgstr "" msgid "Microphone startup delay must be in range 0.0 to 1.0" msgstr "" +#: ports/raspberrypi/bindings/rp2pio/StateMachine.c +#: ports/raspberrypi/common-hal/rp2pio/StateMachine.c +msgid "Mismatched data size" +msgstr "" + #: ports/mimxrt10xx/common-hal/busio/SPI.c ports/stm/common-hal/busio/SPI.c msgid "Missing MISO or MOSI Pin" msgstr "" diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index b7e5a0db44..7fef1a52e0 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -29,6 +29,7 @@ #include "shared-bindings/audiocore/RawSample.h" #include "shared-bindings/audiocore/WaveFile.h" #include "shared-bindings/microcontroller/__init__.h" +#include "bindings/rp2pio/StateMachine.h" #include "supervisor/background_callback.h" #include "py/mpstate.h" @@ -324,7 +325,9 @@ void audio_dma_stop(audio_dma_t *dma) { channel_mask |= 1 << dma->channel[1]; } dma_hw->inte0 &= ~channel_mask; - irq_set_mask_enabled(1 << DMA_IRQ_0, false); + if (!dma_hw->inte0) { + irq_set_mask_enabled(1 << DMA_IRQ_0, false); + } // Run any remaining audio tasks because we remove ourselves from // playing_audio. @@ -442,12 +445,23 @@ STATIC void dma_callback_fun(void *arg) { void isr_dma_0(void) { for (size_t i = 0; i < NUM_DMA_CHANNELS; i++) { uint32_t mask = 1 << i; - if ((dma_hw->intr & mask) != 0 && MP_STATE_PORT(playing_audio)[i] != NULL) { + if ((dma_hw->intr & mask) == 0) { + continue; + } + // acknowledge interrupt early. Doing so late means that you could lose an + // interrupt if the buffer is very small and the DMA operation + // completed by the time callback_add() / dma_complete() returned. This + // affected PIO continuous write more than audio. + dma_hw->ints0 = mask; + if (MP_STATE_PORT(playing_audio)[i] != NULL) { audio_dma_t *dma = MP_STATE_PORT(playing_audio)[i]; // Record all channels whose DMA has completed; they need loading. dma->channels_to_load_mask |= mask; background_callback_add(&dma->callback, dma_callback_fun, (void *)dma); - dma_hw->ints0 = mask; + } + if (MP_STATE_PORT(background_pio)[i] != NULL) { + rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(background_pio)[i]; + rp2pio_statemachine_dma_complete(pio, i); } } } diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.c b/ports/raspberrypi/bindings/rp2pio/StateMachine.c index 98213be265..722b1f7c7f 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -426,6 +426,141 @@ STATIC mp_obj_t rp2pio_statemachine_write(size_t n_args, const mp_obj_t *pos_arg } MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_obj, 2, rp2pio_statemachine_write); +//| def background_write(self, once: Optional[ReadableBuffer]=None, *, loop: Optional[ReadableBuffer]=None) -> None: +//| """Write data to the TX fifo in the background, with optional looping. +//| +//| First, if any previous ``once`` or ``loop`` buffer has not been started, this function blocks until they have. +//| This means that any ``once`` or ``loop`` buffer will be written at least once. +//| Then the ``once`` and/or ``loop`` buffers are queued. and the function returns. +//| The ``once`` buffer (if specified) will be written just once. +//| Finally, the ``loop`` buffer (if specified) will continue being looped indefinitely. +//| +//| Writes to the FIFO will match the input buffer's element size. For example, bytearray elements +//| will perform 8 bit writes to the PIO FIFO. The RP2040's memory bus will duplicate the value into +//| the other byte positions. So, pulling more data in the PIO assembly will read the duplicated values. +//| +//| To perform 16 or 32 bits writes into the FIFO use an `array.array` with a type code of the desired +//| size, or use `memoryview.cast` to change the interpretation of an +//| existing buffer. To send just part of a larger buffer, slice a `memoryview` +//| of it. +//| +//| If a buffer is modified while it is being written out, the updated +//| values will be used. However, because of interactions between CPU +//| writes, DMA and the PIO FIFO are complex, it is difficult to predict +//| the result of modifying multiple values. Instead, alternate between +//| a pair of buffers. +//| +//| Having both a ``once`` and a ``loop`` parameter is to support a special case in PWM generation +//| where a change in duty cycle requires a special transitional buffer to be used exactly once. Most +//| use cases will probably only use one of ``once`` or ``loop``. +//| +//| :param ~Optional[circuitpython_typing.ReadableBuffer] once: Data to be written once +//| :param ~Optional[circuitpython_typing.ReadableBuffer] loop: Data to be written repeatedly +//| """ +//| ... +//| + +STATIC void fill_buf_info(sm_buf_info *info, mp_obj_t obj, size_t *stride_in_bytes) { + if (obj != mp_const_none) { + info->obj = obj; + mp_get_buffer_raise(obj, &info->info, MP_BUFFER_READ); + size_t stride = mp_binary_get_size('@', info->info.typecode, NULL); + if (stride > 4) { + mp_raise_ValueError(translate("Buffer elements must be 4 bytes long or less")); + } + if (*stride_in_bytes && stride != *stride_in_bytes) { + mp_raise_ValueError(translate("Mismatched data size")); + } + *stride_in_bytes = stride; + } else { + memset(info, 0, sizeof(*info)); + } +} + +STATIC mp_obj_t rp2pio_statemachine_background_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_once, ARG_loop }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_once, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_loop, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} }, + }; + rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + check_for_deinit(self); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + sm_buf_info once_info; + sm_buf_info loop_info; + size_t stride_in_bytes = 0; + fill_buf_info(&once_info, args[ARG_once].u_obj, &stride_in_bytes); + fill_buf_info(&loop_info, args[ARG_loop].u_obj, &stride_in_bytes); + if (!stride_in_bytes) { + return mp_const_none; + } + + bool ok = common_hal_rp2pio_statemachine_background_write(self, &once_info, &loop_info, stride_in_bytes); + + if (mp_hal_is_interrupted()) { + return mp_const_none; + } + if (!ok) { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_background_write_obj, 1, rp2pio_statemachine_background_write); + +//| def stop_background_write(self) -> None: +//| """Immediately stop a background write, if one is in progress. Items already in the TX FIFO are not affected.""" +//| +STATIC mp_obj_t rp2pio_statemachine_obj_stop_background_write(mp_obj_t self_in) { + rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in); + bool ok = common_hal_rp2pio_statemachine_stop_background_write(self); + if (mp_hal_is_interrupted()) { + return mp_const_none; + } + if (!ok) { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_stop_background_write_obj, rp2pio_statemachine_obj_stop_background_write); + +//| @property +//| def writing(self) -> bool: +//| """Returns True if a background write is in progress""" +//| +STATIC mp_obj_t rp2pio_statemachine_obj_get_writing(mp_obj_t self_in) { + rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(common_hal_rp2pio_statemachine_get_writing(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_get_writing_obj, rp2pio_statemachine_obj_get_writing); + +const mp_obj_property_t rp2pio_statemachine_writing_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&rp2pio_statemachine_get_writing_obj, + MP_ROM_NONE, + MP_ROM_NONE}, +}; + + +//| @property +//| def pending(self) -> int: +//| """Returns the number of pending buffers for background writing. +//| +//| If the number is 0, then a `StateMachine.background_write` call will not block.""" +//| +STATIC mp_obj_t rp2pio_statemachine_obj_get_pending(mp_obj_t self_in) { + rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(common_hal_rp2pio_statemachine_get_pending(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_get_pending_obj, rp2pio_statemachine_obj_get_pending); + +const mp_obj_property_t rp2pio_statemachine_pending_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&rp2pio_statemachine_get_pending_obj, + MP_ROM_NONE, + MP_ROM_NONE}, +}; //| def readinto(self, buffer: WriteableBuffer, *, start: int = 0, end: Optional[int] = None) -> None: //| """Read into ``buffer``. If the number of bytes to read is 0, nothing happens. The buffer @@ -646,6 +781,10 @@ STATIC const mp_rom_map_elem_t rp2pio_statemachine_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&rp2pio_statemachine_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&rp2pio_statemachine_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_readinto), MP_ROM_PTR(&rp2pio_statemachine_write_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_background_write), MP_ROM_PTR(&rp2pio_statemachine_background_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop_background_write), MP_ROM_PTR(&rp2pio_statemachine_stop_background_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_writing), MP_ROM_PTR(&rp2pio_statemachine_writing_obj) }, + { MP_ROM_QSTR(MP_QSTR_pending), MP_ROM_PTR(&rp2pio_statemachine_pending_obj) }, { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&rp2pio_statemachine_frequency_obj) }, { MP_ROM_QSTR(MP_QSTR_rxstall), MP_ROM_PTR(&rp2pio_statemachine_rxstall_obj) }, diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.h b/ports/raspberrypi/bindings/rp2pio/StateMachine.h index eb3f4addc5..a77574a57a 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.h +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.h @@ -65,6 +65,10 @@ void common_hal_rp2pio_statemachine_run(rp2pio_statemachine_obj_t *self, const u // Writes out the given data. bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes); +bool common_hal_rp2pio_statemachine_background_write(rp2pio_statemachine_obj_t *self, const sm_buf_info *once_obj, const sm_buf_info *loop_obj, uint8_t stride_in_bytes); +bool common_hal_rp2pio_statemachine_stop_background_write(rp2pio_statemachine_obj_t *self); +mp_int_t common_hal_rp2pio_statemachine_get_pending(rp2pio_statemachine_obj_t *self); +bool common_hal_rp2pio_statemachine_get_writing(rp2pio_statemachine_obj_t *self); bool common_hal_rp2pio_statemachine_readinto(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t stride_in_bytes); bool common_hal_rp2pio_statemachine_write_readinto(rp2pio_statemachine_obj_t *self, const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes, diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c index cd1d383008..cfc9442028 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -24,6 +24,8 @@ * THE SOFTWARE. */ +#include + #include "bindings/rp2pio/StateMachine.h" #include "common-hal/microcontroller/__init__.h" @@ -42,6 +44,8 @@ #include "py/objproperty.h" #include "py/runtime.h" +#define NO_DMA_CHANNEL (-1) + // Count how many state machines are using each pin. STATIC uint8_t _pin_reference_count[TOTAL_GPIO_COUNT]; STATIC uint32_t _current_program_id[NUM_PIOS][NUM_PIO_STATE_MACHINES]; @@ -51,6 +55,12 @@ STATIC bool _never_reset[NUM_PIOS][NUM_PIO_STATE_MACHINES]; STATIC uint32_t _current_pins[NUM_PIOS]; STATIC uint32_t _current_sm_pins[NUM_PIOS][NUM_PIO_STATE_MACHINES]; +STATIC int8_t _sm_dma_plus_one[NUM_PIOS][NUM_PIO_STATE_MACHINES]; + +#define SM_DMA_ALLOCATED(pio_index, sm) (_sm_dma_plus_one[(pio_index)][(sm)] != 0) +#define SM_DMA_GET_CHANNEL(pio_index, sm) (_sm_dma_plus_one[(pio_index)][(sm)] - 1) +#define SM_DMA_CLEAR_CHANNEL(pio_index, sm) (_sm_dma_plus_one[(pio_index)][(sm)] = 0) +#define SM_DMA_SET_CHANNEL(pio_isntance, sm, channel) (_sm_dma_plus_one[(pio_index)][(sm)] = (channel) + 1) STATIC PIO pio_instances[2] = {pio0, pio1}; typedef void (*interrupt_handler_type)(void *); @@ -70,8 +80,24 @@ static void rp2pio_statemachine_set_pull(uint32_t pull_pin_up, uint32_t pull_pin } } +STATIC void rp2pio_statemachine_clear_dma(int pio_index, int sm) { + if (SM_DMA_ALLOCATED(pio_index, sm)) { + int channel = SM_DMA_GET_CHANNEL(pio_index, sm); + uint32_t channel_mask = 1u << channel; + dma_hw->inte0 &= ~channel_mask; + if (!dma_hw->inte0) { + irq_set_mask_enabled(1 << DMA_IRQ_0, false); + } + MP_STATE_PORT(background_pio)[channel] = NULL; + dma_channel_abort(channel); + dma_channel_unclaim(channel); + } + SM_DMA_CLEAR_CHANNEL(pio_index, sm); +} + STATIC void _reset_statemachine(PIO pio, uint8_t sm, bool leave_pins) { uint8_t pio_index = pio_get_index(pio); + rp2pio_statemachine_clear_dma(pio_index, sm); uint32_t program_id = _current_program_id[pio_index][sm]; if (program_id == 0) { return; @@ -330,6 +356,9 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, sm_config_set_fifo_join(&c, join); self->sm_config = c; + // no DMA allocated + SM_DMA_CLEAR_CHANNEL(pio_index, state_machine); + pio_sm_init(self->pio, self->state_machine, program_offset, &c); common_hal_rp2pio_statemachine_run(self, init, init_len); @@ -581,6 +610,7 @@ void common_hal_rp2pio_statemachine_set_frequency(rp2pio_statemachine_obj_t *sel void rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self, bool leave_pins) { common_hal_rp2pio_statemachine_stop(self); + (void)common_hal_rp2pio_statemachine_stop_background_write(self); uint8_t sm = self->state_machine; uint8_t pio_index = pio_get_index(self->pio); @@ -848,3 +878,116 @@ uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self) { uint8_t sm = self->state_machine; return _current_program_offset[pio_index][sm]; } + +bool common_hal_rp2pio_statemachine_background_write(rp2pio_statemachine_obj_t *self, const sm_buf_info *once, const sm_buf_info *loop, uint8_t stride_in_bytes) { + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + + int pending_buffers = (once->info.buf != NULL) + (loop->info.buf != NULL); + if (!once->info.buf) { + once = loop; + } + + + if (SM_DMA_ALLOCATED(pio_index, sm)) { + if (stride_in_bytes != self->background_stride_in_bytes) { + mp_raise_ValueError(translate("Mismatched data size")); + } + + while (self->pending_buffers) { + RUN_BACKGROUND_TASKS; + if (self->user_interruptible && mp_hal_is_interrupted()) { + return false; + } + } + + common_hal_mcu_disable_interrupts(); + self->once = *once; + self->loop = *loop; + self->pending_buffers = pending_buffers; + + if (self->dma_completed) { + rp2pio_statemachine_dma_complete(self, SM_DMA_GET_CHANNEL(pio_index, sm)); + self->dma_completed = false; + } + + common_hal_mcu_enable_interrupts(); + + return true; + } + + int channel = dma_claim_unused_channel(false); + if (channel == -1) { + return false; + } + + SM_DMA_SET_CHANNEL(pio_index, sm, channel); + + volatile uint8_t *tx_destination = (volatile uint8_t *)&self->pio->txf[self->state_machine]; + + self->tx_dreq = pio_get_dreq(self->pio, self->state_machine, true); + + dma_channel_config c; + + self->current = *once; + self->once = *loop; + self->loop = *loop; + self->pending_buffers = pending_buffers; + self->dma_completed = false; + self->background_stride_in_bytes = stride_in_bytes; + + c = dma_channel_get_default_config(channel); + channel_config_set_transfer_data_size(&c, _stride_to_dma_size(stride_in_bytes)); + channel_config_set_dreq(&c, self->tx_dreq); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + dma_channel_configure(channel, &c, + tx_destination, + once->info.buf, + once->info.len / stride_in_bytes, + false); + + common_hal_mcu_disable_interrupts(); + MP_STATE_PORT(background_pio)[channel] = self; + dma_hw->inte0 |= 1u << channel; + irq_set_mask_enabled(1 << DMA_IRQ_0, true); + dma_start_channel_mask(1u << channel); + common_hal_mcu_enable_interrupts(); + + return true; +} + +void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel) { + self->current = self->once; + self->once = self->loop; + + if (self->current.info.buf) { + if (self->pending_buffers > 0) { + self->pending_buffers--; + } + dma_channel_set_read_addr(channel, self->current.info.buf, false); + dma_channel_set_trans_count(channel, self->current.info.len / self->background_stride_in_bytes, true); + } else { + self->dma_completed = true; + self->pending_buffers = 0; // should be a no-op + } +} + +bool common_hal_rp2pio_statemachine_stop_background_write(rp2pio_statemachine_obj_t *self) { + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + rp2pio_statemachine_clear_dma(pio_index, sm); + memset(&self->current, 0, sizeof(self->current)); + memset(&self->once, 0, sizeof(self->once)); + memset(&self->loop, 0, sizeof(self->loop)); + self->pending_buffers = 0; + return true; +} + +bool common_hal_rp2pio_statemachine_get_writing(rp2pio_statemachine_obj_t *self) { + return !self->dma_completed; +} + +int common_hal_rp2pio_statemachine_get_pending(rp2pio_statemachine_obj_t *self) { + return self->pending_buffers; +} diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h index 0e8e7a3806..470f0ed8b1 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h @@ -32,6 +32,11 @@ #include "common-hal/microcontroller/Pin.h" #include "src/rp2_common/hardware_pio/include/hardware/pio.h" +typedef struct sm_buf_info { + mp_obj_t obj; + mp_buffer_info_t info; +} sm_buf_info; + typedef struct { mp_obj_base_t base; uint32_t pins; // Bitmask of what pins this state machine uses. @@ -54,6 +59,12 @@ typedef struct { bool in_shift_right; bool user_interruptible; uint8_t offset; + + // dma-related items + volatile int pending_buffers; + sm_buf_info current, once, loop; + int background_stride_in_bytes; + bool dma_completed; } rp2pio_statemachine_obj_t; void reset_rp2pio_statemachine(void); @@ -82,6 +93,7 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self); void rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self, bool leave_pins); +void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel); extern const mp_obj_type_t rp2pio_statemachine_type; diff --git a/ports/raspberrypi/mpconfigport.h b/ports/raspberrypi/mpconfigport.h index 0259ffd023..fd09d8a9ac 100644 --- a/ports/raspberrypi/mpconfigport.h +++ b/ports/raspberrypi/mpconfigport.h @@ -46,6 +46,7 @@ #define MICROPY_PORT_ROOT_POINTERS \ mp_obj_t counting[NUM_PWM_SLICES]; \ mp_obj_t playing_audio[NUM_DMA_CHANNELS]; \ + mp_obj_t background_pio[NUM_DMA_CHANNELS]; \ CIRCUITPY_COMMON_ROOT_POINTERS; #endif // __INCLUDED_MPCONFIGPORT_H