Hello PIO

This program was an introduction to the Programmable Input Output (PIO) on the Raspberry Pi Pico. There are 2 identical PIO blocks in RP2040. Each PIO block has dedicated connections to the bus fabric, GPIO and interrupt controller. These are 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. The resources for the project include the C SDK User Guide, the RP2040 Datasheet and Prof. Hunter's website.


The complete code

HelloPIO.c

/*
 * Parth Sarthi Sharma (pss242@cornell.edu)
 * Code based on examples from Raspberry Pi Foundation.
 * The code initializes a PIO state machine on a given
 * GPIO and turns it ON and OFF repeatedly.
 */
#include "pico/stdlib.h" //Standard library for Pico
#include "hardware/pio.h" //The hardware PIO library
#include "hello.pio.h" //The hello pio header created after compilation

#define LED 25 //Define the default LED Pin

int main(){ //The main function
    PIO pio = pio0; //Identifier for the first (PIO 0) hardware PIO instance
    uint offset = pio_add_program(pio, &hello_program); //Attempt to load the program
    uint sm = pio_claim_unused_sm(pio, true); //Claim a free state machine on a PIO instance
    hello_program_init(pio, sm, offset, LED); //Initialize the hello program

    while(1){ //While eternity
        pio_sm_put_blocking(pio, sm, 1); //Write high to the state machine's TX FIFO, blocking if the FIFO is full
        sleep_ms(500); //Sleep for 500 milliseconds
        pio_sm_put_blocking(pio, sm, 0); //Write low to the state machine's TX FIFO, blocking if the FIFO is full
        sleep_ms(500); //Sleep for 500 milliseconds
    }
}


hello.pio

assembly
.program hello ;Program name

;Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is empty. Write the least significant bit to the OUT pin group.

loop: ;The main infinity loop
    pull ;Removes a 32-bit word from the TX FIFO and places into the OSR
    out pins, 1 ;Shift data from the OSR to other destinations, 1…32 bits at a time
    jmp loop ;Jump to the loop label

;Helper function

%c-sdk{
    static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin){ //Program to initialize the PIO
        pio_sm_config c = hello_program_get_default_config(offset); //Get default configurations for the PIO state machine
        sm_config_set_out_pins(&c, pin, 1); //Set the state machine configurations on the given pin
        pio_gpio_init(pio, pin); //Setup the function select for a GPIO to use output from the given PIO instance
        pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); //Use a state machine to set the same pin direction for multiple consecutive pins for the PIO instance
        pio_sm_init(pio, sm, offset, &c); //Resets the state machine to a consistent state, and configures it
        pio_sm_set_enabled(pio, sm, true); //Enable or disable a PIO state machine
    }
%}


Stepping through the code

Includes

The first lines of code in the C source file include some header files. The headers which come from the C SDK for the Raspberry Pi Pico include pico/stdlib.h is what the SDK calls a "High-Level API." These high-level API's "provide higher level functionality that isn’t hardware related or provides a richer set of functionality above the basic hardware interfaces." The architecture of this SDK is described at length in the SDK manual. All libraries within the SDK are INTERFACE libraries.

The next includes pull in hardware APIs which are not already brought in by pico/stdlib.h. These include hardware/pio.h and hello.pio.h. As the names suggest, these interface libraries give us access to the API's associated with the hardware and the custom hello pio file created in the directory on the RP2040.

Don't forget to link these in the CMakeLists.txt file!

#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hello.pio.h"


Global declarations and defines

The next section of the code is basically a single line which #define's the LED pin number (GPIO 25 is linked to the on-board LED).

#define LED 25


The main function

Initializing the PIO

The first line in the main() initializes the PIO instance. The pio_add_program() function loads the program definition and returns the instruction memory offset the program is loaded at. The pio_claim_unused_sm() function is used to claim a free state machine on a PIO instance. Lastly, the hello_program_init() is called with the given parameters in order to finish configuring the PIO with the given pins and program.

PIO pio = pio0;
uint offset = pio_add_program(pio, &hello_program);
uint sm = pio_claim_unused_sm(pio, true);
hello_program_init(pio, sm, offset, LED);


The infinite while loop

This is the loop which which runs forever and executes the code sequentially. It basically contains 2 subsections: turning the LED on and turning the LED off. In order to do so, it sends in 1 and 0 to the PIO state machine and sleeps for 500 milliseconds.

while(1){
    pio_sm_put_blocking(pio, sm, 1);
    sleep_ms(500);
    pio_sm_put_blocking(pio, sm, 0);
    sleep_ms(500);
}


Expected vs actual output

In order to view the output, I used the GPIO pin 6 and attached an oscilloscope to it. Since, the delay between consecutive state change is 500 ms, the expected frequency of the generated square wave is 1 Hz.

The scope trace below shows the output from the GPIO pin 6. The text in the top left corner of the screen confirms that the frequency of the generated wave is infact 1 Hz. We've obtained the expected result.

Output of the Hello PIO code


CMakeLists.txt

cmake_minimum_required(VERSION 3.13)

include(pico_sdk_import.cmake)

project(HelloPIO)

pico_sdk_init()

add_executable(HelloPIO HelloPIO.c)

pico_generate_pio_header(HelloPIO ${CMAKE_CURRENT_LIST_DIR}/hello.pio)

pico_enable_stdio_usb(HelloPIO 1)
pico_enable_stdio_uart(HelloPIO 0)

pico_add_extra_outputs(HelloPIO)

target_link_libraries(HelloPIO pico_stdlib hardware_pio)