3. PIO

3.1. Overview

There are 2 identical PIO blocks in RP2040. Each PIO block has dedicated connections to the bus fabric, GPIO and interrupt controller. The diagram for a single PIO block is shown in Figure 38 .

Figure 38. PIO block-level diagram. There are two PIO blocks with four state machines each. The four state machines simultaneously execute programs from a shared instruction memory. FIFO data queues buffer data transferred between PIO and the system. GPIO mapping logic allows each state machine to observe and manipulate up to 30 GPIOs.

Figure 38: PIO block-level diagram. The diagram shows a single PIO block connected to an AHB-Lite Bus Slave. It contains four state machines (State Machine 0 to 3), each with its own FIFO and connected to a shared Instruction Memory (32 Instructions, 4 Read Ports). The state machines are connected to a GPIO Output Level x32, GPIO Output Enable x32, and GPIO Input x32. An IRQ Masking block is connected to the state machines via SM IRQs and to the AHB-Lite Bus Slave via FIFO IRQs, outputting IRQ 0 and IRQ 1.

The diagram illustrates the internal architecture of a PIO block. On the left, the AHB-Lite Bus Slave interface connects to a vertical stack of eight FIFOs. These FIFOs are paired with four state machines (State Machine 0 to 3), with two FIFOs per machine. Each state machine is connected to a shared Instruction Memory block at the bottom, which contains 32 instructions and has 4 read ports. The state machines are also connected to a central GPIO control block on the right, which manages GPIO Output Level x32, GPIO Output Enable x32, and GPIO Input x32. An IRQ Masking block at the top receives SM IRQs from the state machines and outputs IRQ 0 and IRQ 1 to the system. FIFO IRQs are also sent from the FIFOs to the IRQ Masking block.

Figure 38: PIO block-level diagram. The diagram shows a single PIO block connected to an AHB-Lite Bus Slave. It contains four state machines (State Machine 0 to 3), each with its own FIFO and connected to a shared Instruction Memory (32 Instructions, 4 Read Ports). The state machines are connected to a GPIO Output Level x32, GPIO Output Enable x32, and GPIO Input x32. An IRQ Masking block is connected to the state machines via SM IRQs and to the AHB-Lite Bus Slave via FIFO IRQs, outputting IRQ 0 and IRQ 1.

The programmable input/output block (PIO) is a versatile hardware interface. It can support a variety of IO standards, including:

PIO is programmable in the same sense as a processor. There are two PIO blocks with four state machines each, that can independently execute sequential programs to manipulate GPIOs and transfer data. Unlike a general purpose processor, PIO state machines are highly specialised for IO, with a focus on determinism, precise timing, and close integration with fixed-function hardware. Each state machine is equipped with:

Each state machine, along with its supporting hardware, occupies approximately the same silicon area as a standard serial interface block, such as an SPI or I2C controller. However, PIO state machines can be configured and reconfigured dynamically to implement numerous different interfaces.

Making state machines programmable in a software-like manner, rather than a fully configurable logic fabric like a CPLD, allows more hardware interfaces to be offered in the same cost and power envelope. This also presents a more familiar programming model, and simpler tool flow, to those who wish to exploit PIO's full flexibility by programming it directly, rather than using a premade interface from the PIO library.

PIO is highly performant as well as flexible, thanks to a carefully selected set of fixed-function hardware inside each state machine. When outputting DPI, PIO can sustain 360Mbps during the active scanline period when running from a 48MHz system clock. In this example, one state machine is handling frame/scanline timing and generating the pixel clock, while another is handling the pixel data, and unpacking run-length-encoded scanlines.

State machines' inputs and outputs are mapped to up to 32 GPIOs (limited to 30 GPIOs for RP2040), and all state machines have independent, simultaneous access to any GPIO. For example, the standard UART code allows TX, RX, CTS and RTS to be any four arbitrary GPIOs, and I2C permits the same for SDA and SCL. The amount of freedom available depends on how exactly a given PIO program chooses to use PIO's pin mapping resources, but at the minimum, an interface can be freely shifted up or down by some number of GPIOs.

3.2. Programmer's Model

The four state machines execute from a shared instruction memory. System software loads programs into this memory, configures the state machines and IO mapping, and then sets the state machines running. PIO programs come from various sources: assembled directly by the user, drawn from the PIO library, or generated programmatically by user software.

From this point on, state machines are generally autonomous, and system software interacts through DMA, interrupts and control registers, as with other peripherals on RP2040. For more complex interfaces, PIO provides a small but flexible set of primitives which allow system software to be more hands-on with state machine control flow.

Figure 39. State machine overview. Data flows in and out through a pair of FIFOs. The state machine executes a program which transfers data between these FIFOs, a set of internal registers, and the pins. The clock divider can reduce the state machine's execution speed by a constant factor.

Figure 39: State machine overview diagram. A central box represents the state machine hardware. On the left, four arrows indicate data flow: 'From TX FIFO' to 'Out Shift', 'To RX FIFO' from 'In Shift', 'To instruction memory' from 'PC', and 'From instruction memory (or bus)' to 'Control Logic'. On the right, two arrows indicate GPIO interaction: 'To GPIO' from 'Scratch X' and 'From GPIO' to 'Control Logic'. Below the box, a double-headed arrow connects to 'IRQ Set, Clear, Status'. Inside the box, components are arranged in a 2x2 grid: 'Out Shift' and 'Scratch X' in the top row, 'In Shift' and 'Scratch Y' in the bottom row, with 'PC' and 'Clock Div' in the bottom right, and 'Control Logic' at the bottom center.
Figure 39: State machine overview diagram. A central box represents the state machine hardware. On the left, four arrows indicate data flow: 'From TX FIFO' to 'Out Shift', 'To RX FIFO' from 'In Shift', 'To instruction memory' from 'PC', and 'From instruction memory (or bus)' to 'Control Logic'. On the right, two arrows indicate GPIO interaction: 'To GPIO' from 'Scratch X' and 'From GPIO' to 'Control Logic'. Below the box, a double-headed arrow connects to 'IRQ Set, Clear, Status'. Inside the box, components are arranged in a 2x2 grid: 'Out Shift' and 'Scratch X' in the top row, 'In Shift' and 'Scratch Y' in the bottom row, with 'PC' and 'Clock Div' in the bottom right, and 'Control Logic' at the bottom center.

3.2.1. PIO Programs

PIO state machines execute short, binary programs.

Programs for common interfaces, such as UART, SPI, or I2C, are available in the PIO library, so in many cases, it is not necessary to write PIO programs. However, the PIO is much more flexible when programmed directly, supporting a wide

variety of interfaces which may not have been foreseen by its designers.

The PIO has a total of nine instructions: JMP , WAIT , IN , OUT , PUSH , PULL , MOV , IRQ , and SET . See Section 3.4 for details on these instructions.

Though the PIO only has a total of nine instructions, it would be difficult to edit PIO program binaries by hand. PIO assembly is a textual format, describing a PIO program, where each command corresponds to one instruction in the output binary. Below is an example program in PIO assembly:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/squarewave.pio Lines 8 - 13

8 .program squarewave
9     set pindirs, 1    ; Set pin to output
10 again:
11     set pins, 1 [1]  ; Drive pin high and then delay for one cycle
12     set pins, 0      ; Drive pin low
13     jmp again        ; Set PC to label `again`

The PIO assembler is included with the SDK, and is called pioasm . This program processes a PIO assembly input text file, which may contain multiple programs, and writes out the assembled programs ready for use. For the SDK these assembled programs are emitted in form of C headers, containing constant arrays: For more information see Section 3.3

3.2.2. Control Flow

On every system clock cycle, each state machine fetches, decodes and executes one instruction. Each instruction takes precisely one cycle, unless it explicitly stalls (such as the WAIT instruction). Instructions may also insert a delay of up to 31 cycles before the next instruction is executed to aid the writing of cycle-exact programs.

The program counter, or PC , points to the location in the instruction memory being executed on this cycle. Generally, PC increments by one each cycle, wrapping at the end of the instruction memory. Jump instructions are an exception and explicitly provide the next value that PC will take.

Our example assembly program (listed as .program squarewave above) shows both of these concepts in practice. It drives a 50/50 duty cycle square wave onto a GPIO, with a period of four cycles. Using some other features (e.g. side-set) this can be made as low as two cycles.

i NOTE

Side-set is where a state machine drives a small number of GPIOs in addition to the main side effects of the instruction it executes. It's described fully in Section 3.5.1 .

The system has write-only access to the instruction memory, which is used to load programs:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/squarewave.c Lines 34 - 38

34 // Load the assembled program directly into the PIO's instruction memory.
35 // Each PIO instance has a 32-slot instruction memory, which all 4 state
36 // machines can see. The system has write-only access.
37 for (uint i = 0; i < count_of(squarewave_program_instructions); ++i)
38     pio->instr_mem[i] = squarewave_program_instructions[i];

The clock divider slows the state machine's execution by a constant factor, represented as a 16.8 fixed-point fractional number. Using the above example, if a clock division of 2.5 were programmed, the square wave would have a period of \( 4 \times 2.5 = 10 \) cycles. This is useful for setting a precise baud rate for a serial interface, such as a UART.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/squarewave.c Lines 42 - 47

42 // Configure state machine 0 to run at sysclk/2.5. The state machines can
43 // run as fast as one instruction per clock cycle, but we can scale their
44 // speed down uniformly to meet some precise frequency target, e.g. for a
45 // UART baud rate. This register has 16 integer divisor bits and 8
46 // fractional divisor bits.
47 pio->sm[0].clkdiv = (uint32_t) (2.5f * (1 << 16));

The above code fragments are part of a complete code example which drives a 12.5MHz square wave out of GPIO 0 (or any other pins we might choose to map). We can also use pins WAIT PIN instruction to stall a state machine's execution for some amount of time, or a JMP PIN instruction to branch on the state of a pin, so control flow can vary based on pin state.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/squarewave.c Lines 51 - 59

51 // There are five pin mapping groups (out, in, set, side-set, jmp pin)
52 // which are used by different instructions or in different circumstances.
53 // Here we're just using SET instructions. Configure state machine 0 SETs
54 // to affect GPIO 0 only; then configure GPIO0 to be controlled by PIO0,
55 // as opposed to e.g. the processors.
56 pio->sm[0].pinctrl =
57     (1 << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
58     (0 << PIO_SM0_PINCTRL_SET_BASE_LSB);
59 gpio_set_function(0, pio_get_funcsel(pio));

The system can start and stop each state machine at any time, via the CTRL register. Multiple state machines can be started simultaneously, and the deterministic nature of PIO means they can stay perfectly synchronised.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/squarewave.c Lines 63 - 67

63 // Set the state machine running. The PIO CTRL register is global within a
64 // PIO instance, so you can start/stop multiple state machines
65 // simultaneously. We're using the register's hardware atomic set alias to
66 // make one bit high without doing a read-modify-write on the register.
67 hw_set_bits(&pio->ctrl, 1 << (PIO_CTRL_SM_ENABLE_LSB + 0));

Most instructions are executed from the instruction memory, but there are other sources, which can be freely mixed:

The last of these is particularly versatile: instructions can be embedded in the stream of data passing through the FIFO. The I2C example uses this to embed e.g. STOP and RESTART line conditions alongside normal data. In the case of MOV and OUT EXEC , the MOV/OUT itself executes in one cycle, and the executee on the next.

3.2.3. Registers

Each state machine possesses a small number of internal registers. These hold input or output data, and temporary values such as loop counter variables.

3.2.3.1. Output Shift Register (OSR)

Figure 40. Output Shift Register (OSR). Data is parcelled out 1...32 bits at a time, and unused data is recycled by a bidirectional shifter. Once empty, the OSR is reloaded from the TX FIFO.

Diagram of the Output Shift Register (OSR) architecture. A 'System' block connects to a 'TX FIFO' block. An arrow labeled 'PULL' goes from the TX FIFO to the 'Output Shift Register' block. The OSR has a 'MUX' input and an 'OUT' output. The 'OUT' output goes to a block labeled 'Pins Output enables Other OUT dests'. A feedback loop labeled 'Unused OUT data' goes from the 'OUT' output to a 'Shifter' block. The 'Shifter' block has a bidirectional connection to the 'MUX' input of the OSR.
Diagram of the Output Shift Register (OSR) architecture. A 'System' block connects to a 'TX FIFO' block. An arrow labeled 'PULL' goes from the TX FIFO to the 'Output Shift Register' block. The OSR has a 'MUX' input and an 'OUT' output. The 'OUT' output goes to a block labeled 'Pins Output enables Other OUT dests'. A feedback loop labeled 'Unused OUT data' goes from the 'OUT' output to a 'Shifter' block. The 'Shifter' block has a bidirectional connection to the 'MUX' input of the OSR.

The Output Shift Register (OSR) holds and shifts output data, between the TX FIFO and the pins (or other destinations, such as the scratch registers).

For example, to stream data through the FIFO and output to the pins at a rate of one byte per two clocks:

1 .program pull_example1
2 loop:
3     out pins, 8
4 public entry_point:
5     pull
6     out pins, 8 [1]
7     out pins, 8 [1]
8     out pins, 8
9     jmp loop

Autopull (see Section 3.5.4 ) allows the hardware to automatically refill the OSR in the majority of cases, with the state machine stalling if it tries to OUT from an empty OSR. This has two benefits:

After configuring autopull, the above program can be simplified to the following, which behaves identically:

1 .program pull_example2
2
3 loop:
4     out pins, 8
5 public entry_point:
6     jmp loop

Program wrapping ( Section 3.5.2 ) allows further simplification and, if desired, an output of 1 byte every system clock cycle.

1 .program pull_example3
2
3 public entry_point:
4 .wrap_target
5     out pins, 8 [1]
6 .wrap

3.2.3.2. Input Shift Register (ISR)

Figure 41. Input Shift Register (ISR). Data enters 1...32 bits at a time, and current contents is shifted left or right to make room. Once full, contents is written to the RX FIFO.

Diagram of the Input Shift Register (ISR) architecture. It shows a 'System' block connected to an 'RX FIFO' block. The 'RX FIFO' is connected to the 'Input Shift Register' via a 'PUSH' signal. The 'Input Shift Register' is connected to a 'Shifter' block via an 'IN' signal. The 'Shifter' is connected to 'Pins Other IN sources'. A feedback loop labeled 'From previous INs' connects the output of the 'Shifter' back to the 'Input Shift Register'.
Diagram of the Input Shift Register (ISR) architecture. It shows a 'System' block connected to an 'RX FIFO' block. The 'RX FIFO' is connected to the 'Input Shift Register' via a 'PUSH' signal. The 'Input Shift Register' is connected to a 'Shifter' block via an 'IN' signal. The 'Shifter' is connected to 'Pins Other IN sources'. A feedback loop labeled 'From previous INs' connects the output of the 'Shifter' back to the 'Input Shift Register'.

Some peripherals, like UARTs, must shift in from the left to get correct bit order, since the wire order is LSB-first; however, the processor may expect the resulting byte to be right-aligned. This is solved by the special null input source, which allows the programmer to shift some number of zeroes into the ISR, following the data.

3.2.3.3. Shift Counters

State machines remember how many bits, in total, have been shifted out of the OSR via OUT instructions, and into the ISR via IN instructions. This information is tracked at all times by a pair of hardware counters — the output shift counter and the input shift counter — each capable of holding values from 0 to 32 inclusive. With each shift operation, the relevant counter is incremented by the shift count, up to the maximum value of 32 (equal to the width of the shift register). The state machine can be configured to perform certain actions when a counter reaches a configurable threshold:

On PIO reset, or the assertion of CTRL_SM_RESTART , the input shift counter is cleared to 0 (nothing yet shifted in), and the output shift counter is initialised to 32 (nothing remaining to be shifted out; fully exhausted). Some other instructions affect the shift counters:

3.2.3.4. Scratch Registers

Each state machine has two 32-bit internal scratch registers, called X and Y .

They are used as:

For example, suppose we wanted to produce a long pulse for "1" data bits, and a short pulse for "0" data bits:

1 .program ws2812_led
2
3 public entry_point:
4     pull
5     set x, 23      ; Loop over 24 bits
6 bitloop:
7     set pins, 1    ; Drive pin high
8     out y, 1 [5]   ; Shift 1 bit out, and write it to y
9     jmp !y skip    ; Skip the extra delay if the bit was 0
10    nop [5]
11 skip:
12    set pins, 0 [5]
13    jmp x-- bitloop ; Jump if x nonzero, and decrement x
14    jmp entry_point

Here X is used as a loop counter, and Y is used as a temporary variable for branching on single bits from the OSR. This program can be used to drive a WS2812 LED interface, although more compact implementations are possible (as few as 3 instructions).

MOV allows the use of the scratch registers to save/restore the shift registers if, for example, you would like to repeatedly shift out the same sequence.

NOTE

A much more compact WS2812 example (4 instructions total) is shown in Section 3.6.2

3.2.3.5. FIFOs

Each state machine has a pair of 4-word deep FIFOs, one for data transfer from system to state machine (TX), and the other for state machine to system (RX). The TX FIFO is written to by system busmasters, such as a processor or DMA controller, and the RX FIFO is written to by the state machine. FIFOs decouple the timing of the PIO state machines and the system bus, allowing state machines to go for longer periods without processor intervention.

FIFOs also generate data request (DREQ) signals, which allow a system DMA controller to pace its reads/writes based on the presence of data in an RX FIFO, or space for new data in a TX FIFO. This allows a processor to set up a long transaction, potentially involving many kilobytes of data, which will proceed with no further processor intervention.

Often, a state machine is only transferring data in one direction. In this case the SHIFTCTRL_FJOIN option can merge the two FIFOs into a single 8-entry FIFO going in one direction only. This is useful for high-bandwidth interfaces such as DPI.

3.2.4. Stalling

State machines may momentarily pause execution for a number of reasons:

In this case, the program counter does not advance, and the state machine will continue executing this instruction on the next cycle. If the instruction specifies some number of delay cycles before the next instruction starts, these do not begin until after the stall clears.

NOTE

Side-set ( Section 3.5.1 ) is not affected by stalls, and always takes place on the first cycle of the attached instruction.

3.2.5. Pin Mapping

PIO controls the output level and direction of up to 32 GPIOs, and can observe their input levels. On every system clock cycle, each state machine may do none, one, or both of the following:

Each of these operations is on one of four contiguous ranges of GPIOs, with the base and count of each range configured via each state machine's PINCTRL register. There is a range for each of OUT , SET , IN and side-set operations. Each range can cover any of the GPIOs accessible to a given PIO block (on RP2040 this is the 30 user GPIOs), and the ranges can overlap.

For each individual GPIO output (level and direction separately), PIO considers all 8 writes that may have occurred on that cycle, and applies the write from the highest-numbered state machine. If the same state machine performs a SET / OUT and a side-set on the same GPIO simultaneously, the side-set is used. If no state machine writes to this GPIO output, its value does not change from the previous cycle.

Generally each state machine's outputs are mapped to a distinct group of GPIOs, implementing some peripheral interface.

3.2.6. IRQ Flags

IRQ flags are state bits which can be set or cleared by state machines or the system. There are 8 in total: all 8 are visible to all state machines, and the lower 4 can also be masked into one of PIO's interrupt request lines, via the IRQ0_INTE and IRQ1_INTE control registers.

They have two main uses:

State machines interact with the flags via the IRQ and WAIT instructions.

3.2.7. Interactions Between State Machines

The instruction memory is implemented as a 1-write 4-read register file, so all four state machines can read an instruction on the same cycle, without stalling.

There are three ways to apply the multiple state machines:

State machines can not communicate data, but they can synchronise with one another by using the IRQ flags. There are 8 flags total (the lower four of which can be masked for use as system IRQs), and each state machine can set or clear any flag using the IRQ instruction, and can wait for a flag to go high or low using the WAIT IRQ instruction. This allows cycle-accurate synchronisation between state machines.

3.3. PIO Assembler (pioasm)

The PIO Assembler parses a PIO source file and outputs the assembled version ready for inclusion in an RP2040 application. This includes C and C++ applications built against the SDK, and Python programs running on the RP2040 MicroPython port.

This section briefly introduces the directives and instructions that can be used in pioasm input. A deeper discussion of how to use pioasm , how it is integrated into the SDK build system, extended features such as code pass through, and the various output formats it can produce, is given in the Raspberry Pi Pico-series C/C++ SDK book.

3.3.1. Directives

The following directives control the assembly of PIO programs:

Table 363. pioasm directives

.define ( PUBLIC ) <symbol> <value>Define an integer symbol named <symbol> with the value <value> (see Section 3.3.2 ). If this .define appears before the first program in the input file, then the define is global to all programs, otherwise it is local to the program in which it occurs. If PUBLIC is specified the symbol will be emitted into the assembled output for use by user code. For the SDK this takes the form of:

#define <program_name>_<symbol> value for program symbols or #define <symbol> value for global symbols
.program <name>Start a new program with the name <name> . Note that that name is used in code so should be alphanumeric/underscore not starting with a digit. The program lasts until another .program directive or the end of the source file. PIO instructions are only allowed within a program
.origin <offset>Optional directive to specify the PIO instruction memory offset at which the program must load. Most commonly this is used for programs that must load at offset 0, because they use data based Jumps with the (absolute) jump target being stored in only a few bits. This directive is invalid outside of a program
.side_set <count> (opt) (pindirs)If this directive is present, <count> indicates the number of side-set bits to be used. Additionally opt may be specified to indicate that a side <value> is optional for instructions (note this requires stealing an extra bit — in addition to the <count> bits — from those available for the instruction delay). Finally, pindirs may be specified to indicate that the side set values should be applied to the PINDIRs and not the PINs. This directive is only valid within a program before the first instruction
.wrap_targetPlace prior to an instruction, this directive specifies the instruction where execution continues due to program wrapping. This directive is invalid outside of a program, may only be used once within a program, and if not specified defaults to the start of the program
.wrapPlaced after an instruction, this directive specifies the instruction after which, in normal control flow (i.e. jmp with false condition, or no jmp ), the program wraps (to .wrap_target instruction). This directive is invalid outside of a program, may only be used once within a program, and if not specified defaults to after the last program instruction.
.lang_opt <lang> <name> <option>Specifies an option for the program related to a particular language generator. (See Language generators ). This directive is invalid outside of a program
.word <value>Stores a raw 16-bit value as an instruction in the program. This directive is invalid outside of a program.

3.3.2. Values

The following types of values can be used to define integer numbers or branch targets

Table 364. Values in pioasm, i.e. <value>

integerAn integer value e.g. 3 or -7
hexA hexadecimal value e.g. 0xf
binaryA binary value e.g. 0b1001
symbolA value defined by a .define (see pioasm_define )
<label>The instruction offset of the label within the program. This makes most sense when used with a JMP instruction (see Section 3.4.2 )
( <expression> )An expression to be evaluated; see expressions . Note that the parentheses are necessary.

3.3.3. Expressions

Expressions may be freely used within pioasm values.

Table 365. Expressions in pioasm i.e. <expression>

<expression> + <expression>The sum of two expressions
<expression> - <expression>The difference of two expressions
<expression> * <expression>The multiplication of two expressions
<expression> / <expression>The integer division of two expressions
- <expression>The negation of another expression
:: <expression>The bit reverse of another expression
<value>Any value (see Section 3.3.2 )

3.3.4. Comments

Line comments are supported with // or ;

C-style block comments are supported via /* and */

3.3.5. Labels

Labels are of the form:

<symbol>:

or

PUBLIC <symbol>:

at the start of a line.

Image: Tip icon

TIP

A label is really just an automatic .define with a value set to the current program instruction offset. A PUBLIC label is exposed to the user code in the same way as a PUBLIC .define .

3.3.6. Instructions

All pioasm instructions follow a common pattern:

<instruction> (side <side_set_value>) ([<delay_value>])

where:

<instruction>Is an assembly instruction detailed in the following sections. (See Section 3.4 )
<side_set_value>Is a value (see Section 3.3.2 ) to apply to the side_set pins at the start of the instruction. Note that the rules for a side-set value via side <side_set_value> are dependent on the .side_set (see pioasm_side_set ) directive for the program. If no .side_set is specified then the side <side_set_value> is invalid, if an optional number of sideset pins is specified then side <side_set_value> may be present, and if a non-optional number of sideset pins is specified, then side <side_set_value> is required. The <side_set_value> must fit within the number of side-set bits specified in the .side_set directive.
<delay_value>Specifies the number of cycles to delay after the instruction completes. The delay_value is specified as a value (see Section 3.3.2 ), and in general is between 0 and 31 inclusive (a 5-bit value), however the number of bits is reduced when sideset is enabled via the .side_set (see pioasm_side_set ) directive. If the <delay_value> is not present, then the instruction has no delay

Image: Note icon

NOTE

pioasm instruction names, keywords and directives are case insensitive; lower case is used in the Assembly Syntax sections below as this is the style used in the SDK.

Image: Note icon

NOTE

Commas appear in some Assembly Syntax sections below, but are entirely optional, e.g. out pins, 3 may be written out pins 3 , and jmp x-- label may be written as jmp x--, label . The Assembly Syntax sections below uses the first style in each case as this is the style used in the SDK.

3.3.7. Pseudoinstructions

Currently pioasm provides one pseudoinstruction, as a convenience:

nopAssembles to mov y, y . "No operation", has no particular side effect, but a useful vehicle for a side-set operation or an extra delay.

3.4. Instruction Set

3.4.1. Summary

PIO instructions are 16 bits long, and have the following encoding:

Table 366. PIO instruction encoding

Bit:1514131211109876543210
JMP000Delay/side-setConditionAddress
WAIT001Delay/side-setPolSourceIndex
IN010Delay/side-setSourceBit count
OUT011Delay/side-setDestinationBit count
PUSH100Delay/side-set0IfFBlk00000
PULL100Delay/side-set1IfEBlk00000
MOV101Delay/side-setDestinationOpSource
IRQ110Delay/side-set0ClrWaitIndex
SET111Delay/side-setDestinationData

All PIO instructions execute in one clock cycle.

The function of the 5-bit Delay/side-set field depends on the state machine's SIDSESET_COUNT configuration:

3.4.2. JMP

3.4.2.1. Encoding

Bit:1514131211109876543210
JMP000Delay/side-setConditionAddress

3.4.2.2. Operation

Set program counter to Address if Condition is true, otherwise no operation.

Delay cycles on a JMP always take effect, whether Condition is true or false, and they take place after Condition is evaluated and the program counter is updated.

JMP PIN branches on the GPIO selected by EXECCTRL_JMP_PIN , a configuration field which selects one out of the maximum of 32 GPIO inputs visible to a state machine, independently of the state machine's other input mapping. The branch is taken if the GPIO is high.

!OSRE compares the bits shifted out since the last PULL with the shift count threshold configured by SHIFTCTRL_PULL_THRESH . This is the same threshold used by autopull (Section 3.5.4).

JMP X-- and JMP Y-- always decrement scratch register X or Y, respectively. The decrement is not conditional on the current value of the scratch register. The branch is conditioned on the initial value of the register, i.e. before the decrement took place: if the register is initially nonzero, the branch is taken.

3.4.2.3. Assembler Syntax

jmp ( <cond> ) <target>

where:

  1. <cond>
    Is an optional condition listed above (e.g. !x for scratch X zero). If a condition code is not specified, the branch is always taken
  2. <target>
    Is a program label or value (see Section 3.3.2) representing instruction offset within the program (the first instruction being offset 0). Note that because the PIO JMP instruction uses absolute addresses in the PIO instruction memory, JMPs need to be adjusted based on the program load offset at runtime. This is handled for you when loading a program with the SDK, but care should be taken when encoding JMP instructions for use by OUT EXEC

3.4.3. WAIT

3.4.3.1. Encoding

Bit:1514131211109876543210
WAIT001Delay/side-setPolSourceIndex

3.4.3.2. Operation

Stall until some condition is met.

Like all stalling instructions (Section 3.2.4), delay cycles begin after the instruction completes . That is, if any delay cycles are present, they do not begin counting until after the wait condition is met.

WAIT x IRQ behaves slightly differently from other WAIT sources:

CAUTION

WAIT 1 IRQ x should not be used with IRQ flags presented to the interrupt controller, to avoid a race condition with a system interrupt handler

3.4.3.3. Assembler Syntax

wait <polarity> gpio <gpio_num>

wait <polarity> pin <pin_num>

wait <polarity> irq <irq_num> ( rel )

where:

3.4.4. IN

3.4.4.1. Encoding

Bit:1514131211109876543210
IN010Delay/side-setSourceBit count

3.4.4.2. Operation

Shift Bit count bits from Source into the Input Shift Register (ISR). Shift direction is configured for each state machine by SHIFTCtrl_IN_SHIFTDIR . Additionally, increase the input shift count by Bit count , saturating at 32.

    • ◦ 010: Y (scratch register Y)
    • ◦ 011: NULL (all zeroes)
    • ◦ 100: Reserved
    • ◦ 101: Reserved
    • ◦ 110: ISR
    • ◦ 111: OSR
    • • Bit count: How many bits to shift into the ISR. 1...32 bits, 32 is encoded as 00000 .

If automatic push is enabled, IN will also push the ISR contents to the RX FIFO if the push threshold is reached ( SHIFTCTRL_PUSH_THRESH ). IN still executes in one cycle, whether an automatic push takes place or not. The state machine will stall if the RX FIFO is full when an automatic push occurs. An automatic push clears the ISR contents to all-zeroes, and clears the input shift count. See Section 3.5.4 .

IN always uses the least significant Bit count bits of the source data. For example, if PINCTRL_IN_BASE is set to 5, the instruction IN PINS, 3 will take the values of pins 5, 6 and 7, and shift these into the ISR. First the ISR is shifted to the left or right to make room for the new input data, then the input data is copied into the gap this leaves. The bit order of the input data is not dependent on the shift direction.

NULL can be used for shifting the ISR's contents. For example, UARTs receive the LSB first, so must shift to the right. After 8 IN PINS, 1 instructions, the input serial data will occupy bits 31...24 of the ISR. An IN NULL, 24 instruction will shift in 24 zero bits, aligning the input data at ISR bits 7...0. Alternatively, the processor or DMA could perform a byte read from FIFO address + 3, which would take bits 31...24 of the FIFO contents.

3.4.4.3. Assembler Syntax

in <source>, <bit_count>

where:

3.4.5. OUT

3.4.5.1. Encoding

Bit:1514131211109876543210
OUT011Delay/side-setDestinationBit count

3.4.5.2. Operation

Shift Bit count bits out of the Output Shift Register (OSR), and write those bits to Destination . Additionally, increase the output shift count by Bit count , saturating at 32.

    • ◦ 011: NULL (discard data)
    • ◦ 100: PINDIRS
    • ◦ 101: PC
    • ◦ 110: ISR (also sets ISR shift counter to Bit count )
    • ◦ 111: EXEC (Execute OSR shift data as instruction)
    • • Bit count: how many bits to shift out of the OSR. 1...32 bits, 32 is encoded as 00000 .

A 32-bit value is written to Destination : the lower Bit count bits come from the OSR, and the remainder are zeroes. This value is the least significant Bit count bits of the OSR if SHIFTCTRL_OUT_SHIFTDIR is to the right, otherwise it is the most significant bits.

PINS and PINDIRS use the OUT pin mapping, as described in Section 3.5.6 .

If automatic pull is enabled, the OSR is automatically refilled from the TX FIFO if the pull threshold, SHIFTCTRL_PULL_THRESH , is reached. The output shift count is simultaneously cleared to 0. In this case, the OUT will stall if the TX FIFO is empty, but otherwise still executes in one cycle. The specifics are given in Section 3.5.4 .

OUT EXEC allows instructions to be included inline in the FIFO datastream. The OUT itself executes on one cycle, and the instruction from the OSR is executed on the next cycle. There are no restrictions on the types of instructions which can be executed by this mechanism. Delay cycles on the initial OUT are ignored, but the executee may insert delay cycles as normal.

OUT PC behaves as an unconditional jump to an address shifted out from the OSR.

3.4.5.3. Assembler Syntax

out <destination>, <bit_count>

where:

3.4.6. PUSH

3.4.6.1. Encoding

Bit:1514131211109876543210
PUSH100Delay/side-set0IfFBlk00000

3.4.6.2. Operation

Push the contents of the ISR into the RX FIFO, as a single 32-bit word. Clear ISR to all-zeroes.

PUSH IFFULL helps to make programs more compact, like autopush. It is useful in cases where the IN would stall at an inappropriate time if autopush were enabled, e.g. if the state machine is asserting some external control signal at this point.

The PIO assembler sets the Block bit by default. If the Block bit is not set, the PUSH does not stall on a full RX FIFO, instead continuing immediately to the next instruction. The FIFO state and contents are unchanged when this happens. The ISR is still cleared to all-zeroes, and the FDEBUG_RXSTALL flag is set (the same as a blocking PUSH or autopush to a full RX FIFO) to indicate data was lost.

3.4.6.3. Assembler Syntax

push ( iffull )

push ( iffull ) block

push ( iffull ) noblock

where:

iffull Is equivalent to IfFull == 1 above. i.e. the default if this is not specified is IfFull == 0

block Is equivalent to Block == 1 above. This is the default if neither block nor noblock are specified

noblock Is equivalent to Block == 0 above.

3.4.7. PULL

3.4.7.1. Encoding

Bit:1514131211109876543210
PULL100Delay/side-set1IfEBlk00000

3.4.7.2. Operation

Load a 32-bit word from the TX FIFO into the OSR.

Some peripherals (UART, SPI...) should halt when no data is available, and pick it up as it comes in; others (I2S) should clock continuously, and it is better to output placeholder or repeated data than to stop clocking. This can be achieved with the Block parameter.

A nonblocking PULL on an empty FIFO has the same effect as MOV OSR, X . The program can either preload scratch register X with a suitable default, or execute a MOV X, OSR after each PULL NOBLOCK , so that the last valid FIFO word will be recycled until new data is available.

PULL IFEMPTY is useful if an OUT with autopull would stall in an inappropriate location when the TX FIFO is empty. IfEmpty permits some of the same program simplifications as autopull – for example, the elimination of an outer loop counter – but the stall occurs at a controlled point in the program.

NOTE

When autopull is enabled, any PULL instruction is a no-op when the OSR is full, so that the PULL instruction behaves as a barrier. OUT NULL, 32 can be used to explicitly discard the OSR contents. See Section 3.5.4.2 for more detail.

3.4.7.3. Assembler Syntax

pull ( ifempty )
pull ( ifempty ) block
pull ( ifempty ) noblock

where:

3.4.8. MOV

3.4.8.1. Encoding

Bit:1514131211109876543210
MOV101Delay/side-setDestinationOpSource

3.4.8.2. Operation

Copy data from Source to Destination .

MOV PC causes an unconditional jump. MOV EXEC has the same behaviour as OUT EXEC (Section 3.4.5), and allows register contents to be executed as an instruction. The MOV itself executes in 1 cycle, and the instruction in Source on the next cycle. Delay cycles on MOV EXEC are ignored, but the executee may insert delay cycles as normal.

The STATUS source has a value of all-ones or all-zeroes, depending on some state machine status such as FIFO full/empty, configured by EXECCTRL_STATUS_SEL .

MOV can manipulate the transferred data in limited ways, specified by the Operation argument. Invert sets each bit in Destination to the logical NOT of the corresponding bit in Source , i.e. 1 bits become 0 bits, and vice versa. Bit reverse sets each bit n in Destination to bit 31 - n in Source , assuming the bits are numbered 0 to 31.

MOV dst, PINS reads pins using the IN pin mapping, and writes the full 32-bit value to the destination without masking. The LSB of the read value is the pin indicated by PINCTRL_IN_BASE , and each successive bit comes from a higher-numbered pin, wrapping after 31.

3.4.8.3. Assembler Syntax

mov <destination>, ( op ) <source>

where:

3.4.9. IRQ

3.4.9.1. Encoding

Bit:1514131211109876543210
IRQ110Delay/side-set0ClrWaitIndex

3.4.9.2. Operation

Set or clear the IRQ flag selected by Index argument.

IRQ flags 4-7 are visible only to the state machines; IRQ flags 0-3 can be routed out to system level interrupts, on either of the PIO's two external interrupt request lines, configured by IRQ0_INTE and IRQ1_INTE .

The modulo addition bit allows relative addressing of 'IRQ' and 'WAIT' instructions, for synchronising state machines which are running the same program. Bit 2 (the third LSB) is unaffected by this addition.

If Wait is set, Delay cycles do not begin until after the wait period elapses.

3.4.9.3. Assembler Syntax

irq < irq_num > ( rel )

irq set < irq_num > ( rel )

irq nowait < irq_num > ( rel )

irq wait < irq_num > ( rel )

irq clear < irq_num > ( rel )

where:

< irq_num > ( rel )      Is a value (see Section 3.3.2 ) specifying The irq number to wait on (0-7). If rel is present, then the actual irq number used is calculating by replacing the low two bits of the irq number ( irq_num 10 ) with the low two bits of the sum ( irq_num 10 + sm_num 10 ) where sm_num 10 is the state machine number

irq Means set the IRQ without waiting

irq set Also means set the IRQ without waiting

irq nowait Again, means set the IRQ without waiting

irq wait Means set the IRQ and wait for it to be cleared before proceeding

irq clear Means clear the IRQ

3.4.10. SET

3.4.10.1. Encoding

Bit:1514131211109876543210
SET111Delay/side-setDestinationData

3.4.10.2. Operation

Write immediate value Data to Destination .

This can be used to assert control signals such as a clock or chip select, or to initialise loop counters. As Data is 5 bits in size, scratch registers can be SET to values from 0-31, which is sufficient for a 32-iteration loop.

The mapping of SET and OUT onto pins is configured independently. They may be mapped to distinct locations, for example if one pin is to be used as a clock signal, and another for data. They may also be overlapping ranges of pins: a UART transmitter might use SET to assert start and stop bits, and OUT instructions to shift out FIFO data to the same pins.

3.4.10.3. Assembler Syntax

set <destination>, <value>

where:

<destination>Is one of the destinations specified above.
<value>The value (see Section 3.3.2 ) to set (valid range 0-31)

3.5. Functional Details

3.5.1. Side-set

Side-set is a feature that allows state machines to change the level or direction of up to 5 pins, concurrently with the main execution of the instruction.

One example where this is necessary is a fast SPI interface: here a clock transition (toggling 1→0 or 0→1) must be simultaneous with a data transition, where a new data bit is shifted from the OSR to a GPIO. In this case an OUT with a side-set would achieve both of these at once.

This makes the timing of the interface more precise, reduces the overall program size (as a separate SET instruction is not needed to toggle the clock pin), and also increases the maximum frequency the SPI can run at.

Side-set also makes GPIO mapping much more flexible, as its mapping is independent from SET . The example I2C code allows SDA and SCL to be mapped to any two arbitrary pins, if clock stretching is disabled. Normally, SCL toggles to synchronise data transfer, and SDA contains the data bits being shifted out. However, some particular I2C sequences such as Start and Stop line conditions, need a fixed pattern to be driven on SDA as well as SCL. The mapping I2C uses to achieve this is:

This lets the state machine serve the two use cases of data on SDA and clock on SCL, or fixed transitions on both SDA and SCL, while still allowing SDA and SCL to be mapped to any two GPIOs of choice.

The side-set data is encoded in the Delay/side-set field of each instruction. Any instruction can be combined with side-set, including instructions which write to the pins, such as OUT PINS or SET PINS . Side-set's pin mapping is independent from OUT and SET mappings, though it may overlap. If side-set and an OUT or SET write to the same pin simultaneously, the side-set data is used.

i NOTE

If an instruction stalls, the side-set still takes effect immediately.

1 .program spi_tx_fast
2 .side_set 1
3
4 loop:
5     out pins, 1    side 0
6     jmp loop      side 1

The spi_tx_fast example shows two benefits of this: data and clock transitions can be more precisely co-aligned, and programs can be made faster overall, with an output of one bit per two system clock cycles in this case. Programs can also be made smaller.

There are four things to configure when using side-set:

  1. 1. The number of MSBs of the Delay/side-set field to use for side-set rather than delay. This is configured by PINCTRL_SIDESET_COUNT . If this is set to 5, delay cycles are not available. If set to 0, no side-set will take place.
  2. 2. Whether to use the most significant of these bits as an enable. Side-set takes place on instructions where the enable is high. If there is no enable bit, every instruction on that state machine will perform a side-set, if SIDESET_COUNT is nonzero. This is configured by EXECCTRL_SIDE_EN .
  3. 3. The GPIO number to map the least-significant side-set bit to. Configured by PINCTRL_SIDESET_BASE .
  4. 4. Whether side-set writes to GPIO levels or GPIO directions. Configured by EXECCTRL_SIDE_PINDIR

In the above example, we have only one side-set data bit, and every instruction performs a side-set, so no enable bit is required. SIDESET_COUNT would be 1, SIDE_EN would be false. SIDE_PINDIR would also be false, as we want to drive the clock high and low, not high- and low-impedance. SIDESET_BASE would select the GPIO the clock is driven from.

3.5.2. Program Wrapping

PIO programs often have an "outer loop": they perform the same sequence of steps, repetitively, as they transfer a stream of data between the FIFOs and the outside world. The square wave program from the introduction is a minimal example of this:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/PIO/squarewave/squarewave.pio Lines 8 - 13

8 .program squarewave
9     set pindirs, 1    ; Set pin to output
10 again:
11     set pins, 1 [1]   ; Drive pin high and then delay for one cycle
12     set pins, 0       ; Drive pin low
13      jmp again      ; Set PC to label `again`

The main body of the program drives a pin high, and then low, producing one period of a square wave. The entire program then loops, driving a periodic output. The jump itself takes one cycle, as does each set instruction, so to keep the high and low periods of the same duration, the set pins, 1 has a single delay cycle added, which makes the state machine idle for one cycle before executing the set pins, 0 instruction. In total, each loop takes four cycles. There are two frustrations here:

As the Program Counter ( PC ) naturally wraps to 0 when incremented past 31, we could solve the second of these by filling the entire instruction memory with a repeating pattern of set pins, 1 and set pins, 0 , but this is wasteful. State machines have a hardware feature, configured via their EXECCTRL control register, which solves this common case.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/squarewave_wrap.pio Lines 12 - 20

12 .program squarewave_wrap
13 ; Like squarewave, but use the state machine's .wrap hardware instead of an
14 ; explicit jmp. This is a free (0-cycle) unconditional jump.
15
16     set pindirs, 1    ; Set pin to output
17 .wrap_target
18     set pins, 1 [1]   ; Drive pin high and then delay for one cycle
19     set pins, 0 [1]   ; Drive pin low and then delay for one cycle
20 .wrap

After executing an instruction from the program memory, state machines use the following logic to update PC :

  1. 1. If the current instruction is a JMP , and the Condition is true, set PC to the Target
  2. 2. Otherwise, if PC matches EXECCTRL_WRAP_TOP , set PC to EXECCTRL_WRAP_BOTTOM
  3. 3. Otherwise, increment PC , or set to 0 if the current value is 31.

The .wrap_target and .wrap assembly directives in pioasm are essentially labels. They export constants which can be written to the WRAP_BOTTOM and WRAP_TOP control fields, respectively:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/generated/squarewave_wrap.pio.h

1 // ----- //
2 // This file is autogenerated by pioasm; do not edit! //
3 // ----- //
4
5 #pragma once
6
7 #include "hardware/pio.h"
8
9 // ----- //
10 // squarewave_wrap //
11 // ----- //
12
13 #define squarewave_wrap_wrap_target 1
14 #define squarewave_wrap_wrap 2
15 #define squarewave_wrap_pio_version 0
16
17 static const uint16_t squarewave_wrap_program_instructions[] = {
18     0xe081, // 0: set    pindirs, 1
19           //      .wrap_target
20     0xe101, // 1: set    pins, 1          [1]
21     0xe100, // 2: set    pins, 0          [1]
22             //      .wrap
23 };
24
25 static const struct pio_program squarewave_wrap_program = {
26     .instructions = squarewave_wrap_program_instructions,
27     .length = 3,
28     .origin = -1,
29     .pio_version = squarewave_wrap_pio_version,
30     .used_gpio_ranges = 0x0
31 #endif
32 };
33
34 static inline pio_sm_config squarewave_wrap_program_get_default_config(uint offset) {
35     pio_sm_config c = pio_get_default_sm_config();
36     sm_config_set_wrap(&c, offset + squarewave_wrap_wrap_target, offset +
37         squarewave_wrap_wrap);
37     return c;
38 }

This is raw output from the PIO assembler, pioasm , which has created a default pio_sm_config object containing the WRAP register values from the program listing. The control register fields could also be initialised directly.

i NOTE

WRAP_BOTTOM and WRAP_TOP are absolute addresses in the PIO instruction memory. If a program is loaded at an offset, the wrap addresses must be adjusted accordingly.

The squarewave_wrap example has delay cycles inserted, so that it behaves identically to the original squarewave program. Thanks to program wrapping, these can now be removed, so that the output toggles twice as fast, while maintaining an even balance of high and low periods.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/squarewave/squarewave_fast.pio Lines 12 - 18

12 .program squarewave_fast
13 ; Like squarewave_wrap, but remove the delay cycles so we can run twice as fast.
14     set pindirs, 1    ; Set pin to output
15 .wrap_target
16     set pins, 1       ; Drive pin high
17     set pins, 0       ; Drive pin low
18 .wrap

3.5.3. FIFO Joining

By default, each state machine possesses a 4-entry FIFO in each direction: one for data transfer from system to state machine (TX), the other for the reverse direction (RX). However, many applications do not require bidirectional data transfer between the system and an individual state machine, but may benefit from deeper FIFOs: in particular, high-bandwidth interfaces such as DPI. For these cases, SHIFTCTRL_FJOIN can merge the two 4-entry FIFOs into a single 8-entry FIFO.

Figure 42. Joinable dual FIFO. A pair of four-entry FIFOs, implemented with four data registers, a 1:4 decoder and a 4:1 multiplexer. Additional multiplexing allows write data and read data to cross between the TX and RX lanes, so that all 8 entries are accessible from both ports

Diagram of a joinable dual FIFO architecture. It shows two parallel paths for TX and RX. Each path has a 4:1 multiplexer at the input and a 1:4 decoder at the output. The TX path is connected to 'TX Write (System)' and 'TX Read (PULL)'. The RX path is connected to 'RX Write (PUSH)' and 'RX Read (System)'. Cross-multiplexing is shown between the TX and RX paths, allowing data to be written to or read from either path through the other's multiplexer/decoder.
Diagram of a joinable dual FIFO architecture. It shows two parallel paths for TX and RX. Each path has a 4:1 multiplexer at the input and a 1:4 decoder at the output. The TX path is connected to 'TX Write (System)' and 'TX Read (PULL)'. The RX path is connected to 'RX Write (PUSH)' and 'RX Read (System)'. Cross-multiplexing is shown between the TX and RX paths, allowing data to be written to or read from either path through the other's multiplexer/decoder.

Another example is a UART: because the TX/CTS and RX/RTS parts of a UART are asynchronous, they are implemented on two separate state machines. It would be wasteful to leave half of each state machine's FIFO resources idle. The ability to join the two halves into just a TX FIFO for the TX/CTS state machine, or just an RX FIFO in the case of the RX/RTS state machine, allows full utilisation. A UART equipped with an 8-deep FIFO can be left alone for twice as long between interrupts as one with only a 4-deep FIFO.

When one FIFO is increased in size (from 4 to 8), the other FIFO on that state machine is reduced to zero. For example, if joining to TX, the RX FIFO is unavailable, and any PUSH instruction will stall. The RX FIFO will appear both RXFULL and RXEMPTY in the FSTAT register. The converse is true if joining to RX: the TX FIFO is unavailable, and the TXFULL and TXEMPTY bits for this state machine will both be set in FSTAT . Setting both FJOIN_RX and FJOIN_TX makes both FIFOs unavailable.

8 FIFO entries is sufficient for 1 word per clock through the RP2040 system DMA, provided the DMA is not slowed by contention with other masters.

⚠ CAUTION

Changing FJOIN discards any data present in the state machine's FIFOs. If this data is irreplaceable, it must be drained beforehand.

3.5.4. Autopush and Autopull

With each OUT instruction, the OSR gradually empties, as data is shifted out. Once empty, it must be refilled: for example, a PULL transfers one word of data from the TX FIFO to the OSR. Similarly, the ISR must be emptied once full. One approach to this is a loop which performs a PULL after an appropriate amount of data has been shifted:

1 .program manual_pull
2 .side_set 1 opt
3
4 .wrap_target
5     set x, 2                ; X = bit count - 2
6     pull                    side 1 [1] ; Stall here if no TX data
7 bitloop:
8     out pins, 1             side 0 [1] ; Shift out data bit and toggle clock low
9     jmp x-- bitloop side 1 [1] ; Loop runs 3 times
10    out pins, 1             side 0      ; Shift out last bit before reloading X
11 .wrap

This program shifts out 4 bits from each FIFO word, with an accompanying bit clock, at a constant rate of 1 bit per 4 cycles. When the TX FIFO is empty, it stalls with the clock high (noting that side-set still takes place on cycles where the

instruction stalls). Figure 43 shows how a state machine would execute this program.

Figure 43. Execution of manual_pull program. X is used as a loop counter. On each iteration, one data bit is shifted out, and the clock is asserted low, then high. A delay cycle on each instruction brings the total up to four cycles per iteration. After the third loop, a fourth bit is shifted out, and the state machine immediately returns to the start of the program to reload the loop counter and pull fresh data, while maintaining the 4 cycles/bit cadence.

Timing diagram for the manual_pull program. It shows the execution of instructions (SET, PULL, OUT, JMP) over 16 clock cycles. The Scratch register X is used as a loop counter, starting at 2 and decrementing to 0, then wrapping to -1 and 2. The Data pin (OUT) shows bits 0, 1, 2, and 3 being shifted out. The Clock pin (side-set) is pulsed low for each bit shift. The OSR shift count is shown as 32, 0, 1, 2, 3, and 4.
System ClockInstructionScratch XData pin (OUT)Clock pin (side-set)OSR shift count
1SET32
2PULL0
3OUT2Bit 0Low1
4JMPHigh
52
6OUT1Bit 1Low
7JMPHigh3
8
9OUT0Bit 2Low
10JMPHigh4
11
12OUT-1Bit 3Low
13SET
14PULL
152
Timing diagram for the manual_pull program. It shows the execution of instructions (SET, PULL, OUT, JMP) over 16 clock cycles. The Scratch register X is used as a loop counter, starting at 2 and decrementing to 0, then wrapping to -1 and 2. The Data pin (OUT) shows bits 0, 1, 2, and 3 being shifted out. The Clock pin (side-set) is pulsed low for each bit shift. The OSR shift count is shown as 32, 0, 1, 2, 3, and 4.

This program has some limitations:

This is a common type of problem for PIO, so each state machine has some extra hardware to handle it. State machines keep track of the total shift count OUT of the OSR and IN to the ISR, and trigger certain actions once these counters reach a programmable threshold.

The manual_pull example can be rewritten to take advantage of automatic pull (autopull):

1 .program autopull
2 .side_set 1
3
4 .wrap_target
5     out pins, 1    side 0    [1]
6     nop           side 1    [1]
7 .wrap

This is shorter and simpler than the original, and can run twice as fast, if the delay cycles are removed, since the hardware refills the OSR "for free". Note that the program does not determine the total number of bits to be shifted before the next pull; the hardware automatically pulls once the programmable threshold, SHIFCTRL_PULL_THRESH , is reached, so the same program could also shift out e.g. 16 or 32 bits from each FIFO word.

Finally, note that the above program is not exactly the same as the original, since it stalls with the clock output low, rather than high. We can change the location of the stall, using the PULL IFEMPTY instruction, which uses the same configurable threshold as autopull:

1 .program somewhat_manual_pull
2 .side_set 1
3
4 .wrap_target
5     out pins, 1    side 0    [1]
6     pull ifempty  side 1    [1]
7 .wrap

Below is a complete example (PIO program, plus a C program to load and run it) which illustrates autopull and autopush both enabled on the same state machine. It programs state machine 0 to loopback data from the TX FIFO to the RX FIFO, with a throughput of one word per two clocks. It also demonstrates how the state machine will stall if it tries to OUT when both the OSR and TX FIFO are empty.

1 .program auto_push_pull
2
3 .wrap_target
4     out x, 32
5     in x, 32
6 .wrap
1 #include "tb.h" // TODO this is built against existing sw tree, so that we get printf etc
2
3 #include "platform.h"
4 #include "pio_regs.h"
5 #include "system.h"
6 #include "hardware.h"
7
8 #include "auto_push_pull.pio.h"
9
10 int main()
11 {
12     tb_init();
13
14     // Load program and configure state machine 0 for autopush/pull with
15     // threshold of 32, and wrapping on program boundary. A threshold of 32 is
16     // encoded by a register value of 00000.
17     for (int i = 0; i < count_of(auto_push_pull_program); ++i)
18         mm_pio->instr_mem[i] = auto_push_pull_program[i];
19     mm_pio->sm[0].shiftctrl =
20         (1u << PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) |
21         (1u << PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) |
22         (0u << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB) |
23         (0u << PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB);
24     mm_pio->sm[0].execctrl =
25         (auto_push_pull_wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
26         (auto_push_pull_wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
27
28     // Start state machine 0
29     hw_set_bits(&mm_pio->ctrl, 1u << (PIO_CTRL_SM_ENABLE_LSB + 0));
30
31     // Push data into TX FIFO, and pop from RX FIFO
32     for (int i = 0; i < 5; ++i)
33         mm_pio->txf[0] = i;
34     for (int i = 0; i < 5; ++i)
35         printf("%d\n", mm_pio->rx[0]);
36
37     return 0;
38 }

Figure 44 shows how the state machine executes the example program. Initially the OSR is empty, so the state machine stalls on the first OUT instruction. Once data is available in the TX FIFO, the state machine transfers this into the OSR. On the next cycle, the OUT can execute using the data in the OSR (in this case, transferring this data to the X scratch register), and the state machine simultaneously refills the OSR with fresh data from the FIFO. Since every IN instruction immediately fills the ISR, the ISR remains empty, and IN transfers data directly from scratch X to the RX FIFO.

Figure 44. Execution of auto_push_pull program. The state machine stalls on an OUT until data has travelled through the TX FIFO into the OSR . Subsequently, the OSR is refilled simultaneously with each OUT operation (due to bit count of 32), and IN data bypasses the ISR and goes straight to the RX FIFO . The state machine stalls again when the FIFO has drained, and the OSR is once again empty.

Timing diagram for the auto_push_pull program execution. The diagram shows the state of various components over time relative to a clock signal. The components are: Current Instruction (OUT, IN, OUT, IN, OUT, IN, OUT, IN, OUT, IN, OUT), Stall (high when instruction is OUT and TX FIFO is empty), TX FIFO Empty (high when TX FIFO is empty), TX FIFO Pop (high when TX FIFO is popped), OSR Count (0=full) (starts at 32, decreases to 0 when TX FIFO is full, then increases back to 32), RX FIFO Push (high when RX FIFO is pushed), ISR Count (0=empty) (starts at 0, increases to 32 when RX FIFO is full, then decreases back to 0), and RX FIFO Push (high when RX FIFO is pushed). The diagram illustrates the state machine's behavior during the execution of the auto_push_pull program, showing how the state machine stalls on an OUT instruction until data has travelled through the TX FIFO into the OSR, and how the OSR is refilled simultaneously with each OUT operation.
Timing diagram for the auto_push_pull program execution. The diagram shows the state of various components over time relative to a clock signal. The components are: Current Instruction (OUT, IN, OUT, IN, OUT, IN, OUT, IN, OUT, IN, OUT), Stall (high when instruction is OUT and TX FIFO is empty), TX FIFO Empty (high when TX FIFO is empty), TX FIFO Pop (high when TX FIFO is popped), OSR Count (0=full) (starts at 32, decreases to 0 when TX FIFO is full, then increases back to 32), RX FIFO Push (high when RX FIFO is pushed), ISR Count (0=empty) (starts at 0, increases to 32 when RX FIFO is full, then decreases back to 0), and RX FIFO Push (high when RX FIFO is pushed). The diagram illustrates the state machine's behavior during the execution of the auto_push_pull program, showing how the state machine stalls on an OUT instruction until data has travelled through the TX FIFO into the OSR, and how the OSR is refilled simultaneously with each OUT operation.

To trigger automatic push or pull at the correct time, the state machine tracks the total shift count of the ISR and OSR , using a pair of saturating 6-bit counters.

On any OUT or IN instruction, the state machine compares the shift counters to the values of SHIFTCTRL_PULL_THRESH and SHIFTCTRL_PUSH_THRESH to decide whether action is required. Autopull and autopush are individually enabled by the SHIFTCTRL_AUTOPULL and SHIFTCTRL_AUTOPUSH fields.

3.5.4.1. Autopush Details

Pseudocode for an 'IN' with autopush enabled:

1 isr = shift_in(isr, input())
2 isr count = saturate(isr count + in count)
3
4 if rx count >= threshold:
5     if rx fifo is full:
6         stall
7     else:
8         push(isr)
9         isr = 0
10        isr count = 0

Note that the hardware performs the above steps in a single machine clock cycle (unless there is a stall).

Threshold is configurable from 1 to 32.

3.5.4.2. Autopull Details

On non-'OUT' cycles, the hardware performs the equivalent of the following pseudocode:

1 if MOV or PULL:
2     osr count = 0
3
4 if osr count >= threshold:
5     if tx fifo not empty:
6         osr = pull()
7         osr count = 0

An autopull can therefore occur at any point between two 'OUT' s, depending on when the data arrives in the FIFO.

On 'OUT' cycles, the sequence is a little different:

1 if osr count >= threshold:
2     if tx fifo not empty:
3         osr = pull()
4         osr count = 0
5     stall
6 else:
7     output(osr)
8     osr = shift(osr, out count)
9     osr count = saturate(osr count + out count)
10
11 if osr count >= threshold:
12     if tx fifo not empty:
13         osr = pull()
14         osr count = 0

The hardware is capable of refilling the OSR simultaneously with shifting out the last of the shift data, as these two operations can proceed in parallel. However, it cannot fill an empty OSR and 'OUT' it on the same cycle, due to the long logic path this would create.

The refill is somewhat asynchronous to your program, but an 'OUT' behaves as a data fence, and the state machine will never 'OUT' data which you didn't write into the FIFO.

Note that a 'MOV' from the OSR is undefined whilst autopull is enabled; you will read either any residual data that has not been shifted out, or a fresh word from the FIFO, depending on a race against system DMA. Likewise, a 'MOV' to the OSR may overwrite data which has just been autopulled. However, data which you 'MOV' into the OSR will never be overwritten, since 'MOV' updates the shift counter.

If you do need to read the OSR contents, you should perform an explicit 'PULL' of some kind. The nondeterminism described above is the cost of the hardware managing pulls automatically. When autopull is enabled, the behaviour of 'PULL' is altered: it becomes a no-op if the OSR is full. This is to avoid a race condition against the system DMA. It behaves as a fence: either an autopull has already taken place, in which case the 'PULL' has no effect, or the program will stall on the 'PULL' until data becomes available in the FIFO.

'PUSH' does not need a similar behaviour, because autopush does not have the same nondeterminism.

3.5.5. Clock Dividers

PIO runs off the system clock, but this is simply too fast for many interfaces, and the number of De lay cycles which can be inserted is limited. Some devices, such as UART, require the signalling rate to be precisely controlled and varied, and ideally multiple state machines can be varied independently while running identical programs. Each state machine is equipped with a clock divider, for this purpose.

Rather than slowing the system clock itself, the clock divider redefines how many system clock periods are considered to be "one cycle", for execution purposes. It does this by generating a clock enable signal, which can pause and resume execution on a per-system-clock-cycle basis. The clock divider generates clock enable pulses at regular intervals, so

that the state machine runs at some steady pace, potentially much slower than the system clock.

Implementing the clock dividers in this way allows interfacing between the state machines and the system to be simpler, lower-latency, and with a smaller footprint. The state machine is completely idle on cycles where clock enable is low, though the system can still access the state machine’s FIFOs and change its configuration.

The clock dividers are 16-bit integer, 8-bit fractional, with first-order delta-sigma for the fractional divider. The clock divider can vary between 1 and 65536, in increments of \( 1 / 256 \) .

If the clock divisor is set to 1, the state machine runs on every cycle, i.e. full speed:

Figure 45. State machine operation with a clock divisor of 1. Once the state machine is enabled via the CTRL register, its clock enable is asserted on every cycle.

Timing diagram for Figure 45 showing System Clock, CLKDIV_INT (1), CLKDIV_FRAC (0), CTRL_SM_ENABLE, and Clock Enable. The Clock Enable signal is a continuous high pulse starting after CTRL_SM_ENABLE is asserted.

The diagram shows a series of system clock cycles. The CLKDIV_INT register is set to 1 and CLKDIV_FRAC is 0. After CTRL_SM_ENABLE is asserted, the Clock Enable signal becomes a continuous high pulse, indicating the state machine runs on every system clock cycle.

Timing diagram for Figure 45 showing System Clock, CLKDIV_INT (1), CLKDIV_FRAC (0), CTRL_SM_ENABLE, and Clock Enable. The Clock Enable signal is a continuous high pulse starting after CTRL_SM_ENABLE is asserted.

In general, an integer clock divisor of \( n \) will cause the state machine to run 1 cycle in every \( n \) , giving an effective clock speed of \( f_{sys} / n \) .

Figure 46. Integer clock divisors yield a periodic clock enable. The clock divider repeatedly counts down from \( n \) , and emits an enable pulse when it reaches 1.

Timing diagram for Figure 46 showing System Clock, CLKDIV_INT (2), CLKDIV_FRAC (0), CTRL_SM_ENABLE, and Clock Enable. The Clock Enable signal is a periodic pulse train with a period of 2 system clock cycles.

The diagram shows the system clock and the state machine registers. CLKDIV_INT is 2 and CLKDIV_FRAC is 0. The Clock Enable signal is a periodic pulse train, where it is high for 1 cycle and low for 1 cycle, repeating every 2 system clock cycles.

Timing diagram for Figure 46 showing System Clock, CLKDIV_INT (2), CLKDIV_FRAC (0), CTRL_SM_ENABLE, and Clock Enable. The Clock Enable signal is a periodic pulse train with a period of 2 system clock cycles.

Fractional division will maintain a steady state division rate of \( n + f / 256 \) , where \( n \) and \( f \) are the integer and fractional fields of this state machine’s CLKDIV register. It does this by selectively extending some division periods from \( n \) cycles to \( n + 1 \) .

Figure 47. Fractional clock division with an average divisor of 2.5. The clock divider maintains a running total of the fractional value from each division period, and every time this value wraps through 1, the integer divisor is increased by one for the next division period.

Timing diagram for Figure 47 showing System Clock, CLKDIV_INT (2), CLKDIV_FRAC (.5), CTRL_SM_ENABLE, and Clock Enable. The Clock Enable signal is a periodic pulse train with a period of 2.5 system clock cycles, achieved by alternating between 2 and 3 cycle periods.

The diagram shows the system clock and the state machine registers. CLKDIV_INT is 2 and CLKDIV_FRAC is 0.5. The Clock Enable signal is a periodic pulse train with a period of 2.5 system clock cycles, achieved by alternating between 2 and 3 cycle periods.

Timing diagram for Figure 47 showing System Clock, CLKDIV_INT (2), CLKDIV_FRAC (.5), CTRL_SM_ENABLE, and Clock Enable. The Clock Enable signal is a periodic pulse train with a period of 2.5 system clock cycles, achieved by alternating between 2 and 3 cycle periods.

For small \( n \) , the jitter introduced by a fractional divider may be unacceptable. However, for larger values, this effect is much less apparent.

NOTE

For fast asynchronous serial, it is recommended to use even divisions or multiples of 1 Mbaud where possible, rather than the traditional multiples of 300, to avoid unnecessary jitter.

3.5.6. GPIO Mapping

Internally, PIO has a 32-bit register for the output levels of each GPIO it can drive, and another register for the output enables (Hi/Lo-Z). On every system clock cycle, each state machine can write to some or all of the GPIOs in each of these registers.

Figure 48. The state machine has two independent output channels, one shared by OUT/SET, and another used by side-set (which can happen at any time). Three independent mappings (first GPIO, number of GPIOs) control which GPIOs OUT, SET and side-set are directed to. Input data is rotated according to which GPIO is mapped to the LSB of the IN data.

Figure 48: State Machine Output and Input Mapping Diagram

The diagram illustrates the internal architecture of a state machine's output and input handling. On the left, a 'State Machine' block has several control inputs: 'JMP pin', 'OUT/SET Strobe /1', 'OUT/SET pins/pindirs /1', 'OUT or SET? /1', 'OUT/SET Data /32', 'Side-set Strobe /1', and 'Side-set Data /5'. These inputs feed into a central 'Per-state-machine output mapping (Rotate + Mask)' block. This block also receives inputs for 'SET count', 'SET base', 'OUT count', 'OUT base', 'Side-set count', 'Side-set base', and 'Side-set pins/pindirs'. The output mapping block produces two 32-bit outputs: 'Pins write data /32' and 'Pins write mask /32', and two 32-bit outputs for pindirs: 'Pindirs write data /32' and 'Pindirs write mask /32'. A 'To priority select' signal is also generated. Below the output mapping block is a 'Per-state-machine input mapping (Rotate)' block. It receives 'IN data /32' and 'Unmapped data for JMP PIN, WAIT GPIO /32' from the state machine, and 'From GPIOs /32' from the output mapping block. It also receives an 'IN base' input. The output of the input mapping block is fed back into the state machine.

Figure 48: State Machine Output and Input Mapping Diagram

The write data and write masks for the output level and output enable registers come from the following sources:

Each OUT/SET/side-set operation writes to a contiguous range of pins, but each of these ranges is independently sized and positioned in the 32-bit GPIO space. This is sufficiently flexible for many applications. For example, if one state machine is implementing some interface such as an SPI on a group of pins, another state machine can run the same program, mapped to a different group of pins, and provide a second SPI interface.

On any given clock cycle, the state machine may perform an OUT or a SET , and may simultaneously perform a side-set. The pin mapping logic generates a 32-bit write mask and write data bus for the output level and output enable registers, based on this request, and the pin mapping configuration.

If a side-set overlaps with an OUT/SET performed by that state machine on the same cycle, the side-set takes precedence in the overlapping region.

3.5.6.1. Output Priority

Figure 49. Per-GPIO priority select of write masks from each state machine. Each GPIO considers level and direction writes from each of the four state machines, and applies the value from the highest-numbered state machine.

Figure 49: Per-GPIO Priority Select of Write Masks Diagram

The diagram shows how four state machines (State Machine 0, 1, 2, and 3) contribute to the output priority select. Each state machine has an 'Output Mapping' block that receives inputs from the state machine and outputs 'PINS' and 'PINDIRS' signals. These signals are fed into a 'Per-GPIO Priority Mask' block. The priority mask block has two outputs: 'D' (Data) and 'E' (Enable). The 'D' output is connected to the 'GPIO output level (<math>\times 32</math>)' register, and the 'E' output is connected to the 'GPIO output direction (<math>\times 32</math>)' register. The diagram illustrates that the highest-numbered state machine (State Machine 3) has the highest priority in selecting the write mask for each GPIO.

Figure 49: Per-GPIO Priority Select of Write Masks Diagram

Each state machine may assert an OUT/SET and a side-set through its pin mapping hardware on each cycle. This generates 32 bits of write data and write mask for the GPIO output level and output enable registers, from each state machine.

For each GPIO, PIO collates the writes from all four state machines, and applies the write from the highest-numbered

state machine. This occurs separately for output levels and output directions – it is possible for a state machine to change both the level and direction of the same pin on the same cycle (e.g. via simultaneous SET and side-set), or for one state machine to change a GPIO's direction while another changes that GPIO's level. If no state machine asserts a write to a GPIO's level or direction, the value does not change.

There is a register stage between each state machine and the pin mapping logic, and a register stage between the input mapping logic and each state machine. Assuming zero propagation delay, a state machine observing its own outputs is subject to the following delays:

3.5.6.2. Input Mapping

The data observed by IN instructions is mapped such that the LSB is the GPIO selected by PINCTRL_IN_BASE , and successively more-significant bits come from successively higher-numbered GPIOs, wrapping after 31.

In other words, the IN bus is a right-rotate of the GPIO input values, by PINCTRL_IN_BASE . If fewer than 32 GPIOs are present, the PIO input is padded with zeroes up to 32 bits.

Some instructions, such as WAIT GPIO , use an absolute GPIO number, rather than an index into the IN data bus. In this case, the right-rotate is not applied.

3.5.6.3. Input Synchronisers

To protect PIO from metastabilities, each GPIO input is equipped with a standard 2-flipflop synchroniser. This adds two cycles of latency to input sampling, but the benefit is that state machines can perform an IN PINS at any point, and will see only a clean high or low level, not some intermediate value that could disturb the state machine circuitry. This is absolutely necessary for asynchronous interfaces such as UART RX.

It is possible to bypass these synchronisers, on a per-GPIO basis. This reduces input latency, but it is then up to the user to guarantee that the state machine does not sample its inputs at inappropriate times. Generally this is only possible for synchronous interfaces such as SPI. Synchronisers are bypassed by setting the corresponding bit in INPUT_SYNC_BYPASS .

⚠ WARNING

Sampling a metastable input can lead to unpredictable state machine behaviour. This should be avoided. Do not disable the synchronizers unless data applied to the pins meets setup and hold times relative to CLK_SYS .

3.5.7. Forced and EXEC'd Instructions

Besides the instruction memory, state machines can execute instructions from 3 other sources:

1 .program exec_example
2
3 hang:
4     jmp hang
5 execute:
6     out exec, 32
7     jmp execute
8
9 .program instructions_to_push
10
11     out x, 32
12     in x, 32
13     push
1  #include "tb.h" // TODO this is built against existing sw tree, so that we get printf etc
2
3  #include "platform.h"
4  #include "pio_regs.h"
5  #include "system.h"
6  #include "hardware.h"
7
8  #include "exec_example.pio.h"
9
10 int main()
11 {
12     tb_init();
13
14     for (int i = 0; i < count_of(exec_example_program); ++i)
15         mm_pio->instr_mem[i] = exec_example_program[i];
16
17     // Enable autopull, threshold of 32
18     mm_pio->sm[0].shiftctrl = (1u << PIO_SM0_SHIFTCTRL_AUTOPULL_LSB);
19
20     // Start state machine 0 -- will sit in "hang" loop
21     hw_set_bits(&mm_pio->ctrl, 1u << (PIO_CTRL_SM_ENABLE_LSB + 0));
22
23     // Force a jump to program location 1
24     mm_pio->sm[0].instr = 0x0000 | 0x1; // jmp execute
25
26     // Feed a mixture of instructions and data into FIFO
27     mm_pio->txf[0] = instructions_to_push_program[0]; // out x, 32
28     mm_pio->txf[0] = 12345678; // data to be OUTed
29     mm_pio->txf[0] = instructions_to_push_program[1]; // in x, 32
30     mm_pio->txf[0] = instructions_to_push_program[2]; // push
31
32     // The program pushed into TX FIFO will return some data in RX FIFO
33     while (mm_pio->fstat & (1u << PIO_FSTAT_RXEMPTY_LSB))
34         ;
35
36     printf("%d\n", mm_pio->rx[0]);
37
38     return 0;
39 }

Here we load an example program into the state machine, which does two things:

The C program sets the state machine running, at which point it enters the hang loop. While the state machine is still running, the C program forces in a jmp instruction, which causes the state machine to break out of the loop.

When an instruction is written to the INSTR register, the state machine immediately decodes and executes that instruction, rather than the instruction it would have fetched from the PIO's instruction memory. The program counter does not advance, so on the next cycle (assuming the instruction forced into the INSTR interface did not stall) the state machine continues to execute its current program from the point where it left off, unless the written instruction itself

manipulated PC .

Delay cycles are ignored on instructions written to the INSTR register, and execute immediately, ignoring the state machine clock divider. This interface is provided for performing initial setup and effecting control flow changes, so it executes instructions in a timely manner, no matter how the state machine is configured.

Instructions written to the INSTR register are permitted to stall, in which case the state machine will latch this instruction internally until it completes. This is signified by the EXECCTRL_EXEC_STALLED flag. This can be cleared by restarting the state machine, or writing a NOP to INSTR .

In the second phase of the example state machine program, the OUT EXEC instruction is used. The OUT itself occupies one execution cycle, and the instruction which the OUT executes is on the next execution cycle. Note that one of the instructions we execute is also an OUT — the state machine is only capable of executing one OUT instruction on any given cycle.

OUT EXEC works by writing the OUT shift data to an internal instruction latch. On the next cycle, the state machine remembers it must execute from this latch rather than the instruction memory, and also knows to not advance PC on this second cycle.

This program will print "12345678" when run.

Warning icon CAUTION

If an instruction written to INSTR stalls, it is stored in the same instruction latch used by OUT EXEC and MOV EXEC , and will overwrite an in-progress instruction there. If EXEC instructions are used, instructions written to INSTR must not stall.

3.6. Examples

These examples illustrate some of PIO's hardware features, by implementing common I/O interfaces.

Looking to get started?

The Raspberry Pi Pico-series C/C++ SDK book has a comprehensive PIO chapter, which walks through writing and building a first PIO application, and goes on to walk through some programs line-by-line. It also covers broader topics such as using PIO with DMA, and goes into much more depth on how PIO can be integrated into your software.

3.6.1. Duplex SPI

Figure 50. In SPI, a host and device exchange data over a bidirectional pair of serial data lines, synchronous with a clock (SCK). Two flags, CPOL and CPHA, specify the clock's behaviour. CPOL is the idle state of the clock: 0 for low, 1 for high. The clock pulses a number of times, transferring one bit in each direction per pulse, but always returns to its idle state. CPHA determines on which edge of the clock data is captured: 0 for leading edge, and 1 for trailing edge. The arrows in the figure show the clock edge where data is captured by both the host and device.

Timing diagram for SPI showing two cases: CPHA=0 CPOL=0 and CPHA=1 CPOL=1. Each case shows a Chip Select signal, an SDO/SDI data line, and an SCK clock signal. Arrows indicate the clock edges where data is captured.

The diagram illustrates two SPI configurations. In the top case, CPHA=0 and CPOL=0. The clock (SCK) is low, and data is captured on the leading edge of each pulse. In the bottom case, CPHA=1 and CPOL=1. The clock (SCK) is high, and data is captured on the trailing edge of each pulse. Both cases show a bidirectional data line (SDO/SDI) and a Chip Select signal that is active-low.

Timing diagram for SPI showing two cases: CPHA=0 CPOL=0 and CPHA=1 CPOL=1. Each case shows a Chip Select signal, an SDO/SDI data line, and an SCK clock signal. Arrows indicate the clock edges where data is captured.

SPI is a common serial interface with a twisty history. The following program implements full-duplex (i.e. transferring data in both directions simultaneously) SPI, with a CPHA parameter of 0.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/spi/spi.pio Lines 14 - 32

14 .program spi_cpha0
15 .side_set 1
16
17 ; Pin assignments:
18 ; - SCK is side-set pin 0
19 ; - MOSI is OUT pin 0
20 ; - MISO is IN pin 0
21 ;
22 ; Autopush and autopull must be enabled, and the serial frame size is set by
23 ; configuring the push/pull threshold. Shift left/right is fine, but you must
24 ; justify the data yourself. This is done most conveniently for frame sizes of
25 ; 8 or 16 bits by using the narrow store replication and narrow load byte
26 ; picking behaviour of RP2040's IO fabric.
27
28 ; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and
29 ; transitions on the trailing edge, or some time before the first leading edge.
30
31     out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if
32     in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low)

This code uses autopush and autopull to continuously stream data from the FIFOs. The entire program runs once for every bit that is transferred, and then loops. The state machine tracks how many bits have been shifted in/out, and automatically pushes/pulls the FIFOs at the correct point. A similar program handles the CPHA=1 case:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/spi/spi.pio Lines 34 - 42

34 .program spi_cpha1
35 .side_set 1
36
37 ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
38 ; is captured on the trailing edge.
39
40     out x, 1 side 0 ; Stall here on empty (keep SCK deasserted)
41     mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
42     in pins, 1 side 0 ; Input data, deassert SCK
i NOTE

These programs do not control the chip select line; chip select is often implemented as a software-controlled GPIO, due to wildly different behaviour between different SPI hardware. The full spi.pio source linked above contains some examples how PIO can implement a hardware chip select line.

A C helper function configures the state machine, connects the GPIOs, and sets the state machine running. Note that the SPI frame size — that is, the number of bits transferred for each FIFO record — can be programmed to any value from 1 to 32, without modifying the program. Once configured, the state machine is set running.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/spi/spi.pio Lines 46 - 72

46 static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits,
47     float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) {
48     pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) :
        spi_cpha0_program_get_default_config(prog_offs);
49     sm_config_set_out_pins(&c, pin_mosi, 1);
50     sm_config_set_in_pins(&c, pin_miso);
51     sm_config_set_sideset_pins(&c, pin_sck);
52     // Only support MSB-first in this example code (shift to left, auto push/pull,
        threshold=nbits)
53     sm_config_set_out_shift(&c, false, true, n_bits);
54     sm_config_set_in_shift(&c, false, true, n_bits);
55     sm_config_set_clkdiv(&c, clkdiv);
56
57     // MOSI, SCK output are low, MISO is input
58     pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi));
59     pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck)
        | (1u << pin_mosi) | (1u << pin_miso));
60     pio_gpio_init(pio, pin_mosi);
61     pio_gpio_init(pio, pin_miso);
62     pio_gpio_init(pio, pin_sck);
63
64     // The pin muxes can be configured to invert the output (among other things
65     // and this is a cheesy way to get CPOL=1
66     gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
67     // SPI is synchronous, so bypass input synchroniser to reduce input delay.
68     hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);
69
70     pio_sm_init(pio, sm, prog_offs, &c);
71     pio_sm_set_enabled(pio, sm, true);
72 }

The state machine will now immediately begin to shift out any data appearing in the TX FIFO, and push received data into the RX FIFO.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/spi/pio_spi.c Lines 18 - 34

18 void __time_critical_func(pio_spi_write8_blocking)(const pio_spi_inst_t *spi, const uint8_t
    *src, size_t len) {
19     size_t tx_remain = len, rx_remain = len;
20     // Do 8 bit accesses on FIFO, so that write data is byte-replicated. This
21     // gets us the left-justification for free (for MSB-first shift-out)
22     io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm];
23     io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxr[spi->sm];
24     while (tx_remain || rx_remain) {
25         if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) {
26             *txfifo = *src++;
27             --tx_remain;
28         }
29     if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) {
30         (void) *rxfifo;
31         --rx_remain;
32     }
33 }
34 }

Putting this all together, this complete C program will loop back some data through a PIO SPI at 1MHz, with all four CPOL/CPHA combinations:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/spi/spi_loopback.c

1  /**
2   * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3   *
4   * SPDX-License-Identifier: BSD-3-Clause
5   */
6
7  #include <stdlib.h>
8  #include <stdio.h>
9
10 #include "pico/stdlib.h"
11 #include "pio_spi.h"
12
13 // This program instantiates a PIO SPI with each of the four possible
14 // CPOL/CPHA combinations, with the serial input and output pin mapped to the
15 // same GPIO. Any data written into the state machine's TX FIFO should then be
16 // serialised, deserialised, and reappear in the state machine's RX FIFO.
17
18 #define PIN_SCK 18
19 #define PIN_MOSI 16
20 #define PIN_MISO 16 // same as MOSI, so we get loopback
21
22 #define BUF_SIZE 20
23
24 void test(const pio_spi_inst_t *spi) {
25     static uint8_t txbuf[BUF_SIZE];
26     static uint8_t rxbuf[BUF_SIZE];
27     printf("TX:");
28     for (int i = 0; i < BUF_SIZE; ++i) {
29         txbuf[i] = rand() >> 16;
30         rxbuf[i] = 0;
31         printf(" %02x", (int) txbuf[i]);
32     }
33     printf("\n");
34
35     pio_spi_write8_read8_blocking(spi, txbuf, rxbuf, BUF_SIZE);
36
37     printf("RX:");
38     bool mismatch = false;
39     for (int i = 0; i < BUF_SIZE; ++i) {
40         printf(" %02x", (int) rxbuf[i]);
41         mismatch = mismatch || rxbuf[i] != txbuf[i];
42     }
43     if (mismatch)
44         printf("\nNope\n");
45     else
46         printf("\nOK\n");
47 }
48
49 int main() {
50     stdio_init_all();
51
52     pio_spi_inst_t spi = {
53         .pio = pio0,
54         .sm = 0
55     };
56     float clkdiv = 31.25f; // 1 MHz @ 125 clk_sys
57     uint cpha0_prog_offs = pio_add_program(spi.pio, &spi_cpha0_program);
58     uint cpha1_prog_offs = pio_add_program(spi.pio, &spi_cpha1_program);
59
60     for (int cpha = 0; cpha <= 1; ++cpha) {
61         for (int cpol = 0; cpol <= 1; ++cpol) {
62             printf("CPHA = %d, CPOL = %d\n", cpha, cpol);
63             pio_spi_init(spi.pio, spi.sm,
64                         cpha ? cpha1_prog_offs : cpha0_prog_offs,
65                         8, // 8 bits per SPI frame
66                         clkdiv,
67                         cpha,
68                         cpol,
69                         PIN_SCK,
70                         PIN_MOSI,
71                         PIN_MISO
72             );
73             test(&spi);
74             sleep_ms(10);
75         }
76     }
77 }

3.6.2. WS2812 LEDs

WS2812 LEDs are driven by a proprietary pulse-width serial format, with a wide positive pulse representing a "1" bit, and narrow positive pulse a "0". Each LED has a serial input and a serial output; LEDs are connected in a chain, with each serial input connected to the previous LED's serial output.

Figure 51. WS2812 line format. Wide positive pulse for 1, narrow positive pulse for 0, very long negative pulse for latch enable

Diagram of the WS2812 line format. It shows a sequence of pulses on a line. The first pulse is wide (1), followed by a narrow pulse (0), then another narrow pulse (0), then a wide pulse (1), and finally a very long negative pulse labeled 'Latch'. The diagram is labeled 'Symbol' and 'Output'.
Diagram of the WS2812 line format. It shows a sequence of pulses on a line. The first pulse is wide (1), followed by a narrow pulse (0), then another narrow pulse (0), then a wide pulse (1), and finally a very long negative pulse labeled 'Latch'. The diagram is labeled 'Symbol' and 'Output'.

LEDs consume 24 bits of pixel data, then pass any additional input data on to their output. In this way a single serial burst can individually program the colour of each LED in a chain. A long negative pulse latches the pixel data into the LEDs.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812.pio Lines 8 - 31

8 .program ws2812
9 .side_set 1
10
11 ; The following constants are selected for broad compatibility with WS2812,
12 ; WS2812B, and SK6812 LEDs. Other constants may support higher bandwidths for
13 ; specific LEDs, such as (7,10,8) for WS2812B LEDs.
14
15 .define public T1 3
16 .define public T2 3
17 .define public T3 4
18
19 .lang_opt python sideset_init = pico.PIO.OUT_HIGH
20 .lang_opt python out_init     = pico.PIO.OUT_HIGH
21 .lang_opt python out_shiftdir = 1
22
23 .wrap_target
24 bitloop:
25     out x, 1          side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
26     jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
27 do_one:
28     jmp bitloop      side 1 [T2 - 1] ; Continue driving high, for a long pulse
29 do_zero:
30     nop              side 0 [T2 - 1] ; Or drive low, for a short pulse
31 .wrap

This program shifts bits from the OSR into X, and produces a wide or narrow pulse on side-set pin 0, based on the value of each data bit. Autopull must be configured, with a threshold of 24. Software can then write 24-bit pixel values into the FIFO, and these will be serialised to a chain of WS2812 LEDs. The .pio file contains a C helper function to set this up:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812.pio Lines 36 - 52

36 static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq,
    bool rgbw) {
37
38     pio_gpio_init(pio, pin);
39     pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
40
41     pio_sm_config c = ws2812_program_get_default_config(offset);
42     sm_config_set_sideset_pins(&c, pin);
43     sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
44     sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
45
46     int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
47     float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
48     sm_config_set_clkdiv(&c, div);
49
50     pio_sm_init(pio, sm, offset, &c);
51     pio_sm_set_enabled(pio, sm, true);
52 }

Because the shift is MSB-first, and our pixels aren't a power of two size (so we can't rely on the narrow write replication behaviour on RP2040 to fan out the bits for us), we need to preshift the values written to the TX FIFO.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812.c Lines 43 - 45

43 static inline void put_pixel(PIO pio, uint sm, uint32_t pixel_grb) {
44     pio_sm_put_blocking(pio, sm, pixel_grb << 8u);
45 }

To DMA the pixels, we could instead set the autopull threshold to 8 bits, set the DMA transfer size to 8 bits, and write a byte at a time into the FIFO. Each pixel would be 3 one-byte transfers. Because of how the bus fabric and DMA on RP2040 work, each byte the DMA transfers will appear replicated four times when written to a 32-bit IO register, so effectively your data is at both ends of the shift register, and you can shift in either direction without worry.

More detail?

The WS2812 example is the subject of a tutorial in the Raspberry Pi Pico-series C/C++ SDK document, in the PIO chapter. The tutorial dissects the ws2812 program line by line, traces through how the program executes, and shows wave diagrams of the GPIO output at every point in the program.

3.6.3. UART TX

Figure 52. UART serial format. The line is high when idle. The transmitter pulls the line down for one bit period to signify the start of a serial frame (the "start bit"), and a small, fixed number of data bits follows. The line returns to the idle state for at least one bit period (the "stop bit") before the next serial frame can begin.

Timing diagram for UART TX showing Bit Clock, TX signal, and State transitions. The TX signal starts high (Idle), transitions low for one bit period (Start), then outputs 8 data bits (0-7), and finally transitions high for one bit period (Stop) before returning to Idle.
Bit ClockTXState
IdleHighIdle
StartLowStart
0LowData (LSB first)
1Low
2Low
3Low
4Low
5Low
6Low
7Low
StopHighStop
IdleHighIdle
Timing diagram for UART TX showing Bit Clock, TX signal, and State transitions. The TX signal starts high (Idle), transitions low for one bit period (Start), then outputs 8 data bits (0-7), and finally transitions high for one bit period (Stop) before returning to Idle.

This program implements the transmit component of a universal asynchronous receive/transmit (UART) serial peripheral. Perhaps it would be more correct to refer to this as a UAT.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_tx/uart_tx.pio Lines 8 - 18

8 .program uart_tx
9 .side_set 1 opt
10
11 ; An 8n1 UART transmit program.
12 ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
13
14     pull            side 1 [7] ; Assert stop bit, or stall with line in idle state
15     set x, 7        side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
16 bitloop:           ; This loop will run 8 times (8n1 UART)
17     out pins, 1      ; Shift 1 bit from OSR to the first OUT pin
18     jmp x-- bitloop  [6] ; Each loop iteration is 8 cycles.

As written, it will:

If the state machine's clock divider is configured to run at 8 times the desired baud rate, this program will transmit well-formed UART serial frames, whenever data is pushed to the TX FIFO either by software or the system DMA. To extend the program to cover different frame sizes (different numbers of data bits), the set x, 7 could be replaced with mov x, y , so that the y scratch register becomes a per-SM configuration register for UART frame size.

The .pio file in the SDK also contains this function, for configuring the pins and the state machine, once the program has been loaded into the PIO instruction memory:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_tx/uart_tx.pio Lines 24 - 51

24 static inline void uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx, uint
    baud) {
25     // Tell PIO to initially drive output-high on the selected pin, then map PIO
26     // onto that pin with the IO muxes.
27     pio_sm_set_pins_with_mask64(pio, sm, 1ull << pin_tx, 1ull << pin_tx);
28     pio_sm_set_pindirs_with_mask64(pio, sm, 1ull << pin_tx, 1ull << pin_tx);
29     pio_gpio_init(pio, pin_tx);
30
31     pio_sm_config c = uart_tx_program_get_default_config(offset);
32
33     // OUT shifts to right, no autopull
34     sm_config_set_out_shift(&c, true, false, 32);
35
36     // We are mapping both OUT and side-set to the same pin, because sometimes
37     // we need to assert user data onto the pin (with OUT) and sometimes
38     // assert constant values (start/stop bit)
39     sm_config_set_out_pins(&c, pin_tx, 1);
40     sm_config_set_sideset_pins(&c, pin_tx);
41
42 // We only need TX, so get an 8-deep FIFO!
43 sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
44
45 // SM transmits 1 bit per 8 execution cycles.
46 float div = (float)clock_get_hz(clk_sys) / (8 * baud);
47 sm_config_set_clkdiv(&c, div);
48
49 pio_sm_init(pio, sm, offset, &c);
50 pio_sm_set_enabled(pio, sm, true);
51 }

The state machine is configured to shift right in out instructions, because UARTs typically send data LSB-first. Once configured, the state machine will print any characters pushed to the TX FIFO.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_tx/uart_tx.pio Lines 53 - 55

53 static inline void uart_tx_program_putc(PIO pio, uint sm, char c) {
54     pio_sm_put_blocking(pio, sm, (uint32_t)c);
55 }

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_tx/uart_tx.pio Lines 57 - 60

57 static inline void uart_tx_program_puts(PIO pio, uint sm, const char *s) {
58     while (*s)
59         uart_tx_program_putc(pio, sm, *s++);
60 }

The example program in the SDK will configure one PIO state machine as a UART TX peripheral, and use it to print a message on GPIO 0 at 115200 baud once per second.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_tx/uart_tx.c

1 /**
2  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6
7 #include "pico/stdlib.h"
8 #include "hardware/pio.h"
9 #include "uart_tx.pio.h"
10
11 // We're going to use PIO to print "Hello, world!" on the same GPIO which we
12 // normally attach UART0 to.
13 #define PIO_TX_PIN 0
14
15 // Check the pin is compatible with the platform
16 #error Attempting to use a pin >= 32 on a platform that does not support it
17
18 int main() {
19     // This is the same as the default UART baud rate on Pico
20     const uint SERIAL_BAUD = 115200;
21
22     PIO pio;
23     uint sm;
24     uint offset;
25
26 // This will find a free pio and state machine for our program and load it for us
27 // We use pio_claim_free_sm_and_add_program_for_gpio_range (for_gpio_range variant)
28 // so we will get a PIO instance suitable for addressing gpios >= 32 if needed and
   supported by the hardware
29 bool success = pio_claim_free_sm_and_add_program_for_gpio_range(&uart_tx_program, &pio,
   &sm, &offset, PIO_TX_PIN, 1, true);
30 hard_assert(success);
31
32 uart_tx_program_init(pio, sm, offset, PIO_TX_PIN, SERIAL_BAUD);
33
34 while (true) {
35     uart_tx_program_puts(pio, sm, "Hello, world! (from PIO!)\r\n");
36     sleep_ms(1000);
37 }
38
39 // This will free resources and unload our program
40 pio_remove_program_and_unclaim_sm(&uart_tx_program, pio, sm, offset);
41 }

With the two PIO instances on RP2040, this could be extended to 8 additional UART TX interfaces, on 8 different pins, with 8 different baud rates.

3.6.4. UART RX

Recalling Figure 52 showing the format of an 8n1 UART:

Timing diagram for an 8n1 UART. It shows three signals: Bit Clock, TX, and State. The Bit Clock is a regular square wave. The TX signal shows a start bit (low), followed by 8 data bits (0, 1, 2, 3, 4, 5, 6, 7) which are sampled at the rising edges of the clock, and then a stop bit (high). The State signal shows 'Idle' (high), 'Start' (low), 'Data (LSB first)' (high for 0, low for 1, high for 2, low for 3, high for 4, low for 5, high for 6, low for 7), and 'Stop' (high).
Timing diagram for an 8n1 UART. It shows three signals: Bit Clock, TX, and State. The Bit Clock is a regular square wave. The TX signal shows a start bit (low), followed by 8 data bits (0, 1, 2, 3, 4, 5, 6, 7) which are sampled at the rising edges of the clock, and then a stop bit (high). The State signal shows 'Idle' (high), 'Start' (low), 'Data (LSB first)' (high for 0, low for 1, high for 2, low for 3, high for 4, low for 5, high for 6, low for 7), and 'Stop' (high).

We can recover the data by waiting for the start bit, sampling 8 times with the correct timing, and pushing the result to the RX FIFO. Below is possibly the shortest program which can do this:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx/uart_rx.pio Lines 8 - 19

8 .program uart_rx_mini
9
10 ; Minimum viable 8n1 UART receiver. Wait for the start bit, then sample 8 bits
11 ; with the correct timing.
12 ; IN pin 0 is mapped to the GPIO used as UART RX.
13 ; Autopush must be enabled, with a threshold of 8.
14
15     wait 0 pin 0          ; Wait for start bit
16     set x, 7 [10]         ; Preload bit counter, delay until eye of first data bit
17 bitloop:                ; Loop 8 times
18     in pins, 1           ; Sample data
19     jmp x-- bitloop [6] ; Each iteration is 8 cycles

This works, but it has some annoying characteristics, like repeatedly outputting NUL characters if the line is stuck low. Ideally, we would want to drop data that is not correctly framed by a start and stop bit (and set some sticky flag to indicate this has happened), and pause receiving when the line is stuck low for long periods. We can add these to our program, at the cost of a few more instructions.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx/uart_rx.pio Lines 44 - 63

44 .program uart_rx
45
46 ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
47 ; break conditions more gracefully.
48 ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
49
50 start:
51     wait 0 pin 0      ; Stall until start bit is asserted
52     set x, 7         [10] ; Preload bit counter, then delay until halfway through
53 bitloop:              ; the first data bit (12 cycles incl wait, set).
54     in pins, 1        ; Shift data bit into ISR
55     jmp x-- bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
56     jmp pin good_stop  ; Check stop bit (should be high)
57
58     irq 4 rel          ; Either a framing error or a break. Set a sticky flag,
59     wait 1 pin 0      ; and wait for line to return to idle state.
60     jmp start          ; Don't push data if we didn't see good framing.
61
62 good_stop:            ; No delay before returning to start; a little slack is
63     push              ; important in case the TX clock is slightly too fast.

The second example does not use autopush (Section 3.5.4), preferring instead to use an explicit push instruction, so that it can condition the push on whether a correct stop bit is seen. The .pio file includes a helper function which configures the state machine and connects it to a GPIO with the pull-up enabled:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx/uart_rx.pio Lines 67 - 85

67 static inline void uart_rx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
68     pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
69     pio_gpio_init(pio, pin);
70     gpio_pull_up(pin);
71
72     pio_sm_config c = uart_rx_program_get_default_config(offset);
73     sm_config_set_in_pins(&c, pin); // for WAIT, IN
74     sm_config_set_jmp_pin(&c, pin); // for JMP
75     // Shift to right, autopush disabled
76     sm_config_set_in_shift(&c, true, false, 32);
77     // Deeper FIFO as we're not doing any TX
78     sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
79     // SM transmits 1 bit per 8 execution cycles.
80     float div = (float)clock_get_hz(clk_sys) / (8 * baud);
81     sm_config_set_clkdiv(&c, div);
82
83     pio_sm_init(pio, sm, offset, &c);
84     pio_sm_set_enabled(pio, sm, true);
85 }

To correctly receive data which is sent LSB-first, the ISR is configured to shift to the right. After shifting in 8 bits, this unfortunately leaves our 8 data bits in bits 31:24 of the ISR, with 24 zeroes in the LSBs. One option here is an in null, 24 instruction to shuffle the ISR contents down to 7:0. Another is to read from the FIFO at an offset of 3 bytes, with an 8-bit read, so that the processor's bus hardware (or the DMA's) picks out the relevant byte for free:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx/uart_rx.pio Lines 87 - 93

87 static inline char uart_rx_program_getc(PIO pio, uint sm) {
88     // 8-bit read from the uppermost byte of the FIFO, as data is left-justified
89     io_rw_8 *rxfifo_shift = (io_rw_8*)&pio->rx[sm] + 3;
90     while (pio_sm_is_rx_fifo_empty(pio, sm))
91         tight_loop_contents();
92     return (char)*rxfifo_shift;
93 }

An example program shows how this UART RX program can be used to receive characters sent by one of the hardware UARTs on RP2040. A wire must be connected from GPIO4 to GPIO3 for this program to function. To make the wrangling of 3 different serial ports a little easier, this program uses core 1 to print out a string on the test UART (UART 1), and the code running on core 0 will pull out characters from the PIO state machine, and pass them along to the UART used for the debug console (UART 0). Another approach here would be interrupt-based IO, using PIO's FIFO IRQs. If the SM0_RXNEMPTY bit is set in the IRQ0_INTE register, then PIO will raise its first interrupt request line whenever there is a character in state machine 0's RX FIFO.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx/uart_rx.c

1  /**
2   * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3   *
4   * SPDX-License-Identifier: BSD-3-Clause
5   */
6
7  #include <stdio.h>
8
9  #include "pico/stdlib.h"
10 #include "pico/multicore.h"
11 #include "hardware/pio.h"
12 #include "hardware/uart.h"
13 #include "uart_rx.pio.h"
14
15 // This program
16 // - Uses UART1 (the spare UART, by default) to transmit some text
17 // - Uses a PIO state machine to receive that text
18 // - Prints out the received text to the default console (UART0)
19 // This might require some reconfiguration on boards where UART1 is the
20 // default UART.
21
22 #define SERIAL_BAUD PICO_DEFAULT_UART_BAUD_RATE
23 #define HARD_UART_INST uart1
24
25 // You'll need a wire from GPIO4 -> GPIO3
26 #define HARD_UART_TX_PIN 4
27 #define PIO_RX_PIN 3
28
29 // Check the pin is compatible with the platform
30 #error Attempting to use a pin>=32 on a platform that does not support it
31
32 // Ask core 1 to print a string, to make things easier on core 0
33 void core1_main() {
34     const char *s = (const char *) multicore_fifo_pop_blocking();
35     uart_puts(HARD_UART_INST, s);
36 }
37
38 int main() {
39     // Console output (also a UART, yes it's confusing)
40     setup_default_uart();
41     printf("Starting PIO UART RX example\n");
42
43     // Set up the hard UART we're going to use to print characters
44     uart_init(HARD_UART_INST, SERIAL_BAUD);
45     gpio_set_function(HARD_UART_TX_PIN, GPIO_FUNC_UART);
46
47     // Set up the state machine we're going to use to receive them.
48     PIO pio;
49     uint sm;
50     uint offset;
51
52     // This will find a free pio and state machine for our program and load it for us
53 // We use pio_claim_free_sm_and_add_program_for_gpio_range (for_gpio_range variant)
54 // so we will get a PIO instance suitable for addressing gpios >= 32 if needed and
    supported by the hardware
55 bool success = pio_claim_free_sm_and_add_program_for_gpio_range(&uart_rx_program, &pio,
    &sm, &offset, PIO_RX_PIN, 1, true);
56 hard_assert(success);
57
58 uart_rx_program_init(pio, sm, offset, PIO_RX_PIN, SERIAL_BAUD);
59 //uart_rx_mini_program_init(pio, sm, offset, PIO_RX_PIN, SERIAL_BAUD);
60
61 // Tell core 1 to print some text to uart1 as fast as it can
62 multicore_launch_core1(core1_main);
63 const char *text = "Hello, world from PIO! (Plus 2 UARTs and 2 cores, for complex
    reasons)\n";
64 multicore_fifo_push_blocking((uint32_t) text);
65
66 // Echo characters received from PIO to the console
67 while (true) {
68     char c = uart_rx_program_getc(pio, sm);
69     putchar(c);
70 }
71
72 // This will free resources and unload our program
73 pio_remove_program_and_unclaim_sm(&uart_rx_program, pio, sm, offset);
74 }

3.6.5. Manchester Serial TX and RX

Figure 53. Manchester serial line code. Each data bit is represented by either a high pulse followed by a low pulse (representing a '0' bit) or a low pulse followed by a high pulse (a '1' bit).

Timing diagram for Manchester serial line code. The diagram shows two signals: 'Data' and 'Line'. The 'Data' signal is a sequence of bits: Idle, 0, 0, 1, 1, 0, 1. The 'Line' signal shows the corresponding voltage levels: a high pulse followed by a low pulse for '0', and a low pulse followed by a high pulse for '1'. The 'Idle' state is represented by a high pulse followed by a low pulse.
Timing diagram for Manchester serial line code. The diagram shows two signals: 'Data' and 'Line'. The 'Data' signal is a sequence of bits: Idle, 0, 0, 1, 1, 0, 1. The 'Line' signal shows the corresponding voltage levels: a high pulse followed by a low pulse for '0', and a low pulse followed by a high pulse for '1'. The 'Idle' state is represented by a high pulse followed by a low pulse.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/manchester_encoding/manchester_encoding.pio Lines 8 - 30

8 .program manchester_tx
9 .side_set 1 opt
10
11 ; Transmit one bit every 12 cycles. a '0' is encoded as a high-low sequence
12 ; (each part lasting half a bit period, or 6 cycles) and a '1' is encoded as a
13 ; low-high sequence.
14 ;
15 ; Side-set bit 0 must be mapped to the GPIO used for TX.
16 ; Autopull must be enabled -- this program does not care about the threshold.
17 ; The program starts at the public label 'start'.
18
19 .wrap_target
20 do_1:
21     nop            side 0 [5] ; Low for 6 cycles (5 delay, +1 for nop)
22     jmp get_bit side 1 [3] ; High for 4 cycles. 'get_bit' takes another 2 cycles
23 do_0:
24     nop            side 1 [5] ; Output high for 6 cycles
25     nop            side 0 [3] ; Output low for 4 cycles
26 public start:
27 get_bit:
28     out x, 1        ; Always shift out one bit from OSR to X, so we can
29     jmp !x do_0     ; branch on it. Autopull refills the OSR when empty.
30 .wrap

Starting from the label called start , this program shifts one data bit at a time into the X register, so that it can branch on

the value. Depending on the outcome, it uses side-set to drive either a 1-0 or 0-1 sequence onto the chosen GPIO. This program uses autopull (Section 3.5.4.2) to automatically replenish the OSR from the TX FIFO once a certain amount of data has been shifted out, without interrupting program control flow or timing. This feature is enabled by a helper function in the .pio file which configures and starts the state machine:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/manchester_encoding/manchester_encoding.pio Lines 33 - 46

33 static inline void manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float
    div) {
34     pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
35     pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
36     pio_gpio_init(pio, pin);
37
38     pio_sm_config c = manchester_tx_program_get_default_config(offset);
39     sm_config_set_sideset_pins(&c, pin);
40     sm_config_set_out_shift(&c, true, true, 32);
41     sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
42     sm_config_set_clkdiv(&c, div);
43     pio_sm_init(pio, sm, offset + manchester_tx_offset_start, &c);
44
45     pio_sm_set_enabled(pio, sm, true);
46 }

Another state machine can be programmed to recover the original data from the transmitted signal:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/manchester_encoding/manchester_encoding.pio Lines 49 - 71

49 .program manchester_rx
50
51 ; Assumes line is idle low, first bit is 0
52 ; One bit is 12 cycles
53 ; a '0' is encoded as 10
54 ; a '1' is encoded as 01
55 ;
56 ; Both the IN base and the JMP pin mapping must be pointed at the GPIO used for RX.
57 ; Autopush must be enabled.
58 ; Before enabling the SM, it should be placed in a 'wait 1, pin' state, so that
59 ; it will not start sampling until the initial line idle state ends.
60
61 start_of_0:                ; We are 0.25 bits into a 0 - signal is high
62     wait 0 pin 0           ; Wait for the 1->0 transition - at this point we are 0.5 into the bit
63     in y, 1 [8]           ; Emit a 0, sleep 3/4 of a bit
64     jmp pin start_of_0 ; If signal is 1 again, it's another 0 bit, otherwise it's a 1
65
66 .wrap_target
67 start_of_1:                ; We are 0.25 bits into a 1 - signal is 1
68     wait 1 pin 0           ; Wait for the 0->1 transition - at this point we are 0.5 into the bit
69     in x, 1 [8]           ; Emit a 1, sleep 3/4 of a bit
70     jmp pin start_of_0 ; If signal is 0 again, it's another 1 bit otherwise it's a 0
71 .wrap

The main complication here is staying aligned to the input transitions, as the transmitter's and receiver's clocks may drift relative to one another. In Manchester code there is always a transition in the centre of the symbol, and based on the initial line state (high or low) we know the direction of this transition, so we can use a wait instruction to resynchronise to the line transitions on every data bit.

This program expects the X and Y registers to be initialised with the values 1 and 0 respectively, so that a constant 1 or 0 can be provided to the in instruction. The code that configures the state machine initialises these registers by executing some set instructions before setting the program running.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/manchester_encoding/manchester_encoding.pio Lines 74 - 94

74 static inline void manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float
    div) {
75     pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
76     pio_gpio_init(pio, pin);
77
78     pio_sm_config c = manchester_rx_program_get_default_config(offset);
79     sm_config_set_in_pins(&c, pin); // for WAIT
80     sm_config_set_jmp_pin(&c, pin); // for JMP
81     sm_config_set_in_shift(&c, true, true, 32);
82     sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
83     sm_config_set_clkdiv(&c, div);
84     pio_sm_init(pio, sm, offset, &c);
85
86     // X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
87     pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
88     pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
89     // Assume line is idle low, and first transmitted bit is 0. Put SM in a
90     // wait state before enabling. RX will begin once the first 0 symbol is
91     // detected.
92     pio_sm_exec(pio, sm, pio_encode_wait_pin(1, 0) | pio_encode_delay(2));
93     pio_sm_set_enabled(pio, sm, true);
94 }

The example C program in the SDK will transmit Manchester serial data from GPIO2 to GPIO3 at approximately 10Mbps (assuming a system clock of 125MHz).

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/manchester_encoding/manchester_encoding.c Lines 20 - 43

20 int main() {
21     stdio_init_all();
22
23     PIO pio = pio0;
24     uint sm_tx = 0;
25     uint sm_rx = 1;
26
27     uint offset_tx = pio_add_program(pio, &manchester_tx_program);
28     uint offset_rx = pio_add_program(pio, &manchester_rx_program);
29     printf("Transmit program loaded at %d\n", offset_tx);
30     printf("Receive program loaded at %d\n", offset_rx);
31
32     manchester_tx_program_init(pio, sm_tx, offset_tx, pin_tx, 1.f);
33     manchester_rx_program_init(pio, sm_rx, offset_rx, pin_rx, 1.f);
34
35     pio_sm_set_enabled(pio, sm_tx, false);
36     pio_sm_put_blocking(pio, sm_tx, 0);
37     pio_sm_put_blocking(pio, sm_tx, 0xff0a55a);
38     pio_sm_put_blocking(pio, sm_tx, 0x12345678);
39     pio_sm_set_enabled(pio, sm_tx, true);
40
41     for (int i = 0; i < 3; ++i)
42         printf("%08x\n", pio_sm_get_blocking(pio, sm_rx));
43 }

3.6.6. Differential Manchester (BMC) TX and RX

Figure 54. Differential Manchester serial line code, also known as biphasic mark code (BMC). The line transitions at the start of every bit period. The presence of a transition in the centre of the bit period signifies a 1 data bit, and the absence, a 0 bit. These encoding rules are the same whether the line has an initial high or low state.

Timing diagram for Differential Manchester serial line code. It shows three signals: Data, Line (starting low), and Line (starting high). The Data signal is a sequence of bits: idle, 0, 0, 1, 1, 0, 1. The Line (starting low) signal shows transitions at the start of each bit period, with a transition in the middle for '1' and no transition for '0'. The Line (starting high) signal shows transitions at the start of each bit period, with a transition in the middle for '0' and no transition for '1'.
Timing diagram for Differential Manchester serial line code. It shows three signals: Data, Line (starting low), and Line (starting high). The Data signal is a sequence of bits: idle, 0, 0, 1, 1, 0, 1. The Line (starting low) signal shows transitions at the start of each bit period, with a transition in the middle for '1' and no transition for '0'. The Line (starting high) signal shows transitions at the start of each bit period, with a transition in the middle for '0' and no transition for '1'.

The transmit program is similar to the Manchester example: it repeatedly shifts a bit from the OSR into X (relying on autopull to refill the OSR in the background), branches, and drives a GPIO up and down based on the value of this bit. The added complication is that the pattern we drive onto the pin depends not just on the value of the data bit, as with vanilla Manchester encoding, but also on the state the line was left in at the end of the last bit period. This is illustrated in Figure 54, where the pattern is inverted if the line is initially high. To cope with this, there are two copies of the test-and-drive code, one for each initial line state, and these are linked together in the correct order by a sequence of jumps.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/differential_manchester/differential_manchester.pio Lines 8 - 35

8 .program differential_manchester_tx
9 .side_set 1 opt
10
11 ; Transmit one bit every 16 cycles. In each bit period:
12 ; - A '0' is encoded as a transition at the start of the bit period
13 ; - A '1' is encoded as a transition at the start *and* in the middle
14 ;
15 ; Side-set bit 0 must be mapped to the data output pin.
16 ; Autopull must be enabled.
17
18 public start:
19 initial_high:
20     out x, 1                ; Start of bit period: always assert transition
21     jmp !x high_0          side 1 [6] ; Test the data bit we just shifted out of OSR
22 high_1:
23     nop
24     jmp initial_high      side 0 [6] ; For '1' bits, also transition in the middle
25 high_0:
26     jmp initial_low       [7] ; Otherwise, the line is stable in the middle
27
28 initial_low:
29     out x, 1                ; Always shift 1 bit from OSR to X so we can
30     jmp !x low_0           side 0 [6] ; branch on it. Autopull refills OSR for us.
31 low_1:
32     nop
33     jmp initial_low       side 1 [6] ; If there are two transitions, return to
34 low_0:
35     jmp initial_high      [7] ; the initial line state is flipped!

The .pio file also includes a helper function to initialise a state machine for differential Manchester TX, and connect it to a chosen GPIO. We arbitrarily choose a 32-bit frame size and LSB-first serialisation ( shift_to_right is true in sm_config_set_out_shift ), but as the program operates on one bit at a time, we could change this by reconfiguring the state machine.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/differential_manchester/differential_manchester.pio Lines 38 - 53

38 static inline void differential_manchester_tx_program_init(PIO pio, uint sm, uint offset,
    uint pin, float div) {
39     pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
40     pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
41     pio_gpio_init(pio, pin);
42
43     pio_sm_config c = differential_manchester_tx_program_get_default_config(offset);
44     sm_config_set_sideset_pins(&c, pin);
45     sm_config_set_out_shift(&c, true, true, 32);
46     sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
47     sm_config_set_clkdiv(&c, div);
48     pio_sm_init(pio, sm, offset + differential_manchester_tx_offset_start, &c);
49
50     // Execute a blocking pull so that we maintain the initial line state until data is
       available
51     pio_sm_exec(pio, sm, pio_encode_pull(false, true));
52     pio_sm_set_enabled(pio, sm, true);
53 }

The RX program uses the following strategy:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/differential_manchester/differential_manchester.pio Lines 55 - 85

55 .program differential_manchester_rx
56
57 ; Assumes line is idle low
58 ; One bit is 16 cycles. In each bit period:
59 ; - A '0' is encoded as a transition at time 0
60 ; - A '1' is encoded as a transition at time 0 and a transition at time T/2
61 ;
62 ; The IN mapping and the JMP pin select must both be mapped to the GPIO used for
63 ; RX data. Autopush must be enabled.
64
65 public start:
66 initial_high:           ; Find rising edge at start of bit period
67     wait 1 pin, 0 [11] ; Delay to eye of second half-period (i.e 3/4 of way
68     jmp pin high_0      ; through bit) and branch on RX pin high/low.
69 high_1:
70     in x, 1             ; Second transition detected (a '1' data symbol)
71     jmp initial_high
72 high_0:
73     in y, 1 [1]         ; Line still high, no centre transition (data is '0')
74     ; Fall-through
75
76 .wrap_target
77 initial_low:           ; Find falling edge at start of bit period
78     wait 0 pin, 0 [11] ; Delay to eye of second half-period
79     jmp pin low_1
80 low_0:
81     in y, 1             ; Line still low, no centre transition (data is '0')
82     jmp initial_high
83 low_1:
84     in x, 1 [1]         ; Second transition detected (data is '1')
85 .wrap

This code assumes that X and Y have the values 1 and 0, respectively. This is arranged for by the included C helper function:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/differential_manchester/differential_manchester.pio Lines 88 - 104

88 static inline void differential_manchester_rx_program_init(PIO pio, uint sm, uint offset,
    uint pin, float div) {
89     pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
90     pio_gpio_init(pio, pin);
91
92     pio_sm_config c = differential_manchester_rx_program_get_default_config(offset);
93     sm_config_set_in_pins(&c, pin); // for WAIT
94     sm_config_set_jump_pin(&c, pin); // for JMP
95     sm_config_set_in_shift(&c, true, true, 32);
96     sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
97     sm_config_set_clkdiv(&c, div);
98     pio_sm_init(pio, sm, offset, &c);
99
100    // X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
101    pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
102    pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
103    pio_sm_set_enabled(pio, sm, true);
104 }

All the pieces now exist to loopback some serial data over a wire between two GPIOs.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/differential_manchester/differential_manchester.c

1  /**
2   * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3   *
4   * SPDX-License-Identifier: BSD-3-Clause
5   */
6
7  #include <stdio.h>
8
9  #include "pico/stdlib.h"
10 #include "hardware/pio.h"
11 #include "differential_manchester.pio.h"
12
13 // Differential serial transmit/receive example
14 // Need to connect a wire from GPIO2 -> GPIO3
15
16 const uint pin_tx = 2;
17 const uint pin_rx = 3;
18
19 int main() {
20     stdio_init_all();
21
22     PIO pio = pio0;
23     uint sm_tx = 0;
24     uint sm_rx = 1;
25
26     uint offset_tx = pio_add_program(pio, &differential_manchester_tx_program);
27     uint offset_rx = pio_add_program(pio, &differential_manchester_rx_program);
28     printf("Transmit program loaded at %d\n", offset_tx);
29     printf("Receive program loaded at %d\n", offset_rx);
30
31     // Configure state machines, set bit rate at 5 Mbps
32     differential_manchester_tx_program_init(pio, sm_tx, offset_tx, pin_tx, 125.f / (16 * 5));
33     differential_manchester_rx_program_init(pio, sm_rx, offset_rx, pin_rx, 125.f / (16 * 5));
34
35     pio_sm_set_enabled(pio, sm_tx, false);
36     pio_sm_put_blocking(pio, sm_tx, 0);
37     pio_sm_put_blocking(pio, sm_tx, 0xff0a55a);
38     pio_sm_put_blocking(pio, sm_tx, 0x12345678);
39     pio_sm_set_enabled(pio, sm_tx, true);
40
41     for (int i = 0; i < 3; ++i)
42         printf("%08x\n", pio_sm_get_blocking(pio, sm_rx));
43 }

3.6.7. I2C

Figure 55. A 1-byte I2C read transfer. In the idle state, both lines float high. The initiator drives SDA low (a Start condition), followed by 7 address bits A6-A0, and a direction bit (Read/nWrite). The target drives SDA low to acknowledge the address (ACK). Data bytes follow. The target serialises data on SDA, clocked out by SCL. Every 9th clock, the initiator pulls SDA low to acknowledge the data, except on the last byte, where it leaves the line high (NAK). Releasing SDA whilst SCL is high is a Stop condition, returning the bus to idle.

Timing diagram for a 1-byte I2C read transfer. The diagram shows three signals: State, SCL, and SDA. The State row is divided into phases: Idle, Start, Address bits (A6, A5, A4, A3, A2, A1, A0), Read, ACK, Data bytes (D7, D6, D5, D4, D3, D2, D1, D0), NAK, Stop, and Idle. SCL is a periodic clock signal. SDA shows a low pulse for Start, followed by address bits, a low pulse for ACK, data bytes being pulled low by the initiator, and a high pulse for NAK and Stop.
Timing diagram for a 1-byte I2C read transfer. The diagram shows three signals: State, SCL, and SDA. The State row is divided into phases: Idle, Start, Address bits (A6, A5, A4, A3, A2, A1, A0), Read, ACK, Data bytes (D7, D6, D5, D4, D3, D2, D1, D0), NAK, Stop, and Idle. SCL is a periodic clock signal. SDA shows a low pulse for Start, followed by address bits, a low pulse for ACK, data bytes being pulled low by the initiator, and a high pulse for NAK and Stop.

I2C is an ubiquitous serial bus first described in the Dead Sea Scrolls, and later used by Philips Semiconductor. Two wires with pull-up resistors form an open-drain bus, and multiple agents address and signal one another over this bus by driving the bus lines low, or releasing them to be pulled high. It has a number of unusual attributes:

The PIO program listed below handles serialisation, clock stretching, and checking of ACKs in the initiator role. It provides a mechanism for escaping PIO instructions in the FIFO datastream, to issue Start/Stop/Restart sequences at appropriate times. Provided no unexpected NAKs are received, this can perform long sequences of I2C transfers from a DMA buffer, without processor intervention.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/i2c/i2c.pio Lines 8 - 73

8 .program i2c
9 .side_set 1 opt pindirs
10
11 ; TX Encoding:
12 ; | 15:10 | 9      | 8:1  | 0   |
13 ; | Instr | Final | Data | NAK |
14 ;
15 ; If Instr has a value n > 0, then this FIFO word has no
16 ; data payload, and the next n + 1 words will be executed as instructions.
17 ; Otherwise, shift out the 8 data bits, followed by the ACK bit.
18 ;
19 ; The Instr mechanism allows stop/start/restart sequences to be programmed
20 ; by the processor, and then carried out by the state machine at defined points
21 ; in the datastream.
22 ;
23 ; The "Final" field should be set for the final byte in a transfer.
24 ; This tells the state machine to ignore a NAK: if this field is not
25 ; set, then any NAK will cause the state machine to halt and interrupt.
26 ;
27 ; Autopull should be enabled, with a threshold of 16.
28 ; Autopush should be enabled, with a threshold of 8.
29 ; The TX FIFO should be accessed with halfword writes, to ensure
30 ; the data is immediately available in the OSR.
31 ;
32 ; Pin mapping:
33 ; - Input pin 0 is SDA, 1 is SCL (if clock stretching used)
34 ; - Jump pin is SDA
35 ; - Side-set pin 0 is SCL
36 ; - Set pin 0 is SDA
37 ; - OUT pin 0 is SDA
38 ; - SCL must be SDA + 1 (for wait mapping)
39 ;
40 ; The OE outputs should be inverted in the system IO controls!
41 ; (It's possible for the inversion to be done in this program,
42 ; but costs 2 instructions: 1 for inversion, and one to cope
43 ; with the side effect of the MOV on TX shift counter.)
44
45 do_nack:
46     jmp y-- entry_point      ; Continue if NAK was expected
47     irq wait 0 rel           ; Otherwise stop, ask for help
48
49 do_byte:
50     set x, 7                 ; Loop 8 times
51 bitloop:
52     out pindirs, 1           [7] ; Serialise write data (all-ones if reading)
53     nop                      side 1 [2] ; SCL rising edge
54     wait 1 pin, 1            [4] ; Allow clock to be stretched
55     in pins, 1               [7] ; Sample read data in middle of SCL pulse
56     jmp x-- bitloop side 0 [7] ; SCL falling edge
57
58     ; Handle ACK pulse
59     out pindirs, 1           [7] ; On reads, we provide the ACK.
60     nop                      side 1 [7] ; SCL rising edge
61     wait 1 pin, 1            [7] ; Allow clock to be stretched
62     jmp pin do_nack side 0 [2] ; Test SDA for ACK/NAK, fall through if ACK
63
64 public entry_point:
65 .wrap_target
66     out x, 6                 ; Unpack Instr count
67     out y, 1                 ; Unpack the NAK ignore bit
68     jmp !x do_byte           ; Instr == 0, this is a data record.
69     out null, 32              ; Instr > 0, remainder of this OSR is invalid
70 do_exec:
71     out exec, 16             ; Execute one instruction per FIFO word
72     jmp x-- do_exec          ; Repeat n + 1 times
73 .wrap

The IO mapping required by the I2C program is quite complex, due to the different ways that the two serial lines must be driven and sampled. One interesting feature is that state machine must drive the output enable high when the output is low, since the bus is open-drain, so the sense of the data is inverted. This could be handled in the PIO program (e.g. mov osr, ~osr ), but instead we can use the IO controls on RP2040 to perform this inversion in the GPIO muxes, saving an instruction.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/i2c/i2c.pio Lines 81 - 121

81 static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint
    pin_scl) {
82     assert(pin_scl == pin_sda + 1);
83     pio_sm_config c = i2c_program_get_default_config(offset);
84
85     // IO mapping
86     sm_config_set_out_pins(&c, pin_sda, 1);
87     sm_config_set_set_pins(&c, pin_sda, 1);
88     sm_config_set_in_pins(&c, pin_sda);
89     sm_config_set_sideset_pins(&c, pin_scl);
90     sm_config_set_jump_pin(&c, pin_sda);
91
92     sm_config_set_out_shift(&c, false, true, 16);
93     sm_config_set_in_shift(&c, false, true, 8);
94
95     float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
96     sm_config_set_clkdiv(&c, div);
97
98     // Try to avoid glitching the bus while connecting the IOs. Get things set
99     // up so that pin is driven down when PIO asserts OE low, and pulled up
100    // otherwise.
101     gpio_pull_up(pin_scl);
102     gpio_pull_up(pin_sda);
103     uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
104     pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
105     pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);
106     pio_gpio_init(pio, pin_sda);
107     gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT);
108     pio_gpio_init(pio, pin_scl);
109     gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);
110     pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);
111
112     // Clear IRQ flag before starting, and make sure flag doesn't actually
113     // assert a system-level interrupt (we're using it as a status flag)
114     pio_set_irq0_source_enabled(pio, (enum pio_interrupt_source) ((uint) pis_interrupt0 +
sm), false);
115     pio_set_irq1_source_enabled(pio, (enum pio_interrupt_source) ((uint) pis_interrupt0 +
sm), false);
116     pio_interrupt_clear(pio, sm);
117
118     // Configure and start SM
119     pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
120     pio_sm_set_enabled(pio, sm, true);
121 }

We can also use the PIO assembler to generate a table of instructions for passing through the FIFO, for Start/Stop/Restart conditions.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/i2c/i2c.pio Lines 126 - 136

126 .program set_scl_sda
127 .side_set 1 opt
128
129 ; Assemble a table of instructions which software can select from, and pass
130 ; into the FIFO, to issue START/STOP/RSTART. This isn't intended to be run as
131 ; a complete program.
132
133     set pindirs, 0 side 0 [7] ; SCL = 0, SDA = 0
134     set pindirs, 1 side 0 [7] ; SCL = 0, SDA = 1
135     set pindirs, 0 side 1 [7] ; SCL = 1, SDA = 0
136     set pindirs, 1 side 1 [7] ; SCL = 1, SDA = 1

The example code does blocking software IO on the state machine's FIFOs, to avoid the extra complexity of setting up the system DMA. For example, an I2C start condition is enqueued like so:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/i2c/pio_i2c.c Lines 69 - 73

69 void pio_i2c_start(PIO pio, uint sm) {
70     pio_i2c_put_or_err(pio, sm, 1u << PIO_I2C_ICOUNT_LSB); // Escape code for 2 instruction
sequence
71     pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // We are
already in idle state, just pull SDA low
72     pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // Also
pull clock low so we can present data
73 }

Because I2C can go wrong at so many points, we need to be able to check the error flag asserted by the state machine, clear the halt and restart it, before asserting a Stop condition and releasing the bus.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/i2c/pio_i2c.c Lines 15 - 17

15 bool pio_i2c_check_error(PIO pio, uint sm) {
16     return pio_interrupt_get(pio, sm);
17 }

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/i2c/pio_i2c.c Lines 19 - 23

19 void pio_i2c_resume_after_error(PIO pio, uint sm) {
20     pio_sm_drain_tx_fifo(pio, sm);
21     pio_sm_exec(pio, sm, (pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >>
        PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
22     pio_interrupt_clear(pio, sm);
23 }

We need some higher-level functions to pass correctly-formatted data through the FIFOs and insert Starts, Stops, NAKs and so on at the correct points. This is enough to present a similar interface to the other hardware I2Cs on RP2040.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/i2c/i2c_bus_scan.c Lines 13 - 42

13 int main() {
14     stdio_init_all();
15
16     PIO pio = pio0;
17     uint sm = 0;
18     uint offset = pio_add_program(pio, &i2c_program);
19     i2c_program_init(pio, sm, offset, PIN_SDA, PIN_SCL);
20
21     printf("\nPIO I2C Bus Scan\n");
22     printf("  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n");
23
24     for (int addr = 0; addr < (1 << 7); ++addr) {
25         if (addr % 16 == 0) {
26             printf("%02x ", addr);
27         }
28         // Perform a 0-byte read from the probe address. The read function
29         // returns a negative result NAK'd any time other than the last data
30         // byte. Skip over reserved addresses.
31         int result;
32         if (reserved_addr(addr))
33             result = -1;
34         else
35             result = pio_i2c_read_blocking(pio, sm, addr, NULL, 0);
36
37         printf(result < 0 ? "." : "@");
38         printf(addr % 16 == 15 ? "\n" : " ");
39     }
40     printf("Done.\n");
41     return 0;
42 }

3.6.8. PWM

Figure 56. Pulse width

modulation (PWM). The state machine outputs positive voltage pulses at regular intervals. The width of these pulses is controlled, so that the line is high for some controlled fraction of the time (the duty cycle). One use of this is to smoothly vary the brightness of an LED, by pulsing it faster than human persistence of vision.

Timing diagram for PWM showing Max count, Counter, Level, and Pulse output over time.

The diagram illustrates the PWM state machine operation. It shows four signals over time: Max count, Counter, Level, and Pulse output. The Counter (Y register) counts down from 3 to 0 in a repeating cycle. The Level (X register) is set to 2, 1, and 3 for different cycles. The Pulse output is asserted high when the Counter reaches the Level value. For example, when Level is 2, the output is high for two counter cycles (3 and 2). When Level is 1, it is high for one cycle (3). When Level is 3, it is high for three cycles (3, 2, and 1). The Max count is indicated as 3.

Timing diagram for PWM showing Max count, Counter, Level, and Pulse output over time.

This program repeatedly counts down to 0 with the Y register, whilst comparing the Y count to a pulse width held in the X register. The output is asserted low before counting begins, and asserted high when the value in Y reaches X. Once Y reaches 0, the process repeats, and the output is once more driven low. The fraction of time that the output is high is therefore proportional to the pulse width stored in X.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/pwm/pwm.pio Lines 10 - 22

10 .program pwm
11 .side_set 1 opt
12
13     pull noblock    side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
14     mov x, osr      ; Copy most-recently-pulled value back to scratch X
15     mov y, isr      ; ISR contains PWM period. Y used as counter.
16 countloop:
17     jmp x!=y noset   ; Set pin high if X == Y, keep the two paths length matched
18     jmp skip        side 1
19 noset:
20     nop              ; Single dummy cycle to keep the two paths the same length
21 skip:
22     jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO

Often, a PWM can be left at a particular pulse width for thousands of pulses, rather than supplying a new pulse width each time. This example highlights how a nonblocking PULL (Section 3.4.7) can achieve this: if the TX FIFO is empty, a nonblocking PULL will copy X to the OSR. After pulling, the program copies the OSR into X, so that it can be compared to the count value in Y. The net effect is that, if a new duty cycle value has not been supplied through the TX FIFO at the start of this period, the duty cycle from the previous period (which has been copied from X to OSR via the failed PULL , and then back to X via the MOV ) is reused , for as many periods as necessary.

Another useful technique shown here is using the ISR as a configuration register, if IN instructions are not required. System software can load an arbitrary 32-bit value into the ISR (by executing instructions directly on the state machine), and the program will copy this value into Y each time it begins counting. The ISR can be used to configure the range of PWM counting, and the state machine's clock divider controls the rate of counting.

To start modulating some pulses, we first need to map the state machine's side-set pins to the GPIO we want to output PWM on, and tell the state machine where the program is loaded in the PIO instruction memory:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/pwm/pwm.pio Lines 25 - 31

25 static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
26     pio_gpio_init(pio, pin);
27     pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
28     pio_sm_config c = pwm_program_get_default_config(offset);
29     sm_config_set_sideset_pins(&c, pin);
30     pio_sm_init(pio, sm, offset, &c);
31 }

A little footwork is required to load the ISR with the desired counting range:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/pwm/pwm.c Lines 14 - 20

14 void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
15     pio_sm_set_enabled(pio, sm, false);
16     pio_sm_put_blocking(pio, sm, period);
17     pio_sm_exec(pio, sm, pio_encode_pull(false, false));
18     pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
19     pio_sm_set_enabled(pio, sm, true);
20 }

Once this is done, the state machine can be enabled, and PWM values written directly to its TX FIFO.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/pwm/pwm.c Lines 23 - 25

23 void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) {
24     pio_sm_put_blocking(pio, sm, level);
25 }

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/pwm/pwm.c Lines 27 - 51

27 int main() {
28     stdio_init_all();
29     #ifndef PICO_DEFAULT_LED_PIN
30     #warning pio/pwm example requires a board with a regular LED
31     puts("Default LED pin was not defined");
32     #else
33
34     // todo get free sm
35     PIO pio = pio0;
36     int sm = 0;
37     uint offset = pio_add_program(pio, &pwm_program);
38     printf("Loaded program at %d\n", offset);
39
40     pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
41     pio_pwm_set_period(pio, sm, (1u << 16) - 1);
42
43     int level = 0;
44     while (true) {
45         printf("Level = %d\n", level);
46         pio_pwm_set_level(pio, sm, level * level);
47         level = (level + 1) % 256;
48         sleep_ms(10);
49     }
50 #endif
51 }

If the TX FIFO is kept topped up with fresh pulse width values, this program will consume a new pulse width for each pulse. Once the FIFO runs dry, the program will again start reusing the most recently supplied value.

3.6.9. Addition

Although not designed for computation, PIO is quite likely Turing-complete, provided a long enough piece of tape can be found. It is conjectured that it could run DOOM, given a sufficiently high clock speed.

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/addition/addition.pio Lines 7 - 25

7 .program addition
8
9 ; Pop two 32 bit integers from the TX FIFO, add them together, and push the
10 ; result to the TX FIFO. Autopush/pull should be disabled as we're using
11 ; explicit push and pull instructions.
12 ;
13 ; This program uses the two's complement identity \(x + y == \sim(\sim x - y)\)
14
15     pull
16     mov x, ~osr
17     pull
18     mov y, osr
19     jmp test      ; this loop is equivalent to the following C code:
20 incr:           ; while (y--)
21     jmp x-- test  ;     x--;
22 test:           ; This has the effect of subtracting y from x, eventually.
23     jmp y-- incr
24     mov isr, ~x
25     push

A full 32-bit addition takes only around one minute at 125MHz. The program pulls two numbers from the TX FIFO and pushes their sum to the RX FIFO, which is perfect for use either with the system DMA, or directly by the processor:

Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pio/addition/addition.c

1  /**
2   * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3   *
4   * SPDX-License-Identifier: BSD-3-Clause
5   */
6
7  #include <stdlib.h>
8  #include <stdio.h>
9
10 #include "pico/stdlib.h"
11 #include "hardware/pio.h"
12 #include "addition.pio.h"
13
14 // Pop quiz: how many additions does the processor do when calling this function
15 uint32_t do_addition(PIO pio, uint sm, uint32_t a, uint32_t b) {
16     pio_sm_put_blocking(pio, sm, a);
17     pio_sm_put_blocking(pio, sm, b);
18     return pio_sm_get_blocking(pio, sm);
19 }
20
21 int main() {
22     stdio_init_all();
23
24     PIO pio = pio0;
25     uint sm = 0;
26     uint offset = pio_add_program(pio, &addition_program);
27     addition_program_init(pio, sm, offset);
28
29     printf("Doing some random additions:\n");
30     for (int i = 0; i < 10; ++i) {
31         uint a = rand() % 100;
32         uint b = rand() % 100;
33         printf("%u + %u = %u\n", a, b, do_addition(pio, sm, a, b));
34     }
35 }

3.6.10. Further Examples

The Raspberry Pi Pico-series C/C++ SDK book has a PIO chapter which goes into depth on some software-centric topics not presented here. It includes a PIO + DMA logic analyser example that can sample every GPIO on every cycle (a

bandwidth of nearly 4Gbps at 125MHz, although this does fill up RP2040's RAM quite quickly).

There are also further examples in the pio/ directory in the Pico Examples repository.

Some of the more experimental example code, such as DPI and SD card support, is currently located in the Pico Extras and Pico Playground repositories. The PIO parts of these are functional, but the surrounding software stacks are still in an experimental state.

3.7. List of Registers

The PIO0 and PIO1 registers start at base addresses of 0x50200000 and 0x50300000 respectively (defined as PIO0_BASE and PIO1_BASE in SDK).

Table 367. List of PIO registers

OffsetNameInfo
0x000CTRLPIO control register
0x004FSTATFIFO status register
0x008FDEBUGFIFO debug register
0x00cFLEVELFIFO levels
0x010TXF0Direct write access to the TX FIFO for this state machine. Each write pushes one word to the FIFO. Attempting to write to a full FIFO has no effect on the FIFO state or contents, and sets the sticky FDEBUG_TXOVER error flag for this FIFO.
0x014TXF1Direct write access to the TX FIFO for this state machine. Each write pushes one word to the FIFO. Attempting to write to a full FIFO has no effect on the FIFO state or contents, and sets the sticky FDEBUG_TXOVER error flag for this FIFO.
0x018TXF2Direct write access to the TX FIFO for this state machine. Each write pushes one word to the FIFO. Attempting to write to a full FIFO has no effect on the FIFO state or contents, and sets the sticky FDEBUG_TXOVER error flag for this FIFO.
0x01cTXF3Direct write access to the TX FIFO for this state machine. Each write pushes one word to the FIFO. Attempting to write to a full FIFO has no effect on the FIFO state or contents, and sets the sticky FDEBUG_TXOVER error flag for this FIFO.
0x020RXF0Direct read access to the RX FIFO for this state machine. Each read pops one word from the FIFO. Attempting to read from an empty FIFO has no effect on the FIFO state, and sets the sticky FDEBUG_RXUNDER error flag for this FIFO. The data returned to the system on a read from an empty FIFO is undefined.
0x024RXF1Direct read access to the RX FIFO for this state machine. Each read pops one word from the FIFO. Attempting to read from an empty FIFO has no effect on the FIFO state, and sets the sticky FDEBUG_RXUNDER error flag for this FIFO. The data returned to the system on a read from an empty FIFO is undefined.
0x028RXF2Direct read access to the RX FIFO for this state machine. Each read pops one word from the FIFO. Attempting to read from an empty FIFO has no effect on the FIFO state, and sets the sticky FDEBUG_RXUNDER error flag for this FIFO. The data returned to the system on a read from an empty FIFO is undefined.
OffsetNameInfo
0x02cRXF3Direct read access to the RX FIFO for this state machine. Each read pops one word from the FIFO. Attempting to read from an empty FIFO has no effect on the FIFO state, and sets the sticky FDEBUG_RXUNDER error flag for this FIFO. The data returned to the system on a read from an empty FIFO is undefined.
0x030IRQState machine IRQ flags register. Write 1 to clear. There are 8 state machine IRQ flags, which can be set, cleared, and waited on by the state machines. There's no fixed association between flags and state machines — any state machine can use any flag.

Any of the 8 flags can be used for timing synchronisation between state machines, using IRQ and WAIT instructions. The lower four of these flags are also routed out to system-level interrupt requests, alongside FIFO status interrupts — see e.g. IRQ0_INTE.
0x034IRQ_FORCEWriting a 1 to each of these bits will forcibly assert the corresponding IRQ. Note this is different to the INTF register: writing here affects PIO internal state. INTF just asserts the processor-facing IRQ signal for testing ISRs, and is not visible to the state machines.
0x038INPUT_SYNC_BYPASSThere is a 2-flipflop synchronizer on each GPIO input, which protects PIO logic from metastabilities. This increases input delay, and for fast synchronous IO (e.g. SPI) these synchronizers may need to be bypassed. Each bit in this register corresponds to one GPIO.
0 → input is synchronized (default)
1 → synchronizer is bypassed
If in doubt, leave this register as all zeroes.
0x03cDBG_PADOUTRead to sample the pad output values PIO is currently driving to the GPIOs. On RP2040 there are 30 GPIOs, so the two most significant bits are hardwired to 0.
0x040DBG_PADOERead to sample the pad output enables (direction) PIO is currently driving to the GPIOs. On RP2040 there are 30 GPIOs, so the two most significant bits are hardwired to 0.
0x044DBG_CFGINFOThe PIO hardware has some free parameters that may vary between chip products.
These should be provided in the chip datasheet, but are also exposed here.
0x048INSTR_MEM0Write-only access to instruction memory location 0
0x04cINSTR_MEM1Write-only access to instruction memory location 1
0x050INSTR_MEM2Write-only access to instruction memory location 2
0x054INSTR_MEM3Write-only access to instruction memory location 3
0x058INSTR_MEM4Write-only access to instruction memory location 4
0x05cINSTR_MEM5Write-only access to instruction memory location 5
0x060INSTR_MEM6Write-only access to instruction memory location 6
0x064INSTR_MEM7Write-only access to instruction memory location 7
OffsetNameInfo
0x068INSTR_MEM8Write-only access to instruction memory location 8
0x06cINSTR_MEM9Write-only access to instruction memory location 9
0x070INSTR_MEM10Write-only access to instruction memory location 10
0x074INSTR_MEM11Write-only access to instruction memory location 11
0x078INSTR_MEM12Write-only access to instruction memory location 12
0x07cINSTR_MEM13Write-only access to instruction memory location 13
0x080INSTR_MEM14Write-only access to instruction memory location 14
0x084INSTR_MEM15Write-only access to instruction memory location 15
0x088INSTR_MEM16Write-only access to instruction memory location 16
0x08cINSTR_MEM17Write-only access to instruction memory location 17
0x090INSTR_MEM18Write-only access to instruction memory location 18
0x094INSTR_MEM19Write-only access to instruction memory location 19
0x098INSTR_MEM20Write-only access to instruction memory location 20
0x09cINSTR_MEM21Write-only access to instruction memory location 21
0x0a0INSTR_MEM22Write-only access to instruction memory location 22
0x0a4INSTR_MEM23Write-only access to instruction memory location 23
0x0a8INSTR_MEM24Write-only access to instruction memory location 24
0x0acINSTR_MEM25Write-only access to instruction memory location 25
0x0b0INSTR_MEM26Write-only access to instruction memory location 26
0x0b4INSTR_MEM27Write-only access to instruction memory location 27
0x0b8INSTR_MEM28Write-only access to instruction memory location 28
0x0bcINSTR_MEM29Write-only access to instruction memory location 29
0x0c0INSTR_MEM30Write-only access to instruction memory location 30
0x0c4INSTR_MEM31Write-only access to instruction memory location 31
0x0c8SM0_CLKDIVClock divisor register for state machine 0
Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256)
0x0ccSM0_EXECCTRLExecution/behavioural settings for state machine 0
0x0d0SM0_SHIFTCTRLControl behaviour of the input/output shift registers for state machine 0
0x0d4SM0_ADDRCurrent instruction address of state machine 0
0x0d8SM0_INSTRRead to see the instruction currently addressed by state machine 0's program counter
Write to execute an instruction immediately (including jumps) and then resume execution.
0x0dcSM0_PINCTRLState machine pin control
0x0e0SM1_CLKDIVClock divisor register for state machine 1
Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256)
0x0e4SM1_EXECCTRLExecution/behavioural settings for state machine 1
OffsetNameInfo
0x0e8SM1_SHIFTCTRLControl behaviour of the input/output shift registers for state machine 1
0x0ecSM1_ADDRCurrent instruction address of state machine 1
0x0f0SM1_INSTRRead to see the instruction currently addressed by state machine 1's program counter
Write to execute an instruction immediately (including jumps) and then resume execution.
0x0f4SM1_PINCTRLState machine pin control
0x0f8SM2_CLKDIVClock divisor register for state machine 2
Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256)
0x0fcSM2_EXECCTRLExecution/behavioural settings for state machine 2
0x100SM2_SHIFTCTRLControl behaviour of the input/output shift registers for state machine 2
0x104SM2_ADDRCurrent instruction address of state machine 2
0x108SM2_INSTRRead to see the instruction currently addressed by state machine 2's program counter
Write to execute an instruction immediately (including jumps) and then resume execution.
0x10cSM2_PINCTRLState machine pin control
0x110SM3_CLKDIVClock divisor register for state machine 3
Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256)
0x114SM3_EXECCTRLExecution/behavioural settings for state machine 3
0x118SM3_SHIFTCTRLControl behaviour of the input/output shift registers for state machine 3
0x11cSM3_ADDRCurrent instruction address of state machine 3
0x120SM3_INSTRRead to see the instruction currently addressed by state machine 3's program counter
Write to execute an instruction immediately (including jumps) and then resume execution.
0x124SM3_PINCTRLState machine pin control
0x128INTRRaw Interrupts
0x12cIRQ0_INTEInterrupt Enable for irq0
0x130IRQ0_INTFInterrupt Force for irq0
0x134IRQ0_INTSInterrupt status after masking & forcing for irq0
0x138IRQ1_INTEInterrupt Enable for irq1
0x13cIRQ1_INTFInterrupt Force for irq1
0x140IRQ1_INTSInterrupt status after masking & forcing for irq1

PIO: CTRL Register

Offset: 0x000

Description

PIO control register

Table 368. CTRL Register

BitsDescriptionTypeReset
31:12Reserved.--
11:8

CLKDIV_RESTART: Restart a state machine’s clock divider from an initial phase of 0. Clock dividers are free-running, so once started, their output (including fractional jitter) is completely determined by the integer/fractional divisor configured in SMx_CLKDIV. This means that, if multiple clock dividers with the same divisor are restarted simultaneously, by writing multiple 1 bits to this field, the execution clocks of those state machines will run in precise lockstep.

Note that setting/clearing SM_ENABLE does not stop the clock divider from running, so once multiple state machines’ clocks are synchronised, it is safe to disable/reenable a state machine, whilst keeping the clock dividers in sync.

Note also that CLKDIV_RESTART can be written to whilst the state machine is running, and this is useful to resynchronise clock dividers after the divisors (SMx_CLKDIV) have been changed on-the-fly.

SC0x0
7:4

SM_RESTART: Write 1 to instantly clear internal SM state which may be otherwise difficult to access and will affect future execution.

Specifically, the following are cleared: input and output shift counters; the contents of the input shift register; the delay counter; the waiting-on-IRQ state; any stalled instruction written to SMx_INSTR or run by OUT/MOV EXEC; any pin write left asserted due to OUT_STICKY.

The program counter, the contents of the output shift register and the X/Y scratch registers are not affected.

SC0x0
3:0

SM_ENABLE: Enable/disable each of the four state machines by writing 1/0 to each of these four bits. When disabled, a state machine will cease executing instructions, except those written directly to SMx_INSTR by the system. Multiple bits can be set/cleared at once to run/halt multiple state machines simultaneously.

RW0x0
PIO: FSTAT Register

Offset: 0x004

Description

FIFO status register

Table 369. FSTAT Register

BitsDescriptionTypeReset
31:28Reserved.--
27:24TXEMPTY: State machine TX FIFO is emptyRO0xf
23:20Reserved.--
19:16TXFULL: State machine TX FIFO is fullRO0x0
15:12Reserved.--
11:8RXEMPTY: State machine RX FIFO is emptyRO0xf
7:4Reserved.--
Offset 0x0 0x4 0x8 0xc BitsName FRCE_ON FRCE_OFF WDSEL DONE DescriptionInfo Force block out of reset (i.e. power it on) Force into reset (i.e. power it off) Set to 1 if this peripheral should be reset when the watchdog Indicates the peripheral’s registers are ready to access. TypeReset
31:28Reserved.- -
27:24TXSTALL: State machine has stalled on empty TX FIFO during a blockingWC 0x0
23:20PULL, or an OUT with autopull enabled. Write 1 to clear. Reserved.- -
19:16TXOVER: TX FIFO overflow (i.e. write-on-full by the system) has occurred. Write 1 to clear. Note that write-on-full does not alter the state or contents ofWC 0x0
15:12on the floor. Reserved.- -
11:8RXUNDER: RX FIFO underflow (i.e. read-on-empty by the system) has occurred. Write 1 to clear. Note that read-on-empty does not perturb the stateWC 0x0
7:4software error. Reserved.- -
3:0RXSTALL or an IN with autopush enabled. This flag is also set when a nonblocking: State machine has stalled on full RX FIFO during a blocking PUSH,WC 0x0
Table 371. FLEVEL BitsFIFO levels DescriptionTypeReset
Register 31:28RX3RO0x0
27:24TX3RO0x0
23:20RX2RO0x0
19:16TX2RO0x0
15:12RX1RO0x0
11:8TX1RO0x0
7:4RX0RO0x0
3:0TX0RO0x0

PIO: FDEBUG Register

Offset: 0x008

Description

FIFO debug register

Table 370. FDEBUG Register

PIO: FLEVEL Register

Offset: 0x00c

Description

FIFO levels

Table 371. FLEVEL Register

PIO: TXF0, TXF1, TXF2, TXF3 Registers

Offsets: 0x010, 0x014, 0x018, 0x01c

Table 372. TXF0, TXF1, TXF2, TXF3 Registers

BitsDescriptionTypeReset
31:0Direct write access to the TX FIFO for this state machine. Each write pushes one word to the FIFO. Attempting to write to a full FIFO has no effect on the FIFO state or contents, and sets the sticky FDEBUG_TXOVER error flag for this FIFO.WF0x00000000

PIO: RXF0, RXF1, RXF2, RXF3 Registers

Offsets: 0x020, 0x024, 0x028, 0x02c

Table 373. RXF0, RXF1, RXF2, RXF3 Registers

BitsDescriptionTypeReset
31:0Direct read access to the RX FIFO for this state machine. Each read pops one word from the FIFO. Attempting to read from an empty FIFO has no effect on the FIFO state, and sets the sticky FDEBUG_RXUNDER error flag for this FIFO. The data returned to the system on a read from an empty FIFO is undefined.RF-

PIO: IRQ Register

Offset: 0x030

Table 374. IRQ Register

BitsDescriptionTypeReset
31:8Reserved.--
7:0State machine IRQ flags register. Write 1 to clear. There are 8 state machine IRQ flags, which can be set, cleared, and waited on by the state machines. There's no fixed association between flags and state machines — any state machine can use any flag.

Any of the 8 flags can be used for timing synchronisation between state machines, using IRQ and WAIT instructions. The lower four of these flags are also routed out to system-level interrupt requests, alongside FIFO status interrupts — see e.g. IRQ0_INTE.
WC0x00

PIO: IRQ_FORCE Register

Offset: 0x034

Table 375. IRQ_FORCE Register

BitsDescriptionTypeReset
31:8Reserved.--
7:0Writing a 1 to each of these bits will forcibly assert the corresponding IRQ. Note this is different to the INTF register: writing here affects PIO internal state. INTF just asserts the processor-facing IRQ signal for testing ISRs, and is not visible to the state machines.WF0x00

PIO: INPUT_SYNC_BYPASS Register

Offset: 0x038

Table 376. INPUT_SYNC_BYPASS Register

BitsDescriptionTypeReset
31:0There is a 2-flipflop synchronizer on each GPIO input, which protects PIO logic from metastabilities. This increases input delay, and for fast synchronous IO (e.g. SPI) these synchronizers may need to be bypassed. Each bit in this register corresponds to one GPIO.
0 → input is synchronized (default)
1 → synchronizer is bypassed
If in doubt, leave this register as all zeroes.
RW0x00000000

PIO: DBG_PADOUT Register

Offset: 0x03c

Table 377.
DBG_PADOUT Register

BitsDescriptionTypeReset
31:0Read to sample the pad output values PIO is currently driving to the GPIOs. On RP2040 there are 30 GPIOs, so the two most significant bits are hardwired to 0.RO0x00000000

PIO: DBG_PADOE Register

Offset: 0x040

Table 378.
DBG_PADOE Register

BitsDescriptionTypeReset
31:0Read to sample the pad output enables (direction) PIO is currently driving to the GPIOs. On RP2040 there are 30 GPIOs, so the two most significant bits are hardwired to 0.RO0x00000000

PIO: DBG_CFGINFO Register

Offset: 0x044

Description

The PIO hardware has some free parameters that may vary between chip products.

These should be provided in the chip datasheet, but are also exposed here.

Table 379.
DBG_CFGINFO
Register

BitsDescriptionTypeReset
31:22Reserved.--
21:16IMEM_SIZE : The size of the instruction memory, measured in units of one instructionRO-
15:12Reserved.--
11:8SM_COUNT : The number of state machines this PIO instance is equipped with.RO-
7:6Reserved.--
5:0FIFO_DEPTH : The depth of the state machine TX/RX FIFOs, measured in words.
Joining fifos via SHIFTCTRL_FJOIN gives one FIFO with double this depth.
RO-

PIO: INSTR_MEM0, INSTR_MEM1, ..., INSTR_MEM30, INSTR_MEM31 Registers

Offsets: 0x048, 0x04c, ..., 0x0c0, 0x0c4

Table 380.
INSTR_MEM0,
INSTR_MEM1, ...,
INSTR_MEM30,
INSTR_MEM31
Registers

Bits 14:10 9:5 4:0 BitsDescription SHIFT DescriptionMASK_MSB : The most-significant bit allowed to pass by the mask (inclusive) MASK_LSB : The least-significant bit allowed to pass by the mask (inclusive) : Logical right-shift applied to accumulator before maskingType RW RW RW TypeReset 0x00 0x00 0x00 Reset
INSTR_MEM1, …, 31:16Reserved.--
INSTR_MEM30, INSTR_MEM31 15:0 RegistersWrite-only access to instruction memory locationN WO0x0000
Table 381. BitsDescriptionTypeReset
SM0_CLKDIV, SM1_CLKDIV, 31:16INT : Effective frequency is sysclk/(int + frac/256).RW0x0001
SM2_CLKDIV, SM3_CLKDIVValue of 0 is interpreted as 65536. If INT is 0, FRAC must also be 0.
Registers SM3_CLKDIV 15:8FRAC : Fractional part of clock divisorRW0x00
7:0Reserved.--
Table 382. BitsDescriptionTypeReset
SM0_EXECCTRL, SM1_EXECCTRL, 31EXEC_STALLED: If 1, an instruction written to SMx_INSTR is stalled, andRO0x0
SM2_EXECCTRL, SM3_EXECCTRLlatched by the state machine. Will clear to 0 once this instruction completes.
Registers SM3_EXECCTRL 30SIDE_EN: If 1, the MSB of the Delay/Side-set instruction field is used as side-RW0x0
29SIDE_PINDIRPINCTRL_SIDESET_COUNT is inclusive of this enable bit. : If 1, side-set data is asserted to pin directions, instead of pinRW0x0
28:24values JMP_PIN: The GPIO number to use as condition for JMP PIN. Unaffected byRW0x00
23:19OUT_EN_SELinput mapping. : Which data bit to use for inline OUT enableRW0x00
18INLINE_OUT_EN: If 1, use a bit of OUT data as an auxiliary write enable When used in conjunction with OUT_STICKY, writes with an enable of 0 willRW0x0
17OUT_STICKYdue to the priority ordering of state machine pin writes (SM0 < SM1 < …) : Continuously assert the most recent OUT/SET to the pinsRW0x0
16:12WRAP_TOP: After reaching this address, execution is wrapped to wrap_bottom.RW0x1f

PIO: SM0_CLKDIV, SM1_CLKDIV, SM2_CLKDIV, SM3_CLKDIV Registers

Offsets: 0x0c8, 0x0e0, 0x0f8, 0x110

Description

Clock divisor register for state machine N

Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256)

Table 381.
SM0_CLKDIV,
SM1_CLKDIV,
SM2_CLKDIV,
SM3_CLKDIV
Registers

PIO: SM0_EXECCTRL, SM1_EXECCTRL, SM2_EXECCTRL, SM3_EXECCTRL Registers

Offsets: 0x0cc, 0x0e4, 0x0fc, 0x114

Description

Execution/behavioural settings for state machine N

Table 382.
SM0_EXECCTRL,
SM1_EXECCTRL,
SM2_EXECCTRL,
SM3_EXECCTRL
Registers

BitsDescriptionTypeReset
11:7WRAP_BOTTOM : After reaching wrap_top, execution is wrapped to this address.RW0x00
6:5Reserved.--
4STATUS_SEL : Comparison used for the MOV x, STATUS instruction.RW0x0
Enumerated values:
0x0 → TXLEVEL: All-ones if TX FIFO level < N, otherwise all-zeroes
0x1 → RXLEVEL: All-ones if RX FIFO level < N, otherwise all-zeroes
3:0STATUS_N : Comparison level for the MOV x, STATUS instructionRW0x0

PIO: SM0_SHIFTCTRL, SM1_SHIFTCTRL, SM2_SHIFTCTRL, SM3_SHIFTCTRL Registers

Offsets: 0x0d0, 0x0e8, 0x100, 0x118

Description

Control behaviour of the input/output shift registers for state machine N

Table 383.
SM0_SHIFTCTRL,
SM1_SHIFTCTRL,
SM2_SHIFTCTRL,
SM3_SHIFTCTRL
Registers

BitsDescriptionTypeReset
31FJOIN_RX : When 1, RX FIFO steals the TX FIFO's storage, and becomes twice as deep.
TX FIFO is disabled as a result (always reads as both full and empty).
FIFOs are flushed when this bit is changed.
RW0x0
30FJOIN_TX : When 1, TX FIFO steals the RX FIFO's storage, and becomes twice as deep.
RX FIFO is disabled as a result (always reads as both full and empty).
FIFOs are flushed when this bit is changed.
RW0x0
29:25PULL_THRESH : Number of bits shifted out of OSR before autopull, or conditional pull (PULL IFEMPTY), will take place.
Write 0 for value of 32.
RW0x00
24:20PUSH_THRESH : Number of bits shifted into ISR before autopush, or conditional push (PUSH IFFULL), will take place.
Write 0 for value of 32.
RW0x00
19OUT_SHIFTDIR : 1 = shift out of output shift register to right. 0 = to left.RW0x1
18IN_SHIFTDIR : 1 = shift input shift register to right (data enters from left). 0 = to left.RW0x1
17AUTOPULL : Pull automatically when the output shift register is emptied, i.e. on or following an OUT instruction which causes the output shift counter to reach or exceed PULL_THRESH.RW0x0
16AUTOPUSH : Push automatically when the input shift register is filled, i.e. on an IN instruction which causes the input shift counter to reach or exceed PUSH_THRESH.RW0x0
15:0Reserved.--

PIO: SM0_ADDR, SM1_ADDR, SM2_ADDR, SM3_ADDR Registers

Offsets: 0x0d4, 0x0ec, 0x104, 0x11c

Table 384. SM0_ADDR,
SM1_ADDR,
SM2_ADDR,
SM3_ADDR Registers

Bits 14:10 9:5 4:0 BitsDescription SHIFT DescriptionMASK_MSB : The most-significant bit allowed to pass by the mask (inclusive) MASK_LSB : The least-significant bit allowed to pass by the mask (inclusive) : Logical right-shift applied to accumulator before maskingType RW RW RW TypeReset 0x00 0x00 0x00 Reset
SM2_ADDR, 31:5 SM3_ADDR RegistersReserved.--
4:0 PIO:Current instruction address of state machineNRO0x00
Table 385. Offsets Bits SM0_INSTR,DescriptionTypeReset
SM1_INSTR, 31:16 SM2_INSTR,Reserved.--
SM3_INSTR Registers 15:0Read to see the instruction currently addressed by state machine counter.N 's program RW-
Table 386. Bits SM0_PINCTRL,DescriptionTypeReset
SM1_PINCTRL, 31:29 SM2_PINCTRL,SIDESET_COUNT: The number of MSBs of the Delay/Side-set instruction fieldRW0x0
SM3_PINCTRL SM3_PINCTRLwhich are used for side-set. Inclusive of the enable bit, if present. Minimum of
Registers Registers0 (all delay bits, no side-set) and maximum of 5 (all side-set, no delay).
28:26SET_COUNT inclusive.: The number of pins asserted by a SET. In the range 0 to 5RW0x5
25:20OUT_COUNT: The number of pins asserted by an OUT PINS, OUT PINDIRS or MOV PINS instruction. In the range 0 to 32 inclusive.RW0x00
19:15IN_BASE: The pin which is mapped to the least-significant bit of a state more-significant data bits, with a modulo of 32 applied to pin number.RW0x00
14:10SIDESET_BASE: The lowest-numbered pin that will be affected by a side-set operation. The MSBs of an instruction’s side-set/delay field (up to 5,RW0x00
9:5SET_BASE: The lowest-numbered pin that will be affected by a SET PINS or of the SET data.RW0x00
4:0OUT_BASE: The lowest-numbered pin that will be affected by an OUT PINS, be the least-significant bit of the OUT or MOV data.RW0x00

PIO: SM0_INSTR, SM1_INSTR, SM2_INSTR, SM3_INSTR Registers

Offsets: 0x0d8, 0x0f0, 0x108, 0x120

Table 385.
SM0_INSTR,
SM1_INSTR,
SM2_INSTR,
SM3_INSTR Registers

PIO: SM0_PINCTRL, SM1_PINCTRL, SM2_PINCTRL, SM3_PINCTRL Registers

Offsets: 0x0dc, 0x0f4, 0x10c, 0x124

Description

State machine pin control

Table 386.
SM0_PINCTRL,
SM1_PINCTRL,
SM2_PINCTRL,
SM3_PINCTRL
Registers

PIO: INTR Register

Offset: 0x128

Description

Raw Interrupts

Table 387. INTR Register

BitsDescriptionTypeReset
31:12Reserved.--
11SM3RO0x0
10SM2RO0x0
9SM1RO0x0
8SM0RO0x0
7SM3_TXNFULLRO0x0
6SM2_TXNFULLRO0x0
5SM1_TXNFULLRO0x0
4SM0_TXNFULLRO0x0
3SM3_RXNEMPTYRO0x0
2SM2_RXNEMPTYRO0x0
1SM1_RXNEMPTYRO0x0
0SM0_RXNEMPTYRO0x0

PIO: IRQ0_INTE Register

Offset: 0x12c

Description

Interrupt Enable for irq0

Table 388. IRQ0_INTE Register

BitsDescriptionTypeReset
31:12Reserved.--
11SM3RW0x0
10SM2RW0x0
9SM1RW0x0
8SM0RW0x0
7SM3_TXNFULLRW0x0
6SM2_TXNFULLRW0x0
5SM1_TXNFULLRW0x0
4SM0_TXNFULLRW0x0
3SM3_RXNEMPTYRW0x0
2SM2_RXNEMPTYRW0x0
1SM1_RXNEMPTYRW0x0
0SM0_RXNEMPTYRW0x0

PIO: IRQ0_INTF Register

Offset: 0x130

Description

Interrupt Force for irq0

Table 389. IRQ0_INTF Register

BitsDescriptionTypeReset
31:12Reserved.--
11SM3RW0x0
10SM2RW0x0
9SM1RW0x0
8SM0RW0x0
7SM3_TXNFULLRW0x0
6SM2_TXNFULLRW0x0
5SM1_TXNFULLRW0x0
4SM0_TXNFULLRW0x0
3SM3_RXNEMPTYRW0x0
2SM2_RXNEMPTYRW0x0
1SM1_RXNEMPTYRW0x0
0SM0_RXNEMPTYRW0x0

PIO: IRQ0_INTS Register

Offset: 0x134

Description

Interrupt status after masking & forcing for irq0

Table 390. IRQ0_INTS Register

BitsDescriptionTypeReset
31:12Reserved.--
11SM3RO0x0
10SM2RO0x0
9SM1RO0x0
8SM0RO0x0
7SM3_TXNFULLRO0x0
6SM2_TXNFULLRO0x0
5SM1_TXNFULLRO0x0
4SM0_TXNFULLRO0x0
3SM3_RXNEMPTYRO0x0
2SM2_RXNEMPTYRO0x0
1SM1_RXNEMPTYRO0x0
0SM0_RXNEMPTYRO0x0

PIO: IRQ1_INTE Register

Offset: 0x138

Description

Interrupt Enable for irq1

Table 391. IRQ1_INTE Register

BitsDescriptionTypeReset
31:12Reserved.--
11SM3RW0x0
10SM2RW0x0
9SM1RW0x0
8SM0RW0x0
7SM3_TXNFULLRW0x0
6SM2_TXNFULLRW0x0
5SM1_TXNFULLRW0x0
4SM0_TXNFULLRW0x0
3SM3_RXNEMPTYRW0x0
2SM2_RXNEMPTYRW0x0
1SM1_RXNEMPTYRW0x0
0SM0_RXNEMPTYRW0x0

PIO: IRQ1_INTF Register

Offset: 0x13c

Description

Interrupt Force for irq1

Table 392. IRQ1_INTF Register

BitsDescriptionTypeReset
31:12Reserved.--
11SM3RW0x0
10SM2RW0x0
9SM1RW0x0
8SM0RW0x0
7SM3_TXNFULLRW0x0
6SM2_TXNFULLRW0x0
5SM1_TXNFULLRW0x0
4SM0_TXNFULLRW0x0
3SM3_RXNEMPTYRW0x0
2SM2_RXNEMPTYRW0x0
1SM1_RXNEMPTYRW0x0
0SM0_RXNEMPTYRW0x0

PIO: IRQ1_INTS Register

Offset: 0x140

Description

Interrupt status after masking & forcing for irq1

Table 393. IRQ1_INTS
Register
BitsDescriptionTypeReset
31:12Reserved.--
11SM3RO0x0
10SM2RO0x0
9SM1RO0x0
8SM0RO0x0
7SM3_TXNFULLRO0x0
6SM2_TXNFULLRO0x0
5SM1_TXNFULLRO0x0
4SM0_TXNFULLRO0x0
3SM3_RXNEMPTYRO0x0
2SM2_RXNEMPTYRO0x0
1SM1_RXNEMPTYRO0x0
0SM0_RXNEMPTYRO0x0