Merge pull request 'Start adding embassy example' (#24) from add-embassy-example into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good

Reviewed-on: #24
This commit is contained in:
Robin Müller 2024-09-17 22:08:31 +02:00
commit 4ed0a806a6
25 changed files with 721 additions and 46 deletions

View File

@ -39,7 +39,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features va41630 - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx
clippy: clippy:
name: Clippy name: Clippy

View File

@ -1,12 +1,14 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"va416xx",
"va416xx-hal",
"vorago-peb1",
"bootloader", "bootloader",
"flashloader", "flashloader",
"examples/simple", "examples/simple",
"va416xx", "examples/embassy",
"va416xx-hal", "examples/rtic",
"vorago-peb1"
] ]
exclude = [ exclude = [
"flashloader/slot-a-blinky", "flashloader/slot-a-blinky",

View File

@ -25,6 +25,9 @@ It also contains the following helper crates:
crate contains a sample flashloader which is able to update the redundant images in the NVM which crate contains a sample flashloader which is able to update the redundant images in the NVM which
is compatible to the provided bootloader as well. is compatible to the provided bootloader as well.
- The `examples` folder contains various example applications crates for the HAL and the PAC. - The `examples` folder contains various example applications crates for the HAL and the PAC.
This folder also contains dedicated example applications using the
[`RTIC`](https://rtic.rs/2/book/en/) and [`embassy`](https://github.com/embassy-rs/embassy)
native Rust RTOSes.
## Using the `.cargo/config.toml` file ## Using the `.cargo/config.toml` file

View File

@ -25,7 +25,9 @@ pipeline {
stage('Docs') { stage('Docs') {
steps { steps {
sh """ sh """
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx
""" """
} }
} }

25
examples/README.md Normal file
View File

@ -0,0 +1,25 @@
VA416xx Example Applications
========
This folder contains various examples
Consult the main README first for setup of the repository.
## Simple examples
```rs
cargo run --example blinky
```
You can have a look at the `simple/examples` folder to see all available simple examples
## RTIC example
```rs
cargo run --bin rtic-example
```
## Embassy example
```rs
cargo run --bin embassy-example
```

View File

@ -0,0 +1,40 @@
[package]
name = "embassy-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
rtt-target = { version = "0.5" }
panic-rtt-target = { version = "0.1" }
critical-section = "1"
embassy-sync = { version = "0.6.0" }
embassy-time = { version = "0.3.2" }
embassy-time-driver = { version = "0.1" }
[dependencies.once_cell]
version = "1"
default-features = false
features = ["critical-section"]
[dependencies.embassy-executor]
version = "0.6.0"
features = [
"arch-cortex-m",
"executor-thread",
"executor-interrupt",
"integrated-timers",
]
[dependencies.va416xx-hal]
path = "../../va416xx-hal"
features = ["va41630"]
[features]
default = ["ticks-hz-1_000"]
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]

View File

@ -0,0 +1,4 @@
#![no_std]
pub mod time_driver;
pub use time_driver::init;

View File

@ -0,0 +1,46 @@
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
const EXTCLK_FREQ: u32 = 40_000_000;
// main is itself an async function.
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
rtt_init_print!();
rprintln!("VA416xx Embassy Demo");
let mut dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: Only called once here.
unsafe {
embassy_example::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
)
};
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let mut ticker = Ticker::every(Duration::from_secs(1));
loop {
ticker.next().await;
rprintln!("Current time: {}", Instant::now().as_secs());
led.toggle().ok();
}
}

View File

@ -0,0 +1,323 @@
//! This is a sample time driver implementation for the VA416xx family of devices, supporting
//! one alarm and requiring/reserving 2 TIM peripherals. You could adapt this implementation to
//! support more alarms.
use core::{
cell::Cell,
mem, ptr,
sync::atomic::{AtomicU32, AtomicU8, Ordering},
};
use critical_section::CriticalSection;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ};
use once_cell::sync::OnceCell;
use va416xx_hal::{
clock::Clocks,
enable_interrupt,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim},
};
pub type TimekeeperClk = pac::Tim15;
pub type AlarmClk0 = pac::Tim14;
pub type AlarmClk1 = pac::Tim13;
pub type AlarmClk2 = pac::Tim12;
/// Initialization method for embassy
///
/// # Safety
/// This has to be called once at initialization time to initiate the time driver for
/// embassy.
pub unsafe fn init(
syscfg: &mut pac::Sysconfig,
irq_router: &pac::IrqRouter,
timekeeper: TimekeeperClk,
alarm: AlarmClk0,
clocks: &Clocks,
) {
enable_and_init_irq_router(syscfg, irq_router);
DRIVER.init(syscfg, timekeeper, alarm, clocks)
}
const fn alarm_tim(idx: usize) -> &'static pac::tim0::RegisterBlock {
// Safety: This is a static memory-mapped peripheral.
match idx {
0 => unsafe { &*AlarmClk0::ptr() },
1 => unsafe { &*AlarmClk1::ptr() },
2 => unsafe { &*AlarmClk2::ptr() },
_ => {
panic!("invalid alarm timer index")
}
}
}
const fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock {
// Safety: This is a memory-mapped peripheral.
unsafe { &*TimekeeperClk::ptr() }
}
struct AlarmState {
timestamp: Cell<u64>,
// This is really a Option<(fn(*mut ()), *mut ())>
// but fn pointers aren't allowed in const yet
callback: Cell<*const ()>,
ctx: Cell<*mut ()>,
}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
callback: Cell::new(ptr::null()),
ctx: Cell::new(ptr::null_mut()),
}
}
}
unsafe impl Send for AlarmState {}
const ALARM_COUNT: usize = 1;
static SCALE: OnceCell<u64> = OnceCell::new();
pub struct TimerDriverEmbassy {
periods: AtomicU32,
alarm_count: AtomicU8,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<CriticalSectionRawMutex, [AlarmState; ALARM_COUNT]>,
}
impl TimerDriverEmbassy {
fn init(
&self,
syscfg: &mut pac::Sysconfig,
timekeeper: TimekeeperClk,
alarm_tim: AlarmClk0,
clocks: &Clocks,
) {
enable_tim_clk(syscfg, TimekeeperClk::TIM_ID);
assert_tim_reset_for_two_cycles(syscfg, TimekeeperClk::TIM_ID);
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
SCALE
.set((TimekeeperClk::clock(clocks).raw() / TICK_HZ as u32) as u64)
.unwrap();
timekeeper
.rst_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Decrementing counter.
timekeeper
.cnt_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Switch on. Timekeeping should always be done.
unsafe {
enable_interrupt(TimekeeperClk::IRQ);
}
timekeeper.ctrl().modify(|_, w| w.irq_enb().set_bit());
timekeeper.enable().write(|w| unsafe { w.bits(1) });
enable_tim_clk(syscfg, AlarmClk0::TIM_ID);
assert_tim_reset_for_two_cycles(syscfg, AlarmClk0::TIM_ID);
// Explicitely disable alarm timer until needed.
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
unsafe {
enable_interrupt(AlarmClk0::IRQ);
}
}
// Should be called inside the IRQ of the timekeeper timer.
fn on_interrupt_timekeeping(&self) {
self.next_period();
}
// Should be called inside the IRQ of the alarm timer.
fn on_interrupt_alarm(&self, idx: usize) {
critical_section::with(|cs| {
if self.alarms.borrow(cs)[idx].timestamp.get() <= self.now() {
self.trigger_alarm(idx, cs)
}
})
}
fn next_period(&self) {
let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
let t = (period as u64) << 32;
critical_section::with(|cs| {
for i in 0..ALARM_COUNT {
let alarm = &self.alarms.borrow(cs)[i];
let at = alarm.timestamp.get();
let alarm_tim = alarm_tim(0);
if at < t {
self.trigger_alarm(i, cs);
} else {
let remaining_ticks = (at - t) * *SCALE.get().unwrap();
if remaining_ticks <= u32::MAX as u64 {
alarm_tim.enable().write(|w| unsafe { w.bits(0) });
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(remaining_ticks as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) })
}
}
}
})
}
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
// safety: we're allowed to assume the AlarmState is created by us, and
// we never create one that's out of bounds.
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
}
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
alarm_tim(n).ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = &self.alarms.borrow(cs)[n];
// Setting the maximum value disables the alarm.
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
// safety:
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get());
}
}
impl Driver for TimerDriverEmbassy {
fn now(&self) -> u64 {
if SCALE.get().is_none() {
return 0;
}
let mut period1: u32;
let mut period2: u32;
let mut counter_val: u32;
loop {
// Acquire ensures that we get the latest value of `periods` and
// no instructions can be reordered before the load.
period1 = self.periods.load(Ordering::Acquire);
counter_val = u32::MAX - timekeeping_tim().cnt_value().read().bits();
// Double read to protect against race conditions when the counter is overflowing.
period2 = self.periods.load(Ordering::Relaxed);
if period1 == period2 {
let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap();
return now;
}
}
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
let id = self
.alarm_count
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
if x < ALARM_COUNT as u8 {
Some(x + 1)
} else {
None
}
});
match id {
Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None,
}
}
fn set_alarm_callback(
&self,
alarm: embassy_time_driver::AlarmHandle,
callback: fn(*mut ()),
ctx: *mut (),
) {
critical_section::with(|cs| {
let alarm = self.get_alarm(cs, alarm);
alarm.callback.set(callback as *const ());
alarm.ctx.set(ctx);
})
}
fn set_alarm(&self, alarm: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool {
if SCALE.get().is_none() {
return false;
}
critical_section::with(|cs| {
let n = alarm.id();
let alarm_tim = alarm_tim(n.into());
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = self.get_alarm(cs, alarm);
alarm.timestamp.set(timestamp);
let t = self.now();
if timestamp <= t {
alarm.timestamp.set(u64::MAX);
return false;
}
// If it hasn't triggered yet, setup the relevant reset value, regardless of whether
// the interrupts are enabled or not. When they are enabled at a later point, the
// right value is already set.
// If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
// is not missed.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
let timer_ticks = (safe_timestamp - t) * *SCALE.get().unwrap();
alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
if timer_ticks <= u32::MAX as u64 {
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(timer_ticks as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
}
// If it's too far in the future, don't enable timer yet.
// It will be enabled later by `next_period`.
true
})
}
}
time_driver_impl!(
static DRIVER: TimerDriverEmbassy = TimerDriverEmbassy {
periods: AtomicU32::new(0),
alarm_count: AtomicU8::new(0),
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [AlarmState::new(); ALARM_COUNT])
});
#[interrupt]
#[allow(non_snake_case)]
fn TIM15() {
DRIVER.on_interrupt_timekeeping()
}
#[interrupt]
#[allow(non_snake_case)]
fn TIM14() {
DRIVER.on_interrupt_alarm(0)
}

24
examples/rtic/Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "rtic-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
rtt-target = { version = "0.5" }
rtic-sync = { version = "1.3", features = ["defmt-03"] }
panic-rtt-target = { version = "0.1.3" }
[dependencies.va416xx-hal]
path = "../../va416xx-hal"
features = ["va41630"]
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]

57
examples/rtic/src/main.rs Normal file
View File

@ -0,0 +1,57 @@
//! RTIC minimal blinky
#![no_main]
#![no_std]
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
mod app {
use cortex_m::asm;
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtic_monotonics::systick::prelude::*;
use rtic_monotonics::Monotonic;
use rtt_target::{rprintln, rtt_init_default};
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
pac,
};
#[local]
struct Local {
led: Pin<PG5, OutputReadablePushPull>,
}
#[shared]
struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 10_000);
#[init]
fn init(_ctx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC template --");
let mut dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let led = portg.pg5.into_readable_push_pull_output();
blinky::spawn().ok();
(Shared {}, Local { led })
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
#[task(
priority = 3,
local=[led],
)]
async fn blinky(cx: blinky::Context) {
loop {
cx.local.led.toggle().ok();
Mono::delay(200.millis()).await;
}
}
}

View File

@ -4,11 +4,11 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
critical-section = "1"
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
rtic-sync = { version = "1.3", features = ["defmt-03"] }
embedded-hal = "1" embedded-hal = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
nb = "1" nb = "1"

View File

@ -4,13 +4,14 @@
use core::cell::Cell; use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use critical_section::Mutex;
use embedded_hal::delay::DelayNs; use embedded_hal::delay::DelayNs;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock}; use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pwm::CountdownTimer; use va416xx_hal::pwm::CountdownTimer;
use va416xx_hal::{ use va416xx_hal::{
pac::{self, interrupt}, pac::{self, interrupt},
@ -45,6 +46,7 @@ fn main() -> ! {
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig) .freeze(&mut dp.sysconfig)
.unwrap(); .unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly // Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
// statically. // statically.
let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe { let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe {
@ -88,10 +90,10 @@ fn transfer_example_8_bit(
(0..64).for_each(|i| { (0..64).for_each(|i| {
src_buf[i] = i as u8; src_buf[i] = i as u8;
}); });
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false); DMA_DONE_FLAG.borrow(cs).set(false);
}); });
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false); DMA_ACTIVE_FLAG.borrow(cs).set(false);
}); });
// Safety: The source and destination buffer are valid for the duration of the DMA transfer. // Safety: The source and destination buffer are valid for the duration of the DMA transfer.
@ -112,7 +114,7 @@ fn transfer_example_8_bit(
// Use polling for completion status. // Use polling for completion status.
loop { loop {
let mut dma_done = false; let mut dma_done = false;
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() { if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 8 bit transfer"); rprintln!("DMA0 is active with 8 bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false); DMA_ACTIVE_FLAG.borrow(cs).set(false);
@ -143,10 +145,10 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<
DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16; DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16;
}); });
} }
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false); DMA_DONE_FLAG.borrow(cs).set(false);
}); });
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false); DMA_ACTIVE_FLAG.borrow(cs).set(false);
}); });
// Safety: The source and destination buffer are valid for the duration of the DMA transfer. // Safety: The source and destination buffer are valid for the duration of the DMA transfer.
@ -170,7 +172,7 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<
// Use polling for completion status. // Use polling for completion status.
loop { loop {
let mut dma_done = false; let mut dma_done = false;
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() { if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 16-bit transfer"); rprintln!("DMA0 is active with 16-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false); DMA_ACTIVE_FLAG.borrow(cs).set(false);
@ -206,10 +208,10 @@ fn transfer_example_32_bit(
(0..16).for_each(|i| { (0..16).for_each(|i| {
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32; src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
}); });
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false); DMA_DONE_FLAG.borrow(cs).set(false);
}); });
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false); DMA_ACTIVE_FLAG.borrow(cs).set(false);
}); });
// Safety: The source and destination buffer are valid for the duration of the DMA transfer. // Safety: The source and destination buffer are valid for the duration of the DMA transfer.
@ -230,7 +232,7 @@ fn transfer_example_32_bit(
// Use polling for completion status. // Use polling for completion status.
loop { loop {
let mut dma_done = false; let mut dma_done = false;
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() { if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 32-bit transfer"); rprintln!("DMA0 is active with 32-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false); DMA_ACTIVE_FLAG.borrow(cs).set(false);
@ -260,7 +262,7 @@ fn transfer_example_32_bit(
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn DMA_DONE0() { fn DMA_DONE0() {
// Notify the main loop that the DMA transfer is finished. // Notify the main loop that the DMA transfer is finished.
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(true); DMA_DONE_FLAG.borrow(cs).set(true);
}); });
} }
@ -269,7 +271,7 @@ fn DMA_DONE0() {
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn DMA_ACTIVE0() { fn DMA_ACTIVE0() {
// Notify the main loop that the DMA 0 is active now. // Notify the main loop that the DMA 0 is active now.
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(true); DMA_ACTIVE_FLAG.borrow(cs).set(true);
}); });
} }

View File

@ -3,12 +3,14 @@
#![no_std] #![no_std]
use core::cell::Cell; use core::cell::Cell;
use cortex_m::interrupt::Mutex; use cortex_m::asm;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use critical_section::Mutex;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::{ use va416xx_hal::{
irq_router::enable_and_init_irq_router,
pac::{self, interrupt}, pac::{self, interrupt},
prelude::*, prelude::*,
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER}, timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
@ -35,19 +37,21 @@ fn main() -> ! {
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig) .freeze(&mut dp.sysconfig)
.unwrap(); .unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks); let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks); let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
second_timer.start(1.Hz());
second_timer.listen(); second_timer.listen();
second_timer.start(1.Hz());
loop { loop {
let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get()); let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get());
if current_ms - last_ms >= 1000 { if current_ms >= last_ms + 1000 {
last_ms = current_ms; // To prevent drift.
last_ms += 1000;
rprintln!("MS counter: {}", current_ms); rprintln!("MS counter: {}", current_ms);
let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get()); let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get());
rprintln!("Second counter: {}", second); rprintln!("Second counter: {}", second);
} }
cortex_m::asm::delay(10000); asm::delay(1000);
} }
} }
@ -60,7 +64,7 @@ fn TIM0() {
#[interrupt] #[interrupt]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn TIM1() { fn TIM1() {
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
let mut sec = SEC_COUNTER.borrow(cs).get(); let mut sec = SEC_COUNTER.borrow(cs).get();
sec += 1; sec += 1;
SEC_COUNTER.borrow(cs).set(sec); SEC_COUNTER.borrow(cs).set(sec);

View File

@ -3,11 +3,12 @@
#![no_std] #![no_std]
use core::cell::Cell; use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use critical_section::Mutex;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pac::{self, interrupt}; use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::prelude::*; use va416xx_hal::prelude::*;
use va416xx_hal::wdt::Wdt; use va416xx_hal::wdt::Wdt;
@ -40,6 +41,7 @@ fn main() -> ! {
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig) .freeze(&mut dp.sysconfig)
.unwrap(); .unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw()); let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
let mut last_interrupt_counter = 0; let mut last_interrupt_counter = 0;
@ -49,7 +51,7 @@ fn main() -> ! {
if TEST_MODE != TestMode::AllowReset { if TEST_MODE != TestMode::AllowReset {
wdt_ctrl.feed(); wdt_ctrl.feed();
} }
let interrupt_counter = cortex_m::interrupt::free(|cs| WDT_INTRPT_COUNT.borrow(cs).get()); let interrupt_counter = critical_section::with(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
if interrupt_counter > last_interrupt_counter { if interrupt_counter > last_interrupt_counter {
rprintln!("interrupt counter has increased to {}", interrupt_counter); rprintln!("interrupt counter has increased to {}", interrupt_counter);
last_interrupt_counter = interrupt_counter; last_interrupt_counter = interrupt_counter;
@ -65,7 +67,7 @@ fn main() -> ! {
#[interrupt] #[interrupt]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn WATCHDOG() { fn WATCHDOG() {
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
WDT_INTRPT_COUNT WDT_INTRPT_COUNT
.borrow(cs) .borrow(cs)
.set(WDT_INTRPT_COUNT.borrow(cs).get() + 1); .set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);

View File

@ -108,6 +108,7 @@ mod app {
use spacepackets::ecss::{ use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket, tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
}; };
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::{ use va416xx_hal::{
clock::ClkgenExt, clock::ClkgenExt,
edac, edac,
@ -163,6 +164,7 @@ mod app {
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ)) .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut cx.device.sysconfig) .freeze(&mut cx.device.sysconfig)
.unwrap(); .unwrap();
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
setup_edac(&mut cx.device.sysconfig); setup_edac(&mut cx.device.sysconfig);
let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
@ -488,7 +490,7 @@ mod app {
.unwrap(); .unwrap();
Mono::delay(2.millis()).await; Mono::delay(2.millis()).await;
} }
Mono::delay(30.millis()).await; Mono::delay(50.millis()).await;
} }
} }

View File

@ -25,6 +25,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixes for the SPI implementation where the clock divider values were not calculated - Fixes for the SPI implementation where the clock divider values were not calculated
correctly correctly
- Fixes for UART IRQ handler implementation - Fixes for UART IRQ handler implementation
- Add new IRQ router initialization method `irq_router::enable_and_init_irq_router`. This method
also sets the initial values of some registers to 0 where the datasheet and the actual reset
value are inconsistent, which can lead to weird bugs like IRQs not being triggered properly.
## Added ## Added

View File

@ -12,6 +12,7 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies] [dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
critical-section = "1"
nb = "1" nb = "1"
paste = "1" paste = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"

View File

@ -494,7 +494,7 @@ pub struct Clocks {
impl Clocks { impl Clocks {
/// Returns the frequency of the HBO clock /// Returns the frequency of the HBO clock
pub fn hbo(&self) -> Hertz { pub const fn hbo(&self) -> Hertz {
HBO_FREQ HBO_FREQ
} }
@ -504,23 +504,23 @@ impl Clocks {
} }
/// Returns system clock divied by 2. /// Returns system clock divied by 2.
pub fn apb1(&self) -> Hertz { pub const fn apb1(&self) -> Hertz {
self.apb1 self.apb1
} }
/// Returns system clock divied by 4. /// Returns system clock divied by 4.
pub fn apb2(&self) -> Hertz { pub const fn apb2(&self) -> Hertz {
self.apb2 self.apb2
} }
/// Returns the system (core) frequency /// Returns the system (core) frequency
pub fn sysclk(&self) -> Hertz { pub const fn sysclk(&self) -> Hertz {
self.sysclk self.sysclk
} }
/// Returns the ADC clock frequency which has a separate divider. /// Returns the ADC clock frequency which has a separate divider.
#[cfg(not(feature = "va41628"))] #[cfg(not(feature = "va41628"))]
pub fn adc_clk(&self) -> Hertz { pub const fn adc_clk(&self) -> Hertz {
self.adc_clk self.adc_clk
} }
} }

View File

@ -0,0 +1,18 @@
use crate::{
clock::{PeripheralSelect, SyscfgExt},
pac,
};
pub fn enable_and_init_irq_router(sysconfig: &mut pac::Sysconfig, irq_router: &pac::IrqRouter) {
sysconfig.enable_peripheral_clock(PeripheralSelect::IrqRouter);
sysconfig.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter);
unsafe {
irq_router.dmasel0().write_with_zero(|w| w);
irq_router.dmasel1().write_with_zero(|w| w);
irq_router.dmasel2().write_with_zero(|w| w);
irq_router.dmasel3().write_with_zero(|w| w);
irq_router.adcsel().write_with_zero(|w| w);
irq_router.dacsel0().write_with_zero(|w| w);
irq_router.dacsel1().write_with_zero(|w| w);
}
}

View File

@ -1,3 +1,26 @@
//! This is the **H**ardware **A**bstraction **L**ayer (HAL) for the VA416xx MCU family.
//!
//! It is an additional hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx).
//! It is the result of reading the datasheet for the device and encoding a type-safe layer over the
//! raw PAC. This crate also implements traits specified by the
//! [embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
//! various drivers in the embedded rust ecosystem.
//! You have to enable one of the following device features to use this crate depending on
//! which chip you are using:
//! - `va41630`
//! - `va41629`
//! - `va41628`
//! - `va41620`
//!
//! When using this HAL and writing applications for the VA416xx family in general, it is strongly
//! recommended that you set up the clock properly, because the default internal HBO clock
//! is not very accurate. You can use the [crate::clock] module for this. If you are working
//! with interrupts, it is strongly recommended to set up the IRQ router with the
//! [crate::irq_router] module at the very least because that peripheral has confusing and/or
//! faulty register reset values which might leads to weird bugs and glitches.
#![no_std] #![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(test)] #[cfg(test)]
@ -22,6 +45,7 @@ pub mod dma;
pub mod edac; pub mod edac;
pub mod gpio; pub mod gpio;
pub mod i2c; pub mod i2c;
pub mod irq_router;
pub mod nvm; pub mod nvm;
pub mod pwm; pub mod pwm;
pub mod spi; pub mod spi;

View File

@ -5,7 +5,8 @@
//! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs) //! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs)
use core::cell::Cell; use core::cell::Cell;
use cortex_m::interrupt::Mutex; use cortex_m::asm;
use critical_section::Mutex;
use crate::clock::Clocks; use crate::clock::Clocks;
use crate::gpio::{ use crate::gpio::{
@ -169,6 +170,14 @@ macro_rules! tim_markers {
}; };
} }
pub const fn const_clock<Tim: ValidTim + ?Sized>(_: &Tim, clocks: &Clocks) -> Hertz {
if Tim::TIM_ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
}
}
tim_markers!( tim_markers!(
(pac::Tim0, 0, pac::Interrupt::TIM0), (pac::Tim0, 0, pac::Interrupt::TIM0),
(pac::Tim1, 1, pac::Interrupt::TIM1), (pac::Tim1, 1, pac::Interrupt::TIM1),
@ -328,19 +337,27 @@ valid_pin_and_tims!(
/// ///
/// Only the bit related to the corresponding TIM peripheral is modified /// Only the bit related to the corresponding TIM peripheral is modified
#[inline] #[inline]
fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) { pub fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
syscfg syscfg
.tim_reset() .tim_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) }) .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) })
} }
#[inline] #[inline]
fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) { pub fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
syscfg syscfg
.tim_reset() .tim_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) }) .modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) })
} }
#[inline]
pub fn assert_tim_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, tim_id: u8) {
assert_tim_reset(syscfg, tim_id);
asm::nop();
asm::nop();
deassert_tim_reset(syscfg, tim_id);
}
pub type TimRegBlock = pac::tim0::RegisterBlock; pub type TimRegBlock = pac::tim0::RegisterBlock;
/// Register interface. /// Register interface.
@ -481,7 +498,7 @@ pub struct CountdownTimer<TIM: ValidTim> {
} }
#[inline] #[inline]
fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
syscfg syscfg
.tim_clk_enable() .tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) }); .modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
@ -579,9 +596,10 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn load(&mut self, timeout: impl Into<Hertz>) { pub fn load(&mut self, timeout: impl Into<Hertz>) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit()); self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
self.curr_freq = timeout.into(); self.curr_freq = timeout.into();
self.rst_val = self.clock.raw() / self.curr_freq.raw(); self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1;
self.set_reload(self.rst_val); self.set_reload(self.rst_val);
self.set_count(0); // Decrementing counter, to set the reset value.
self.set_count(self.rst_val);
} }
#[inline(always)] #[inline(always)]
@ -601,7 +619,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
#[inline(always)] #[inline(always)]
pub fn enable(&mut self) { pub fn enable(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.enable().set_bit()); self.tim.reg().enable().write(|w| unsafe { w.bits(1) });
} }
#[inline(always)] #[inline(always)]
@ -778,7 +796,7 @@ pub fn set_up_ms_tick<Tim: ValidTim>(
/// This function can be called in a specified interrupt handler to increment /// This function can be called in a specified interrupt handler to increment
/// the MS counter /// the MS counter
pub fn default_ms_irq_handler() { pub fn default_ms_irq_handler() {
cortex_m::interrupt::free(|cs| { critical_section::with(|cs| {
let mut ms = MS_COUNTER.borrow(cs).get(); let mut ms = MS_COUNTER.borrow(cs).get();
ms += 1; ms += 1;
MS_COUNTER.borrow(cs).set(ms); MS_COUNTER.borrow(cs).set(ms);
@ -787,7 +805,7 @@ pub fn default_ms_irq_handler() {
/// Get the current MS tick count /// Get the current MS tick count
pub fn get_ms_ticks() -> u32 { pub fn get_ms_ticks() -> u32 {
cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get()) critical_section::with(|cs| MS_COUNTER.borrow(cs).get())
} }
pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>); pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);

View File

@ -410,5 +410,65 @@
] ]
} }
}, },
{
"type": "cortex-debug",
"request": "launch",
"name": "Embassy Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "embassy-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/embassy-example",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "RTIC Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "embassy-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/rtic-example",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
] ]
} }

View File

@ -186,5 +186,18 @@
"kind": "build", "kind": "build",
} }
}, },
{
"label": "rtic-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"rtic-example"
],
"group": {
"kind": "build",
}
},
] ]
} }