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.
/*
* 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
}
}
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
}
%}
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"
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 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);
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);
}
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.
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)