mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-11-25 21:19:35 +01:00
Resource
trait, docs, examples and rtfm-syntax related changes
This commit is contained in:
parent
23425f2f06
commit
c7b9507a57
22 changed files with 1482 additions and 171 deletions
7
.cargo/config
Normal file
7
.cargo/config
Normal file
|
@ -0,0 +1,7 @@
|
|||
[target.thumbv7m-none-eabi]
|
||||
runner = 'arm-none-eabi-gdb'
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
"-C", "linker=arm-none-eabi-ld",
|
||||
"-Z", "linker-flavor=ld",
|
||||
]
|
14
Cargo.toml
14
Cargo.toml
|
@ -19,6 +19,16 @@ static-ref = "0.2.0"
|
|||
[dependencies.cortex-m-rtfm-macros]
|
||||
path = "macros"
|
||||
|
||||
[dev-dependencies]
|
||||
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
|
||||
compiletest_rs = "0.2.8"
|
||||
stm32f103xx = "0.7.1"
|
||||
|
||||
[dev-dependencies.cortex-m-rt]
|
||||
features = ["abort-on-panic"]
|
||||
version = "0.3.3"
|
||||
|
||||
[dev-dependencies.stm32f103xx]
|
||||
features = ["rt"]
|
||||
version = "0.7.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
5
Xargo.toml
Normal file
5
Xargo.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
[dependencies.core]
|
||||
stage = 0
|
||||
|
||||
[dependencies.compiler_builtins]
|
||||
stage = 1
|
81
examples/full-syntax.rs
Normal file
81
examples/full-syntax.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
//! A showcase of the `app!` macro syntax
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(const_fn)]
|
||||
#![feature(proc_macro)]
|
||||
#![no_std]
|
||||
|
||||
#[macro_use(task)]
|
||||
extern crate cortex_m_rtfm as rtfm;
|
||||
extern crate stm32f103xx;
|
||||
|
||||
use rtfm::{app, Resource, Threshold};
|
||||
|
||||
app! {
|
||||
device: stm32f103xx,
|
||||
|
||||
resources: {
|
||||
static CO_OWNED: u32 = 0;
|
||||
static OWNED: bool = false;
|
||||
static SHARED: bool = false;
|
||||
},
|
||||
|
||||
init: {
|
||||
path: init_, // this is a path to the "init" function
|
||||
},
|
||||
|
||||
idle: {
|
||||
locals: {
|
||||
static COUNTER: u32 = 0;
|
||||
},
|
||||
path: idle_, // this is a path to the "idle" function
|
||||
resources: [OWNED, SHARED],
|
||||
},
|
||||
|
||||
tasks: {
|
||||
SYS_TICK: {
|
||||
priority: 1,
|
||||
resources: [CO_OWNED, SHARED],
|
||||
},
|
||||
|
||||
TIM2: {
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
resources: [CO_OWNED],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fn init_(_p: init::Peripherals, _r: init::Resources) {}
|
||||
|
||||
fn idle_(t: &mut Threshold, l: &mut idle::Locals, mut r: idle::Resources) -> ! {
|
||||
loop {
|
||||
*l.COUNTER += 1;
|
||||
|
||||
**r.OWNED != **r.OWNED;
|
||||
|
||||
if **r.OWNED {
|
||||
if r.SHARED.claim(t, |shared, _| **shared) {
|
||||
rtfm::wfi();
|
||||
}
|
||||
} else {
|
||||
r.SHARED.claim_mut(t, |shared, _| **shared = !**shared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task!(SYS_TICK, sys_tick, Local {
|
||||
static STATE: bool = true;
|
||||
});
|
||||
|
||||
fn sys_tick(_t: &mut Threshold, l: &mut Local, r: SYS_TICK::Resources) {
|
||||
*l.STATE = !*l.STATE;
|
||||
|
||||
**r.CO_OWNED += 1;
|
||||
}
|
||||
|
||||
task!(TIM2, tim2);
|
||||
|
||||
fn tim2(_t: &mut Threshold, r: TIM2::Resources) {
|
||||
**r.CO_OWNED += 1;
|
||||
}
|
69
examples/generics.rs
Normal file
69
examples/generics.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
//! Working with resources in a generic fashion
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(proc_macro)]
|
||||
#![no_std]
|
||||
|
||||
#[macro_use(task)]
|
||||
extern crate cortex_m_rtfm as rtfm;
|
||||
extern crate stm32f103xx;
|
||||
|
||||
use rtfm::{app, Resource, Threshold};
|
||||
use stm32f103xx::{SPI1, GPIOA};
|
||||
|
||||
app! {
|
||||
device: stm32f103xx,
|
||||
|
||||
tasks: {
|
||||
EXTI0: {
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
resources: [GPIOA, SPI1],
|
||||
},
|
||||
|
||||
EXTI1: {
|
||||
enabled: true,
|
||||
priority: 2,
|
||||
resources: [GPIOA, SPI1],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fn init(_p: init::Peripherals) {}
|
||||
|
||||
fn idle() -> ! {
|
||||
loop {
|
||||
rtfm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
// a generic function to use resources in any task (regardless of its priority)
|
||||
fn work<G, S>(t: &mut Threshold, gpioa: &G, spi1: &S)
|
||||
where
|
||||
G: Resource<Data = GPIOA>,
|
||||
S: Resource<Data = SPI1>,
|
||||
{
|
||||
gpioa.claim(t, |_gpioa, t| {
|
||||
// drive NSS low
|
||||
|
||||
spi1.claim(t, |_spi1, _| {
|
||||
// transfer data
|
||||
});
|
||||
|
||||
// drive NSS high
|
||||
});
|
||||
}
|
||||
|
||||
task!(EXTI0, exti0);
|
||||
|
||||
// this task needs critical sections to access the resources
|
||||
fn exti0(t: &mut Threshold, r: EXTI0::Resources) {
|
||||
work(t, &r.GPIOA, &r.SPI1);
|
||||
}
|
||||
|
||||
task!(EXTI1, exti1);
|
||||
|
||||
// this task has direct access to the resources
|
||||
fn exti1(t: &mut Threshold, r: EXTI1::Resources) {
|
||||
work(t, r.GPIOA, r.SPI1);
|
||||
}
|
125
examples/nested.rs
Normal file
125
examples/nested.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
//! Nesting claims and how the preemption threshold works
|
||||
//!
|
||||
//! If you run this program you'll hit the breakpoints as indicated by the
|
||||
//! letters in the comments: A, then B, then C, etc.
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(const_fn)]
|
||||
#![feature(proc_macro)]
|
||||
#![no_std]
|
||||
|
||||
#[macro_use(task)]
|
||||
extern crate cortex_m_rtfm as rtfm;
|
||||
extern crate stm32f103xx;
|
||||
|
||||
use stm32f103xx::Interrupt;
|
||||
use rtfm::{app, Resource, Threshold};
|
||||
|
||||
app! {
|
||||
device: stm32f103xx,
|
||||
|
||||
resources: {
|
||||
static LOW: u64 = 0;
|
||||
static HIGH: u64 = 0;
|
||||
},
|
||||
|
||||
tasks: {
|
||||
EXTI0: {
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
resources: [LOW, HIGH],
|
||||
},
|
||||
|
||||
EXTI1: {
|
||||
enabled: true,
|
||||
priority: 2,
|
||||
resources: [LOW],
|
||||
},
|
||||
|
||||
EXTI2: {
|
||||
enabled: true,
|
||||
priority: 3,
|
||||
resources: [HIGH],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fn init(_p: init::Peripherals, _r: init::Resources) {}
|
||||
|
||||
fn idle() -> ! {
|
||||
// sets task `exti0` as pending
|
||||
//
|
||||
// because `exti0` has higher priority than `idle` it will be executed
|
||||
// immediately
|
||||
rtfm::set_pending(Interrupt::EXTI0); // ~> exti0
|
||||
|
||||
loop {
|
||||
rtfm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
task!(EXTI0, exti0);
|
||||
|
||||
fn exti0(t: &mut Threshold, r: EXTI0::Resources) {
|
||||
// because this task has a priority of 1 the preemption threshold is also 1
|
||||
|
||||
// A
|
||||
rtfm::bkpt();
|
||||
|
||||
// because `exti1` has higher priority than `exti0` it can preempt it
|
||||
rtfm::set_pending(Interrupt::EXTI1); // ~> exti1
|
||||
|
||||
// a claim creates a critical section
|
||||
r.LOW.claim_mut(t, |_low, t| {
|
||||
// this claim increases the preemption threshold to 2
|
||||
// just high enough to not race with task `exti1` for access to the
|
||||
// `LOW` resource
|
||||
|
||||
// C
|
||||
rtfm::bkpt();
|
||||
|
||||
// now `exti1` can't preempt this task because its priority is equal to
|
||||
// the current preemption threshold
|
||||
rtfm::set_pending(Interrupt::EXTI1);
|
||||
|
||||
// but `exti2` can, because its priority is higher than the current
|
||||
// preemption threshold
|
||||
rtfm::set_pending(Interrupt::EXTI2); // ~> exti2
|
||||
|
||||
// E
|
||||
rtfm::bkpt();
|
||||
|
||||
// claims can be nested
|
||||
r.HIGH.claim_mut(t, |_high, _| {
|
||||
// This claim increases the preemption threshold to 3
|
||||
|
||||
// now `exti2` can't preempt this task
|
||||
rtfm::set_pending(Interrupt::EXTI2);
|
||||
|
||||
// F
|
||||
rtfm::bkpt();
|
||||
});
|
||||
|
||||
// upon leaving the critical section the preemption threshold drops to 2
|
||||
// and `exti2` immediately preempts this task
|
||||
// ~> exti2
|
||||
});
|
||||
|
||||
// once again the preemption threshold drops to 1
|
||||
// now the pending `exti1` can preempt this task
|
||||
// ~> exti1
|
||||
}
|
||||
|
||||
task!(EXTI1, exti1);
|
||||
|
||||
fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {
|
||||
// B, H
|
||||
rtfm::bkpt();
|
||||
}
|
||||
|
||||
task!(EXTI2, exti2);
|
||||
|
||||
fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) {
|
||||
// D, G
|
||||
rtfm::bkpt();
|
||||
}
|
91
examples/one-task.rs
Normal file
91
examples/one-task.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
//! An application with one task
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(const_fn)]
|
||||
#![feature(proc_macro)]
|
||||
#![no_std]
|
||||
|
||||
extern crate cortex_m;
|
||||
#[macro_use(task)]
|
||||
extern crate cortex_m_rtfm as rtfm;
|
||||
extern crate stm32f103xx;
|
||||
|
||||
use cortex_m::peripheral::SystClkSource;
|
||||
use rtfm::{app, Threshold};
|
||||
|
||||
app! {
|
||||
device: stm32f103xx,
|
||||
|
||||
// Here tasks are declared
|
||||
//
|
||||
// Each task corresponds to an interrupt or an exception. Every time the
|
||||
// interrupt or exception becomes *pending* the corresponding task handler
|
||||
// will be executed.
|
||||
tasks: {
|
||||
// Here we declare that we'll use the SYS_TICK exception as a task
|
||||
SYS_TICK: {
|
||||
// This is the priority of the task.
|
||||
// 1 is the lowest priority a task can have.
|
||||
// The maximum priority is determined by the number of priority bits
|
||||
// the device has. This device has 4 priority bits so 16 is the
|
||||
// maximum value.
|
||||
priority: 1,
|
||||
|
||||
// These are the *resources* associated with this task
|
||||
//
|
||||
// The peripherals that the task needs can be listed here
|
||||
resources: [GPIOC],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn init(p: init::Peripherals) {
|
||||
// power on GPIOC
|
||||
p.RCC.apb2enr.modify(|_, w| w.iopcen().enabled());
|
||||
|
||||
// configure PC13 as output
|
||||
p.GPIOC.bsrr.write(|w| w.bs13().set());
|
||||
p.GPIOC
|
||||
.crh
|
||||
.modify(|_, w| w.mode13().output().cnf13().push());
|
||||
|
||||
// configure the system timer to generate one interrupt every second
|
||||
p.SYST.set_clock_source(SystClkSource::Core);
|
||||
p.SYST.set_reload(8_000_000); // 1s
|
||||
p.SYST.enable_interrupt();
|
||||
p.SYST.enable_counter();
|
||||
}
|
||||
|
||||
fn idle() -> ! {
|
||||
loop {
|
||||
rtfm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
// This binds the `sys_tick` handler to the `SYS_TICK` task
|
||||
//
|
||||
// This particular handler has local state associated to it. The value of the
|
||||
// `STATE` variable will be preserved across invocations of this handler
|
||||
task!(SYS_TICK, sys_tick, Locals {
|
||||
static STATE: bool = false;
|
||||
});
|
||||
|
||||
// This is the task handler of the SYS_TICK exception
|
||||
//
|
||||
// `t` is the preemption threshold token. We won't use it this time.
|
||||
// `l` is the data local to this task. The type here must match the one declared
|
||||
// in `task!`.
|
||||
// `r` is the resources this task has access to. `SYS_TICK::Resources` has one
|
||||
// field per resource declared in `app!`.
|
||||
fn sys_tick(_t: &mut Threshold, l: &mut Locals, r: SYS_TICK::Resources) {
|
||||
// toggle state
|
||||
*l.STATE = !*l.STATE;
|
||||
|
||||
if *l.STATE {
|
||||
// set the pin PC13 high
|
||||
r.GPIOC.bsrr.write(|w| w.bs13().set());
|
||||
} else {
|
||||
// set the pin PC13 low
|
||||
r.GPIOC.bsrr.write(|w| w.br13().reset());
|
||||
}
|
||||
}
|
70
examples/preemption.rs
Normal file
70
examples/preemption.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
//! Two tasks running at different priorities with access to the same resource
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(const_fn)]
|
||||
#![feature(proc_macro)]
|
||||
#![no_std]
|
||||
|
||||
#[macro_use(task)]
|
||||
extern crate cortex_m_rtfm as rtfm;
|
||||
extern crate stm32f103xx;
|
||||
|
||||
use rtfm::{app, Resource, Threshold};
|
||||
|
||||
app! {
|
||||
device: stm32f103xx,
|
||||
|
||||
resources: {
|
||||
static COUNTER: u64 = 0;
|
||||
},
|
||||
|
||||
tasks: {
|
||||
// the task `SYS_TICK` has higher priority than `TIM2`
|
||||
SYS_TICK: {
|
||||
priority: 2,
|
||||
resources: [COUNTER],
|
||||
},
|
||||
|
||||
TIM2: {
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
resources: [COUNTER],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fn init(_p: init::Peripherals, _r: init::Resources) {
|
||||
// ..
|
||||
}
|
||||
|
||||
fn idle() -> ! {
|
||||
loop {
|
||||
rtfm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
task!(SYS_TICK, sys_tick);
|
||||
|
||||
fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) {
|
||||
// ..
|
||||
|
||||
// this task can't be preempted by `tim2` so it has direct access to the
|
||||
// resource data
|
||||
**r.COUNTER += 1;
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
task!(TIM2, tim2);
|
||||
|
||||
fn tim2(t: &mut Threshold, mut r: TIM2::Resources) {
|
||||
// ..
|
||||
|
||||
// as this task runs at lower priority it needs a critical section to
|
||||
// prevent `sys_tick` from preempting it while it modifies this resource
|
||||
// data. The critical section is required to prevent data races which can
|
||||
// lead to data corruption or data loss
|
||||
r.COUNTER.claim_mut(t, |counter, _t| { **counter += 1; });
|
||||
|
||||
// ..
|
||||
}
|
75
examples/two-tasks.rs
Normal file
75
examples/two-tasks.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
//! Two tasks running at the same priority with access to the same resource
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(const_fn)]
|
||||
#![feature(proc_macro)]
|
||||
#![no_std]
|
||||
|
||||
#[macro_use(task)]
|
||||
extern crate cortex_m_rtfm as rtfm;
|
||||
extern crate stm32f103xx;
|
||||
|
||||
use rtfm::{app, Threshold};
|
||||
|
||||
app! {
|
||||
device: stm32f103xx,
|
||||
|
||||
// Resources that are plain data, not peripherals
|
||||
resources: {
|
||||
// Declaration of resources looks like the declaration of `static`
|
||||
// variables
|
||||
static COUNTER: u64 = 0;
|
||||
},
|
||||
|
||||
tasks: {
|
||||
SYS_TICK: {
|
||||
priority: 1,
|
||||
// Both this task and TIM2 have access to the `COUNTER` resource
|
||||
resources: [COUNTER],
|
||||
},
|
||||
|
||||
// An interrupt as a task
|
||||
TIM2: {
|
||||
// For interrupts the `enabled` field must be specified. It
|
||||
// indicates if the interrupt will be enabled or disabled once
|
||||
// `idle` starts
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
resources: [COUNTER],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// when data resources are declared in the top `resources` field, `init` will
|
||||
// have full access to them
|
||||
fn init(_p: init::Peripherals, _r: init::Resources) {
|
||||
// ..
|
||||
}
|
||||
|
||||
fn idle() -> ! {
|
||||
loop {
|
||||
rtfm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
task!(SYS_TICK, sys_tick);
|
||||
|
||||
// As both tasks are running at the same priority one can't preempt the other.
|
||||
// Thus both tasks have direct access to the resource
|
||||
fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) {
|
||||
// ..
|
||||
|
||||
**r.COUNTER += 1;
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
task!(TIM2, tim2);
|
||||
|
||||
fn tim2(_t: &mut Threshold, r: TIM2::Resources) {
|
||||
// ..
|
||||
|
||||
**r.COUNTER += 1;
|
||||
|
||||
// ..
|
||||
}
|
49
examples/zero-tasks.rs
Normal file
49
examples/zero-tasks.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
//! Minimal example with zero tasks
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(proc_macro)] // IMPORTANT always include this feature gate
|
||||
#![no_std]
|
||||
|
||||
extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename
|
||||
extern crate stm32f103xx; // the device crate
|
||||
|
||||
// import the procedural macro
|
||||
use rtfm::app;
|
||||
|
||||
// This macro call indicates that this is a RTFM application
|
||||
//
|
||||
// This macro will expand to a `main` function so you don't need to supply
|
||||
// `main` yourself.
|
||||
app! {
|
||||
// this is a path to the device crate
|
||||
device: stm32f103xx,
|
||||
}
|
||||
|
||||
// The initialization phase.
|
||||
//
|
||||
// This runs first and within a *global* critical section. Nothing can preempt
|
||||
// this function.
|
||||
fn init(p: init::Peripherals) {
|
||||
// This function has access to all the peripherals of the device
|
||||
p.GPIOA;
|
||||
p.RCC;
|
||||
// ..
|
||||
|
||||
// You'll hit this breakpoint first
|
||||
rtfm::bkpt();
|
||||
}
|
||||
|
||||
// The idle loop.
|
||||
//
|
||||
// This runs afterwards and has a priority of 0. All tasks can preempt this
|
||||
// function. This function can never return so it must contain some sort of
|
||||
// endless loop.
|
||||
fn idle() -> ! {
|
||||
// And then this breakpoint
|
||||
rtfm::bkpt();
|
||||
|
||||
loop {
|
||||
// This puts the processor to sleep until there's a task to service
|
||||
rtfm::wfi();
|
||||
}
|
||||
}
|
55
gen-examples.sh
Normal file
55
gen-examples.sh
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Converts the examples in the `examples` directory into documentation in the
|
||||
# `examples` module (`src/examples/*.rs`)
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
local examples=(
|
||||
zero-tasks
|
||||
one-task
|
||||
two-tasks
|
||||
preemption
|
||||
nested
|
||||
generics
|
||||
full-syntax
|
||||
)
|
||||
|
||||
rm -rf src/examples
|
||||
|
||||
mkdir src/examples
|
||||
|
||||
cat >src/examples/mod.rs <<'EOF'
|
||||
//! Examples
|
||||
// Auto-generated. Do not modify.
|
||||
EOF
|
||||
|
||||
local i=0 out=
|
||||
for ex in ${examples[@]}; do
|
||||
name=_${i}_${ex//-/_}
|
||||
out=src/examples/${name}.rs
|
||||
|
||||
echo "pub mod $name;" >> src/examples/mod.rs
|
||||
|
||||
grep '//!' examples/$ex.rs > $out
|
||||
echo '//!' >> $out
|
||||
echo '//! ```' >> $out
|
||||
grep -v '//!' examples/$ex.rs | (
|
||||
IFS=''
|
||||
|
||||
while read line; do
|
||||
echo "//! $line" >> $out;
|
||||
done
|
||||
)
|
||||
echo '//! ```' >> $out
|
||||
echo '// Auto-generated. Do not modify.' >> $out
|
||||
|
||||
|
||||
chmod -x $out
|
||||
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
|
||||
chmod -x src/examples/mod.rs
|
||||
}
|
||||
|
||||
main
|
|
@ -64,15 +64,16 @@ fn idle(
|
|||
let ty = &resource.ty;
|
||||
|
||||
lfields.push(quote! {
|
||||
pub #name: #ty,
|
||||
pub #name: #krate::Static<#ty>,
|
||||
});
|
||||
|
||||
lexprs.push(quote! {
|
||||
#name: #expr,
|
||||
#name: unsafe { #krate::Static::new(#expr) },
|
||||
});
|
||||
}
|
||||
|
||||
mod_items.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
pub struct Locals {
|
||||
#(#lfields)*
|
||||
}
|
||||
|
@ -114,19 +115,24 @@ fn idle(
|
|||
let ty = &resource.ty;
|
||||
|
||||
rfields.push(quote! {
|
||||
pub #name: &'static mut #ty,
|
||||
pub #name: &'static mut ::#krate::Static<#ty>,
|
||||
});
|
||||
|
||||
rexprs.push(quote! {
|
||||
#name: &mut *#super_::#name.get(),
|
||||
#name: #krate::Static::ref_mut(
|
||||
&mut *#super_::#name.get(),
|
||||
),
|
||||
});
|
||||
} else {
|
||||
rfields.push(quote! {
|
||||
pub #name: &'static mut ::#device::#name,
|
||||
pub #name:
|
||||
&'static mut ::#krate::Static<::#device::#name>,
|
||||
});
|
||||
|
||||
rexprs.push(quote! {
|
||||
#name: &mut *::#device::#name.get(),
|
||||
#name: ::krate::Static::ref_mut(
|
||||
&mut *::#device::#name.get(),
|
||||
),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -329,8 +335,8 @@ fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
|
|||
let ty = &resource.ty;
|
||||
|
||||
root.push(quote! {
|
||||
static #name: #krate::Resource<#ty> =
|
||||
#krate::Resource::new(#expr);
|
||||
static #name: #krate::Cell<#ty> =
|
||||
#krate::Cell::new(#expr);
|
||||
});
|
||||
} else {
|
||||
// Peripheral
|
||||
|
@ -343,26 +349,30 @@ fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
|
|||
let ty = &resource.ty;
|
||||
|
||||
root.push(quote! {
|
||||
static #name: #krate::Resource<#ty> =
|
||||
#krate::Resource::new(#expr);
|
||||
static #name: #krate::Cell<#ty> =
|
||||
#krate::Cell::new(#expr);
|
||||
});
|
||||
|
||||
impl_items.push(quote! {
|
||||
pub fn borrow<'cs>(
|
||||
type Data = #ty;
|
||||
|
||||
fn borrow<'cs>(
|
||||
&'cs self,
|
||||
cs: &'cs #krate::CriticalSection,
|
||||
_cs: &'cs #krate::CriticalSection,
|
||||
) -> &'cs #krate::Static<#ty> {
|
||||
unsafe { #name.borrow(cs) }
|
||||
unsafe { #krate::Static::ref_(&*#name.get()) }
|
||||
}
|
||||
|
||||
pub fn borrow_mut<'cs>(
|
||||
fn borrow_mut<'cs>(
|
||||
&'cs mut self,
|
||||
cs: &'cs #krate::CriticalSection,
|
||||
_cs: &'cs #krate::CriticalSection,
|
||||
) -> &'cs mut #krate::Static<#ty> {
|
||||
unsafe { #name.borrow_mut(cs) }
|
||||
unsafe {
|
||||
#krate::Static::ref_mut(&mut *#name.get())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn claim<R, F>(
|
||||
fn claim<R, F>(
|
||||
&self,
|
||||
t: &mut #krate::Threshold,
|
||||
f: F,
|
||||
|
@ -373,16 +383,18 @@ fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
|
|||
&mut #krate::Threshold) -> R
|
||||
{
|
||||
unsafe {
|
||||
#name.claim(
|
||||
#krate::claim(
|
||||
#name.get(),
|
||||
#ceiling,
|
||||
#device::NVIC_PRIO_BITS,
|
||||
t,
|
||||
f,
|
||||
|data| #krate::Static::ref_(&*data),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn claim_mut<R, F>(
|
||||
fn claim_mut<R, F>(
|
||||
&mut self,
|
||||
t: &mut #krate::Threshold,
|
||||
f: F,
|
||||
|
@ -393,45 +405,77 @@ fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
|
|||
&mut #krate::Threshold) -> R
|
||||
{
|
||||
unsafe {
|
||||
#name.claim_mut(
|
||||
#krate::claim(
|
||||
#name.get(),
|
||||
#ceiling,
|
||||
#device::NVIC_PRIO_BITS,
|
||||
t,
|
||||
f,
|
||||
|data| #krate::Static::ref_mut(&mut *data),
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
root.push(quote! {
|
||||
static #name: #krate::Peripheral<#device::#name> =
|
||||
#krate::Peripheral::new(#device::#name);
|
||||
});
|
||||
|
||||
impl_items.push(quote! {
|
||||
pub fn borrow<'cs>(
|
||||
type Data = #device::#name;
|
||||
|
||||
fn borrow<'cs>(
|
||||
&'cs self,
|
||||
cs: &'cs #krate::CriticalSection,
|
||||
) -> &'cs #device::#name {
|
||||
unsafe { #name.borrow(cs) }
|
||||
_cs: &'cs #krate::CriticalSection,
|
||||
) -> &'cs #krate::Static<#name> {
|
||||
unsafe { #krate::Static::ref_(&*#name.get()) }
|
||||
}
|
||||
|
||||
pub fn claim<R, F>(
|
||||
fn borrow_mut<'cs>(
|
||||
&'cs mut self,
|
||||
_cs: &'cs #krate::CriticalSection,
|
||||
) -> &'cs mut #krate::Static<#name> {
|
||||
unsafe {
|
||||
#krate::Static::ref_mut(&mut *#name.get())
|
||||
}
|
||||
}
|
||||
|
||||
fn claim<R, F>(
|
||||
&self,
|
||||
t: &mut #krate::Threshold,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(
|
||||
&#device::#name,
|
||||
&#krate::Static<#name>,
|
||||
&mut #krate::Threshold) -> R
|
||||
{
|
||||
unsafe {
|
||||
#name.claim(
|
||||
#krate::claim(
|
||||
#device::#name.get(),
|
||||
#ceiling,
|
||||
#device::NVIC_PRIO_BITS,
|
||||
t,
|
||||
f,
|
||||
|data| #krate::Static::ref_(&*data),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn claim_mut<R, F>(
|
||||
&mut self,
|
||||
t: &mut #krate::Threshold,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(
|
||||
&mut #krate::Static<#name>,
|
||||
&mut #krate::Threshold) -> R
|
||||
{
|
||||
unsafe {
|
||||
#krate::claim(
|
||||
#device::#name.get(),
|
||||
#ceiling,
|
||||
#device::NVIC_PRIO_BITS,
|
||||
t,
|
||||
f,
|
||||
|data| #krate::Static::ref_mut(&mut *data),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -439,9 +483,8 @@ fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
|
|||
}
|
||||
|
||||
impls.push(quote! {
|
||||
#[allow(dead_code)]
|
||||
#[allow(unsafe_code)]
|
||||
impl _resource::#name {
|
||||
impl #krate::Resource for _resource::#name {
|
||||
#(#impl_items)*
|
||||
}
|
||||
});
|
||||
|
@ -512,11 +555,14 @@ fn tasks(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
|
|||
});
|
||||
} else {
|
||||
fields.push(quote! {
|
||||
pub #name: &'a mut ::#device::#name,
|
||||
pub #name:
|
||||
&'a mut ::#krate::Static<::#device::#name>,
|
||||
});
|
||||
|
||||
exprs.push(quote! {
|
||||
#name: &mut *::#device::#name.get(),
|
||||
#name: ::#krate::Static::ref_mut(
|
||||
&mut *::#device::#name.get(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -558,12 +604,13 @@ fn tasks(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
|
|||
|
||||
let priority = task.priority;
|
||||
root.push(quote!{
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unsafe_code)]
|
||||
mod #name {
|
||||
#[deny(dead_code)]
|
||||
pub const #name: u8 = #priority;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[deny(const_err)]
|
||||
const CHECK_PRIORITY: (u8, u8) = (
|
||||
#priority - 1,
|
||||
|
|
6
memory.x
Normal file
6
memory.x
Normal file
|
@ -0,0 +1,6 @@
|
|||
/* STM32F103C8V6 */
|
||||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 20K
|
||||
}
|
53
src/examples/_0_zero_tasks.rs
Normal file
53
src/examples/_0_zero_tasks.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
//! Minimal example with zero tasks
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! #![deny(unsafe_code)]
|
||||
//! #![feature(proc_macro)] // IMPORTANT always include this feature gate
|
||||
//! #![no_std]
|
||||
//!
|
||||
//! extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename
|
||||
//! extern crate stm32f103xx; // the device crate
|
||||
//!
|
||||
//! // import the procedural macro
|
||||
//! use rtfm::app;
|
||||
//!
|
||||
//! // This macro call indicates that this is a RTFM application
|
||||
//! //
|
||||
//! // This macro will expand to a `main` function so you don't need to supply
|
||||
//! // `main` yourself.
|
||||
//! app! {
|
||||
//! // this is a path to the device crate
|
||||
//! device: stm32f103xx,
|
||||
//! }
|
||||
//!
|
||||
//! // The initialization phase.
|
||||
//! //
|
||||
//! // This runs first and within a *global* critical section. Nothing can preempt
|
||||
//! // this function.
|
||||
//! fn init(p: init::Peripherals) {
|
||||
//! // This function has access to all the peripherals of the device
|
||||
//! p.GPIOA;
|
||||
//! p.RCC;
|
||||
//! // ..
|
||||
//!
|
||||
//! // You'll hit this breakpoint first
|
||||
//! rtfm::bkpt();
|
||||
//! }
|
||||
//!
|
||||
//! // The idle loop.
|
||||
//! //
|
||||
//! // This runs afterwards and has a priority of 0. All tasks can preempt this
|
||||
//! // function. This function can never return so it must contain some sort of
|
||||
//! // endless loop.
|
||||
//! fn idle() -> ! {
|
||||
//! // And then this breakpoint
|
||||
//! rtfm::bkpt();
|
||||
//!
|
||||
//! loop {
|
||||
//! // This puts the processor to sleep until there's a task to service
|
||||
//! rtfm::wfi();
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
// Auto-generated. Do not modify.
|
95
src/examples/_1_one_task.rs
Normal file
95
src/examples/_1_one_task.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
//! An application with one task
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! #![deny(unsafe_code)]
|
||||
//! #![feature(const_fn)]
|
||||
//! #![feature(proc_macro)]
|
||||
//! #![no_std]
|
||||
//!
|
||||
//! extern crate cortex_m;
|
||||
//! #[macro_use(task)]
|
||||
//! extern crate cortex_m_rtfm as rtfm;
|
||||
//! extern crate stm32f103xx;
|
||||
//!
|
||||
//! use cortex_m::peripheral::SystClkSource;
|
||||
//! use rtfm::{app, Threshold};
|
||||
//!
|
||||
//! app! {
|
||||
//! device: stm32f103xx,
|
||||
//!
|
||||
//! // Here tasks are declared
|
||||
//! //
|
||||
//! // Each task corresponds to an interrupt or an exception. Every time the
|
||||
//! // interrupt or exception becomes *pending* the corresponding task handler
|
||||
//! // will be executed.
|
||||
//! tasks: {
|
||||
//! // Here we declare that we'll use the SYS_TICK exception as a task
|
||||
//! SYS_TICK: {
|
||||
//! // This is the priority of the task.
|
||||
//! // 1 is the lowest priority a task can have.
|
||||
//! // The maximum priority is determined by the number of priority bits
|
||||
//! // the device has. This device has 4 priority bits so 16 is the
|
||||
//! // maximum value.
|
||||
//! priority: 1,
|
||||
//!
|
||||
//! // These are the *resources* associated with this task
|
||||
//! //
|
||||
//! // The peripherals that the task needs can be listed here
|
||||
//! resources: [GPIOC],
|
||||
//! },
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn init(p: init::Peripherals) {
|
||||
//! // power on GPIOC
|
||||
//! p.RCC.apb2enr.modify(|_, w| w.iopcen().enabled());
|
||||
//!
|
||||
//! // configure PC13 as output
|
||||
//! p.GPIOC.bsrr.write(|w| w.bs13().set());
|
||||
//! p.GPIOC
|
||||
//! .crh
|
||||
//! .modify(|_, w| w.mode13().output().cnf13().push());
|
||||
//!
|
||||
//! // configure the system timer to generate one interrupt every second
|
||||
//! p.SYST.set_clock_source(SystClkSource::Core);
|
||||
//! p.SYST.set_reload(8_000_000); // 1s
|
||||
//! p.SYST.enable_interrupt();
|
||||
//! p.SYST.enable_counter();
|
||||
//! }
|
||||
//!
|
||||
//! fn idle() -> ! {
|
||||
//! loop {
|
||||
//! rtfm::wfi();
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // This binds the `sys_tick` handler to the `SYS_TICK` task
|
||||
//! //
|
||||
//! // This particular handler has local state associated to it. The value of the
|
||||
//! // `STATE` variable will be preserved across invocations of this handler
|
||||
//! task!(SYS_TICK, sys_tick, Locals {
|
||||
//! static STATE: bool = false;
|
||||
//! });
|
||||
//!
|
||||
//! // This is the task handler of the SYS_TICK exception
|
||||
//! //
|
||||
//! // `t` is the preemption threshold token. We won't use it this time.
|
||||
//! // `l` is the data local to this task. The type here must match the one declared
|
||||
//! // in `task!`.
|
||||
//! // `r` is the resources this task has access to. `SYS_TICK::Resources` has one
|
||||
//! // field per resource declared in `app!`.
|
||||
//! fn sys_tick(_t: &mut Threshold, l: &mut Locals, r: SYS_TICK::Resources) {
|
||||
//! // toggle state
|
||||
//! *l.STATE = !*l.STATE;
|
||||
//!
|
||||
//! if *l.STATE {
|
||||
//! // set the pin PC13 high
|
||||
//! r.GPIOC.bsrr.write(|w| w.bs13().set());
|
||||
//! } else {
|
||||
//! // set the pin PC13 low
|
||||
//! r.GPIOC.bsrr.write(|w| w.br13().reset());
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
// Auto-generated. Do not modify.
|
79
src/examples/_2_two_tasks.rs
Normal file
79
src/examples/_2_two_tasks.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
//! Two tasks running at the same priority with access to the same resource
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! #![deny(unsafe_code)]
|
||||
//! #![feature(const_fn)]
|
||||
//! #![feature(proc_macro)]
|
||||
//! #![no_std]
|
||||
//!
|
||||
//! #[macro_use(task)]
|
||||
//! extern crate cortex_m_rtfm as rtfm;
|
||||
//! extern crate stm32f103xx;
|
||||
//!
|
||||
//! use rtfm::{app, Threshold};
|
||||
//!
|
||||
//! app! {
|
||||
//! device: stm32f103xx,
|
||||
//!
|
||||
//! // Resources that are plain data, not peripherals
|
||||
//! resources: {
|
||||
//! // Declaration of resources looks like the declaration of `static`
|
||||
//! // variables
|
||||
//! static COUNTER: u64 = 0;
|
||||
//! },
|
||||
//!
|
||||
//! tasks: {
|
||||
//! SYS_TICK: {
|
||||
//! priority: 1,
|
||||
//! // Both this task and TIM2 have access to the `COUNTER` resource
|
||||
//! resources: [COUNTER],
|
||||
//! },
|
||||
//!
|
||||
//! // An interrupt as a task
|
||||
//! TIM2: {
|
||||
//! // For interrupts the `enabled` field must be specified. It
|
||||
//! // indicates if the interrupt will be enabled or disabled once
|
||||
//! // `idle` starts
|
||||
//! enabled: true,
|
||||
//! priority: 1,
|
||||
//! resources: [COUNTER],
|
||||
//! },
|
||||
//! },
|
||||
//! }
|
||||
//!
|
||||
//! // when data resources are declared in the top `resources` field, `init` will
|
||||
//! // have full access to them
|
||||
//! fn init(_p: init::Peripherals, _r: init::Resources) {
|
||||
//! // ..
|
||||
//! }
|
||||
//!
|
||||
//! fn idle() -> ! {
|
||||
//! loop {
|
||||
//! rtfm::wfi();
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! task!(SYS_TICK, sys_tick);
|
||||
//!
|
||||
//! // As both tasks are running at the same priority one can't preempt the other.
|
||||
//! // Thus both tasks have direct access to the resource
|
||||
//! fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) {
|
||||
//! // ..
|
||||
//!
|
||||
//! **r.COUNTER += 1;
|
||||
//!
|
||||
//! // ..
|
||||
//! }
|
||||
//!
|
||||
//! task!(TIM2, tim2);
|
||||
//!
|
||||
//! fn tim2(_t: &mut Threshold, r: TIM2::Resources) {
|
||||
//! // ..
|
||||
//!
|
||||
//! **r.COUNTER += 1;
|
||||
//!
|
||||
//! // ..
|
||||
//! }
|
||||
//! ```
|
||||
// Auto-generated. Do not modify.
|
74
src/examples/_3_preemption.rs
Normal file
74
src/examples/_3_preemption.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
//! Two tasks running at different priorities with access to the same resource
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! #![deny(unsafe_code)]
|
||||
//! #![feature(const_fn)]
|
||||
//! #![feature(proc_macro)]
|
||||
//! #![no_std]
|
||||
//!
|
||||
//! #[macro_use(task)]
|
||||
//! extern crate cortex_m_rtfm as rtfm;
|
||||
//! extern crate stm32f103xx;
|
||||
//!
|
||||
//! use rtfm::{app, Resource, Threshold};
|
||||
//!
|
||||
//! app! {
|
||||
//! device: stm32f103xx,
|
||||
//!
|
||||
//! resources: {
|
||||
//! static COUNTER: u64 = 0;
|
||||
//! },
|
||||
//!
|
||||
//! tasks: {
|
||||
//! // the task `SYS_TICK` has higher priority than `TIM2`
|
||||
//! SYS_TICK: {
|
||||
//! priority: 2,
|
||||
//! resources: [COUNTER],
|
||||
//! },
|
||||
//!
|
||||
//! TIM2: {
|
||||
//! enabled: true,
|
||||
//! priority: 1,
|
||||
//! resources: [COUNTER],
|
||||
//! },
|
||||
//! },
|
||||
//! }
|
||||
//!
|
||||
//! fn init(_p: init::Peripherals, _r: init::Resources) {
|
||||
//! // ..
|
||||
//! }
|
||||
//!
|
||||
//! fn idle() -> ! {
|
||||
//! loop {
|
||||
//! rtfm::wfi();
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! task!(SYS_TICK, sys_tick);
|
||||
//!
|
||||
//! fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) {
|
||||
//! // ..
|
||||
//!
|
||||
//! // this task can't be preempted by `tim2` so it has direct access to the
|
||||
//! // resource data
|
||||
//! **r.COUNTER += 1;
|
||||
//!
|
||||
//! // ..
|
||||
//! }
|
||||
//!
|
||||
//! task!(TIM2, tim2);
|
||||
//!
|
||||
//! fn tim2(t: &mut Threshold, mut r: TIM2::Resources) {
|
||||
//! // ..
|
||||
//!
|
||||
//! // as this task runs at lower priority it needs a critical section to
|
||||
//! // prevent `sys_tick` from preempting it while it modifies this resource
|
||||
//! // data. The critical section is required to prevent data races which can
|
||||
//! // lead to data corruption or data loss
|
||||
//! r.COUNTER.claim_mut(t, |counter, _t| { **counter += 1; });
|
||||
//!
|
||||
//! // ..
|
||||
//! }
|
||||
//! ```
|
||||
// Auto-generated. Do not modify.
|
129
src/examples/_4_nested.rs
Normal file
129
src/examples/_4_nested.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
//! Nesting claims and how the preemption threshold works
|
||||
//!
|
||||
//! If you run this program you'll hit the breakpoints as indicated by the
|
||||
//! letters in the comments: A, then B, then C, etc.
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! #![deny(unsafe_code)]
|
||||
//! #![feature(const_fn)]
|
||||
//! #![feature(proc_macro)]
|
||||
//! #![no_std]
|
||||
//!
|
||||
//! #[macro_use(task)]
|
||||
//! extern crate cortex_m_rtfm as rtfm;
|
||||
//! extern crate stm32f103xx;
|
||||
//!
|
||||
//! use stm32f103xx::Interrupt;
|
||||
//! use rtfm::{app, Resource, Threshold};
|
||||
//!
|
||||
//! app! {
|
||||
//! device: stm32f103xx,
|
||||
//!
|
||||
//! resources: {
|
||||
//! static LOW: u64 = 0;
|
||||
//! static HIGH: u64 = 0;
|
||||
//! },
|
||||
//!
|
||||
//! tasks: {
|
||||
//! EXTI0: {
|
||||
//! enabled: true,
|
||||
//! priority: 1,
|
||||
//! resources: [LOW, HIGH],
|
||||
//! },
|
||||
//!
|
||||
//! EXTI1: {
|
||||
//! enabled: true,
|
||||
//! priority: 2,
|
||||
//! resources: [LOW],
|
||||
//! },
|
||||
//!
|
||||
//! EXTI2: {
|
||||
//! enabled: true,
|
||||
//! priority: 3,
|
||||
//! resources: [HIGH],
|
||||
//! },
|
||||
//! },
|
||||
//! }
|
||||
//!
|
||||
//! fn init(_p: init::Peripherals, _r: init::Resources) {}
|
||||
//!
|
||||
//! fn idle() -> ! {
|
||||
//! // sets task `exti0` as pending
|
||||
//! //
|
||||
//! // because `exti0` has higher priority than `idle` it will be executed
|
||||
//! // immediately
|
||||
//! rtfm::set_pending(Interrupt::EXTI0); // ~> exti0
|
||||
//!
|
||||
//! loop {
|
||||
//! rtfm::wfi();
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! task!(EXTI0, exti0);
|
||||
//!
|
||||
//! fn exti0(t: &mut Threshold, r: EXTI0::Resources) {
|
||||
//! // because this task has a priority of 1 the preemption threshold is also 1
|
||||
//!
|
||||
//! // A
|
||||
//! rtfm::bkpt();
|
||||
//!
|
||||
//! // because `exti1` has higher priority than `exti0` it can preempt it
|
||||
//! rtfm::set_pending(Interrupt::EXTI1); // ~> exti1
|
||||
//!
|
||||
//! // a claim creates a critical section
|
||||
//! r.LOW.claim_mut(t, |_low, t| {
|
||||
//! // this claim increases the preemption threshold to 2
|
||||
//! // just high enough to not race with task `exti1` for access to the
|
||||
//! // `LOW` resource
|
||||
//!
|
||||
//! // C
|
||||
//! rtfm::bkpt();
|
||||
//!
|
||||
//! // now `exti1` can't preempt this task because its priority is equal to
|
||||
//! // the current preemption threshold
|
||||
//! rtfm::set_pending(Interrupt::EXTI1);
|
||||
//!
|
||||
//! // but `exti2` can, because its priority is higher than the current
|
||||
//! // preemption threshold
|
||||
//! rtfm::set_pending(Interrupt::EXTI2); // ~> exti2
|
||||
//!
|
||||
//! // E
|
||||
//! rtfm::bkpt();
|
||||
//!
|
||||
//! // claims can be nested
|
||||
//! r.HIGH.claim_mut(t, |_high, _| {
|
||||
//! // This claim increases the preemption threshold to 3
|
||||
//!
|
||||
//! // now `exti2` can't preempt this task
|
||||
//! rtfm::set_pending(Interrupt::EXTI2);
|
||||
//!
|
||||
//! // F
|
||||
//! rtfm::bkpt();
|
||||
//! });
|
||||
//!
|
||||
//! // upon leaving the critical section the preemption threshold drops to 2
|
||||
//! // and `exti2` immediately preempts this task
|
||||
//! // ~> exti2
|
||||
//! });
|
||||
//!
|
||||
//! // once again the preemption threshold drops to 1
|
||||
//! // now the pending `exti1` can preempt this task
|
||||
//! // ~> exti1
|
||||
//! }
|
||||
//!
|
||||
//! task!(EXTI1, exti1);
|
||||
//!
|
||||
//! fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {
|
||||
//! // B, H
|
||||
//! rtfm::bkpt();
|
||||
//! }
|
||||
//!
|
||||
//! task!(EXTI2, exti2);
|
||||
//!
|
||||
//! fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) {
|
||||
//! // D, G
|
||||
//! rtfm::bkpt();
|
||||
//! }
|
||||
//! ```
|
||||
// Auto-generated. Do not modify.
|
73
src/examples/_5_generics.rs
Normal file
73
src/examples/_5_generics.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
//! Working with resources in a generic fashion
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! #![deny(unsafe_code)]
|
||||
//! #![feature(proc_macro)]
|
||||
//! #![no_std]
|
||||
//!
|
||||
//! #[macro_use(task)]
|
||||
//! extern crate cortex_m_rtfm as rtfm;
|
||||
//! extern crate stm32f103xx;
|
||||
//!
|
||||
//! use rtfm::{app, Resource, Threshold};
|
||||
//! use stm32f103xx::{SPI1, GPIOA};
|
||||
//!
|
||||
//! app! {
|
||||
//! device: stm32f103xx,
|
||||
//!
|
||||
//! tasks: {
|
||||
//! EXTI0: {
|
||||
//! enabled: true,
|
||||
//! priority: 1,
|
||||
//! resources: [GPIOA, SPI1],
|
||||
//! },
|
||||
//!
|
||||
//! EXTI1: {
|
||||
//! enabled: true,
|
||||
//! priority: 2,
|
||||
//! resources: [GPIOA, SPI1],
|
||||
//! },
|
||||
//! },
|
||||
//! }
|
||||
//!
|
||||
//! fn init(_p: init::Peripherals) {}
|
||||
//!
|
||||
//! fn idle() -> ! {
|
||||
//! loop {
|
||||
//! rtfm::wfi();
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // a generic function to use resources in any task (regardless of its priority)
|
||||
//! fn work<G, S>(t: &mut Threshold, gpioa: &G, spi1: &S)
|
||||
//! where
|
||||
//! G: Resource<Data = GPIOA>,
|
||||
//! S: Resource<Data = SPI1>,
|
||||
//! {
|
||||
//! gpioa.claim(t, |_gpioa, t| {
|
||||
//! // drive NSS low
|
||||
//!
|
||||
//! spi1.claim(t, |_spi1, _| {
|
||||
//! // transfer data
|
||||
//! });
|
||||
//!
|
||||
//! // drive NSS high
|
||||
//! });
|
||||
//! }
|
||||
//!
|
||||
//! task!(EXTI0, exti0);
|
||||
//!
|
||||
//! // this task needs critical sections to access the resources
|
||||
//! fn exti0(t: &mut Threshold, r: EXTI0::Resources) {
|
||||
//! work(t, &r.GPIOA, &r.SPI1);
|
||||
//! }
|
||||
//!
|
||||
//! task!(EXTI1, exti1);
|
||||
//!
|
||||
//! // this task has direct access to the resources
|
||||
//! fn exti1(t: &mut Threshold, r: EXTI1::Resources) {
|
||||
//! work(t, r.GPIOA, r.SPI1);
|
||||
//! }
|
||||
//! ```
|
||||
// Auto-generated. Do not modify.
|
85
src/examples/_6_full_syntax.rs
Normal file
85
src/examples/_6_full_syntax.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
//! A showcase of the `app!` macro syntax
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! #![deny(unsafe_code)]
|
||||
//! #![feature(const_fn)]
|
||||
//! #![feature(proc_macro)]
|
||||
//! #![no_std]
|
||||
//!
|
||||
//! #[macro_use(task)]
|
||||
//! extern crate cortex_m_rtfm as rtfm;
|
||||
//! extern crate stm32f103xx;
|
||||
//!
|
||||
//! use rtfm::{app, Resource, Threshold};
|
||||
//!
|
||||
//! app! {
|
||||
//! device: stm32f103xx,
|
||||
//!
|
||||
//! resources: {
|
||||
//! static CO_OWNED: u32 = 0;
|
||||
//! static OWNED: bool = false;
|
||||
//! static SHARED: bool = false;
|
||||
//! },
|
||||
//!
|
||||
//! init: {
|
||||
//! path: init_, // this is a path to the "init" function
|
||||
//! },
|
||||
//!
|
||||
//! idle: {
|
||||
//! locals: {
|
||||
//! static COUNTER: u32 = 0;
|
||||
//! },
|
||||
//! path: idle_, // this is a path to the "idle" function
|
||||
//! resources: [OWNED, SHARED],
|
||||
//! },
|
||||
//!
|
||||
//! tasks: {
|
||||
//! SYS_TICK: {
|
||||
//! priority: 1,
|
||||
//! resources: [CO_OWNED, SHARED],
|
||||
//! },
|
||||
//!
|
||||
//! TIM2: {
|
||||
//! enabled: true,
|
||||
//! priority: 1,
|
||||
//! resources: [CO_OWNED],
|
||||
//! },
|
||||
//! },
|
||||
//! }
|
||||
//!
|
||||
//! fn init_(_p: init::Peripherals, _r: init::Resources) {}
|
||||
//!
|
||||
//! fn idle_(t: &mut Threshold, l: &mut idle::Locals, mut r: idle::Resources) -> ! {
|
||||
//! loop {
|
||||
//! *l.COUNTER += 1;
|
||||
//!
|
||||
//! **r.OWNED != **r.OWNED;
|
||||
//!
|
||||
//! if **r.OWNED {
|
||||
//! if r.SHARED.claim(t, |shared, _| **shared) {
|
||||
//! rtfm::wfi();
|
||||
//! }
|
||||
//! } else {
|
||||
//! r.SHARED.claim_mut(t, |shared, _| **shared = !**shared);
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! task!(SYS_TICK, sys_tick, Local {
|
||||
//! static STATE: bool = true;
|
||||
//! });
|
||||
//!
|
||||
//! fn sys_tick(_t: &mut Threshold, l: &mut Local, r: SYS_TICK::Resources) {
|
||||
//! *l.STATE = !*l.STATE;
|
||||
//!
|
||||
//! **r.CO_OWNED += 1;
|
||||
//! }
|
||||
//!
|
||||
//! task!(TIM2, tim2);
|
||||
//!
|
||||
//! fn tim2(_t: &mut Threshold, r: TIM2::Resources) {
|
||||
//! **r.CO_OWNED += 1;
|
||||
//! }
|
||||
//! ```
|
||||
// Auto-generated. Do not modify.
|
9
src/examples/mod.rs
Normal file
9
src/examples/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
//! Examples
|
||||
// Auto-generated. Do not modify.
|
||||
pub mod _0_zero_tasks;
|
||||
pub mod _1_one_task;
|
||||
pub mod _2_two_tasks;
|
||||
pub mod _3_preemption;
|
||||
pub mod _4_nested;
|
||||
pub mod _5_generics;
|
||||
pub mod _6_full_syntax;
|
288
src/lib.rs
288
src/lib.rs
|
@ -1,3 +1,57 @@
|
|||
//! Real Time For the Masses (RTFM), a framework for building concurrent
|
||||
//! applications, for ARM Cortex-M microcontrollers
|
||||
//!
|
||||
//! This crate is based on [the RTFM framework] created by the Embedded Systems
|
||||
//! group at [Luleå University of Technology][ltu], led by Prof. Per Lindgren,
|
||||
//! and uses a simplified version of the Stack Resource Policy as scheduling
|
||||
//! policy (check the [references] for details).
|
||||
//!
|
||||
//! [the RTFM framework]: http://www.rtfm-lang.org/
|
||||
//! [ltu]: https://www.ltu.se/?l=en
|
||||
//! [per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
|
||||
//! [references]: ./index.html#references
|
||||
//!
|
||||
//! # Features
|
||||
//!
|
||||
//! - **Event triggered tasks** as the unit of concurrency.
|
||||
//! - Support for prioritization of tasks and, thus, **preemptive
|
||||
//! multitasking**.
|
||||
//! - **Efficient and data race free memory sharing** through fine grained *non
|
||||
//! global* critical sections.
|
||||
//! - **Deadlock free execution** guaranteed at compile time.
|
||||
//! - **Minimal scheduling overhead** as the scheduler has no "software
|
||||
//! component": the hardware does all the scheduling.
|
||||
//! - **Highly efficient memory usage**: All the tasks share a single call stack
|
||||
//! and there's no hard dependency on a dynamic memory allocator.
|
||||
//! - **All Cortex M devices are fully supported**.
|
||||
//! - This task model is amenable to known WCET (Worst Case Execution Time)
|
||||
//! analysis and scheduling analysis techniques. (Though we haven't yet
|
||||
//! developed Rust friendly tooling for that.)
|
||||
//!
|
||||
//! # Constraints
|
||||
//!
|
||||
//! - Tasks must run to completion. That's it, tasks can't contain endless
|
||||
//! loops. However, you can run an endless event loop in the `idle` function.
|
||||
//!
|
||||
//! - Task priorities must remain constant at runtime.
|
||||
//!
|
||||
//! # Dependencies
|
||||
//!
|
||||
//! - A device crate generated using [`svd2rust`] v0.11.x. The input SVD file
|
||||
//! *must* contain [`<cpu>`] information.
|
||||
//! - A `start` lang time: Vanilla `main` must be supported in binary crates.
|
||||
//! You can use the [`cortex-m-rt`] crate to fulfill the requirement
|
||||
//!
|
||||
//! [`svd2rust`]: https://docs.rs/svd2rust/0..0/svd2rust/
|
||||
//! [`<cpu>`]: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_cpu.html
|
||||
//! [`cortex-m-rt`]: https://docs.rs/cortex-m-rt/0.3.0/cortex_m_rt/
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! In increasing grade of complexity: [examples](./examples/index.html)
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(warnings)]
|
||||
#![feature(asm)]
|
||||
#![feature(const_fn)]
|
||||
#![feature(optin_builtin_traits)]
|
||||
|
@ -17,11 +71,73 @@ pub use cortex_m::interrupt::free as atomic;
|
|||
pub use static_ref::Static;
|
||||
use cortex_m::interrupt::Nr;
|
||||
#[cfg(not(armv6m))]
|
||||
use cortex_m::register::{basepri_max, basepri};
|
||||
use cortex_m::register::{basepri, basepri_max};
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn claim<T, U, R, F, G>(
|
||||
data: T,
|
||||
pub mod examples;
|
||||
|
||||
/// A resource, a means to share data between tasks
|
||||
pub trait Resource {
|
||||
/// The data protected by the resource
|
||||
type Data;
|
||||
|
||||
/// Borrows the resource data for the duration of a *global* critical
|
||||
/// section
|
||||
fn borrow<'cs>(
|
||||
&'cs self,
|
||||
cs: &'cs CriticalSection,
|
||||
) -> &'cs Static<Self::Data>;
|
||||
|
||||
/// Mutable variant of `borrow`
|
||||
fn borrow_mut<'cs>(
|
||||
&'cs mut self,
|
||||
cs: &'cs CriticalSection,
|
||||
) -> &'cs mut Static<Self::Data>;
|
||||
|
||||
/// Claims the resource data for the span of the closure `f`. For the
|
||||
/// duration of the closure other tasks that may access the resource data
|
||||
/// are prevented from preempting the current task.
|
||||
fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&Static<Self::Data>, &mut Threshold) -> R;
|
||||
|
||||
/// Mutable variant of `claim`
|
||||
fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Static<Self::Data>, &mut Threshold) -> R;
|
||||
}
|
||||
|
||||
impl<T> Resource for Static<T> {
|
||||
type Data = T;
|
||||
|
||||
fn borrow<'cs>(&'cs self, _cs: &'cs CriticalSection) -> &'cs Static<T> {
|
||||
self
|
||||
}
|
||||
|
||||
fn borrow_mut<'cs>(
|
||||
&'cs mut self,
|
||||
_cs: &'cs CriticalSection,
|
||||
) -> &'cs mut Static<T> {
|
||||
self
|
||||
}
|
||||
|
||||
fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&Static<Self::Data>, &mut Threshold) -> R,
|
||||
{
|
||||
f(self, t)
|
||||
}
|
||||
|
||||
fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Static<Self::Data>, &mut Threshold) -> R,
|
||||
{
|
||||
f(self, t)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn claim<T, U, R, F, G>(
|
||||
data: *mut T,
|
||||
ceiling: u8,
|
||||
nvic_prio_bits: u8,
|
||||
t: &mut Threshold,
|
||||
|
@ -30,10 +146,10 @@ unsafe fn claim<T, U, R, F, G>(
|
|||
) -> R
|
||||
where
|
||||
F: FnOnce(U, &mut Threshold) -> R,
|
||||
G: FnOnce(T) -> U,
|
||||
G: FnOnce(*mut T) -> U,
|
||||
{
|
||||
let max_priority = 1 << nvic_prio_bits;
|
||||
if ceiling > t.0 {
|
||||
if ceiling > t.value {
|
||||
match () {
|
||||
#[cfg(armv6m)]
|
||||
() => {
|
||||
|
@ -47,7 +163,7 @@ where
|
|||
let old = basepri::read();
|
||||
let hw = (max_priority - ceiling) << (8 - nvic_prio_bits);
|
||||
basepri_max::write(hw);
|
||||
let ret = f(g(data), &mut Threshold(ceiling));
|
||||
let ret = f(g(data), &mut Threshold::new(ceiling));
|
||||
basepri::write(old);
|
||||
ret
|
||||
}
|
||||
|
@ -58,139 +174,44 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Peripheral<P>
|
||||
where
|
||||
P: 'static,
|
||||
{
|
||||
// FIXME(rustc/LLVM bug?) storing the ceiling in the resource de-optimizes
|
||||
// claims (the ceiling value gets loaded at runtime rather than inlined)
|
||||
// ceiling: u8,
|
||||
peripheral: cortex_m::peripheral::Peripheral<P>,
|
||||
}
|
||||
|
||||
impl<P> Peripheral<P> {
|
||||
pub const fn new(peripheral: cortex_m::peripheral::Peripheral<P>) -> Self {
|
||||
Peripheral { peripheral }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn borrow<'cs>(
|
||||
&'static self,
|
||||
_cs: &'cs CriticalSection,
|
||||
) -> &'cs P {
|
||||
&*self.peripheral.get()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn claim<R, F>(
|
||||
&'static self,
|
||||
ceiling: u8,
|
||||
nvic_prio_bits: u8,
|
||||
t: &mut Threshold,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&P, &mut Threshold) -> R,
|
||||
{
|
||||
claim(
|
||||
&self.peripheral,
|
||||
ceiling,
|
||||
nvic_prio_bits,
|
||||
t,
|
||||
f,
|
||||
|peripheral| &*peripheral.get(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> *mut P {
|
||||
self.peripheral.get()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<P> Sync for Peripheral<P>
|
||||
where
|
||||
P: Send,
|
||||
{
|
||||
}
|
||||
|
||||
pub struct Resource<T> {
|
||||
// FIXME(rustc/LLVM bug?) storing the ceiling in the resource de-optimizes
|
||||
// claims (the ceiling value gets loaded at runtime rather than inlined)
|
||||
// ceiling: u8,
|
||||
#[doc(hidden)]
|
||||
pub struct Cell<T> {
|
||||
data: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T> Resource<T> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Resource {
|
||||
data: UnsafeCell::new(value),
|
||||
#[doc(hidden)]
|
||||
impl<T> Cell<T> {
|
||||
pub const fn new(data: T) -> Self {
|
||||
Cell {
|
||||
data: UnsafeCell::new(data),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn borrow<'cs>(
|
||||
&'static self,
|
||||
_cs: &'cs CriticalSection,
|
||||
) -> &'cs Static<T> {
|
||||
Static::ref_(&*self.data.get())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn borrow_mut<'cs>(
|
||||
&'static self,
|
||||
_cs: &'cs CriticalSection,
|
||||
) -> &'cs mut Static<T> {
|
||||
Static::ref_mut(&mut *self.data.get())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn claim<R, F>(
|
||||
&'static self,
|
||||
ceiling: u8,
|
||||
nvic_prio_bits: u8,
|
||||
t: &mut Threshold,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&Static<T>, &mut Threshold) -> R,
|
||||
{
|
||||
claim(&self.data, ceiling, nvic_prio_bits, t, f, |data| {
|
||||
Static::ref_(&*data.get())
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn claim_mut<R, F>(
|
||||
&'static self,
|
||||
ceiling: u8,
|
||||
nvic_prio_bits: u8,
|
||||
t: &mut Threshold,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&mut Static<T>, &mut Threshold) -> R,
|
||||
{
|
||||
claim(&self.data, ceiling, nvic_prio_bits, t, f, |data| {
|
||||
Static::ref_mut(&mut *data.get())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self) -> *mut T {
|
||||
self.data.get()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for Resource<T>
|
||||
unsafe impl<T> Sync for Cell<T>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
}
|
||||
|
||||
pub struct Threshold(u8);
|
||||
/// Preemption threshold token
|
||||
///
|
||||
/// The preemption threshold indicates the priority a task must have to preempt
|
||||
/// the current context. For example a threshold of 2 indicates that only
|
||||
/// interrupts / exceptions with a priority of 3 or greater can preempt the
|
||||
/// current context
|
||||
pub struct Threshold {
|
||||
value: u8,
|
||||
}
|
||||
|
||||
impl Threshold {
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn new(value: u8) -> Self {
|
||||
Threshold(value)
|
||||
Threshold { value }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,14 +227,15 @@ where
|
|||
nvic.set_pending(interrupt);
|
||||
}
|
||||
|
||||
/// Binds a task `$handler` to the interrupt / exception `$NAME`
|
||||
#[macro_export]
|
||||
macro_rules! task {
|
||||
($NAME:ident, $body:path) => {
|
||||
($NAME:ident, $handler:path) => {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unsafe_code)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn $NAME() {
|
||||
let f: fn(&mut $crate::Threshold, ::$NAME::Resources) = $body;
|
||||
let f: fn(&mut $crate::Threshold, ::$NAME::Resources) = $handler;
|
||||
|
||||
f(
|
||||
&mut $crate::Threshold::new(::$NAME::$NAME),
|
||||
|
@ -221,11 +243,13 @@ macro_rules! task {
|
|||
);
|
||||
}
|
||||
};
|
||||
($NAME:ident, $body:path, $local:ident {
|
||||
$($var:ident: $ty:ty = $expr:expr;)+
|
||||
|
||||
($NAME:ident, $handler:path, $locals:ident {
|
||||
$(static $var:ident: $ty:ty = $expr:expr;)+
|
||||
}) => {
|
||||
struct $local {
|
||||
$($var: $ty,)+
|
||||
#[allow(non_snake_case)]
|
||||
struct $locals {
|
||||
$($var: $crate::Static<$ty>,)+
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -234,17 +258,17 @@ macro_rules! task {
|
|||
pub unsafe extern "C" fn $NAME() {
|
||||
let f: fn(
|
||||
&mut $crate::Threshold,
|
||||
&mut $local,
|
||||
&mut $locals,
|
||||
::$NAME::Resources,
|
||||
) = $body;
|
||||
) = $handler;
|
||||
|
||||
static mut LOCAL: $local = $local {
|
||||
$($var: $expr,)+
|
||||
static mut LOCALS: $locals = $locals {
|
||||
$($var: unsafe { $crate::Static::new($expr) },)+
|
||||
};
|
||||
|
||||
f(
|
||||
&mut $crate::Threshold::new(::$NAME::$NAME),
|
||||
&mut LOCAL,
|
||||
&mut LOCALS,
|
||||
::$NAME::Resources::new(),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue