Blink an LED using rp235x-pac crate
Motivation
I have been experimenting with Hubris
OS to port to riscv
ISA. I currently have access to Pico 2 w
Board for development and testing. Hubris is a real time operating system designed for embedded devices and it has a very unique trait wherein that hardware that it runs on should have Physical memory protection unit, not necessarily Memory management unit and each and every task the OS spawns must be allocated its own memory region and memory protected. This trait helps in isolating tasks and preventing them from interfering with each other’s memory. So theoretically speaking, we could compile different tasks seperately and link them together to form a complete system image. This direction has already been prototyped by exhubris
. I have successfully implemented idle
and supervisor
tasks which are absolutely necessary for an Hubris built application. But for me to actually get a visual feedback that this final image works, I need to implement a blinky
task that will toggle the LED on and off. For more information checkout my ongoing prototype in forked repo’s Hubris and exhubris.
There’s actually a blocker as to use HAL
(Hardware Abstraction Layer) in this context. Since Hubris requires strict memory protection and isolation between tasks, using a traditional HAL could lead to potential violations of these principles. For this reason, Hubris pre-built application demos actually use PAC
(Peripheral Access Crate) to interact with hardware in a way that is compatible with its memory protection model. You can checkout this issue for additional context.
I have researched online about using rp235x-pac
crate to control the GPIO pins on the Pico 2 W board and toggle the LED. But i didn’t find any examples or documentation specifically addressing this use case. And more importantly i could not use inbuilt LED to blink as is connected to wifi chip present in pico 2 w board, this is not the case in pico 2 board. Hence, I’m toggling an external LED connected to one of the GPIO pins.
Problem Statement
To blink an LED connected to one of the GPIO pins on Pico 2 (w) board. We are not allowed to use HAL crate, so we need to use PAC directly to manipulate the GPIO registers.
Implementation Details
I tried to use RP2350 Datasheet and rp235x-pac to implement on my own and found out that it’s pretty hard. Hence i went down the path of reverse engineering the HAL crate and how it interacts with the PAC to control the GPIO pins. This implementation is basically my reverse-engineered version of the HAL crate, tailored to work within the constraints of Hubris OS. From this point onward you could forget everything about Hubris OS and only concentrate on how to use rp235x-pac
crate to control the GPIO pins. Because the underlying principles of using PAC for direct hardware access remain the same regardless of the OS constraints.
Image Metadata
As per RP2350 Datasheet Section:5.9.5. Minimum Viable Image Metadata
A minimum amount of metadata (i.e. a valid `IMAGE_DEF` block) must be embedded in
any binary for the bootrom to recognise it as a valid program image, as opposed to,
for example, blank flash contents or a disconnected flash device. This must appear
within the first 4 kB of a flash image, or anywhere in a RAM or OTP image.
As per the datasheet, we know we have to create the following Image Metadata for riscv cores –>
/// A Block as understood by the Boot ROM.
///
/// This is an Image Definition Block
/// It contains within the special start and end markers
/// the Boot ROM is looking for.
#[derive(Debug)]
#[repr(C)]
pub struct ImageDefBlock {
marker_start: u32,
item: u32,
length: u32,
offset: u32,
marker_end: u32,
}
/// Tell the Boot ROM about our application
/// Refer RP2350 Datasheet, Section: 5.9.5.2. Minimum RISC-V IMAGE_DEF
#[link_section = ".start_block"]
#[used]
pub static IMAGE_DEF: ImageDefBlock = ImageDefBlock {
marker_start: 0xffffded3,
item: 0x11010142,
length: 0x000001ff,
offset: 0x00000000,
marker_end: 0xab123579,
};
link_section = “.start_block” is used to place the
IMAGE_DEF
block in a specific section of the binary, ensuring it is located within the first 4 kB of a flash image.
GPIO Setup
To setup GPIO on rp235x, we need access to 3 peripherals: SIO, IO_BANK0, and PADS_BANK0.
- SIO: Single-cycle I/O for fast GPIO operations
- IO_BANK0: GPIO function selection and control
- PADS_BANK0: Electrical characteristics and pad isolation
Get an instance of this peripherals by using the rp235x-pac
crate.
use rp235x_pac::{IO_BANK0, PADS_BANK0, SIO};
let sio: SIO = unsafe { SIO::steal() };
let io_bank0 = unsafe { IO_BANK0::steal() };
let pads_bank0 = unsafe { PADS_BANK0::steal() };
Now for example we could consider any GPIO pin to connect external LED for testing, i used GPIO22. we need to setup this pin as OUTPUT.
const LED_PIN: usize = 22;
let mask = 1u32 << LED_PIN as u32;
// Set GPIO as output
sio.gpio_oe_set().write(|w| unsafe { w.bits(mask) });
And then we need to configure the PADS such that input enable on, output disable is off and remove pad isolation to attach a function to this GPIO.
// Configure pad settings
pads_bank0.gpio(LED_PIN).modify(|_, w| {
// Set input enable on, output disable off
// RP2350: input enable defaults to off, so this is important!
w.ie().set_bit();
w.od().clear_bit();
// RP2350: remove pad isolation now a function is wired up
w.iso().clear_bit();
w
});
And then we set the GPIO function to SIO (Single-cycle IO subsystem) using which we write to the GPIO.
use rp235x_pac::io_bank0::gpio::gpio_ctrl::FUNCSEL_A;
// Zero all fields apart from fsel; we want this IO to do what the peripheral tells it.
// This doesn't affect e.g. pullup/pulldown, as these are in pad controls.
unsafe {
io_bank0
.gpio(LED_PIN)
.gpio_ctrl()
.write_with_zero(|w| w.funcsel().variant(FUNCSEL_A::SIO))
};
Finally we could use the SIO to toggle the LED state.
// To Turn on LED
sio.gpio_out_set().write(|w| unsafe { w.bits(mask) });
// To Turn off LED
sio.gpio_out_clr().write(|w| unsafe { w.bits(mask) });
This is the minimum code that is required to control GPIO by using the PAC instead of depending on the HAL.
Running on Pico 2(w) board
To run the code, you could checkout my rp-hal
crate and run the blinky
example as follows –>
git clone https://github.com/Karthik-d-k/rp-hal.git -b riscv
cd rp-hal/rp235x-hal-examples
cargo rrr-blinky
cargo rrr-blinky will compile and flash the code successfully and run the code using
picotool
. Be sure to checkout the documentation on how to set-up the required tools to flash and run.
Conclusion
Full script is available on github rp-hal-blinky
If you are looking for hubris task which is almost similar to the rp-hal example, you could checkout the same in exhubris-blinky
If you are interested in exploring the exact differences as to which PAC code compares to HAL code, you could look into the github-commit