From 72126e73d95a83d2fbdb6b4024af8fb3c44cb8a2 Mon Sep 17 00:00:00 2001 From: NED KONZ Date: Fri, 20 Mar 2026 15:16:26 -0700 Subject: [PATCH 1/6] rp2: Add RP2350 PIO v1 assembler support. Add support for new PIO v1 instruction features on RP2350: - wait: add jmp_pin source (PINCTRL_JMP_PIN, src=3) with a 2-bit offset argument. - irq: add next_pio/prev_pio index modifiers for cross-PIO IRQ targeting. - asm_pio: add in_count parameter encoding SHIFTCTRL.IN_COUNT (bits [4:0]) to mask unneeded IN-mapped pins to zero. - asm_pio: fix fifo_join encoding to support RP2350 TXGET(4), TXPUT(8) and PUTGET(12) modes via SHIFTCTRL bits [15:14]. All changes are backward-compatible with RP2040 PIO v0. Tested on RPI_PICO2 hardware: 26/26 encoding tests passed, covering new instructions, RP2350 FIFO join modes, in_count, and regression checks for all existing instruction encodings. Also update RP2 docs to cover new changes. Signed-off-by: NED KONZ --- docs/library/rp2.PIO.rst | 10 +++++----- docs/library/rp2.rst | 41 +++++++++++++++++++++++++++++---------- docs/rp2/tutorial/pio.rst | 34 ++++++++++++++++++-------------- ports/rp2/modules/rp2.py | 16 +++++++++++++-- 4 files changed, 69 insertions(+), 32 deletions(-) diff --git a/docs/library/rp2.PIO.rst b/docs/library/rp2.PIO.rst index f922456c8c483..de1ef007ab374 100644 --- a/docs/library/rp2.PIO.rst +++ b/docs/library/rp2.PIO.rst @@ -4,7 +4,7 @@ class PIO -- advanced PIO usage =============================== -The :class:`PIO` class gives access to an instance of the RP2040's PIO +The :class:`PIO` class gives access to an instance of the RP2040/RP2350's PIO (programmable I/O) interface. The preferred way to interact with PIO is using :class:`rp2.StateMachine`, the @@ -18,8 +18,8 @@ Constructors .. class:: PIO(id) - Gets the PIO instance numbered *id*. The RP2040 has two PIO instances, - numbered 0 and 1. + Gets the PIO instance numbered *id*. The RP2040 has two PIO instances + (0 and 1); the RP2350 has three (0, 1 and 2). Raises a ``ValueError`` if any other argument is provided. @@ -56,8 +56,8 @@ Methods .. method:: PIO.state_machine(id, [program, ...]) - Gets the state machine numbered *id*. On the RP2040, each PIO instance has - four state machines, numbered 0 to 3. + Gets the state machine numbered *id*. Each PIO instance has four state + machines, numbered 0 to 3. Optionally initialize it with a *program*: see `StateMachine.init`. diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst index a215e98b51e55..479499a67ff77 100644 --- a/docs/library/rp2.rst +++ b/docs/library/rp2.rst @@ -23,7 +23,7 @@ The ``rp2`` module includes functions for assembling PIO programs. For running PIO programs, see :class:`rp2.StateMachine`. -.. function:: asm_pio(*, out_init=None, set_init=None, sideset_init=None, side_pindir=False, in_shiftdir=PIO.SHIFT_LEFT, out_shiftdir=PIO.SHIFT_LEFT, autopush=False, autopull=False, push_thresh=32, pull_thresh=32, fifo_join=PIO.JOIN_NONE) +.. function:: asm_pio(*, out_init=None, set_init=None, sideset_init=None, side_pindir=False, in_shiftdir=PIO.SHIFT_LEFT, out_shiftdir=PIO.SHIFT_LEFT, autopush=False, autopull=False, push_thresh=32, pull_thresh=32, fifo_join=PIO.JOIN_NONE, in_count=0) Assemble a PIO program. @@ -56,9 +56,15 @@ For running PIO programs, see :class:`rp2.StateMachine`. - *autopush* configures whether auto-push is enabled. - *autopull* configures whether auto-pull is enabled. - - *fifo_join* configures whether the 4-word TX and RX FIFOs should be - combined into a single 8-word FIFO for one direction only. The options - are `PIO.JOIN_NONE`, `PIO.JOIN_RX` and `PIO.JOIN_TX`. + - *fifo_join* configures the FIFO mode. The options are `PIO.JOIN_NONE`, + `PIO.JOIN_RX` and `PIO.JOIN_TX`. On RP2350 the additional modes + ``PIO.JOIN_TXGET`` (4), ``PIO.JOIN_TXPUT`` (8) and ``PIO.JOIN_PUTGET`` + (12) enable random read/write access to the RX FIFO storage registers + via ``StateMachine.putget()``. + - *in_count* (RP2350 only) is the number of IN-mapped pins that are not + masked to zero when read by ``in_(pins, ...)``, ``wait(pin, ...)``, or + ``mov(x, pins)``. Pins beyond this count read as zero. Default is 0, + which means all 32 pins are visible (no masking). .. function:: asm_pio_encode(instr, sideset_count, sideset_opt=False) @@ -93,7 +99,9 @@ PIO-machine instructions. In MicroPython, PIO assembly routines are written as a Python function with the decorator ``@rp2.asm_pio()``, and they use Python syntax. Such routines support standard Python variables and arithmetic, as well as the following custom functions that encode PIO instructions and direct the -assembler. See sec 3.4 of the RP2040 datasheet for further details. +assembler. See sec 3.4 of the RP2040 datasheet or sec 3.5 of the RP2350 +datasheet for further details. Instructions marked *RP2350 only* encode +correctly on RP2040 but produce undefined behaviour at runtime. wrap_target() Specify the location where execution continues after program wrapping. @@ -138,9 +146,15 @@ wait(polarity, src, index) Block, waiting for high/low on a pin or IRQ line. - *polarity*: 0 or 1, whether to wait for a low or high value - - *src*: one of: ``gpio`` (absolute pin), ``pin`` (pin relative to - StateMachine's ``in_base`` argument), ``irq`` - - *index*: 0-31, the index for *src* + - *src*: one of: + + - ``gpio``: absolute GPIO pin number + - ``pin``: pin index relative to the StateMachine's ``in_base`` + - ``irq``: IRQ flag number + - ``jmp_pin``: *(RP2350 only)* PINCTRL_JMP_PIN, with *index* as a + 2-bit offset (0-3) added to the configured JMP pin + + - *index*: 0-31 for ``gpio``/``pin``/``irq``; 0-3 for ``jmp_pin`` in_(src, bit_count) Shift data in from *src* to ISR. @@ -188,7 +202,9 @@ pull(...) mov(dest, src) Move into *dest* the value from *src*. - - *dest*: one of: ``pins``, ``x``, ``y``, ``exec``, ``pc``, ``isr``, ``osr`` + - *dest*: one of: ``pins``, ``x``, ``y``, ``exec``, ``pc``, ``isr``, + ``osr``, ``pindirs`` *(RP2350 only — sets direction of all OUT-mapped + pins; use* ``null`` *or* ``invert(null)`` *as src)* - *src*: one of: ``pins``, ``x``, ``y``, ``null``, ``status``, ``isr``, ``osr``; this argument can be optionally modified by wrapping it in ``invert()`` or ``reverse()`` (but not both together) @@ -207,9 +223,14 @@ irq(...) If ``block`` is used then the instruction stalls until the flag is cleared by another entity. If ``clear`` is used then the flag is cleared instead of being set. Relative IRQ indices add the state machine ID to the IRQ index - with modulo-4 addition. IRQs 0-3 are visible from to the processor, 4-7 are + with modulo-4 addition. IRQs 0-3 are visible from the processor, 4-7 are internal to the state machines. + On RP2350, the index argument can be wrapped in ``next_pio()`` or + ``prev_pio()`` to target IRQ flags in a neighbouring PIO block instead of + the current one, e.g. ``irq(next_pio(2))`` or ``irq(clear, prev_pio(0))``. + Cross-PIO IRQ flags are observed on the next cycle with no delay penalty. + set(dest, data) Set *dest* with the value *data*. diff --git a/docs/rp2/tutorial/pio.rst b/docs/rp2/tutorial/pio.rst index 4e519650eea6d..34ae5641ca2d3 100644 --- a/docs/rp2/tutorial/pio.rst +++ b/docs/rp2/tutorial/pio.rst @@ -1,21 +1,25 @@ Programmable IO =============== -The RP2040 has hardware support for standard communication protocols like I2C, -SPI and UART. For protocols where there is no hardware support, or where there -is a requirement of custom I/O behaviour, Programmable Input Output (PIO) comes -into play. Also, some MicroPython applications make use of a technique called -bit banging in which pins are rapidly turned on and off to transmit data. This -can make the entire process slow as the processor concentrates on bit banging -rather than executing other logic. However, PIO allows bit banging to happen -in the background while the CPU is executing the main work. - -Along with the two central Cortex-M0+ processing cores, the RP2040 has two PIO -blocks each of which has four independent state machines. These state machines -can transfer data to/from other entities using First-In-First-Out (FIFO) buffers, -which allow the state machine and main processor to work independently yet also -synchronise their data. Each FIFO has four words (each of 32 bits) which can be -linked to the DMA to transfer larger amounts of data. +The RP2040 and RP2350 have hardware support for standard communication +protocols like I2C, SPI and UART. For protocols where there is no hardware +support, or where there is a requirement of custom I/O behaviour, Programmable +Input Output +(PIO) comes into play. Also, some MicroPython applications make use of a +technique called bit banging in which pins are rapidly turned on and off to +transmit data. This can make the entire process slow as the processor +concentrates on bit banging rather than executing other logic. However, PIO +allows bit banging to happen in the background while the CPU is executing the +main work. + +The RP2040 has two PIO blocks each with four independent state machines (8 +total). The RP2350 adds a third PIO block for 12 state machines in total, and +introduces PIO version 1 with additional instruction capabilities (see +`PIO assembly language instructions`). These state machines can transfer +data to/from other entities using First-In-First-Out (FIFO) buffers, which +allow the state machine and main processor to work independently yet also +synchronise their data. Each FIFO has four words (each of 32 bits) which can +be linked to the DMA to transfer larger amounts of data. All PIO instructions follow a common pattern:: diff --git a/ports/rp2/modules/rp2.py b/ports/rp2/modules/rp2.py index 831f3416b2150..5cfdf401fa0c2 100644 --- a/ports/rp2/modules/rp2.py +++ b/ports/rp2/modules/rp2.py @@ -35,6 +35,7 @@ def __init__( push_thresh=32, pull_thresh=32, fifo_join=PIO.JOIN_NONE, + in_count=0, ): # array is a built-in module so importing it here won't require # scanning the filesystem. @@ -42,14 +43,19 @@ def __init__( self.labels = {} execctrl = side_pindir << 29 + # fifo_join encodes the legacy JOIN_NONE/TX/RX modes in bits [31:30] (values 0-2), + # and the RP2350 FJOIN_RX_GET/PUT modes in bits [15:14] (values 4/8/12). + # in_count (RP2350): SHIFTCTRL bits [4:0], masks unneeded IN-mapped pins to zero. shiftctrl = ( - fifo_join << 30 + (fifo_join & 3) << 30 + | (fifo_join >> 2) << 14 | (pull_thresh & 0x1F) << 25 | (push_thresh & 0x1F) << 20 | out_shiftdir << 19 | in_shiftdir << 18 | autopull << 17 | autopush << 16 + | (in_count & 0x1F) ) self.prog = [array("H"), -1, -1, -1, execctrl, shiftctrl, out_init, set_init, sideset_init] @@ -135,7 +141,9 @@ def jmp(self, cond, label=None): def wait(self, polarity, src, index): if src == 6: - src = 1 # "pin" + src = 1 # "pin" (IN-mapped pins) + elif src == 3: + pass # "jmp_pin" (RP2350: PINCTRL_JMP_PIN with 2-bit offset in index) elif src != 0: src = 2 # "irq" return self.word(0x2000 | polarity << 7 | src << 5 | index) @@ -184,6 +192,7 @@ def set(self, dest, data): "gpio": 0, # "pin": see below, translated to 1 # "irq": see below function, translated to 2 + "jmp_pin": 3, # RP2350: PINCTRL_JMP_PIN source for wait, index is 2-bit offset (0-3) # source/dest constants for in_, out, mov, set "pins": 0, "x": 1, @@ -216,6 +225,9 @@ def set(self, dest, data): # "block": see above "clear": 0x40, "rel": lambda x: x | 0x10, + # RP2350: cross-PIO irq modifiers; applied to the irq index, e.g. irq(next_pio(2)) + "next_pio": lambda x: x | 0x08, + "prev_pio": lambda x: x | 0x18, } From dd6e8f3547fa994e8c84550cdb126d8a30e621a4 Mon Sep 17 00:00:00 2001 From: NED KONZ Date: Sat, 21 Mar 2026 09:13:21 -0700 Subject: [PATCH 2/6] docs/rp2: Add missing reference. Signed-off-by: NED KONZ --- docs/library/rp2.rst | 2 ++ docs/rp2/tutorial/pio.rst | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst index 479499a67ff77..53fa6a010728c 100644 --- a/docs/library/rp2.rst +++ b/docs/library/rp2.rst @@ -91,6 +91,8 @@ For running PIO programs, see :class:`rp2.StateMachine`. an error assembling a PIO program. +.. _pio_assembly_instructions: + PIO assembly language instructions ---------------------------------- diff --git a/docs/rp2/tutorial/pio.rst b/docs/rp2/tutorial/pio.rst index 34ae5641ca2d3..8193c4ee9b4ef 100644 --- a/docs/rp2/tutorial/pio.rst +++ b/docs/rp2/tutorial/pio.rst @@ -15,7 +15,8 @@ main work. The RP2040 has two PIO blocks each with four independent state machines (8 total). The RP2350 adds a third PIO block for 12 state machines in total, and introduces PIO version 1 with additional instruction capabilities (see -`PIO assembly language instructions`). These state machines can transfer +:ref:`PIO assembly language instructions `). +These state machines can transfer data to/from other entities using First-In-First-Out (FIFO) buffers, which allow the state machine and main processor to work independently yet also synchronise their data. Each FIFO has four words (each of 32 bits) which can From 0f1498cba3c8ee86f15627fc702e6f3c0e059200 Mon Sep 17 00:00:00 2001 From: NED KONZ Date: Sat, 21 Mar 2026 09:54:28 -0700 Subject: [PATCH 3/6] rp2/rp2_pio: Add RP2350 PIO v1 C layer features. Add RP2350-specific support to the PIO C layer: - PIO.version(): returns PIO hardware version (0=RP2040, 1=RP2350). - PIO.JOIN_RX_GET and PIO.JOIN_RX_PUT constants for new FJOIN FIFO modes (fifo_join values 4 and 8, encoding SHIFTCTRL bits 14/15). - StateMachine.init() in_count kwarg: overrides SHIFTCTRL IN_COUNT bits [4:0] at runtime, guarded by PICO_RP2350. - PIO.sm_mask_enable(sm_mask, next_mask, prev_mask): atomically enables SMs on this and neighbouring PIOs via CTRL NEXT/PREV_PIO_MASK and NEXTPREV_SM_ENABLE bits. - StateMachine.putget(index[, value]): direct host read/write access to rxf_putget[sm][n] registers for FJOIN_RX_PUT/GET modes. The IRQ handler (pio_irq0) requires no change: RP2350 ints0 for IRQ0 still covers only SM0-3, identical layout to RP2040. Signed-off-by: NED KONZ --- ports/rp2/rp2_pio.c | 79 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 81351431cb937..a5911dfd48392 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -487,6 +487,46 @@ static mp_obj_t rp2_pio_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k } static MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_irq_obj, 1, rp2_pio_irq); +// PIO.version() +static mp_obj_t rp2_pio_version(mp_obj_t self_in) { + #if PICO_RP2350 + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT((self->pio->dbg_cfginfo >> PIO_DBG_CFGINFO_VERSION_LSB) & 0xf); + #else + (void)self_in; + return MP_OBJ_NEW_SMALL_INT(0); + #endif +} +static MP_DEFINE_CONST_FUN_OBJ_1(rp2_pio_version_obj, rp2_pio_version); + +#if PICO_RP2350 +// PIO.sm_mask_enable(sm_mask, next_mask=0, prev_mask=0) +// Atomically enable SMs on this PIO and neighbouring PIOs via CTRL NEXT/PREV_PIO_MASK bits. +static mp_obj_t rp2_pio_sm_mask_enable(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_sm_mask, ARG_next_mask, ARG_prev_mask }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sm_mask, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_next_mask, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_prev_mask, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + 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); + + uint32_t ctrl = args[ARG_sm_mask].u_int & 0xf; + uint32_t next_mask = args[ARG_next_mask].u_int & 0xf; + uint32_t prev_mask = args[ARG_prev_mask].u_int & 0xf; + if (next_mask || prev_mask) { + ctrl |= (next_mask << PIO_CTRL_NEXT_PIO_MASK_LSB) + | (prev_mask << PIO_CTRL_PREV_PIO_MASK_LSB) + | PIO_CTRL_NEXTPREV_SM_ENABLE_BITS; + } + self->pio->ctrl = ctrl; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_sm_mask_enable_obj, 1, rp2_pio_sm_mask_enable); +#endif + static const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { #if PICO_PIO_USE_GPIO_BASE { MP_ROM_QSTR(MP_QSTR_gpio_base), MP_ROM_PTR(&rp2_pio_gpio_base_obj) }, @@ -495,6 +535,10 @@ static const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_remove_program), MP_ROM_PTR(&rp2_pio_remove_program_obj) }, { MP_ROM_QSTR(MP_QSTR_state_machine), MP_ROM_PTR(&rp2_pio_state_machine_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&rp2_pio_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&rp2_pio_version_obj) }, + #if PICO_RP2350 + { MP_ROM_QSTR(MP_QSTR_sm_mask_enable), MP_ROM_PTR(&rp2_pio_sm_mask_enable_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_IN_LOW), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_IN_HIGH), MP_ROM_INT(1) }, @@ -507,6 +551,10 @@ static const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_JOIN_NONE), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_JOIN_TX), MP_ROM_INT(1) }, { MP_ROM_QSTR(MP_QSTR_JOIN_RX), MP_ROM_INT(2) }, + #if PICO_RP2350 + { MP_ROM_QSTR(MP_QSTR_JOIN_RX_GET), MP_ROM_INT(4) }, + { MP_ROM_QSTR(MP_QSTR_JOIN_RX_PUT), MP_ROM_INT(8) }, + #endif { MP_ROM_QSTR(MP_QSTR_IRQ_SM0), MP_ROM_INT(0x100) }, { MP_ROM_QSTR(MP_QSTR_IRQ_SM1), MP_ROM_INT(0x200) }, @@ -618,7 +666,10 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel enum { ARG_prog, ARG_freq, ARG_in_base, ARG_out_base, ARG_set_base, ARG_jmp_pin, ARG_sideset_base, - ARG_in_shiftdir, ARG_out_shiftdir, ARG_push_thresh, ARG_pull_thresh + ARG_in_shiftdir, ARG_out_shiftdir, ARG_push_thresh, ARG_pull_thresh, + #if PICO_RP2350 + ARG_in_count, + #endif }; static const mp_arg_t allowed_args[] = { { MP_QSTR_prog, MP_ARG_REQUIRED | MP_ARG_OBJ }, @@ -632,6 +683,9 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel { MP_QSTR_out_shiftdir, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_push_thresh, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_pull_thresh, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + #if PICO_RP2350 + { MP_QSTR_in_count, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + #endif }; // Parse the arguments. @@ -728,6 +782,9 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel asm_pio_override_shiftctrl(args[ARG_out_shiftdir].u_obj, PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS, PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB, &config); asm_pio_override_shiftctrl(args[ARG_push_thresh].u_obj, PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS, PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB, &config); asm_pio_override_shiftctrl(args[ARG_pull_thresh].u_obj, PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS, PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB, &config); + #if PICO_RP2350 + asm_pio_override_shiftctrl(args[ARG_in_count].u_obj, PIO_SM0_SHIFTCTRL_IN_COUNT_BITS, PIO_SM0_SHIFTCTRL_IN_COUNT_LSB, &config); + #endif // Configure the state machine. pio_sm_set_config(self->pio, self->sm, &config); @@ -1003,6 +1060,23 @@ static mp_obj_t rp2_state_machine_irq(size_t n_args, const mp_obj_t *pos_args, m } static MP_DEFINE_CONST_FUN_OBJ_KW(rp2_state_machine_irq_obj, 1, rp2_state_machine_irq); +#if PICO_RP2350 +// StateMachine.putget(index[, value]) -- read or write RXF_PUTGET register slot (FJOIN_RX_PUT/GET mode) +static mp_obj_t rp2_state_machine_putget(size_t n_args, const mp_obj_t *args) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t index = mp_obj_get_int(args[1]); + if (index < 0 || index > 3) { + mp_raise_ValueError(MP_ERROR_TEXT("index out of range")); + } + if (n_args > 2) { + self->pio->rxf_putget[self->sm][index] = mp_obj_get_int_truncated(args[2]); + return mp_const_none; + } + return mp_obj_new_int_from_uint(self->pio->rxf_putget[self->sm][index]); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_state_machine_putget_obj, 2, 3, rp2_state_machine_putget); +#endif + static const mp_rom_map_elem_t rp2_state_machine_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&rp2_state_machine_init_obj) }, { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&rp2_state_machine_active_obj) }, @@ -1013,6 +1087,9 @@ static const mp_rom_map_elem_t rp2_state_machine_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_rx_fifo), MP_ROM_PTR(&rp2_state_machine_rx_fifo_obj) }, { MP_ROM_QSTR(MP_QSTR_tx_fifo), MP_ROM_PTR(&rp2_state_machine_tx_fifo_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&rp2_state_machine_irq_obj) }, + #if PICO_RP2350 + { MP_ROM_QSTR(MP_QSTR_putget), MP_ROM_PTR(&rp2_state_machine_putget_obj) }, + #endif }; static MP_DEFINE_CONST_DICT(rp2_state_machine_locals_dict, rp2_state_machine_locals_dict_table); From 62eb6ce96ba4d67f570a8338c7a56ecf6d772fbc Mon Sep 17 00:00:00 2001 From: NED KONZ Date: Sat, 21 Mar 2026 10:22:53 -0700 Subject: [PATCH 4/6] docs/rp2: Document RP2350 PIO v1 additions. Add documentation for the new RP2350-specific PIO C layer features: - PIO.version(): returns PIO hardware version (0=RP2040, 1=RP2350). - PIO.JOIN_RX_PUT / PIO.JOIN_RX_GET: new FIFO join mode constants, with explanation of access direction (SM writes/host reads vs host writes/SM reads). - PIO.sm_mask_enable(): atomic cross-PIO SM enable via CTRL NEXT/PREV_PIO_MASK bits, with example. - StateMachine.init(): add in_count kwarg (RP2350 SHIFTCTRL IN_COUNT). - StateMachine.putget(): direct RXF_PUTGET register access, with FIFO join mode direction semantics explained and a usage example. Also update StateMachine class description and constructor to mention RP2350 and its 12 state machines. Signed-off-by: NED KONZ --- docs/library/rp2.PIO.rst | 46 +++++++++++++++++++++++++++ docs/library/rp2.StateMachine.rst | 52 ++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/docs/library/rp2.PIO.rst b/docs/library/rp2.PIO.rst index de1ef007ab374..51e6d1d9396b1 100644 --- a/docs/library/rp2.PIO.rst +++ b/docs/library/rp2.PIO.rst @@ -72,6 +72,34 @@ Methods Optionally configure it. +.. method:: PIO.version() + + Returns the hardware version of this PIO instance as an integer: + 0 for PIO v0 (RP2040), 1 for PIO v1 (RP2350). + + This can be used to detect at runtime which PIO features are available. + +.. method:: PIO.sm_mask_enable(sm_mask, *, next_mask=0, prev_mask=0) + + Atomically enable state machines across PIO blocks in a single write to + the ``CTRL`` register. + + *sm_mask* is a 4-bit mask of state machines (bits 0–3) to enable on + this PIO instance. + + *next_mask* and *prev_mask* are 4-bit masks of state machines to enable + simultaneously on the neighbouring (next/previous) PIO instance. + Setting either causes the ``NEXTPREV_SM_ENABLE`` bit to be asserted in + the same write, making the enable across all specified PIO blocks + simultaneous. + + Only available on RP2350 (PIO v1). + + Example — start SM0 on PIO0 and SM0 on PIO1 at exactly the same time:: + + p0 = rp2.PIO(0) + p0.sm_mask_enable(0b0001, next_mask=0b0001) + Constants --------- @@ -96,6 +124,24 @@ Constants These constants are used for the *fifo_join* argument to `asm_pio`. +.. data:: PIO.JOIN_RX_PUT + PIO.JOIN_RX_GET + + RP2350 (PIO v1) FIFO join modes, used for the *fifo_join* argument to + `asm_pio`. + + ``JOIN_RX_PUT``: the state machine writes to its RX FIFO entries with + ``MOV rxfifo[n], ISR``, and the host reads them with + `StateMachine.putget`. The entries act as status registers: the state + machine updates them and the host reads them at any time. + + ``JOIN_RX_GET``: the host writes to the RX FIFO entries with + `StateMachine.putget`, and the state machine reads them with + ``MOV OSR, rxfifo[n]``. The entries act as configuration registers: + the host updates them and the state machine reads them at any time. + + Only available on RP2350. + .. data:: PIO.IRQ_SM0 PIO.IRQ_SM1 PIO.IRQ_SM2 diff --git a/docs/library/rp2.StateMachine.rst b/docs/library/rp2.StateMachine.rst index 4984be0b2183c..d0dba1f593673 100644 --- a/docs/library/rp2.StateMachine.rst +++ b/docs/library/rp2.StateMachine.rst @@ -1,11 +1,11 @@ .. currentmodule:: rp2 .. _rp2.StateMachine: -class StateMachine -- access to the RP2040's programmable I/O interface -======================================================================= +class StateMachine -- access to the RP2040/RP2350's programmable I/O interface +=============================================================================== -The :class:`StateMachine` class gives access to the RP2040's PIO (programmable -I/O) interface. +The :class:`StateMachine` class gives access to the RP2040/RP2350's PIO +(programmable I/O) interface. For assembling PIO programs, see :func:`rp2.asm_pio`. @@ -15,9 +15,8 @@ Constructors .. class:: StateMachine(id, [program, ...]) - Get the state machine numbered *id*. The RP2040 has two identical PIO - instances, each with 4 state machines: so there are 8 state machines in - total, numbered 0 to 7. + Get the state machine numbered *id*. The RP2040 has 8 state machines in + total (numbered 0 to 7); the RP2350 has 12 (numbered 0 to 11). Optionally initialize it with the given program *program*: see `StateMachine.init`. @@ -26,7 +25,7 @@ Constructors Methods ------- -.. method:: StateMachine.init(program, freq=-1, *, in_base=None, out_base=None, set_base=None, jmp_pin=None, sideset_base=None, in_shiftdir=None, out_shiftdir=None, push_thresh=None, pull_thresh=None) +.. method:: StateMachine.init(program, freq=-1, *, in_base=None, out_base=None, set_base=None, jmp_pin=None, sideset_base=None, in_shiftdir=None, out_shiftdir=None, push_thresh=None, pull_thresh=None, in_count=None) Configure the state machine instance to run the given *program*. @@ -57,6 +56,9 @@ Methods re-pushing is triggered. - *pull_thresh* is the threshold in bits before auto-pull or conditional re-pulling is triggered. + - *in_count* overrides the ``IN_COUNT`` field in ``SHIFTCTRL`` (bits 4:0), + masking unneeded IN-mapped pins to zero. Only effective on RP2350. + Usually set via the `asm_pio` decorator instead. Note: pins used for *in_base* need to be configured manually for input (or otherwise) so that the PIO can see the desired signal (they could be input @@ -145,6 +147,40 @@ Methods Optionally configure it. +.. method:: StateMachine.putget(index, [value]) + + Read or write a single RX FIFO storage register by index (0–3). This is + only meaningful when the state machine's FIFO join mode is + `PIO.JOIN_RX_PUT` or `PIO.JOIN_RX_GET`. + + The direction of access depends on the FIFO join mode: + + - `PIO.JOIN_RX_GET` (host writes, SM reads): call ``putget(index, value)`` + to write a configuration word that the state machine can read with + ``MOV OSR, rxfifo[n]``. + - `PIO.JOIN_RX_PUT` (SM writes, host reads): call ``putget(index)`` to + read a status word that the state machine wrote with + ``MOV rxfifo[n], ISR``. + + *index* must be in the range 0–3; a ``ValueError`` is raised otherwise. + + If *value* is given, writes it to the selected FIFO register and returns + ``None``. If *value* is omitted, returns the current value of the + selected FIFO register as an integer. + + Only available on RP2350 (PIO v1). + + Example — host writes configuration registers for a UART TX program:: + + @rp2.asm_pio(fifo_join=rp2.PIO.JOIN_RX_GET) + def uart_tx(): + ... + + sm = rp2.StateMachine(0, uart_tx) + sm.putget(0, 8) # configure: 8 data bits + sm.putget(1, 1) # configure: 1 stop bit + sm.active(1) + Buffer protocol --------------- From db745b4e5503a073003749501835f8163caa2cda Mon Sep 17 00:00:00 2001 From: NED KONZ Date: Sun, 22 Mar 2026 09:52:52 -0700 Subject: [PATCH 5/6] rp2: Add DREQ constants for all PIO instances. Signed-off-by: NED KONZ --- ports/rp2/modrp2.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/ports/rp2/modrp2.c b/ports/rp2/modrp2.c index 5a43c11e71928..2fb885396406d 100644 --- a/ports/rp2/modrp2.c +++ b/ports/rp2/modrp2.c @@ -30,6 +30,7 @@ #include "modrp2.h" #include "pico/stdlib.h" #include "hardware/gpio.h" +#include "hardware/pio.h" #include "hardware/sync.h" #include "hardware/structs/ioqspi.h" #include "hardware/structs/sio.h" @@ -97,6 +98,36 @@ static const mp_rom_map_elem_t rp2_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_DMA), MP_ROM_PTR(&rp2_dma_type) }, { MP_ROM_QSTR(MP_QSTR_bootsel_button), MP_ROM_PTR(&rp2_bootsel_button_obj) }, + // DREQ constants for PIO0 and PIO1 (available on RP2040 and RP2350). + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_TX0), MP_ROM_INT(DREQ_PIO0_TX0) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_TX1), MP_ROM_INT(DREQ_PIO0_TX1) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_TX2), MP_ROM_INT(DREQ_PIO0_TX2) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_TX3), MP_ROM_INT(DREQ_PIO0_TX3) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_RX0), MP_ROM_INT(DREQ_PIO0_RX0) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_RX1), MP_ROM_INT(DREQ_PIO0_RX1) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_RX2), MP_ROM_INT(DREQ_PIO0_RX2) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO0_RX3), MP_ROM_INT(DREQ_PIO0_RX3) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_TX0), MP_ROM_INT(DREQ_PIO1_TX0) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_TX1), MP_ROM_INT(DREQ_PIO1_TX1) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_TX2), MP_ROM_INT(DREQ_PIO1_TX2) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_TX3), MP_ROM_INT(DREQ_PIO1_TX3) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_RX0), MP_ROM_INT(DREQ_PIO1_RX0) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_RX1), MP_ROM_INT(DREQ_PIO1_RX1) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_RX2), MP_ROM_INT(DREQ_PIO1_RX2) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO1_RX3), MP_ROM_INT(DREQ_PIO1_RX3) }, + + #if NUM_PIOS >= 3 + // DREQ constants for PIO2 (RP2350 only). + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_TX0), MP_ROM_INT(DREQ_PIO2_TX0) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_TX1), MP_ROM_INT(DREQ_PIO2_TX1) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_TX2), MP_ROM_INT(DREQ_PIO2_TX2) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_TX3), MP_ROM_INT(DREQ_PIO2_TX3) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_RX0), MP_ROM_INT(DREQ_PIO2_RX0) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_RX1), MP_ROM_INT(DREQ_PIO2_RX1) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_RX2), MP_ROM_INT(DREQ_PIO2_RX2) }, + { MP_ROM_QSTR(MP_QSTR_DREQ_PIO2_RX3), MP_ROM_INT(DREQ_PIO2_RX3) }, + #endif + #if MICROPY_PY_NETWORK_CYW43 // Deprecated (use network.country instead). { MP_ROM_QSTR(MP_QSTR_country), MP_ROM_PTR(&mod_network_country_obj) }, From 8d4431c6cec13c4734e160e7b78703d060688289 Mon Sep 17 00:00:00 2001 From: Ned Konz Date: Wed, 31 Dec 2025 13:14:11 -0800 Subject: [PATCH 6/6] rp2: Correct bit access for rp2_state_machine_init. If you call rp2.StateMachine.init() with pull_thresh=32 it will overwrite the FJOIN_TX bit in the SMx_SHIFTCTRL register. This PR fixes that issue. Signed-off-by: Ned Konz --- ports/rp2/rp2_pio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index a5911dfd48392..230a23af248a6 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -230,7 +230,7 @@ typedef struct _asm_pio_config_t { static void asm_pio_override_shiftctrl(mp_obj_t arg, uint32_t bits, uint32_t lsb, pio_sm_config *config) { if (arg != mp_const_none) { - config->shiftctrl = (config->shiftctrl & ~bits) | (mp_obj_get_int(arg) << lsb); + config->shiftctrl = (config->shiftctrl & ~bits) | ((mp_obj_get_int(arg) << lsb) & bits); } }