Use Emulation to Run RP2 PIO Code on Other Ports
The RP2 chip's PIO peripheral is a powerful piece of hardware capable of performing tasks that might otherwise require very fast bit-banging code, but it's also quite difficult to validate the correctness of a complicated PIO program as this code can then "only" be run on real hardware.
I'm planning to implement a library that's API-compatible with the rp2.StateMachine interface, to allow other ports to run PIO programs via emulation. I don't know if this can ever allow practical real-time I/O on any of the microcontroller ports; my primary use-case is to enable using the unix port to run test cases involving PIO functionality.
Given that initial goal, I intend to implement this by wrapping the rp2040-pio-emulator python package. A later stab at this idea might re-implement it in C later, if someone can figure out a path to doing this in real-time on microcontroller hardware.
rp2: Add execctrl option for the PIO decorator.
Summary
The rp2 PIO assembler includes support for instructions like
mov(y, status)
which can be used to detect when the TX and RX FIFOs pass a configurable threshold, for example to trigger an interrupt handler to fill/empty them.
However, to actually use these instructions, the relevant threshold needs to be configured in the execctrl register, and at present, only the side_pindir bit can be set.
Expose an execctrl= keyword argument in the PIO decorator to do this, together with constants in rp2.PIO which can be used like
@rp2.asm_pio(execctrl = rp2.PIO.STATUS_TXLEVEL + n)
def program():
[...]
mov(y, status)
jmp(not_y, "skip")
irq(rel(0))
label("skip")
[...]
to generate an interrupt if the TX FIFO has fewer than n words left.
This also fixes a bug with the existing side_pindir option, which is meant to be boolean. Previously, if it was passed an integer other than 0 or 1, unintended high bits in the execctrl value would be set, not just the intended bit 29.
Testing
I've tested (and used) this functionality on a real rp2350 board.
Unfortunately there are no in-tree tests at all for the rp2 PIO assembler yet, so there isn't a natural place to add a simple test for this new option. I propose to write a full set of tests to cover as much as I can of that decorator, but will submit in a separate PR.
Trade-offs and Alternatives
It is possible to work around the lack of assembler execctrl support by patching the word of the program array that corresponds to the execctrl register, for example
program[4] = program[4] & ~127 | 1 # set status if txlevel < 1
but this assumes undocumented internal details of the rp2 pio module.
Forked: https://github.com/AJMansfield/micropython-lib
Of course, this will miss a lot of what makes the PIOs really work: cycle accurate timing, the ability to lock multiple PIOs together with synchronized clocks, etc. It seems that for a lot of the uses, the PIOs are pretty special (and good!) and that a lot of the protocol applications for them depend on these features.
Still, an interesting project.
Really the main thing I'm hoping to accomplish with the library is a way to extend my TDD workflow to include tests of the PIO programs and associated drivers -- just that since I'm going to the effort, I might as well try to make it usable on real hardware, too.
I've already got a small set of hacked-together mocks for some of micropython's machine peripherals (Pin, I2C, etc), so that I can run my test cases directly on my development machine (and inside my CI/CD pipelines) -- and maybe that can become its own library for me to publish at some point.
But even if emulation can't match real time performance for any but the slowest clock dividers, my test code would already be injecting a time-travel mock anyway (i.e. an alternative implementation of
timewhose tick values only advance when test code tells them to).