From baa5a76fc09855dd0899077c09d72ba5d8681442 Mon Sep 17 00:00:00 2001 From: NitiKaur Date: Wed, 7 Jul 2021 02:40:32 +0530 Subject: [PATCH] docs/rp2: Add reference for PIO assembly instructions, and PIO tutorial. --- docs/library/rp2.rst | 152 ++++++++++++++++++++++++++++++++++++ docs/rp2/tutorial/intro.rst | 5 ++ docs/rp2/tutorial/pio.rst | 123 +++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 docs/rp2/tutorial/pio.rst diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst index 5d168bce20..43143fe089 100644 --- a/docs/library/rp2.rst +++ b/docs/library/rp2.rst @@ -72,6 +72,158 @@ For running PIO programs, see :class:`rp2.StateMachine`. an error assembling a PIO program. +PIO assembly language instructions +---------------------------------- + +PIO state machines are programmed in a custom assembly language with nine core +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. + +wrap_target() + Specify the location where execution continues after program wrapping. + By default this is the start of the PIO routine. + +wrap() + Specify the location where the program finishes and wraps around. + If this directive is not used then it is added automatically at the end of + the PIO routine. Wrapping does not cost any execution cycles. + +label(label) + Define a label called *label* at the current location. *label* can be a + string or integer. + +word(instr, label=None) + Insert an arbitrary 16-bit word in the assembled output. + + - *instr*: the 16-bit value + - *label*: if given, look up the label and logical-or the label's value with + *instr* + +jmp(...) + This instruction takes two forms: + + jmp(label) + - *label*: label to jump to unconditionally + + jmp(cond, label) + - *cond*: the condition to check, one of: + + - ``not_x``, ``not_y``: true if register is zero + - ``x_dec``, ``y_dec``: true if register is non-zero, and do post + decrement + - ``x_not_y``: true if X is not equal to Y + - ``pin``: true if the input pin is set + - ``not_osre``: true if OSR is not empty (hasn't reached its + threshold) + + - *label*: label to jump to if condition is true + +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* + +in_(src, bit_count) + Shift data in from *src* to ISR. + + - *src*: one of: ``pins``, ``x``, ``y``, ``null``, ``isr``, ``osr`` + - *bit_count*: number of bits to shift in (1-32) + +out(dest, bit_count) + Shift data out from OSR to *dest*. + + - *dest*: one of: ``pins``, ``x``, ``y``, ``pindirs``, ``pc``, ``isr``, + ``exec`` + - *bit_count*: number of bits to shift out (1-32) + +push(...) + Push ISR to the RX FIFO, then clear ISR to zero. + This instruction takes the following forms: + + - push() + - push(block) + - push(noblock) + - push(iffull) + - push(iffull, block) + - push(iffull, noblock) + + If ``block`` is used then the instruction stalls if the RX FIFO is full. + The default is to block. If ``iffull`` is used then it only pushes if the + input shift count has reached its threshold. + +pull(...) + Pull from the TX FIFO into OSR. + This instruction takes the following forms: + + - pull() + - pull(block) + - pull(noblock) + - pull(ifempty) + - pull(ifempty, block) + - pull(ifempty, noblock) + + If ``block`` is used then the instruction stalls if the TX FIFO is empty. + The default is to block. If ``ifempty`` is used then it only pulls if the + output shift count has reached its threshold. + +mov(dest, src) + Move into *dest* the value from *src*. + + - *dest*: one of: ``pins``, ``x``, ``y``, ``exec``, ``pc``, ``isr``, ``osr`` + - *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) + +irq(...) + Set or clear an IRQ flag. + This instruction takes two forms: + + irq(index) + - *index*: 0-7, or ``rel(0)`` to ``rel(7)`` + + irq(mode, index) + - *mode*: one of: ``block``, ``clear`` + - *index*: 0-7, or ``rel(0)`` to ``rel(7)`` + + 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 + internal to the state machines. + +set(dest, data) + Set *dest* with the value *data*. + + - *dest*: ``pins``, ``x``, ``y``, ``pindirs`` + - *data*: value (0-31) + +nop() + This is a pseudoinstruction that assembles to ``mov(y, y)`` and has no side + effect. + +.side(value) + This is a modifier which can be applied to any instruction, and is used to + control side-set pin values. + + - *value*: the value (bits) to output on the side-set pins + +.delay(value) + This is a modifier which can be applied to any instruction, and specifies + how many cycles to delay for after the instruction executes. + + - *value*: cycles to delay, 0-31 (maximum value reduced if side-set pins are + used) + +[value] + This is a modifier and is equivalent to ``.delay(value)``. + + Classes ------- diff --git a/docs/rp2/tutorial/intro.rst b/docs/rp2/tutorial/intro.rst index 5609ab3798..69c3e6b0a5 100644 --- a/docs/rp2/tutorial/intro.rst +++ b/docs/rp2/tutorial/intro.rst @@ -4,3 +4,8 @@ Getting started with MicroPython on the RP2xxx ============================================== Let's get started! + +.. toctree:: + :maxdepth: 1 + + pio.rst diff --git a/docs/rp2/tutorial/pio.rst b/docs/rp2/tutorial/pio.rst new file mode 100644 index 0000000000..9981aed832 --- /dev/null +++ b/docs/rp2/tutorial/pio.rst @@ -0,0 +1,123 @@ +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. + +All PIO instructions follow a common pattern:: + + .side() [] + +The side-set ``.side(...)`` and delay ``[...]`` parts are both optional, and if +specified allow the instruction to perform more than one operation. This keeps +PIO programs small and efficient. + +There are nine instructions which perform the following tasks: + +- ``jmp()`` transfers control to a different part of the code +- ``wait()`` pauses until a particular action happens +- ``in_()`` shifts the bits from a source (scratch register or set of pins) to the + input shift register +- ``out()`` shifts the bits from the output shift register to a destination +- ``push()`` sends data to the RX FIFO +- ``pull()`` receives data from the TX FIFO +- ``mov()`` moves data from a source to a destination +- ``irq()`` sets or clears an IRQ flag +- ``set()`` writes a literal value to a destination + +The instruction modifiers are: + +- ``.side()`` sets the side-set pins at the start of the instruction +- ``[]`` delays for a certain number of cycles after execution of the instruction + +There are also directives: + +- ``wrap_target()`` specifies where the program execution will get continued from +- ``wrap()`` specifies the instruction where the control flow of the program will + get wrapped from +- ``label()`` sets a label for use with ``jmp()`` instructions +- ``word()`` emits a raw 16-bit value which acts as an instruction in the program + +An example +---------- + +Take the ``pio_1hz.py`` example for a simple understanding of how to use the PIO +and state machines. Below is the code for reference. + +.. code-block:: python3 + + # Example using PIO to blink an LED and raise an IRQ at 1Hz. + + import time + from machine import Pin + import rp2 + + + @rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) + def blink_1hz(): + # Cycles: 1 + 1 + 6 + 32 * (30 + 1) = 1000 + irq(rel(0)) + set(pins, 1) + set(x, 31) [5] + label("delay_high") + nop() [29] + jmp(x_dec, "delay_high") + + # Cycles: 1 + 7 + 32 * (30 + 1) = 1000 + set(pins, 0) + set(x, 31) [6] + label("delay_low") + nop() [29] + jmp(x_dec, "delay_low") + + + # Create the StateMachine with the blink_1hz program, outputting on Pin(25). + sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(25)) + + # Set the IRQ handler to print the millisecond timestamp. + sm.irq(lambda p: print(time.ticks_ms())) + + # Start the StateMachine. + sm.active(1) + +This creates an instance of class :class:`rp2.StateMachine` which runs the +``blink_1hz`` program at 2000Hz, and connects to pin 25. The ``blink_1hz`` +program uses the PIO to blink an LED connected to this pin at 1Hz, and also +raises an IRQ as the LED turns on. This IRQ then calls the ``lambda`` function +which prints out a millisecond timestamp. + +The ``blink_1hz`` program is a PIO assembler routine. It connects to a single +pin which is configured as an output and starts out low. The instructions do +the following: + +- ``irq(rel(0))`` raises the IRQ associated with the state machine. +- The LED is turned on via the ``set(pins, 1)`` instruction. +- The value 31 is put into register X, and then there is a delay for 5 more + cycles, specified by the ``[5]``. +- The ``nop() [29]`` instruction waits for 30 cycles. +- The ``jmp(x_dec, "delay_high")`` will keep looping to the ``delay_high`` label + as long as the register X is non-zero, and will also post-decrement X. Since + X starts with the value 31 this jump will happen 31 times, so the ``nop() [29]`` + runs 32 times in total (note there is also one instruction cycle taken by the + ``jmp`` for each of these 32 loops). +- ``set(pins, 0)`` will turn the LED off by setting pin 25 low. +- Another 32 loops of ``nop() [29]`` and ``jmp(...)`` will execute. +- Because ``wrap_target()`` and ``wrap()`` are not specified, their default will + be used and execution of the program will wrap around from the bottom to the + top. This wrapping does not cost any execution cycles. + +The entire routine takes exactly 2000 cycles of the state machine. Setting the +frequency of the state machine to 2000Hz makes the LED blink at 1Hz.