Merge pull request #6300 from jepler/pio-continuous

rp2040: add a background write with looping to StateMachines
This commit is contained in:
Dan Halbert 2022-04-26 10:09:02 -04:00 committed by GitHub
commit c8e8171cec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 321 additions and 3 deletions

View File

@ -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 ""

View File

@ -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;
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);
}
}
}

View File

@ -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) },

View File

@ -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,

View File

@ -24,6 +24,8 @@
* THE SOFTWARE.
*/
#include <string.h>
#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;
}

View File

@ -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;

View File

@ -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