From 33f5598acc2eb84e549c1168fad1c042c5cb61a1 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 19 Apr 2022 11:35:43 -0500 Subject: [PATCH 1/8] Stub 'continuous write' functionality --- .../bindings/rp2pio/StateMachine.c | 77 +++++++++++++++++++ .../bindings/rp2pio/StateMachine.h | 2 + .../common-hal/rp2pio/StateMachine.c | 8 ++ 3 files changed, 87 insertions(+) diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.c b/ports/raspberrypi/bindings/rp2pio/StateMachine.c index 98213be265..8c735479f5 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -426,7 +426,82 @@ 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 continuous_write(self, buffer: Optional[ReadableBuffer], *, start: int = 0, end: Optional[int] = None) -> None: +//| """Write the data contained in ``buffer`` to the state machine repeatedly until stopped. If the buffer is empty or None, an existing continuous_write is canceled. +//| +//| 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. +//| +//| To atomically change from one buffer to another, simply call +//| `StateMachine.continuous_write` again with a different buffer. +//| The call will only return once outputting the new buffer has started. +//| +//| If the 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. +//| +//| :param ~circuitpython_typing.ReadableBuffer buffer: Write out the data in this buffer +//| :param int start: Start of the slice of ``buffer`` to write out: ``buffer[start:end]`` +//| :param int end: End of the slice; this index is not included. Defaults to ``len(buffer)``""" +//| ... +//| +STATIC mp_obj_t rp2pio_statemachine_start_continuous_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buffer, ARG_start, ARG_end }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, + }; + 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); + mp_buffer_info_t bufinfo = {}; + if (args[ARG_buffer].u_obj != mp_const_none) { + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + } + int32_t start = args[ARG_start].u_int; + size_t length = bufinfo.len; + normalize_buffer_bounds(&start, args[ARG_end].u_int, &length); + bool ok = true; + if (length == 0) { + ok = common_hal_rp2pio_statemachine_end_continuous_write(self); + } else { + uint8_t *original_pointer = bufinfo.buf; + int stride_in_bytes = mp_binary_get_size('@', bufinfo.typecode, NULL); + if (stride_in_bytes > 4) { + mp_raise_ValueError(translate("Buffer elements must be 4 bytes long or less")); + } + + ok = common_hal_rp2pio_statemachine_start_continuous_write(self, ((uint8_t *)bufinfo.buf) + start, length, stride_in_bytes); + } + if (!ok) { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_start_continuous_write_obj, 2, rp2pio_statemachine_start_continuous_write); + +//| def end_continuous_write(self) -> None: +//| """Stop a continuous write, if one is in progress.""" +//| +STATIC mp_obj_t rp2pio_statemachine_obj_end_continuous_write(mp_obj_t self_in) { + rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in); + bool ok = common_hal_rp2pio_statemachine_end_continuous_write(self); + if (!ok) { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_end_continuous_write_obj, rp2pio_statemachine_obj_end_continuous_write); //| 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 //| includes any data added to the fifo even if it was added before this was called. @@ -646,6 +721,8 @@ 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_start_continuous_write), MP_ROM_PTR(&rp2pio_statemachine_start_continuous_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_end_continuous_write), MP_ROM_PTR(&rp2pio_statemachine_end_continuous_write_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..e43ee0f94e 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.h +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.h @@ -65,6 +65,8 @@ 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_start_continuous_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes); +bool common_hal_rp2pio_statemachine_end_continuous_write(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..78118d20f1 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -848,3 +848,11 @@ 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_start_continuous_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes) { + return false; +} + +bool common_hal_rp2pio_statemachine_end_continuous_write(rp2pio_statemachine_obj_t *self) { + return false; +} From 33d6d556754b40cceeb7d4bc92cfbacddeca5a9e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 19 Apr 2022 15:14:50 -0500 Subject: [PATCH 2/8] the basics work the sequence has to be a minimum length, 8 entries, but this problem is not detected. I don't THINK this is an insurmountable problem. --- ports/raspberrypi/audio_dma.c | 16 ++- .../bindings/rp2pio/StateMachine.c | 2 +- .../bindings/rp2pio/StateMachine.h | 2 +- .../common-hal/rp2pio/StateMachine.c | 116 +++++++++++++++++- .../common-hal/rp2pio/StateMachine.h | 10 ++ ports/raspberrypi/mpconfigport.h | 1 + 6 files changed, 139 insertions(+), 8 deletions(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index b7e5a0db44..4bb89601ff 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,13 +445,20 @@ 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; + } + 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(continuous_pio)[i] != NULL) { + rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(continuous_pio)[i]; + rp2pio_statemachine_dma_complete(pio, i); + } + dma_hw->ints0 = mask; } } diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.c b/ports/raspberrypi/bindings/rp2pio/StateMachine.c index 8c735479f5..c3b0d52642 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -480,7 +480,7 @@ STATIC mp_obj_t rp2pio_statemachine_start_continuous_write(size_t n_args, const mp_raise_ValueError(translate("Buffer elements must be 4 bytes long or less")); } - ok = common_hal_rp2pio_statemachine_start_continuous_write(self, ((uint8_t *)bufinfo.buf) + start, length, stride_in_bytes); + ok = common_hal_rp2pio_statemachine_start_continuous_write(self, args[ARG_buffer].u_obj, ((uint8_t *)bufinfo.buf) + start, length, stride_in_bytes); } if (!ok) { mp_raise_OSError(MP_EIO); diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.h b/ports/raspberrypi/bindings/rp2pio/StateMachine.h index e43ee0f94e..2c8e78a3d2 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.h +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.h @@ -65,7 +65,7 @@ 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_start_continuous_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes); +bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_obj_t *self, mp_obj_t buf_obj, const uint8_t *data, size_t len, uint8_t stride_in_bytes); bool common_hal_rp2pio_statemachine_end_continuous_write(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, diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c index 78118d20f1..1c4cbcf96e 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -42,6 +42,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]; @@ -330,6 +332,9 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, sm_config_set_fifo_join(&c, join); self->sm_config = c; + // no DMA allocated + self->dma_channel[0] = self->dma_channel[1] = NO_DMA_CHANNEL; + pio_sm_init(self->pio, self->state_machine, program_offset, &c); common_hal_rp2pio_statemachine_run(self, init, init_len); @@ -581,6 +586,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_end_continuous_write(self); uint8_t sm = self->state_machine; uint8_t pio_index = pio_get_index(self->pio); @@ -849,10 +855,114 @@ uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self) { return _current_program_offset[pio_index][sm]; } -bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes) { - return false; +#define HERE(fmt, ...) (mp_printf(&mp_plat_print, "%s: %d: " fmt "\n", __FILE__, __LINE__,##__VA_ARGS__)) + +bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_obj_t *self, mp_obj_t buf_obj, const uint8_t *data, size_t len, uint8_t stride_in_bytes) { + if (self->dma_channel[0] != NO_DMA_CHANNEL && stride_in_bytes == self->continuous_stride_in_bytes) { + HERE("updating channel pending=%d\n", self->pending_set_data); + while (self->pending_set_data) { + RUN_BACKGROUND_TASKS; + if (self->user_interruptible && mp_hal_is_interrupted()) { + (void)common_hal_rp2pio_statemachine_end_continuous_write(self); + return false; + } + } + + common_hal_mcu_disable_interrupts(); + self->next_buffer = data; + self->next_size = len / stride_in_bytes; + self->pending_set_data = true; + common_hal_mcu_enable_interrupts(); + + // need to keep a reference alive to the buffer, lest the GC collect it while its lone remaining pointer is in the DMA peripheral register + self->buf_objs[++self->buf_obj_idx % 2] = buf_obj; + return true; + } + + common_hal_rp2pio_statemachine_end_continuous_write(self); + + for (int i = 0; i < 2; i++) { + HERE("allocating channel %d", i); + if (self->dma_channel[i] == -1) { + self->dma_channel[i] = dma_claim_unused_channel(false); + HERE("got channel %d", self->dma_channel[i]); + MP_STATE_PORT(continuous_pio)[self->dma_channel[i]] = self; + } + if (self->dma_channel[i] == -1) { + HERE("allocating channel %d failed", i); + (void)common_hal_rp2pio_statemachine_end_continuous_write(self); + return false; + } + } + + 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->pending_set_data = false; + self->continuous_stride_in_bytes = stride_in_bytes; + self->buf_objs[0] = buf_obj; + self->buf_objs[1] = NULL; + + self->next_buffer = data; + self->next_size = len / stride_in_bytes; + + c = dma_channel_get_default_config(self->dma_channel[0]); + 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); + // channel_config_set_chain_to(&c, self->dma_channel[1]); + dma_channel_configure(self->dma_channel[0], &c, + tx_destination, + data, + len / stride_in_bytes, + false); + + #if 0 + channel_config_set_chain_to(&c, self->dma_channel[0]); + dma_channel_configure(self->dma_channel[1], &c, + tx_destination, + data, + len / stride_in_bytes, + false); + #endif + + dma_hw->inte0 |= (1 << self->dma_channel[0]) | (1 << self->dma_channel[1]); + irq_set_mask_enabled(1 << DMA_IRQ_0, true); + + HERE("OK let's go"); + dma_start_channel_mask(1u << self->dma_channel[0]); + return true; +} + +void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel) { + HERE("dma complete[%d] pending set data=%d busy=%d,%d %d@%p", channel, self->pending_set_data, dma_channel_is_busy(self->dma_channel[0]), dma_channel_is_busy(self->dma_channel[1]), + self->next_size, self->next_buffer); + + dma_channel_set_read_addr(channel, self->next_buffer, false); + dma_channel_set_trans_count(channel, self->next_size, true); + + self->pending_set_data = false; } bool common_hal_rp2pio_statemachine_end_continuous_write(rp2pio_statemachine_obj_t *self) { - return false; + for (int i = 0; i < 2; i++) { + if (self->dma_channel[i] == NO_DMA_CHANNEL) { + continue; + } + int channel = self->dma_channel[i]; + 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(continuous_pio)[channel] = NULL; + dma_channel_abort(channel); + dma_channel_unclaim(channel); + self->dma_channel[i] = NO_DMA_CHANNEL; + } + return true; } diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h index 0e8e7a3806..776fa0ce89 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h @@ -54,6 +54,15 @@ typedef struct { bool in_shift_right; bool user_interruptible; uint8_t offset; + + // dma-related items + uint8_t buf_obj_idx; + const uint8_t *next_buffer; + size_t next_size; + int dma_channel[2]; + mp_obj_t buf_objs[2]; + int continuous_stride_in_bytes; + volatile int pending_set_data; } rp2pio_statemachine_obj_t; void reset_rp2pio_statemachine(void); @@ -82,6 +91,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..f4835bf2e9 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 continuous_pio[NUM_DMA_CHANNELS]; \ CIRCUITPY_COMMON_ROOT_POINTERS; #endif // __INCLUDED_MPCONFIGPORT_H From b128f180cac750ccf180328684edf50151dfe0d3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 19 Apr 2022 16:33:51 -0500 Subject: [PATCH 3/8] switch to single single dma channel, fix some bugs --- .../bindings/rp2pio/StateMachine.c | 6 ++ .../common-hal/rp2pio/StateMachine.c | 97 ++++++++++--------- .../common-hal/rp2pio/StateMachine.h | 1 - 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.c b/ports/raspberrypi/bindings/rp2pio/StateMachine.c index c3b0d52642..6032d3c554 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -482,6 +482,9 @@ STATIC mp_obj_t rp2pio_statemachine_start_continuous_write(size_t n_args, const ok = common_hal_rp2pio_statemachine_start_continuous_write(self, args[ARG_buffer].u_obj, ((uint8_t *)bufinfo.buf) + start, length, stride_in_bytes); } + if (mp_hal_is_interrupted()) { + return mp_const_none; + } if (!ok) { mp_raise_OSError(MP_EIO); } @@ -495,6 +498,9 @@ MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_start_continuous_write_obj, 2, rp STATIC mp_obj_t rp2pio_statemachine_obj_end_continuous_write(mp_obj_t self_in) { rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in); bool ok = common_hal_rp2pio_statemachine_end_continuous_write(self); + if (mp_hal_is_interrupted()) { + return mp_const_none; + } if (!ok) { mp_raise_OSError(MP_EIO); } diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c index 1c4cbcf96e..85ce22e577 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -53,6 +53,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 *); @@ -72,8 +78,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(continuous_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; @@ -333,7 +355,7 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, self->sm_config = c; // no DMA allocated - self->dma_channel[0] = self->dma_channel[1] = NO_DMA_CHANNEL; + 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); @@ -858,7 +880,10 @@ uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self) { #define HERE(fmt, ...) (mp_printf(&mp_plat_print, "%s: %d: " fmt "\n", __FILE__, __LINE__,##__VA_ARGS__)) bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_obj_t *self, mp_obj_t buf_obj, const uint8_t *data, size_t len, uint8_t stride_in_bytes) { - if (self->dma_channel[0] != NO_DMA_CHANNEL && stride_in_bytes == self->continuous_stride_in_bytes) { + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + + if (SM_DMA_ALLOCATED(pio_index, sm) && stride_in_bytes == self->continuous_stride_in_bytes) { HERE("updating channel pending=%d\n", self->pending_set_data); while (self->pending_set_data) { RUN_BACKGROUND_TASKS; @@ -881,19 +906,15 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o common_hal_rp2pio_statemachine_end_continuous_write(self); - for (int i = 0; i < 2; i++) { - HERE("allocating channel %d", i); - if (self->dma_channel[i] == -1) { - self->dma_channel[i] = dma_claim_unused_channel(false); - HERE("got channel %d", self->dma_channel[i]); - MP_STATE_PORT(continuous_pio)[self->dma_channel[i]] = self; - } - if (self->dma_channel[i] == -1) { - HERE("allocating channel %d failed", i); - (void)common_hal_rp2pio_statemachine_end_continuous_write(self); - return false; - } + HERE("allocating dma channel"); + int channel = dma_claim_unused_channel(false); + if (channel == -1) { + HERE("allocating DMA channel failed"); + return false; } + HERE("got channel %d", channel); + + SM_DMA_SET_CHANNEL(pio_index, sm, channel); volatile uint8_t *tx_destination = (volatile uint8_t *)&self->pio->txf[self->state_machine]; @@ -909,37 +930,31 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o self->next_buffer = data; self->next_size = len / stride_in_bytes; - c = dma_channel_get_default_config(self->dma_channel[0]); + 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); - // channel_config_set_chain_to(&c, self->dma_channel[1]); - dma_channel_configure(self->dma_channel[0], &c, + dma_channel_configure(channel, &c, tx_destination, data, len / stride_in_bytes, false); - - #if 0 - channel_config_set_chain_to(&c, self->dma_channel[0]); - dma_channel_configure(self->dma_channel[1], &c, - tx_destination, - data, - len / stride_in_bytes, - false); - #endif - - dma_hw->inte0 |= (1 << self->dma_channel[0]) | (1 << self->dma_channel[1]); - irq_set_mask_enabled(1 << DMA_IRQ_0, true); - HERE("OK let's go"); - dma_start_channel_mask(1u << self->dma_channel[0]); + + common_hal_mcu_disable_interrupts(); + MP_STATE_PORT(continuous_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(); + + HERE("mark"); return true; } void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel) { - HERE("dma complete[%d] pending set data=%d busy=%d,%d %d@%p", channel, self->pending_set_data, dma_channel_is_busy(self->dma_channel[0]), dma_channel_is_busy(self->dma_channel[1]), + HERE("dma complete[%d] pending set data=%d %sbusy %d@%p", channel, self->pending_set_data, dma_channel_is_busy(channel) ? "not " : "", self->next_size, self->next_buffer); dma_channel_set_read_addr(channel, self->next_buffer, false); @@ -949,20 +964,8 @@ void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int chann } bool common_hal_rp2pio_statemachine_end_continuous_write(rp2pio_statemachine_obj_t *self) { - for (int i = 0; i < 2; i++) { - if (self->dma_channel[i] == NO_DMA_CHANNEL) { - continue; - } - int channel = self->dma_channel[i]; - 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(continuous_pio)[channel] = NULL; - dma_channel_abort(channel); - dma_channel_unclaim(channel); - self->dma_channel[i] = NO_DMA_CHANNEL; - } + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + rp2pio_statemachine_clear_dma(pio_index, sm); return true; } diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h index 776fa0ce89..5a0deb5ed0 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h @@ -59,7 +59,6 @@ typedef struct { uint8_t buf_obj_idx; const uint8_t *next_buffer; size_t next_size; - int dma_channel[2]; mp_obj_t buf_objs[2]; int continuous_stride_in_bytes; volatile int pending_set_data; From ebc426d9e6447df377e62ff58dac3b15aa42dbd2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 19 Apr 2022 16:39:04 -0500 Subject: [PATCH 4/8] Remove debug prints, improve docs --- ports/raspberrypi/bindings/rp2pio/StateMachine.c | 9 +++++---- ports/raspberrypi/common-hal/rp2pio/StateMachine.c | 11 ----------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.c b/ports/raspberrypi/bindings/rp2pio/StateMachine.c index 6032d3c554..464985782a 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -426,7 +426,7 @@ 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 continuous_write(self, buffer: Optional[ReadableBuffer], *, start: int = 0, end: Optional[int] = None) -> None: +//| def start_continuous_write(self, buffer: Optional[ReadableBuffer], *, start: int = 0, end: Optional[int] = None) -> None: //| """Write the data contained in ``buffer`` to the state machine repeatedly until stopped. If the buffer is empty or None, an existing continuous_write is canceled. //| //| Writes to the FIFO will match the input buffer's element size. For example, bytearray elements @@ -434,11 +434,12 @@ MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_obj, 2, rp2pio_statemachine //| 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. +//| size, or use `memoryview.cast` to change the interpretation of an existing buffer. //| //| To atomically change from one buffer to another, simply call -//| `StateMachine.continuous_write` again with a different buffer. -//| The call will only return once outputting the new buffer has started. +//| `StateMachine.continuous_write` again with a different buffer with the same element size. +//| The call will only return once DMA has started putting the previous +//| buffer's data into the PIO FIFO. //| //| If the buffer is modified while it is being written out, the updated //| values will be used. However, because of interactions between CPU diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c index 85ce22e577..7e35b33bd7 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -877,14 +877,11 @@ uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self) { return _current_program_offset[pio_index][sm]; } -#define HERE(fmt, ...) (mp_printf(&mp_plat_print, "%s: %d: " fmt "\n", __FILE__, __LINE__,##__VA_ARGS__)) - bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_obj_t *self, mp_obj_t buf_obj, const uint8_t *data, size_t len, uint8_t stride_in_bytes) { uint8_t pio_index = pio_get_index(self->pio); uint8_t sm = self->state_machine; if (SM_DMA_ALLOCATED(pio_index, sm) && stride_in_bytes == self->continuous_stride_in_bytes) { - HERE("updating channel pending=%d\n", self->pending_set_data); while (self->pending_set_data) { RUN_BACKGROUND_TASKS; if (self->user_interruptible && mp_hal_is_interrupted()) { @@ -906,13 +903,10 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o common_hal_rp2pio_statemachine_end_continuous_write(self); - HERE("allocating dma channel"); int channel = dma_claim_unused_channel(false); if (channel == -1) { - HERE("allocating DMA channel failed"); return false; } - HERE("got channel %d", channel); SM_DMA_SET_CHANNEL(pio_index, sm, channel); @@ -940,7 +934,6 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o data, len / stride_in_bytes, false); - HERE("OK let's go"); common_hal_mcu_disable_interrupts(); MP_STATE_PORT(continuous_pio)[channel] = self; @@ -949,14 +942,10 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o dma_start_channel_mask(1u << channel); common_hal_mcu_enable_interrupts(); - HERE("mark"); return true; } void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel) { - HERE("dma complete[%d] pending set data=%d %sbusy %d@%p", channel, self->pending_set_data, dma_channel_is_busy(channel) ? "not " : "", - self->next_size, self->next_buffer); - dma_channel_set_read_addr(channel, self->next_buffer, false); dma_channel_set_trans_count(channel, self->next_size, true); From 144eb5dfd4f6e83a88776d6548a5cd5917dee04d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 19 Apr 2022 16:42:27 -0500 Subject: [PATCH 5/8] fix lost DMA IRQ --- ports/raspberrypi/audio_dma.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 4bb89601ff..01efde6a9a 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -448,6 +448,11 @@ void isr_dma_0(void) { 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. @@ -458,7 +463,6 @@ void isr_dma_0(void) { rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(continuous_pio)[i]; rp2pio_statemachine_dma_complete(pio, i); } - dma_hw->ints0 = mask; } } From 457aba79f4a7b6d94602f4b7ecd9ee04d387dec7 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 20 Apr 2022 08:09:38 -0500 Subject: [PATCH 6/8] fix doc build --- ports/raspberrypi/bindings/rp2pio/StateMachine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.c b/ports/raspberrypi/bindings/rp2pio/StateMachine.c index 464985782a..d74ba51daf 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -437,7 +437,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_obj, 2, rp2pio_statemachine //| size, or use `memoryview.cast` to change the interpretation of an existing buffer. //| //| To atomically change from one buffer to another, simply call -//| `StateMachine.continuous_write` again with a different buffer with the same element size. +//| `StateMachine.start_continuous_write` again with a different buffer with the same element size. //| The call will only return once DMA has started putting the previous //| buffer's data into the PIO FIFO. //| From 989fb828d464d63cad2baae7cbe1397819d18358 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 23 Apr 2022 13:09:36 -0500 Subject: [PATCH 7/8] Revamp background writing Now a 'once' and a 'loop' buffer can be specified. 'once' is useful for things like writing a neopixel strip in the background, if you can guarantee the buffer contents are stable until the write is complete. 'loop' is useful for periodic things, like pwm & servos. both together are useful for some special cases of pwm/servo, where a transitional waveform needs to be played for one repetition and then a new waveform needs to be played after that. The API is renamed to reflect that it's a more generic 'background' operation. --- locale/circuitpython.pot | 7 +- ports/raspberrypi/audio_dma.c | 4 +- .../bindings/rp2pio/StateMachine.c | 139 ++++++++++++------ .../bindings/rp2pio/StateMachine.h | 6 +- .../common-hal/rp2pio/StateMachine.c | 87 +++++++---- .../common-hal/rp2pio/StateMachine.h | 15 +- ports/raspberrypi/mpconfigport.h | 2 +- 7 files changed, 179 insertions(+), 81 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index d7e14eabe5..48e4e2ab7b 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1530,6 +1530,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 "" @@ -3019,7 +3024,7 @@ msgstr "" msgid "complex values not supported" msgstr "" -#: extmod/moduzlib.c shared-module/zlib/DecompIO.c +#: extmod/moduzlib.c msgid "compression header" msgstr "" diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 01efde6a9a..7fef1a52e0 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -459,8 +459,8 @@ void isr_dma_0(void) { dma->channels_to_load_mask |= mask; background_callback_add(&dma->callback, dma_callback_fun, (void *)dma); } - if (MP_STATE_PORT(continuous_pio)[i] != NULL) { - rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(continuous_pio)[i]; + 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 d74ba51daf..560cdaecc7 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -426,63 +426,79 @@ 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 start_continuous_write(self, buffer: Optional[ReadableBuffer], *, start: int = 0, end: Optional[int] = None) -> None: -//| """Write the data contained in ``buffer`` to the state machine repeatedly until stopped. If the buffer is empty or None, an existing continuous_write is canceled. +//| 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. +//| 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. //| -//| To atomically change from one buffer to another, simply call -//| `StateMachine.start_continuous_write` again with a different buffer with the same element size. -//| The call will only return once DMA has started putting the previous -//| buffer's data into the PIO FIFO. -//| -//| If the buffer is modified while it is being written out, the updated +//| 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. //| -//| :param ~circuitpython_typing.ReadableBuffer buffer: Write out the data in this buffer -//| :param int start: Start of the slice of ``buffer`` to write out: ``buffer[start:end]`` -//| :param int end: End of the slice; this index is not included. Defaults to ``len(buffer)``""" +//| 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 mp_obj_t rp2pio_statemachine_start_continuous_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_buffer, ARG_start, ARG_end }; + +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_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, + { 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); - mp_buffer_info_t bufinfo = {}; - if (args[ARG_buffer].u_obj != mp_const_none) { - mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + 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; } - int32_t start = args[ARG_start].u_int; - size_t length = bufinfo.len; - normalize_buffer_bounds(&start, args[ARG_end].u_int, &length); - bool ok = true; - if (length == 0) { - ok = common_hal_rp2pio_statemachine_end_continuous_write(self); - } else { - uint8_t *original_pointer = bufinfo.buf; - int stride_in_bytes = mp_binary_get_size('@', bufinfo.typecode, NULL); - if (stride_in_bytes > 4) { - mp_raise_ValueError(translate("Buffer elements must be 4 bytes long or less")); - } - ok = common_hal_rp2pio_statemachine_start_continuous_write(self, args[ARG_buffer].u_obj, ((uint8_t *)bufinfo.buf) + start, length, stride_in_bytes); - } + 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; } @@ -491,14 +507,14 @@ STATIC mp_obj_t rp2pio_statemachine_start_continuous_write(size_t n_args, const } return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_start_continuous_write_obj, 2, rp2pio_statemachine_start_continuous_write); +MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_background_write_obj, 1, rp2pio_statemachine_background_write); -//| def end_continuous_write(self) -> None: -//| """Stop a continuous write, if one is in progress.""" +//| 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_end_continuous_write(mp_obj_t self_in) { +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_end_continuous_write(self); + bool ok = common_hal_rp2pio_statemachine_stop_background_write(self); if (mp_hal_is_interrupted()) { return mp_const_none; } @@ -507,8 +523,45 @@ STATIC mp_obj_t rp2pio_statemachine_obj_end_continuous_write(mp_obj_t self_in) { } 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}, +}; -MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_end_continuous_write_obj, rp2pio_statemachine_obj_end_continuous_write); //| 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 //| includes any data added to the fifo even if it was added before this was called. @@ -728,8 +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_start_continuous_write), MP_ROM_PTR(&rp2pio_statemachine_start_continuous_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_end_continuous_write), MP_ROM_PTR(&rp2pio_statemachine_end_continuous_write_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 2c8e78a3d2..a77574a57a 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.h +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.h @@ -65,8 +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_start_continuous_write(rp2pio_statemachine_obj_t *self, mp_obj_t buf_obj, const uint8_t *data, size_t len, uint8_t stride_in_bytes); -bool common_hal_rp2pio_statemachine_end_continuous_write(rp2pio_statemachine_obj_t *self); +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 7e35b33bd7..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" @@ -86,7 +88,7 @@ STATIC void rp2pio_statemachine_clear_dma(int pio_index, int sm) { if (!dma_hw->inte0) { irq_set_mask_enabled(1 << DMA_IRQ_0, false); } - MP_STATE_PORT(continuous_pio)[channel] = NULL; + MP_STATE_PORT(background_pio)[channel] = NULL; dma_channel_abort(channel); dma_channel_unclaim(channel); } @@ -608,7 +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_end_continuous_write(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); @@ -877,32 +879,43 @@ uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self) { return _current_program_offset[pio_index][sm]; } -bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_obj_t *self, mp_obj_t buf_obj, 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, 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; - if (SM_DMA_ALLOCATED(pio_index, sm) && stride_in_bytes == self->continuous_stride_in_bytes) { - while (self->pending_set_data) { + 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()) { - (void)common_hal_rp2pio_statemachine_end_continuous_write(self); return false; } } common_hal_mcu_disable_interrupts(); - self->next_buffer = data; - self->next_size = len / stride_in_bytes; - self->pending_set_data = true; + 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(); - // need to keep a reference alive to the buffer, lest the GC collect it while its lone remaining pointer is in the DMA peripheral register - self->buf_objs[++self->buf_obj_idx % 2] = buf_obj; return true; } - common_hal_rp2pio_statemachine_end_continuous_write(self); - int channel = dma_claim_unused_channel(false); if (channel == -1) { return false; @@ -916,13 +929,12 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o dma_channel_config c; - self->pending_set_data = false; - self->continuous_stride_in_bytes = stride_in_bytes; - self->buf_objs[0] = buf_obj; - self->buf_objs[1] = NULL; - - self->next_buffer = data; - self->next_size = len / stride_in_bytes; + 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)); @@ -931,12 +943,12 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o channel_config_set_write_increment(&c, false); dma_channel_configure(channel, &c, tx_destination, - data, - len / stride_in_bytes, + once->info.buf, + once->info.len / stride_in_bytes, false); common_hal_mcu_disable_interrupts(); - MP_STATE_PORT(continuous_pio)[channel] = self; + 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); @@ -946,15 +958,36 @@ bool common_hal_rp2pio_statemachine_start_continuous_write(rp2pio_statemachine_o } void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel) { - dma_channel_set_read_addr(channel, self->next_buffer, false); - dma_channel_set_trans_count(channel, self->next_size, true); + self->current = self->once; + self->once = self->loop; - self->pending_set_data = false; + 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_end_continuous_write(rp2pio_statemachine_obj_t *self) { +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 5a0deb5ed0..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. @@ -56,12 +61,10 @@ typedef struct { uint8_t offset; // dma-related items - uint8_t buf_obj_idx; - const uint8_t *next_buffer; - size_t next_size; - mp_obj_t buf_objs[2]; - int continuous_stride_in_bytes; - volatile int pending_set_data; + 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); diff --git a/ports/raspberrypi/mpconfigport.h b/ports/raspberrypi/mpconfigport.h index f4835bf2e9..fd09d8a9ac 100644 --- a/ports/raspberrypi/mpconfigport.h +++ b/ports/raspberrypi/mpconfigport.h @@ -46,7 +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 continuous_pio[NUM_DMA_CHANNELS]; \ + mp_obj_t background_pio[NUM_DMA_CHANNELS]; \ CIRCUITPY_COMMON_ROOT_POINTERS; #endif // __INCLUDED_MPCONFIGPORT_H From 1a89a2d366f69fb101d3ead530ec613c5c673bec Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 23 Apr 2022 13:25:59 -0500 Subject: [PATCH 8/8] fix doc build --- ports/raspberrypi/bindings/rp2pio/StateMachine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/raspberrypi/bindings/rp2pio/StateMachine.c b/ports/raspberrypi/bindings/rp2pio/StateMachine.c index 560cdaecc7..722b1f7c7f 100644 --- a/ports/raspberrypi/bindings/rp2pio/StateMachine.c +++ b/ports/raspberrypi/bindings/rp2pio/StateMachine.c @@ -426,7 +426,7 @@ 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: +//| 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.