The Embedded Rust Book
- Автор: authors Collective of
- Год: 2021
- Язык: английский
- Жанр: Прочая компьютерная литература
Электронная книга - «The Embedded Rust Book». Краткое содержание книги:
https://docs.rust-embedded.org/book/
volatile bool signalled = false;
void ISR() {
// Signal that the interrupt has occurred
signalled = true;
}
void driver() {
while(true) {
// Sleep until signalled
while(!signalled) { WFI(); }
// Reset signalled indicator
signalled = false;
// Perform some task that was waiting for the interrupt
run_task();
}
}
The equivalent in Rust would use volatile methods on each access:
static mut SIGNALLED: bool = false;
#[interrupt]
fn ISR() {
// Signal that the interrupt has occurred
// (In real code, you should consider a higher level primitive,
// such as an atomic type).
unsafe { core::ptr::write_volatile(&mut SIGNALLED, true) };
}
fn driver() {
loop {
// Sleep until signalled
while unsafe { !core::ptr::read_volatile(&SIGNALLED) } {}
// Reset signalled indicator
unsafe { core::ptr::write_volatile(&mut SIGNALLED, false) };
// Perform some task that was waiting for the interrupt
run_task();
}
}
A few things are worth noting in the code sample:
• We can pass &mut SIGNALLED into the function requiring *mut T, since &mut T automatically converts to a *mut T (and the same for *const T)
• We need unsafe blocks for the read_volatile/write_volatile methods, since they are unsafe functions. It is the programmer's responsibility to ensure safe use: see the methods' documentation for further details.
It is rare to require these functions directly in your code, as they will usually be taken care of for you by higher-level libraries. For memory mapped peripherals, the peripheral access crates will implement volatile access automatically, while for concurrency primitives there are better abstractions available (see the Concurrency chapter).
Packed and Aligned Types
In embedded C it is common to tell the compiler a variable must have a certain alignment or a struct must be packed rather than aligned, usually to meet specific hardware or protocol requirements.
In Rust this is controlled by the repr attribute on a struct or union. The default representation provides no guarantees of layout, so should not be used for code that interoperates with hardware or C. The compiler may re-order struct members or insert padding and the behaviour may change with future versions of Rust.
struct Foo {
x: u16,
y: u8,
z: u16,
}
fn main() {
let v = Foo { x: 0, y: 0, z: 0 };
println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z);
}
// 0x7ffecb3511d0 0x7ffecb3511d4 0x7ffecb3511d2
// Note ordering has been changed to x, z, y to improve packing.
To ensure layouts that are interoperable with C, use repr(C):
#[repr(C)]
struct Foo {
x: u16,
y: u8,
z: u16,
}
fn main() {
let v = Foo { x: 0, y: 0, z: 0 };
println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z);
}
// 0x7fffd0d84c60 0x7fffd0d84c62 0x7fffd0d84c64
// Ordering is preserved and the layout will not change over time.
// `z` is two-byte aligned so a byte of padding exists between `y` and `z`.
To ensure a packed representation, use repr(packed):
#[repr(packed)]
struct Foo {
x: u16,
y: u8,
z: u16,
}
fn main() {
let v = Foo { x: 0, y: 0, z: 0 };
// Unsafe is required to borrow a field of a packed struct.
unsafe { println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z) };
}
// 0x7ffd33598490 0x7ffd33598492 0x7ffd33598493
// No padding has been inserted between `y` and `z`, so now `z` is unaligned.
Note that using repr(packed) also sets the alignment of the type to 1.
Finally, to specify a specific alignment, use repr(align(n)), where n is the number of bytes to align to (and must be a power of two):
#[repr(C)]
#[repr(align(4096))]
struct Foo {
x: u16,
y: u8,
z: u16,
}
fn main() {
let v = Foo { x: 0, y: 0, z: 0 };
let u = Foo { x: 0, y: 0, z: 0 };
println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z);
println!("{:p} {:p} {:p}", &u.x, &u.y, &u.z);
}
// 0x7ffec909a000 0x7ffec909a002 0x7ffec909a004
// 0x7ffec909b000 0x7ffec909b002 0x7ffec909b004
// The two instances `u` and `v` have been placed on 4096-byte alignments,
// evidenced by the `000` at the end of their addresses.
Note we can combine repr(C) with repr(align(n)) to obtain an aligned and C-compatible layout. It is not permissible to combine repr(align(n)) with repr(packed), since repr(packed) sets the alignment to 1. It is also not permissible for a repr(packed) type to contain a repr(align(n)) type.
For further details on type layouts, refer to the type layout chapter of the Rust Reference.
Other Resources
• In this book:
• A little C with your Rust
• A little Rust with your C
• The Rust Embedded FAQs
• Rust Pointers for C Programmers
• I used to use pointers - now what?
Interoperability
Interoperability between Rust and C code is always dependent on transforming data between the two languages. For this purposes there are two dedicated modules in the stdlib called std::ffi and std::os::raw.