Skip to content

Commit c8e8171

Browse files
authored
Merge pull request #6300 from jepler/pio-continuous
rp2040: add a background write with looping to StateMachines
2 parents bf2fd53 + 1a89a2d commit c8e8171

File tree

7 files changed

+321
-3
lines changed

7 files changed

+321
-3
lines changed

locale/circuitpython.pot

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,11 @@ msgstr ""
15351535
msgid "Microphone startup delay must be in range 0.0 to 1.0"
15361536
msgstr ""
15371537

1538+
#: ports/raspberrypi/bindings/rp2pio/StateMachine.c
1539+
#: ports/raspberrypi/common-hal/rp2pio/StateMachine.c
1540+
msgid "Mismatched data size"
1541+
msgstr ""
1542+
15381543
#: ports/mimxrt10xx/common-hal/busio/SPI.c ports/stm/common-hal/busio/SPI.c
15391544
msgid "Missing MISO or MOSI Pin"
15401545
msgstr ""

ports/raspberrypi/audio_dma.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "shared-bindings/audiocore/RawSample.h"
3030
#include "shared-bindings/audiocore/WaveFile.h"
3131
#include "shared-bindings/microcontroller/__init__.h"
32+
#include "bindings/rp2pio/StateMachine.h"
3233
#include "supervisor/background_callback.h"
3334

3435
#include "py/mpstate.h"
@@ -324,7 +325,9 @@ void audio_dma_stop(audio_dma_t *dma) {
324325
channel_mask |= 1 << dma->channel[1];
325326
}
326327
dma_hw->inte0 &= ~channel_mask;
327-
irq_set_mask_enabled(1 << DMA_IRQ_0, false);
328+
if (!dma_hw->inte0) {
329+
irq_set_mask_enabled(1 << DMA_IRQ_0, false);
330+
}
328331

329332
// Run any remaining audio tasks because we remove ourselves from
330333
// playing_audio.
@@ -442,12 +445,23 @@ STATIC void dma_callback_fun(void *arg) {
442445
void isr_dma_0(void) {
443446
for (size_t i = 0; i < NUM_DMA_CHANNELS; i++) {
444447
uint32_t mask = 1 << i;
445-
if ((dma_hw->intr & mask) != 0 && MP_STATE_PORT(playing_audio)[i] != NULL) {
448+
if ((dma_hw->intr & mask) == 0) {
449+
continue;
450+
}
451+
// acknowledge interrupt early. Doing so late means that you could lose an
452+
// interrupt if the buffer is very small and the DMA operation
453+
// completed by the time callback_add() / dma_complete() returned. This
454+
// affected PIO continuous write more than audio.
455+
dma_hw->ints0 = mask;
456+
if (MP_STATE_PORT(playing_audio)[i] != NULL) {
446457
audio_dma_t *dma = MP_STATE_PORT(playing_audio)[i];
447458
// Record all channels whose DMA has completed; they need loading.
448459
dma->channels_to_load_mask |= mask;
449460
background_callback_add(&dma->callback, dma_callback_fun, (void *)dma);
450-
dma_hw->ints0 = mask;
461+
}
462+
if (MP_STATE_PORT(background_pio)[i] != NULL) {
463+
rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(background_pio)[i];
464+
rp2pio_statemachine_dma_complete(pio, i);
451465
}
452466
}
453467
}

ports/raspberrypi/bindings/rp2pio/StateMachine.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,141 @@ STATIC mp_obj_t rp2pio_statemachine_write(size_t n_args, const mp_obj_t *pos_arg
426426
}
427427
MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_obj, 2, rp2pio_statemachine_write);
428428

429+
//| def background_write(self, once: Optional[ReadableBuffer]=None, *, loop: Optional[ReadableBuffer]=None) -> None:
430+
//| """Write data to the TX fifo in the background, with optional looping.
431+
//|
432+
//| First, if any previous ``once`` or ``loop`` buffer has not been started, this function blocks until they have.
433+
//| This means that any ``once`` or ``loop`` buffer will be written at least once.
434+
//| Then the ``once`` and/or ``loop`` buffers are queued. and the function returns.
435+
//| The ``once`` buffer (if specified) will be written just once.
436+
//| Finally, the ``loop`` buffer (if specified) will continue being looped indefinitely.
437+
//|
438+
//| Writes to the FIFO will match the input buffer's element size. For example, bytearray elements
439+
//| will perform 8 bit writes to the PIO FIFO. The RP2040's memory bus will duplicate the value into
440+
//| the other byte positions. So, pulling more data in the PIO assembly will read the duplicated values.
441+
//|
442+
//| To perform 16 or 32 bits writes into the FIFO use an `array.array` with a type code of the desired
443+
//| size, or use `memoryview.cast` to change the interpretation of an
444+
//| existing buffer. To send just part of a larger buffer, slice a `memoryview`
445+
//| of it.
446+
//|
447+
//| If a buffer is modified while it is being written out, the updated
448+
//| values will be used. However, because of interactions between CPU
449+
//| writes, DMA and the PIO FIFO are complex, it is difficult to predict
450+
//| the result of modifying multiple values. Instead, alternate between
451+
//| a pair of buffers.
452+
//|
453+
//| Having both a ``once`` and a ``loop`` parameter is to support a special case in PWM generation
454+
//| where a change in duty cycle requires a special transitional buffer to be used exactly once. Most
455+
//| use cases will probably only use one of ``once`` or ``loop``.
456+
//|
457+
//| :param ~Optional[circuitpython_typing.ReadableBuffer] once: Data to be written once
458+
//| :param ~Optional[circuitpython_typing.ReadableBuffer] loop: Data to be written repeatedly
459+
//| """
460+
//| ...
461+
//|
462+
463+
STATIC void fill_buf_info(sm_buf_info *info, mp_obj_t obj, size_t *stride_in_bytes) {
464+
if (obj != mp_const_none) {
465+
info->obj = obj;
466+
mp_get_buffer_raise(obj, &info->info, MP_BUFFER_READ);
467+
size_t stride = mp_binary_get_size('@', info->info.typecode, NULL);
468+
if (stride > 4) {
469+
mp_raise_ValueError(translate("Buffer elements must be 4 bytes long or less"));
470+
}
471+
if (*stride_in_bytes && stride != *stride_in_bytes) {
472+
mp_raise_ValueError(translate("Mismatched data size"));
473+
}
474+
*stride_in_bytes = stride;
475+
} else {
476+
memset(info, 0, sizeof(*info));
477+
}
478+
}
479+
480+
STATIC mp_obj_t rp2pio_statemachine_background_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
481+
enum { ARG_once, ARG_loop };
482+
static const mp_arg_t allowed_args[] = {
483+
{ MP_QSTR_once, MP_ARG_OBJ, {.u_obj = mp_const_none} },
484+
{ MP_QSTR_loop, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} },
485+
};
486+
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
487+
check_for_deinit(self);
488+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
489+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
490+
491+
sm_buf_info once_info;
492+
sm_buf_info loop_info;
493+
size_t stride_in_bytes = 0;
494+
fill_buf_info(&once_info, args[ARG_once].u_obj, &stride_in_bytes);
495+
fill_buf_info(&loop_info, args[ARG_loop].u_obj, &stride_in_bytes);
496+
if (!stride_in_bytes) {
497+
return mp_const_none;
498+
}
499+
500+
bool ok = common_hal_rp2pio_statemachine_background_write(self, &once_info, &loop_info, stride_in_bytes);
501+
502+
if (mp_hal_is_interrupted()) {
503+
return mp_const_none;
504+
}
505+
if (!ok) {
506+
mp_raise_OSError(MP_EIO);
507+
}
508+
return mp_const_none;
509+
}
510+
MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_background_write_obj, 1, rp2pio_statemachine_background_write);
511+
512+
//| def stop_background_write(self) -> None:
513+
//| """Immediately stop a background write, if one is in progress. Items already in the TX FIFO are not affected."""
514+
//|
515+
STATIC mp_obj_t rp2pio_statemachine_obj_stop_background_write(mp_obj_t self_in) {
516+
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in);
517+
bool ok = common_hal_rp2pio_statemachine_stop_background_write(self);
518+
if (mp_hal_is_interrupted()) {
519+
return mp_const_none;
520+
}
521+
if (!ok) {
522+
mp_raise_OSError(MP_EIO);
523+
}
524+
return mp_const_none;
525+
}
526+
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_stop_background_write_obj, rp2pio_statemachine_obj_stop_background_write);
527+
528+
//| @property
529+
//| def writing(self) -> bool:
530+
//| """Returns True if a background write is in progress"""
531+
//|
532+
STATIC mp_obj_t rp2pio_statemachine_obj_get_writing(mp_obj_t self_in) {
533+
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in);
534+
return mp_obj_new_bool(common_hal_rp2pio_statemachine_get_writing(self));
535+
}
536+
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_get_writing_obj, rp2pio_statemachine_obj_get_writing);
537+
538+
const mp_obj_property_t rp2pio_statemachine_writing_obj = {
539+
.base.type = &mp_type_property,
540+
.proxy = {(mp_obj_t)&rp2pio_statemachine_get_writing_obj,
541+
MP_ROM_NONE,
542+
MP_ROM_NONE},
543+
};
544+
545+
546+
//| @property
547+
//| def pending(self) -> int:
548+
//| """Returns the number of pending buffers for background writing.
549+
//|
550+
//| If the number is 0, then a `StateMachine.background_write` call will not block."""
551+
//|
552+
STATIC mp_obj_t rp2pio_statemachine_obj_get_pending(mp_obj_t self_in) {
553+
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in);
554+
return mp_obj_new_int(common_hal_rp2pio_statemachine_get_pending(self));
555+
}
556+
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_get_pending_obj, rp2pio_statemachine_obj_get_pending);
557+
558+
const mp_obj_property_t rp2pio_statemachine_pending_obj = {
559+
.base.type = &mp_type_property,
560+
.proxy = {(mp_obj_t)&rp2pio_statemachine_get_pending_obj,
561+
MP_ROM_NONE,
562+
MP_ROM_NONE},
563+
};
429564

430565
//| def readinto(self, buffer: WriteableBuffer, *, start: int = 0, end: Optional[int] = None) -> None:
431566
//| """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[] = {
646781
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&rp2pio_statemachine_readinto_obj) },
647782
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&rp2pio_statemachine_write_obj) },
648783
{ MP_ROM_QSTR(MP_QSTR_write_readinto), MP_ROM_PTR(&rp2pio_statemachine_write_readinto_obj) },
784+
{ MP_ROM_QSTR(MP_QSTR_background_write), MP_ROM_PTR(&rp2pio_statemachine_background_write_obj) },
785+
{ MP_ROM_QSTR(MP_QSTR_stop_background_write), MP_ROM_PTR(&rp2pio_statemachine_stop_background_write_obj) },
786+
{ MP_ROM_QSTR(MP_QSTR_writing), MP_ROM_PTR(&rp2pio_statemachine_writing_obj) },
787+
{ MP_ROM_QSTR(MP_QSTR_pending), MP_ROM_PTR(&rp2pio_statemachine_pending_obj) },
649788

650789
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&rp2pio_statemachine_frequency_obj) },
651790
{ MP_ROM_QSTR(MP_QSTR_rxstall), MP_ROM_PTR(&rp2pio_statemachine_rxstall_obj) },

ports/raspberrypi/bindings/rp2pio/StateMachine.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ void common_hal_rp2pio_statemachine_run(rp2pio_statemachine_obj_t *self, const u
6565

6666
// Writes out the given data.
6767
bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes);
68+
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);
69+
bool common_hal_rp2pio_statemachine_stop_background_write(rp2pio_statemachine_obj_t *self);
70+
mp_int_t common_hal_rp2pio_statemachine_get_pending(rp2pio_statemachine_obj_t *self);
71+
bool common_hal_rp2pio_statemachine_get_writing(rp2pio_statemachine_obj_t *self);
6872
bool common_hal_rp2pio_statemachine_readinto(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t stride_in_bytes);
6973
bool common_hal_rp2pio_statemachine_write_readinto(rp2pio_statemachine_obj_t *self,
7074
const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes,

0 commit comments

Comments
 (0)