Added examples for async crates + fixed codegen for non-Copy arguments

This commit is contained in:
Emil Fresk 2023-01-27 20:20:14 +01:00 committed by Henrik Tjäder
parent d23de62823
commit 922f1ad0eb
13 changed files with 152 additions and 77 deletions

2
rtic-channel/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
Cargo.lock
target/

View file

@ -67,7 +67,7 @@ impl<T, const N: usize> Channel<T, N> {
/// Split the queue into a `Sender`/`Receiver` pair.
pub fn split<'a>(&'a mut self) -> (Sender<'a, T, N>, Receiver<'a, T, N>) {
// Fill free queue
for idx in 0..(N - 1) as u8 {
for idx in 0..N as u8 {
debug_assert!(!self.freeq.get_mut().is_full());
// SAFETY: This safe as the loop goes from 0 to the capacity of the underlying queue.
@ -114,11 +114,26 @@ macro_rules! make_channel {
/// Error state for when the receiver has been dropped.
pub struct NoReceiver<T>(pub T);
impl<T> core::fmt::Debug for NoReceiver<T>
where
T: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "NoReceiver({:?})", self.0)
}
}
/// A `Sender` can send to the channel and can be cloned.
pub struct Sender<'a, T, const N: usize>(&'a Channel<T, N>);
unsafe impl<'a, T, const N: usize> Send for Sender<'a, T, N> {}
impl<'a, T, const N: usize> core::fmt::Debug for Sender<'a, T, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Sender")
}
}
impl<'a, T, const N: usize> Sender<'a, T, N> {
#[inline(always)]
fn send_footer(&mut self, idx: u8, val: T) {
@ -204,7 +219,7 @@ impl<'a, T, const N: usize> Sender<'a, T, N> {
// Return the index
Poll::Ready(Ok(idx))
} else {
return Poll::Pending;
Poll::Pending
}
})
.await;
@ -267,16 +282,29 @@ impl<'a, T, const N: usize> Clone for Sender<'a, T, N> {
/// A receiver of the channel. There can only be one receiver at any time.
pub struct Receiver<'a, T, const N: usize>(&'a Channel<T, N>);
unsafe impl<'a, T, const N: usize> Send for Receiver<'a, T, N> {}
impl<'a, T, const N: usize> core::fmt::Debug for Receiver<'a, T, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Receiver")
}
}
/// Error state for when all senders has been dropped.
pub struct NoSender;
impl core::fmt::Debug for NoSender {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "NoSender")
}
}
impl<'a, T, const N: usize> Receiver<'a, T, N> {
/// Receives a value if there is one in the channel, non-blocking.
/// Note; this does not check if the channel is closed.
pub fn try_recv(&mut self) -> Option<T> {
// Try to get a ready slot.
let ready_slot =
critical_section::with(|cs| self.0.access(cs).readyq.pop_front().map(|rs| rs));
let ready_slot = critical_section::with(|cs| self.0.access(cs).readyq.pop_front());
if let Some(rs) = ready_slot {
// Read the value from the slots, note; this memcpy is not under a critical section.

View file

@ -1,6 +1,5 @@
//! ...
use core::cell::UnsafeCell;
use core::marker::PhantomPinned;
use core::ptr::null_mut;
use core::sync::atomic::{AtomicPtr, Ordering};
@ -9,27 +8,6 @@ use critical_section as cs;
pub type WaitQueue = LinkedList<Waker>;
struct MyLinkPtr<T>(UnsafeCell<*mut Link<T>>);
impl<T> MyLinkPtr<T> {
#[inline(always)]
fn new(val: *mut Link<T>) -> Self {
Self(UnsafeCell::new(val))
}
/// SAFETY: Only use this in a critical section, and don't forget them barriers.
#[inline(always)]
unsafe fn load_relaxed(&self) -> *mut Link<T> {
unsafe { *self.0.get() }
}
/// SAFETY: Only use this in a critical section, and don't forget them barriers.
#[inline(always)]
unsafe fn store_relaxed(&self, val: *mut Link<T>) {
unsafe { self.0.get().write(val) }
}
}
/// A FIFO linked list for a wait queue.
pub struct LinkedList<T> {
head: AtomicPtr<Link<T>>, // UnsafeCell<*mut Link<T>>

View file

@ -10,3 +10,4 @@ cortex-m = { version = "0.7.6" }
embedded-hal-async = "0.2.0-alpha.0"
fugit = { version = "0.3.6", features = ["defmt"] }
rtic-time = { version = "1.0.0", path = "../rtic-time" }
atomic-polyfill = "1"

View file

@ -2,13 +2,11 @@
use super::Monotonic;
pub use super::{TimeoutError, TimerQueue};
use core::{
ops::Deref,
sync::atomic::{AtomicU32, Ordering},
};
use atomic_polyfill::{AtomicU32, Ordering};
use core::ops::Deref;
use cortex_m::peripheral::SYST;
use embedded_hal_async::delay::DelayUs;
use fugit::ExtU32;
pub use fugit::ExtU32;
const TIMER_HZ: u32 = 1_000;

View file

@ -38,6 +38,9 @@ version_check = "0.9"
lm3s6965 = "0.1.3"
cortex-m-semihosting = "0.5.0"
systick-monotonic = "1.0.0"
rtic-time = { path = "../rtic-time" }
rtic-channel = { path = "../rtic-channel" }
rtic-monotonics = { path = "../rtic-monotonics" }
[dev-dependencies.panic-semihosting]
features = ["exit"]

View file

@ -0,0 +1,6 @@
Sender 1 sending: 1
Sender 2 sending: 2
Sender 3 sending: 3
Receiver got: 1
Receiver got: 2
Receiver got: 5

View file

@ -1,3 +1 @@
foo 1, 1
foo 1, 2
foo 2, 3

View file

@ -0,0 +1,61 @@
//! examples/async-channel.rs
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
#![feature(type_alias_impl_trait)]
use panic_semihosting as _;
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
mod app {
use cortex_m_semihosting::{debug, hprintln};
use rtic_channel::*;
#[shared]
struct Shared {}
#[local]
struct Local {}
#[init]
fn init(_: init::Context) -> (Shared, Local) {
let (s, r) = make_channel!(u32, 5);
receiver::spawn(r).unwrap();
sender1::spawn(s.clone()).unwrap();
sender2::spawn(s.clone()).unwrap();
sender3::spawn(s).unwrap();
(Shared {}, Local {})
}
#[task]
async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, 5>) {
while let Ok(val) = receiver.recv().await {
hprintln!("Receiver got: {}", val);
if val == 5 {
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
}
}
}
#[task]
async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, 5>) {
hprintln!("Sender 1 sending: 1");
sender.send(1).await.unwrap();
}
#[task]
async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, 5>) {
hprintln!("Sender 2 sending: 2");
sender.send(2).await.unwrap();
}
#[task]
async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, 5>) {
hprintln!("Sender 3 sending: 3");
sender.send(5).await.unwrap();
}
}

View file

@ -7,7 +7,9 @@ use panic_semihosting as _;
#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)]
mod app {
use cortex_m_semihosting::{debug, hprintln};
use systick_monotonic::*;
use rtic_monotonics::systick_monotonic::*;
rtic_monotonics::make_systick_timer_queue!(TIMER);
#[shared]
struct Shared {}
@ -15,12 +17,12 @@ mod app {
#[local]
struct Local {}
#[monotonic(binds = SysTick, default = true)]
type MyMono = Systick<100>;
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
hprintln!("init").unwrap();
hprintln!("init");
let systick = Systick::start(cx.core.SYST, 12_000_000);
TIMER.initialize(systick);
foo::spawn().ok();
bar::spawn().ok();
@ -40,23 +42,23 @@ mod app {
#[task]
async fn foo(_cx: foo::Context) {
hprintln!("hello from foo").ok();
monotonics::delay(100.millis()).await;
hprintln!("bye from foo").ok();
hprintln!("hello from foo");
TIMER.delay(100.millis()).await;
hprintln!("bye from foo");
}
#[task]
async fn bar(_cx: bar::Context) {
hprintln!("hello from bar").ok();
monotonics::delay(200.millis()).await;
hprintln!("bye from bar").ok();
hprintln!("hello from bar");
TIMER.delay(200.millis()).await;
hprintln!("bye from bar");
}
#[task]
async fn baz(_cx: baz::Context) {
hprintln!("hello from baz").ok();
monotonics::delay(300.millis()).await;
hprintln!("bye from baz").ok();
hprintln!("hello from baz");
TIMER.delay(300.millis()).await;
hprintln!("bye from baz");
debug::exit(debug::EXIT_SUCCESS);
}

View file

@ -4,6 +4,7 @@
#![deny(warnings)]
#![no_main]
#![no_std]
#![feature(type_alias_impl_trait)]
use panic_semihosting as _;
@ -18,20 +19,16 @@ mod app {
struct Local {}
#[init]
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
fn init(_: init::Context) -> (Shared, Local) {
foo::spawn(1, 1).unwrap();
foo::spawn(1, 2).unwrap();
foo::spawn(2, 3).unwrap();
assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached
(Shared {}, Local {}, init::Monotonics())
(Shared {}, Local {})
}
#[task(capacity = 3)]
fn foo(_c: foo::Context, x: i32, y: u32) {
hprintln!("foo {}, {}", x, y).unwrap();
if x == 2 {
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
}
#[task]
async fn foo(_c: foo::Context, x: i32, y: u32) {
hprintln!("foo {}, {}", x, y);
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
}
}

View file

@ -157,14 +157,17 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
#[allow(non_snake_case)]
#[doc(hidden)]
pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> {
// SAFETY: If `try_allocate` suceeds one must call `spawn`, which we do.
unsafe {
if #exec_name.try_allocate() {
let f = #name(unsafe { #name::Context::new() } #(,#input_untupled)*);
#exec_name.spawn(f);
#pend_interrupt
if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) {
#pend_interrupt
Ok(())
} else {
Err(#input_tupled)
Ok(())
} else {
Err(#input_tupled)
}
}
}

View file

@ -70,25 +70,23 @@ impl<F: Future> AsyncTaskExecutor<F> {
self.pending.store(true, Ordering::Release);
}
/// Spawn a future
/// Allocate the executor. To use with `spawn`.
#[inline(always)]
pub fn spawn(&self, future: impl Fn() -> F) -> bool {
pub unsafe fn try_allocate(&self) -> bool {
// Try to reserve the executor for a future.
if self
.running
self.running
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
// This unsafe is protected by `running` being false and the atomic setting it to true.
unsafe {
self.task.get().write(MaybeUninit::new(future()));
}
self.set_pending();
}
true
} else {
false
/// Spawn a future
#[inline(always)]
pub unsafe fn spawn(&self, future: F) {
// This unsafe is protected by `running` being false and the atomic setting it to true.
unsafe {
self.task.get().write(MaybeUninit::new(future));
}
self.set_pending();
}
/// Poll the future in the executor.