Cleaning Up Parallel I/O

Last time, we added Rust's libcore to our program, enabling us to write idiomatic Rust code that runs on a microcontroller. This provides us with a good base for extending our program in the future. However, before we go ahead and make it do something else than blinking an LED, I'd like to do some cleaning up first.

When I started to make the LED blink in the first place, I didn't really explain how I was doing it. I just wrote some cryptic code with the promise to explain it later. Well, later has finally arrived.

As I explained before, I'm using an Arduino Due board for development. The Due uses Atmel's SAM3X8E microcontroller. Atmel provides a very thorough data sheet, which I'm going to refer to throughout the rest of this article.

For blinking the LED, we're using a feature called Parallel I/O. Parallel I/O allows us to set an electric signal on the various I/O pins. One of those pins is wired to the built-in LED on the Arduino Due. To blink the LED, we simply have to repeatedly set and clear the signal on that pin.

Let's take a look at some of the constants we define in our program:

const PB_PIO_ENABLE       : *mut u32 = 0x400E1000 as *mut u32;
const PB_OUTPUT_ENABLE    : *mut u32 = 0x400E1010 as *mut u32;
const PB_SET_OUTPUT_DATA  : *mut u32 = 0x400E1030 as *mut u32;
const PB_CLEAR_OUTPUT_DATA: *mut u32 = 0x400E1034 as *mut u32;

ARM microcontrollers, which the SAM3X8E is an example of, use a technique called memory-mapped I/O for controlling input/output. This means, as opposed to having to use special CPU instruction, a program writes to or reads from specific memory addresses to perform I/O functions.

Those addresses aren't actually mapped to RAM, so writing to them won't change a value in main memory. Instead, the hardware recognizes that the program wrote to a special address and performs whatever function is associated with that address.

In the piece of code above, we define 4 of those addresses (called registers)[1]:

To do anything with those registers, we need to know which values to write into them. Here's another constant from our program:

const PB27_MASK: u32 = 0x08000000;

Each of the registers is 32 bits long, which means they can be used to control up to 32 different pins. The pin that is wired to the built-in LED is pin 27[5][6]. If you were to write the above constant in binary, it would be all zeros, except for the one bit that identifies pin 27.

To do something with the pin, we just write the above constant into one of the registers[7]. Here's the code that does that:


loop {

We enable pin 27 and set it to output mode. Then we set and clear the pin in a loop (with pauses in between), thereby causing the LED to blink.

This code does what it's supposed to in a straight-forward way. There are some problems with it, however:

Luckily, there's a better approach. Since the registers of a controller are located next to each other, we can use a struct to access them:

pub struct Pio {
	pub pio_enable : u32,
	pub pio_disable: u32,
	pub pio_status : u32,

	pub _reserved_1: u32,

	pub output_enable : u32,
	pub output_disable: u32,
	pub output_status : u32,

	pub _reserved_2: u32,

	pub glitch_input_filter_enable : u32,
	pub glitch_input_filter_disable: u32,
	pub glitch_input_filter_status : u32,

	pub _reserved_3: u32,

	pub set_output_data   : u32,
	pub clear_output_data : u32,
	pub output_data_status: u32,
	pub pin_data_status   : u32,

	pub interrupt_enable : u32,
	pub interrupt_disable: u32,
	pub interrupt_mask   : u32,
	pub interrupt_status : u32,

	pub multi_driver_enable : u32,
	pub multi_driver_disable: u32,
	pub multi_driver_status : u32,

	pub _reserved_4: u32,

	pub pull_up_disable   : u32,
	pub pull_up_enable    : u32,
	pub pad_pull_up_status: u32,

	pub _reserved_5: u32,

	pub peripheral_ab_select: u32,

	pub _reserved_6: [u32; 3],

	pub system_clock_glitch_input_filter_select                 : u32,
	pub debouncing_input_filter_select                          : u32,
	pub glitch_or_debouncing_input_filter_clock_selection_status: u32,
	pub slow_clock_divider_debouncing                           : u32,

	pub _reserved_7: [u32; 4],

	pub output_write_enable : u32,
	pub output_write_disable: u32,
	pub output_write_status : u32,

	pub _reserved_8: u32,

	pub additional_interrupt_modes_enable : u32,
	pub additional_interrupt_modes_disable: u32,
	pub additional_interrupt_modes_mask   : u32,

	pub _reserved_9: u32,

	pub edge_select      : u32,
	pub level_select     : u32,
	pub edge_level_status: u32,

	pub _reserved_a: u32,

	pub falling_edge_low_level_select: u32,
	pub rising_edge_high_level_select: u32,
	pub fall_rise_low_high_status    : u32,

	pub _reserved_b: u32,

	pub lock_status         : u32,
	pub write_protect_mode  : u32,
	pub write_protect_status: u32,

Now we can just provide the base address for each controller[8]:

pub const PIO_A: *mut Pio = 0x400E0E00 as *mut Pio;
pub const PIO_B: *mut Pio = 0x400E1000 as *mut Pio;
pub const PIO_C: *mut Pio = 0x400E1200 as *mut Pio;
pub const PIO_D: *mut Pio = 0x400E1400 as *mut Pio;
pub const PIO_E: *mut Pio = 0x400E1600 as *mut Pio;
pub const PIO_F: *mut Pio = 0x400E1800 as *mut Pio;

And finally, let's be thorough and define constants to access all of the up to 32 pins per controller:

pub const P0 : u32 = 0x00000001;
pub const P1 : u32 = 0x00000002;
pub const P2 : u32 = 0x00000004;
pub const P3 : u32 = 0x00000008;
pub const P4 : u32 = 0x00000010;
pub const P5 : u32 = 0x00000020;
pub const P6 : u32 = 0x00000040;
pub const P7 : u32 = 0x00000080;
pub const P8 : u32 = 0x00000100;
pub const P9 : u32 = 0x00000200;
pub const P10: u32 = 0x00000400;
pub const P11: u32 = 0x00000800;
pub const P12: u32 = 0x00001000;
pub const P13: u32 = 0x00002000;
pub const P14: u32 = 0x00004000;
pub const P15: u32 = 0x00008000;
pub const P16: u32 = 0x00010000;
pub const P17: u32 = 0x00020000;
pub const P18: u32 = 0x00040000;
pub const P19: u32 = 0x00080000;
pub const P20: u32 = 0x00100000;
pub const P21: u32 = 0x00200000;
pub const P22: u32 = 0x00400000;
pub const P23: u32 = 0x00800000;
pub const P24: u32 = 0x01000000;
pub const P25: u32 = 0x02000000;
pub const P26: u32 = 0x04000000;
pub const P27: u32 = 0x08000000;
pub const P28: u32 = 0x10000000;
pub const P29: u32 = 0x20000000;
pub const P30: u32 = 0x40000000;
pub const P31: u32 = 0x80000000;

Done! Now we can access all of the features of all the available parallel I/O controllers. The updated LED blinking code doesn't look much different from before:

(*PIO_B).pio_enable    = P27;
(*PIO_B).output_enable = P27;

loop {
	(*PIO_B).set_output_data = P27;
	(*PIO_B).clear_output_data = P27;

That's it for today. As always, the full code is available on GitHub. See you next time!