Motivation

One of the motivations behind using Rust for embedded programming is to exploit the type system to make sure
certain kinds of errors are caught at compile time itself. Jorge Aparicio has been
doing pioneering work in this area; please do spend some time going through the articles on his blog and you will
come away as excited as I am! You should at least read this post on the new I/O framework
before going any further!

Ok, now that you have gone through that article, let’s see how you can get a minimal program working on your
TI Tiva/Stellaris launchpad board!

Setting up the tools

Embedded development in Rust requires the nightly compiler; you are at the bleeding edge when using nightly and can
expect things to break depending on the particular version you are using. I did a rustup update nightly yesterday and
discovered that I was unable to compile code which worked perfectly with a previous version. You can install an older
version to get things running once again:

Build and flash the code to your launchpad; you should see the RED LED lighting up!

cd launchpad-new-io
make release; make flash

How does it work?

A lot of work in microcontroller programming revolves around digging through datasheets and programming manuals (which may be over
a thousand pages long) and finding out the device registers and the magical bit patterns you need to write to these
registers to initialize a peripheral and make it do something. Get a single bit wrong and you can spend the next few hours (days …)
debugging code rather than working out in the gym or playing with your kids!

ARM processor manufacturers are supposed to provide an SVD specification of
their CPU’s. An SVD file is an XML file which describes the peripherals contained in a processor - name of peripheral, the base
address associated with its registers, name of each register, its offset, size, reset value, whether you can read/write to it etc.

It is obvious that we are describing a timer peripheral (TIMER_A0) and its associated registers, their
memory mapped addresses and reset values, specific fields within each register (for example a one bit wide
field called TAIFG), the possible values for these fields (TAIFG_0 and TAIFG_1 in this case).

What if there is a program which takes an SVD file and automatically generates code which will help us
access these registers using the peripheral/register/field names specified?

If you check the Cargo.toml file in the git repo which you cloned just now, you will see that it has
a line:

tm4c123x = "0.6.0"

This is the tm4c123x crate, and it contains a large Rust file which has
been auto-generated by svd2rust from the SVD file describing the tm4c123 processor (it looks like TI does not
provide SVD files for this processor - instead it provides a dslite file. The tool dslite2svd
generates SVD files from dslite files)!

fnmain(){letp=tm4c123x::Peripherals::take().unwrap();letgpiof=p.GPIO_PORTF;letsysctl=p.SYSCTL;// Enable PORTFsysctl.rcgcgpio.modify(|_,w|w.r5().bit(true));// Need to wait for sometime after modifying rcgcgpiolet_t=sysctl.rcgcgpio.read();// Enable digital IO on the specified pingpiof.den.modify(|r,w|unsafe{w.bits(r.bits()|(1<<PIN_RED_LED))});// Make it an output pingpiof.dir.modify(|r,w|unsafe{w.bits(r.bits()|(1<<PIN_RED_LED))});// Write a 1 to the pin - RED LED lights up!gpiof.data.modify(|r,w|unsafe{w.bits(r.bits()|(1<<PIN_RED_LED))});loop{}}

All the functions that you invoke in main have been auto-generated by svd2rust!

Let’s look at some specific functions.

To use the peripherals, you need to call the take function. You can call this only once.

GPIOF and SYSCTL are the two peripherals we are interested in. We use a SYSCTL register called
RCGCGPIO to enable each GPIO port. The fifth bit of RCGCGPIO (called R5) needs to be set to enable PORTF.

Look at the line which does this:

sysctl.rcgcgpio.modify(|_,w|w.r5().bit(true));

The modify function accepts a closure with two parameters; the first one is a read-only representation
of the bits of the register and the second one is a mutable struct which initially has the same bit
pattern. When you invoke:

w.r5().bit(true)

you are modifying this bit pattern: in this case, set the R5 bit to 1. After the closure is invoked, the
new bit pattern in w is written to the RCGCGPIO register (note that names like RCGCGPIO, R5 etc are
exactly those names used in the processor manual).

Here is what the modify function looks like (taken from the tm4c123x crate):

The digital functionality of each pin of a GPIO port has to be separately enabled. We
do this by writing 1 to a bit position corresponding to the pin connected to the RED
LED on the launchpad board.

r.bits() gives us the current value of the bits of DEN, it gets modified and written
back to the DEN register.

I was wondering for some time as to why w.bits is unsafe. Seems like it is because
this function lets you write arbitrary bit patterns to registers - some bits of a
register may be reserved for special purposes and changing those bits accidentally
can cause problems (Note that bits doesn’t directly write to the register - it is done
by the modify function. bits simply enables unsafe behaviour).

[Note: I still feel a bit unsatisfied by this explanation. Would love to get
some clarification]

The next two functions behave in similar ways; set the direction of the pin to OUTPUT
and then make the pin go HIGH.

Zero cost abstractions!

The tm4c123x crate seems to be using a lot of abstractions (functions called with
closures as parameters, multiple levels of indirection involving structures and function calls) for
doing something as simple as setting/clearing a bit. This will surely make C programmers
unhappy, that is, until they see the actual machine code produced by the compiler in
release mode!

The first five lines perform a read from RCGCGPIO, sets the 5th bit of that value and
writes it back to RCGCGPIO. The remaining lines also do similar operations, on the
DEN, DIR and DATA registers. This is as good as hand-coded assembly; the compiler has
broken down all the abstractions to perform the minimal amount of work which gets the job
done!

Moving ahead

Let’s say you have written the driver for an accelerometer; won’t it be great if you can use
it unmodified with your STM32 Nucleo boards, TI launchpads, BeagleBone, Raspberry Pi, etc?

This is what Jorge Aparicio plans to achieve with his embedded-hal! The
embedded-hal is a set of traits, for example:

/// Single digital output pinpubtraitOutputPin{/// Is the output pin high?fnis_high(&self)->bool;/// Is the output pin low?fnis_low(&self)->bool;/// Sets the pin lowfnset_low(&mutself);/// Sets the pin highfnset_high(&mutself);}

If you have an implementation of the embedded-hal for your favourite board, and if you have a peripheral
driver on crates.io targeted at the embedded-hal, you will easily be able to get this
driver running on your board, and any other for which there is an implementation of the embedded-hal!