Skip to content

Commit

Permalink
Merge branch 'add-embassy-example' of egit.irs.uni-stuttgart.de:rust/…
Browse files Browse the repository at this point in the history
…va416xx-rs into add-embassy-example
  • Loading branch information
robamu committed Sep 16, 2024
2 parents 3a23865 + 4a9d086 commit a23cba8
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 63 deletions.
11 changes: 8 additions & 3 deletions examples/embassy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "embassy"
name = "embassy-example"
version = "0.1.0"
edition = "2021"

Expand All @@ -12,12 +12,17 @@ panic-rtt-target = { version = "0.1" }
critical-section = "1"

embassy-sync = { version = "0.6.0" }
embassy-time = { version = "0.3.2", features = ["tick-hz-32_768"] }
embassy-time = { version = "0.3.2", features = ["tick-hz-1_000"] }
embassy-time-driver = { version = "0.1" }

[dependencies.embassy-executor]
version = "0.6.0"
features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "integrated-timers"]
features = [
"arch-cortex-m",
"executor-thread",
"executor-interrupt",
"integrated-timers",
]

[dependencies.va416xx-hal]
path = "../../va416xx-hal"
Expand Down
202 changes: 153 additions & 49 deletions examples/embassy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,50 @@
use core::{
cell::Cell,
mem, ptr,
sync::atomic::{AtomicU32, AtomicU8, Ordering}, u32,
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};
use va416xx_hal::pac;
use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ};
use va416xx_hal::{
clock::Clocks,
enable_interrupt,
pac::{self, interrupt},
pwm::{assert_tim_reset, deassert_tim_reset, enable_tim_clk, ValidTim},
};

pub type TimekeeperClk = pac::Tim15;
pub type AlarmClk0 = pac::Tim14;
pub type AlarmClk1 = pac::Tim13;
pub type AlarmClk2 = pac::Tim12;

/// This has to be called to initiate the time driver for embassy.
pub fn init(
syscfg: &mut pac::Sysconfig,
timekeeper: TimekeeperClk,
alarm: AlarmClk0,
clocks: &Clocks,
) {
DRIVER.init(syscfg, timekeeper, alarm, clocks)
}

fn alarm_tim() -> &'static pac::tim0::RegisterBlock {
unsafe { &*pac::Tim23::ptr() }
const fn alarm_tim(idx: usize) -> &'static pac::tim0::RegisterBlock {
// Safety: This is a memory-mapped peripheral.
match idx {
0 => unsafe { &*AlarmClk0::ptr() },
1 => unsafe { &*AlarmClk1::ptr() },
2 => unsafe { &*AlarmClk2::ptr() },
_ => {
panic!("invalid alarm timer index")
}
}
}

fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock {
unsafe { &*pac::Tim23::ptr() }
const fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock {
// Safety: This is a memory-mapped peripheral.
unsafe { &*TimekeeperClk::ptr() }
}

struct AlarmState {
Expand All @@ -41,36 +70,92 @@ impl AlarmState {
unsafe impl Send for AlarmState {}

const ALARM_COUNT: usize = 1;
// Margin value which is used when detecting whether an alarm should fire soon.
const TIMER_MARGIN: u64 = 0xc000_0000;

pub struct EmbassyVa416xxTimeDriver {
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 EmbassyVa416xxTimeDriver {
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(syscfg, TimekeeperClk::TIM_ID);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_tim_reset(syscfg, TimekeeperClk::TIM_ID);

let rst_value = TimekeeperClk::clock(clocks).raw() / TICK_HZ as u32 - 1;
// Safety: We have a valid instance of the tim peripheral.
timekeeper
.rst_value()
.write(|w| unsafe { w.bits(rst_value) });
//timekeeper
//.cnt_value()
//.write(|w| unsafe { w.bits(rst_value) });
// Switch on. Timekeeping should always be done.
timekeeper.ctrl().modify(|_, w| {
w.irq_enb().set_bit();
w.enable().set_bit()
});
unsafe {
enable_interrupt(TimekeeperClk::IRQ);
}

enable_tim_clk(syscfg, AlarmClk0::TIM_ID);
assert_tim_reset(syscfg, AlarmClk0::TIM_ID);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_tim_reset(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);
}
}

fn on_interrupt_timekeeping(&self) {
self.next_period();
}

fn on_interrupt_alarm(&self, idx: usize) {
critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs)[idx];
let at = alarm.timestamp.get();
let period = self.periods.load(Ordering::Relaxed);
let t = (period as u64) << 32;

if at < t + u32::MAX as u64 {
//alarm_tim().
// just enable it. `set_alarm` has already set the correct CC val.
alarm_tim().ctrl().modify(|_, w| w.irq_enb().set_bit());
}
})
critical_section::with(|cs| self.trigger_alarm(idx, cs))
}

fn next_period(&self) {
self.periods.fetch_add(1, Ordering::Release);
let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
critical_section::with(|cs| {
for i in 0..ALARM_COUNT {
let alarm = &self.alarms.borrow(cs)[i];
let at = alarm.timestamp.get();
let t = (period as u64) << 32;
if at < t + TIMER_MARGIN {
//let rst_val = alarm_tim(i).rst_value().read().bits();
// alarm_tim(i).cnt_value()
//alarm_tim(i)
//.cnt_value()
//.write(|w| unsafe { w.bits(rst_val) });
alarm_tim(i).ctrl().modify(|_, w| {
w.irq_enb().set_bit();
w.enable().set_bit()
});
}
}
})
}

fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
Expand All @@ -80,8 +165,10 @@ impl EmbassyVa416xxTimeDriver {
}

fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
let r = alarm_tim();
r.ctrl().modify(|_, w| w.irq_enb().clear_bit());
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.
Expand All @@ -97,7 +184,7 @@ impl EmbassyVa416xxTimeDriver {
}
}

impl Driver for EmbassyVa416xxTimeDriver {
impl Driver for TimerDriverEmbassy {
fn now(&self) -> u64 {
let mut period1: u32;
let mut period2: u32;
Expand All @@ -107,9 +194,10 @@ impl Driver for EmbassyVa416xxTimeDriver {
// no instructions can be reordered before the load.
period1 = self.periods.load(Ordering::Acquire);

counter_val = timekeeping_tim().cnt_value().read().bits();
counter_val = timekeeping_tim().rst_value().read().bits()
- timekeeping_tim().cnt_value().read().bits();

period2 = self.periods.load(Ordering::Acquire);
period2 = self.periods.load(Ordering::Relaxed);
if period1 == period2 {
break;
}
Expand Down Expand Up @@ -145,48 +233,52 @@ impl Driver for EmbassyVa416xxTimeDriver {

fn set_alarm(&self, alarm: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool {
critical_section::with(|cs| {
let n = alarm.id() as _;
let n = alarm.id();
let alarm = self.get_alarm(cs, alarm);
alarm.timestamp.set(timestamp);

let r = alarm_tim();
let alarm_tim = alarm_tim(n.into());

let t = self.now();
if timestamp <= t {
r.ctrl().modify(|_, w| w.irq_enb().clear_bit());
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});

alarm.timestamp.set(u64::MAX);

return false;
}

// If it hasn't triggered yet, setup it in the compare channel.
// 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.

// Write the CC value regardless of whether we're going to enable it now or not.
// This way, when we enable it later, the right value is already set.

// nrf52 docs say:
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
// To workaround this, we never write a timestamp smaller than N+3.
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
//
// It is impossible for rtc to tick more than once because
// - this code takes less time than 1 tick
// - it runs with interrupts disabled so nothing else can preempt it.
// 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);
r.cc[n].write(|w| unsafe { w.bits(safe_timestamp as u32 & 0xFFFFFF) });
alarm_tim
.rst_value()
.write(|w| unsafe { w.bits((safe_timestamp & u32::MAX as u64) as u32) });

let diff = timestamp - t;
if diff < 0xc00000 {
r.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
if diff < TIMER_MARGIN {
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().set_bit();
w.enable().set_bit()
});
} else {
// If it's too far in the future, don't setup the compare channel yet.
// It will be setup later by `next_period`.
r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
// If it's too far in the future, don't enable timer yet.
// It will be enabled later by `next_period`.
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
}

true
Expand All @@ -195,8 +287,20 @@ impl Driver for EmbassyVa416xxTimeDriver {
}

time_driver_impl!(
static DRIVER: EmbassyVa416xxTimeDriver = EmbassyVa416xxTimeDriver {
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)
}
19 changes: 16 additions & 3 deletions examples/embassy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
#![no_std]
#![no_main]
use cortex_m::asm;
use embassy_executor::Spawner;
use embassy_time::Timer;
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac};
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 RTT Demo");
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();
embassy_example::init(&mut dp.sysconfig, dp.tim15, dp.tim14, &clocks);
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
loop {
Timer::after_millis(200).await;
Timer::after_millis(2000).await;
led.toggle().ok();
}
}
Loading

0 comments on commit a23cba8

Please sign in to comment.