4 releases
0.1.3 | Jul 30, 2019 |
---|---|
0.1.2 | Jul 29, 2019 |
0.1.1 | Jul 29, 2019 |
0.1.0 | Jul 27, 2019 |
#1943 in Embedded development
66KB
1K
SLoC
LPC11xx Async HAL
Asynchronous HAL for the NXP LPC111x/LPC11Cxx/LPC11xxL/LPC11xxXL family of Cortex-M0 microcontrollers using async-await syntax.
Example
Below is a minimal echo server using the UART driver. The microcontroller will wake up from sleep only to send/receive data in the UART interrupt and to issue additional UART commands from the async fn
inside PendSV
and will be sleeping the rest of the time.
use lpc11xx_async_hal::uart;
// Initialize the UART driver ahead of time, e.g. 9600 baud at 12MHz
// The UART peripheral must have power, a clock, configured pins etc
let uart = uart::Driver::initialize(
device.UART, // < lpc11xx::UART
uart::Options {
clock_divisor: 71,
fractional_div: 0,
fractional_mul: 1,
flow_control: false,
rx_threshold: uart::RxThreshold::default(),
},
);
// The actual logic, to be polled from the PendSV interrupt handler
async fn echo_server(mut uart: uart::Driver) -> ! {
let (mut sender, mut receiver) = uart.split();
sender.write(b"Type some text: ").await;
loop {
let mut buffer = [0; 20];
let bytes_read = receiver.read(&mut buffer).await;
sender.write(&buffer[..bytes_read]).await;
}
}
Status
Async drivers for the following peripherals are currently available:
Peripheral | Missing features | Won't implement |
---|---|---|
UART | Autobaud, RS-485 | |
I2C | Slave Mode | Monitor Mode |
ADC | Timer Match | |
SysTick |
More are in development, contributions and suggestions welcome.
Caveats
The Rust async-await feature is still under heavy development to say nothing of its #![no_std]
integration so this crate should be viewed as a proof of concept and ongoing experiment in writing automatically generated asynchronous state machines on LPC11xx devices. It works fine and does useful things though.
No-std support
An additional problem is brought about when using async-await in #![no_std]
crates; essentially, it doesn't work because the compiler machinery to generate the resulting futures depends on thread-local storage which is in std
. You can work around this by using the core-futures-stateless
helper crate which will patch libcore to make #![feature(async_await)]
work:
[dependencies.core]
package = "core-futures-stateless"
version = "0.1.0"
This can be removed once fixed in libcore. Note that this will (unfortunately) change the crate name of all types in the core
crate in compiler messages.
Futures behaviour
This crate is built exclusively around the PendSV
facility in Cortex-M processors. That is, all futures depend on being polled inside the PendSV
exception handler, and all peripheral interrupt handlers will raise PendSV
to progress the futures. As a result the task::Context
associated with your main async task is irrelevant and you should create a dummy Waker
that does absolutely nothing. The core-futures-stateless
crate provides a convenience method task::stateless_waker()
which does just that.
use core::future::Future;
use core::task::{stateless_waker, Context};
// your executor somewhere inside the PendSV exception handler
match Future::poll(your_pinned_future, &mut Context::from_waker(stateless_waker())) {
Poll::Ready(x) => { /* future resolved */ },
Poll::Pending => { /* go back to sleep */ },
}
In the future the crate might become context-aware but in the meantime this is the way it works for simplicity and because using PendSV
is quite a natural approach to polling futures.
Memory safety
There is currently a minor soundness hole in this crate in that interrupt handlers reference stack-like memory from currently running futures, which means that Drop
impls for Future types must run to ensure memory safety. I am not sure how to fix this without significant compromises in performance or ergonomics and don't really consider this a major issue from a pragmatic standpoint. Leak futures at your own peril.
Dependencies
~3MB
~75K SLoC