mirror of
https://github.com/nkbai/book.git
synced 2026-09-03 21:22:46 +08:00
Isolate peripherals chapter
This commit is contained in:
@@ -24,12 +24,9 @@ more information and coordination
|
||||
- [The Borrow Checker](./peripherals/borrowck.md)
|
||||
- [Singletons](./peripherals/singletons.md)
|
||||
- [Peripherals in Rust](./peripherals/rusty.md)
|
||||
- [Typestate Programming](./typestate-programming/typestate-programming.md)
|
||||
- [Peripherals as State Machines](./typestate-programming/state-machines.md)
|
||||
- [Design Contracts](./typestate-programming/design-contracts.md)
|
||||
- [Zero Cost Abstractions](./typestate-programming/zero-cost-abstractions.md)
|
||||
- [Static Guarantees](./static-guarantees/static-guarantees.md)
|
||||
<!-- TODO: Define Sections -->
|
||||
- [Portability](./portability/portability.md)
|
||||
- [The Trait System](./portability/traits.md)
|
||||
<!-- TODO: Define more sections -->
|
||||
- [Concurrency](./concurrency/concurrency.md)
|
||||
<!-- TODO: Define Sections -->
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
# Portability
|
||||
|
||||
> ❌: This section has not yet been written. Please refer to [embedded-wg#119](https://github.com/rust-lang-nursery/embedded-wg/issues/119) for discussion of this section.
|
||||
|
||||
## Building something bigger
|
||||
|
||||
> drivers that work for more than one chip
|
||||
|
||||

|
||||
|
||||
* Device Crates (one per chip)
|
||||
* HAL Implementation Crates (one per chip)
|
||||
* `embedded-hal` (only one)
|
||||
* Driver Crates (one per external component)
|
||||
> ❌: This section has not yet been written. Please refer to [embedded-wg#119](https://github.com/rust-lang-nursery/embedded-wg/issues/119) for discussion of this section.
|
||||
@@ -1,50 +0,0 @@
|
||||
# The Trait System
|
||||
|
||||
```rust
|
||||
/// Single digital push-pull output pin
|
||||
pub trait OutputPin {
|
||||
/// Drives the pin low
|
||||
fn set_low(&mut self);
|
||||
|
||||
/// Drives the pin high
|
||||
fn set_high(&mut self);
|
||||
}
|
||||
```
|
||||
|
||||
[rust-embedded/embedded-hal](https://github.com/rust-embedded/embedded-hal/blob/master/src/digital.rs)
|
||||
|
||||
```rust
|
||||
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
|
||||
|
||||
|
||||
```rust
|
||||
impl<SPI, CS, E> L3gd20<SPI, CS>
|
||||
where
|
||||
SPI: Transfer<u8, Error = E> + Write<u8, Error = E>,
|
||||
CS: OutputPin,
|
||||
{
|
||||
/// Creates a new driver from a SPI peripheral
|
||||
/// and a NCS (active low chip select) pin
|
||||
pub fn new(spi: SPI, cs: CS) -> Result<Self, E> {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
[japaric/l3gd20](https://github.com/japaric/l3gd20/blob/master/src/lib.rs)
|
||||
|
||||
## N\*M >>> N+M
|
||||
|
||||
> to re-use a driver, just implement the embedded-hal interface
|
||||
@@ -1,48 +0,0 @@
|
||||
# Design Contracts
|
||||
|
||||
In
|
||||
|
||||
```rust
|
||||
use gpio::{InputGpio, OutputGpio};
|
||||
|
||||
struct GpioPin;
|
||||
|
||||
impl GpioPin {
|
||||
fn into_input(self) -> InputGpio {
|
||||
self.set_input_mode();
|
||||
InputGpio
|
||||
}
|
||||
fn into_output(self) -> OutputGpio {
|
||||
self.set_output_mode();
|
||||
OutputGpio
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Use type transitions to enforce setup steps
|
||||
* Like the builder pattern in "normal" Rust
|
||||
|
||||
```rust
|
||||
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();
|
||||
}
|
||||
```
|
||||
|
||||
* You have to have the right types to have the interfaces you want
|
||||
|
||||
## Enforce design contracts
|
||||
|
||||
> entirely at compile time
|
||||
>
|
||||
> no runtime cost
|
||||
>
|
||||
> no room for human error
|
||||
@@ -1,5 +0,0 @@
|
||||
# Peripherals as State Machines
|
||||
|
||||
The peripherals of a microcontroller can be thought of as set of state machines. For example, a simplified [GPIO pin]
|
||||
|
||||
[GPIO pin]: https://en.wikipedia.org/wiki/General-purpose_input/output
|
||||
@@ -1,63 +0,0 @@
|
||||
# Typestate Programming
|
||||
|
||||
The concept of [typestates] describes the encoding of information about the current state of an object into the type of that object. Although this can sound a little arcane, if you have used the [Builder Pattern] in Rust, you have already started using Typestate Programming!
|
||||
|
||||
[typestates]: https://en.wikipedia.org/wiki/Typestate_analysis
|
||||
[Builder Pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
struct Foo {
|
||||
inner: u32,
|
||||
}
|
||||
|
||||
struct FooBuilder {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
impl FooBuilder {
|
||||
pub fn new(starter: u32) -> Self {
|
||||
Self {
|
||||
a: starter,
|
||||
b: starter,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn double_a(self) -> Self {
|
||||
Self {
|
||||
a: self.a * 2,
|
||||
b: self.b,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_foo(self) -> Foo {
|
||||
Foo {
|
||||
inner: self.a + self.b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = FooBuilder::new(10)
|
||||
.double_a()
|
||||
.into_foo();
|
||||
|
||||
println!("{:#?}", x);
|
||||
}
|
||||
```
|
||||
|
||||
In this example, there is no direct way to create a `Foo` object. We must create a `FooBuilder`, and properly initialize it before we can obtain the `Foo` object we want.
|
||||
|
||||
This minimal example encodes two states:
|
||||
|
||||
* `FooBuilder`, which represents an "unconfigured", or "configuration in process" state
|
||||
* `Foo`, which represents a "configured", or "ready to use" state.
|
||||
|
||||
## Strong Types
|
||||
|
||||
Because Rust has a [Strong Type System], there is no easy way to magically create an instance of `Foo`, or to turn a `FooBuilder` into a `Foo` without calling the `into_foo()` method. Additionally, calling the `into_foo()` method consumes the original `FooBuilder` structure, meaning it can not be reused without the creation of a new instance.
|
||||
|
||||
[Strong Type System]: https://en.wikipedia.org/wiki/Strong_and_weak_typing
|
||||
|
||||
This allows us to represent the states of our system as types, and to include the necessary actions for state transitions into the methods that exchange one type for another. By creating a `FooBuilder`, and exchanging it for a `Foo` object, we have walked through the steps of a basic state machine.
|
||||
@@ -1,78 +0,0 @@
|
||||
# Zero Cost Abstractions
|
||||
|
||||
## "no runtime cost"?
|
||||
|
||||
```rust
|
||||
use core::mem::size_of;
|
||||
|
||||
let _ = size_of::<GpioPin>(); // == 0
|
||||
let _ = size_of::<InputGpio>(); // == 0
|
||||
let _ = size_of::<OutputGpio>(); // == 0
|
||||
let _ = size_of::<()>(); // == 0
|
||||
```
|
||||
|
||||
## Zero Sized Types
|
||||
|
||||
```rust
|
||||
struct GpioPin;
|
||||
```
|
||||
|
||||
> acts real at compile time
|
||||
>
|
||||
> doesn't exist in the binary
|
||||
>
|
||||
> no RAM, no CPU, no space
|
||||
>
|
||||
> Evaporates at compile time
|
||||
|
||||
## What if our `OutputGpio` has multiple modes?
|
||||
|
||||
> (it does)
|
||||
|
||||
---
|
||||
|
||||
```rust
|
||||
pub struct PushPull; // good for general usage
|
||||
pub struct OpenDrain; // used when multiple devices could drive a bus
|
||||
|
||||
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> { ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```rust
|
||||
/// This kind of LED only works with OpenDrain settings
|
||||
struct DrainLed {
|
||||
pin: OutputGpio<OpenDrain>,
|
||||
}
|
||||
|
||||
impl DrainLed {
|
||||
fn new(pin: OutputGpio<OpenDrain>) -> Self { ... }
|
||||
fn toggle(&self) -> bool { ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```rust
|
||||
/// This kind of LED works with any output
|
||||
struct LedDriver<MODE> {
|
||||
pin: OutputGpio<MODE>,
|
||||
}
|
||||
|
||||
/// Generically support any OutputGpio variant!
|
||||
impl<MODE> LedDriver<MODE> {
|
||||
fn new(pin: OutputGpio<MODE>) -> LedDriver<MODE> { ... }
|
||||
fn toggle(&self) -> bool { ... }
|
||||
}
|
||||
```
|
||||
|
||||
* Nested zero sized types are still zero sized, no matter how deep you nest them
|
||||
Reference in New Issue
Block a user