James Munns
@bitshiftmask
james.munns@ferrous-systems.com
embedded rust isn’t a new idea
stabilizing things behind the scenes
if they can do it, so can we!
#![no_std]
where we’re going, we don’t need operating systems
$ rustup default stable
$ cargo build --target thumbv7em-none-eabihf
building firmware doesn’t have to be complex
batteries included
computers you don’t sit in front of
phenomenal hardware powers
itty bitty living space
connecting your CPU to the physical world
herding bits
0x2000_0000
is a real place
0x0000_0000
is too
write code!
fn do_something() {
let mut serial = SerialPort::new();
let speed = serial.read_speed();
// Be careful, we have to go slow!
if speed != SerialPort::SER_PORT_SPEED_LO {
serial.write_speed(SerialPort::SER_PORT_SPEED_LO)
}
// First, send some pre-data
something_else();
// Okay, lets send some slow data
// ...
}
hardware is basically nothing but mutable global state
We should be able to share any number of read-only accesses to these peripherals
If something has read-write access to a peripheral, it should be the only reference
ownership and borrows
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object.
https://en.wikipedia.org/wiki/Singleton_pattern
static mut THE_SERIAL_PORT: SerialPort = SerialPort;
fn main() {
let _ = unsafe {
THE_SERIAL_PORT.read_speed();
}
}
close, but not quite there
struct Peripherals {
serial: Option<SerialPort>,
}
impl Peripherals {
fn take_serial(&mut self) -> SerialPort {
let p = replace(&mut self.serial, None);
p.unwrap()
}
}
static mut PERIPHERALS: Peripherals = Peripherals {
serial: Some(SerialPort),
};
take what you need, but only once
fn main() {
let serial_1 = unsafe { PERIPHERALS.take_serial() };
// This panics!
// let serial_2 = unsafe { PERIPHERALS.take_serial() };
}
small runtime overhead, big impact
how do singletons make a difference?
This is allowed to change hardware settings:
This isn’t:
enforce whether code should or should not make changes to hardware
at compile time*
*: only works across one application, but for bare metal systems, we usually only have one anyway
you don’t have to write all of that code
this is rust. there’s a tool for that
it’s called svd2rust, and it turns XML files into rust code. like bindgen, but for hardware
what else can we do with Rust now?
putting those strong types to work
take things one bit (of input or output) at a time
impl LedPin {
fn new(pin: OutputGpio) -> Self { ... }
fn toggle(&mut self) -> bool { ... }
}
fn main() {
let gpio_1 = unsafe { PERIPHERALS.take_gpio_1() };
// This won't work, the types are wrong!
// let led_1 = LedPin::new(gpio_1);
let mut led_1 = LedPin::new(gpio_1.into_output());
let _ = led_1.toggle();
}
entirely at compile time
no runtime cost
no room for human error
acts real at compile time
doesn’t exist in the binary
no RAM, no CPU, no space
oh, you bet we can
OutputGpio
has multiple modes?(it does)
pub struct PushPull; // good for general usage
pub struct OpenDrain; // better for high power LEDs
pub struct OutputGpio<MODE> {
_mode: MODE
}
impl<MODE> OutputGpio<MODE> {
fn default() -> OutputGpio<OpenDrain> { ... }
fn into_push_pull(self) -> OutputGpio<PushPull> { ... }
fn into_open_drain(self) -> OutputGpio<OpenDrain> { ... }
}
traits of a successful project
drivers that work for more than one chip
impl<MODE> OutputPin for OutputGpio<MODE> {
fn set_low(&mut self) {
self.set_pin_low()
}
fn set_high(&mut self) {
self.set_pin_high()
}
}
this goes in your chip crate
to re-use a driver, just implement the embedded-hal interface
all good things must end
treat hardware resources the way you treat any other struct. let the compiler manage who has access to what
use types to enforce state transitions, pre-conditions, and post-conditions. stop trying to keep that all in your head or in the comments
enable code reuse for your embedded projects. no more copy and pasting from project to project
take advantage of modern language features, package management, tooling, and a group of people willing to work with you
you don’t have to use all of this…
icon by Freepik from flaticon.com
github.com/rust-embedded/wg
ferrous-systems.com
Getting Something For Nothing
James Munns
@bitshiftmask
james.munns@ferrous-systems.com
https://github.com/ferrous-systems/rustconf-james-2018
todo: explain these differences
Wait, but if you generate all the code for the peripherals, how do you add functionality? or create instances?
error[E0451]: field `register` of struct `nrf52::PIN_CNF` is private
--> src/main.rs:22:9
|
22 | register: VolatileCell::new(10)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `register` is private
this is our pin configuration register
// src/p0/mod.rs
#[repr(C)]
pub struct RegisterBlock {
_reserved0: [u8; 1284usize],
pub out: OUT,
pub detectmode: DETECTMODE,
_reserved1: [u8; 472usize],
pub pin_cnf: [PIN_CNF; 32],
}
this is the larger block of registers that the PIN_CNF array lives in
like DLC for someone else’s code