added and created embassy library

This commit is contained in:
Robin Müller 2025-02-13 17:06:53 +01:00
parent 13a86ac291
commit 69f8671412
15 changed files with 627 additions and 396 deletions

View File

@ -1,13 +1,11 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "gdb-multiarch -q -x jlink/jlink.gdb"
# runner = "gdb-multiarch -q -x jlink/jlink.gdb"
# runner = "arm-none-eabi-gdb -q -x jlink/jlink-reva.gdb"
# runner = "gdb-multiarch -q -x jlink/jlink-reva.gdb"
# Probe-rs is currently problematic, possibly because of the
# ROM protection?
# runner = "probe-rs run --chip-description-path ./scripts/VA416xx_Series.yaml"
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
runner = "probe-rs run --chip VA416xx_RAM --protocol swd"
rustflags = [
"-C",

View File

@ -3,6 +3,7 @@ resolver = "2"
members = [
"va416xx",
"va416xx-hal",
"va416xx-embassy",
"vorago-peb1",
"bootloader",
"flashloader",

View File

@ -119,3 +119,9 @@ configuration variables in your `settings.json`:
The provided VS Code configurations also provide an integrated RTT logger, which you can access
via the terminal at `RTT Ch:0 console`. In order for the RTT block address detection to
work properly, `objdump-multiarch` and `nm-multiarch` need to be installed.
### Using the RTT Viewer
The RTT viewer can be used to display log messages received from the target. The base address
for the RTT block placement is 0x1fff8000. It is recommended to use a search range of 0x1000 around
that base address when using the RTT viewer.

View File

@ -20,6 +20,14 @@ cargo run --bin rtic-example
## Embassy example
Blinky with time driver IRQs in library
```rs
cargo run --bin embassy-example
```
Blinky with custom time driver IRQs
```rs
cargo run --bin embassy-example --no-default-features --features custom-irqs
```

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
cfg-if = "1"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
@ -13,9 +14,9 @@ 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" }
embassy-sync = { version = "0.6" }
embassy-time = { version = "0.4" }
va416xx-embassy = { path = "../../va416xx-embassy", default-features = false }
[dependencies.ringbuf]
version = "0.4"
@ -27,12 +28,11 @@ default-features = false
features = ["critical-section"]
[dependencies.embassy-executor]
version = "0.6.0"
version = "0.7"
features = [
"arch-cortex-m",
"executor-thread",
"executor-interrupt",
"integrated-timers",
]
[dependencies.va416xx-hal]
@ -40,6 +40,7 @@ path = "../../va416xx-hal"
features = ["va41630"]
[features]
default = ["ticks-hz-1_000"]
default = ["ticks-hz-1_000", "va416xx-embassy/irq-tim14-tim15"]
custom-irqs = []
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]

View File

@ -64,7 +64,7 @@ async fn main(spawner: Spawner) {
.unwrap();
// Safety: Only called once here.
unsafe {
embassy_example::init(
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,

View File

@ -1,6 +1,2 @@
#![no_std]
pub mod time_driver;
pub const EXTCLK_FREQ: u32 = 40_000_000;
pub use time_driver::init;

View File

@ -8,6 +8,13 @@ use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
cfg_if::cfg_if! {
if #[cfg(feature = "custom-irqs")] {
use va416xx_hal::pac::interrupt;
va416xx_embassy::embassy_time_driver_irqs!(timekeeper_irq = TIM12, alarm_irq = TIM11);
}
}
// main is itself an async function.
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
@ -26,14 +33,26 @@ async fn main(_spawner: Spawner) {
.unwrap();
// Safety: Only called once here.
unsafe {
embassy_example::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
)
};
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks
);
} else {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim12,
dp.tim11,
&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));

View File

@ -1,323 +0,0 @@
//! 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},
timer::{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)
}

View File

@ -1,6 +1,6 @@
target remote localhost:2331
monitor halt
monitor reset
# *try* to stop at the user entry point (it might be gone due to inlining)
break main

View File

@ -0,0 +1,13 @@
Change Log
=======
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.1.0] 2025-02-13
Initial release

View File

@ -0,0 +1,37 @@
[package]
name = "va416xx-embassy"
version = "0.1.0"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Embassy-rs support for the Vorago VA416xx family of microcontrollers"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
license = "Apache-2.0"
keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
[dependencies]
critical-section = "1"
embassy-sync = "0.6"
embassy-executor = "0.7"
embassy-time-driver = "0.2"
embassy-time-queue-utils = "0.1"
portable-atomic = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
va416xx-hal = { version = "0.3", path = "../va416xx-hal" }
[features]
default = ["irq-tim14-tim15"]
irqs-in-lib = []
# This determines the reserved interrupt functions for the embassy time drivers. Only one
# is allowed to be selected!
irq-tim14-tim15 = ["irqs-in-lib"]
irq-tim13-tim14 = ["irqs-in-lib"]
# These TIMs are clocked slower!
irq-tim22-tim23 = ["irqs-in-lib"]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

398
va416xx-embassy/src/lib.rs Normal file
View File

@ -0,0 +1,398 @@
//! # Embassy-rs support for the Vorago VA416xx MCU family
//!
//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
//! VA416xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM
//! peripherals provided by the VA416xx family for this purpose.
//!
//! ## Usage
//!
//! This library only exposes the [embassy::init] method which sets up the time driver. This
//! function must be called once at the start of the application.
//!
//! This implementation requires two TIM peripherals provided by the VA108xx device.
//! The user can freely specify the two used TIM peripheral by passing the concrete TIM instances
//! into the [init] method. If the interrupt handlers are provided by the library, the ID of the
//! used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
//! can only be checked at run-time, and a run-time assertion will panic on the embassy
//! initialization in case of a missmatch.
//!
//! The application also requires two interrupt handlers to handle the timekeeper and alarm
//! interrupts. By default, this library will define the interrupt handler inside the library
//! itself by using the `irq-tim14-tim15` feature flag. This library exposes three combinations:
//!
//! - `irq-tim14-tim15`: Uses [pac::Interrupt::TIM14] for alarm and [pac::Interrupt::TIM15]
//! for timekeeper
//! - `irq-tim13-tim14`: Uses [pac::Interrupt::TIM13] for alarm and [pac::Interrupt::TIM14]
//! for timekeeper
//! - `irq-tim22-tim23`: Uses [pac::Interrupt::TIM22] for alarm and [pac::Interrupt::TIM23]
//! for timekeeper
//!
//! You can disable the default features and then specify one of the features above to use the
//! documented combination of IRQs. It is also possible to specify custom IRQs by importing and
//! using the [embassy_time_driver_irqs] macro to declare the IRQ handlers in the
//! application code. If this is done, [embassy::init_with_custom_irqs] must be used
//! method to pass the IRQ numbers to the library.
//!
//! ## Examples
//!
//! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy)
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use core::{
cell::{Cell, RefCell},
sync::atomic::{AtomicU32, Ordering},
};
use critical_section::{CriticalSection, Mutex};
use embassy_time_driver::{time_driver_impl, Driver, TICK_HZ};
use embassy_time_queue_utils::Queue;
use once_cell::sync::OnceCell;
use va416xx_hal::{
clock::Clocks,
enable_interrupt,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
pwm::ValidTim,
timer::{
assert_tim_reset_for_two_cycles, enable_tim_clk, get_tim_raw, TimRegInterface,
TIM_IRQ_OFFSET,
},
};
time_driver_impl!(
static TIME_DRIVER: TimerDriver = TimerDriver {
periods: AtomicU32::new(0),
alarms: Mutex::new(AlarmState::new()),
queue: Mutex::new(RefCell::new(Queue::new())),
});
/// Macro to define the IRQ handlers for the time driver.
///
/// By default, the code generated by this macro will be defined inside the library depending on
/// the feature flags specified. However, the macro is exported to allow users to specify the
/// interrupt handlers themselves.
///
/// Please note that you have to explicitely import the [macro@va108xx_hal::pac::interrupt]
/// macro in the application code in case this macro is used there.
#[macro_export]
macro_rules! embassy_time_driver_irqs {
(
timekeeper_irq = $timekeeper_irq:ident,
alarm_irq = $alarm_irq:ident
) => {
const TIMEKEEPER_IRQ: pac::Interrupt = pac::Interrupt::$timekeeper_irq;
#[interrupt]
#[allow(non_snake_case)]
fn $timekeeper_irq() {
// Safety: We call it once here.
unsafe { $crate::time_driver().on_interrupt_timekeeping() }
}
const ALARM_IRQ: pac::Interrupt = pac::Interrupt::$alarm_irq;
#[interrupt]
#[allow(non_snake_case)]
fn $alarm_irq() {
// Safety: We call it once here.
unsafe { $crate::time_driver().on_interrupt_alarm() }
}
};
}
// Provide three combinations of IRQs for the time driver by default.
#[cfg(feature = "irq-tim14-tim15")]
embassy_time_driver_irqs!(timekeeper_irq = TIM15, alarm_irq = TIM14);
#[cfg(feature = "irq-tim13-tim14")]
embassy_time_driver_irqs!(timekeeper_irq = TIM14, alarm_irq = TIM13);
#[cfg(feature = "irq-tim22-tim23")]
embassy_time_driver_irqs!(timekeeper_irq = TIM23, alarm_irq = TIM22);
/// Expose the time driver so the user can specify the IRQ handlers themselves.
pub fn time_driver() -> &'static TimerDriver {
&TIME_DRIVER
}
/// Initialization method for embassy
///
/// If the interrupt handlers are provided by the library, the ID of the
/// used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
/// can only be checked at run-time, and a run-time assertion will panic on the embassy
/// initialization in case of a missmatch.
///
/// # Safety
///
/// This has to be called once at initialization time to initiate the time driver for
/// embassy.
pub unsafe fn init<
TimekeeperTim: TimRegInterface + ValidTim,
AlarmTim: TimRegInterface + ValidTim,
>(
syscfg: &mut pac::Sysconfig,
irq_router: &pac::IrqRouter,
timekeeper: TimekeeperTim,
alarm: AlarmTim,
clocks: &Clocks,
) {
#[cfg(feature = "irqs-in-lib")]
assert_eq!(
TimekeeperTim::ID,
TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Timekeeper TIM and IRQ missmatch"
);
#[cfg(feature = "irqs-in-lib")]
assert_eq!(
AlarmTim::ID,
ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Alarm TIM and IRQ missmatch"
);
enable_and_init_irq_router(syscfg, irq_router);
TIME_DRIVER.init(syscfg, timekeeper, alarm, clocks)
}
struct AlarmState {
timestamp: Cell<u64>,
}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
}
}
}
unsafe impl Send for AlarmState {}
static SCALE: OnceCell<u64> = OnceCell::new();
static TIMEKEEPER_TIM: OnceCell<u8> = OnceCell::new();
static ALARM_TIM: OnceCell<u8> = OnceCell::new();
pub struct TimerDriver {
periods: AtomicU32,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<AlarmState>,
queue: Mutex<RefCell<Queue>>,
}
impl TimerDriver {
fn init<TimekeeperTim: TimRegInterface + ValidTim, AlarmTim: TimRegInterface + ValidTim>(
&self,
syscfg: &mut pac::Sysconfig,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
clocks: &Clocks,
) {
if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
return;
}
ALARM_TIM.set(alarm_tim.tim_id()).ok();
TIMEKEEPER_TIM.set(timekeeper_tim.tim_id()).ok();
enable_tim_clk(syscfg, timekeeper_tim.tim_id());
assert_tim_reset_for_two_cycles(syscfg, alarm_tim.tim_id());
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
SCALE
.set((TimekeeperTim::clock(clocks).raw() / TICK_HZ as u32) as u64)
.unwrap();
let timekeeper_tim_regs = timekeeper_tim.reg_block();
timekeeper_tim_regs
.rst_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Decrementing counter.
timekeeper_tim_regs
.cnt_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Switch on. Timekeeping should always be done.
unsafe {
enable_interrupt(TimekeeperTim::IRQ);
}
timekeeper_tim_regs
.ctrl()
.modify(|_, w| w.irq_enb().set_bit());
timekeeper_tim_regs.enable().write(|w| unsafe { w.bits(1) });
enable_tim_clk(syscfg, AlarmTim::ID);
assert_tim_reset_for_two_cycles(syscfg, AlarmTim::ID);
let alarm_tim_regs = alarm_tim.reg_block();
// Explicitely disable alarm timer until needed.
alarm_tim_regs.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(AlarmTim::IRQ);
}
}
fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock {
TIMEKEEPER_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
fn alarm_tim() -> &'static pac::tim0::RegisterBlock {
ALARM_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
/// Should be called inside the IRQ of the timekeeper timer.
///
/// # Safety
///
/// This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_timekeeping(&self) {
self.next_period();
}
/// Should be called inside the IRQ of the alarm timer.
///
/// # Safety
///
///This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_alarm(&self) {
critical_section::with(|cs| {
if self.alarms.borrow(cs).timestamp.get() <= self.now() {
self.trigger_alarm(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| {
let alarm = &self.alarms.borrow(cs);
let at = alarm.timestamp.get();
if at < t {
self.trigger_alarm(cs);
} else {
let alarm_tim = Self::alarm_tim();
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 trigger_alarm(&self, cs: CriticalSection) {
Self::alarm_tim().ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = &self.alarms.borrow(cs);
// Setting the maximum value disables the alarm.
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
let mut next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
}
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
if SCALE.get().is_none() {
return false;
}
let alarm_tim = Self::alarm_tim();
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = self.alarms.borrow(cs);
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).checked_mul(*SCALE.get().unwrap());
alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) {
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(timer_ticks.unwrap() 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
}
}
impl Driver for TimerDriver {
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 - Self::timekeeper_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;
}
}
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
})
}
}

View File

@ -43,7 +43,7 @@ macro_rules! pwm_common_func {
#[inline]
fn enable_pwm_a(&mut self) {
self.reg
.reg()
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
}
@ -51,7 +51,7 @@ macro_rules! pwm_common_func {
#[inline]
fn enable_pwm_b(&mut self) {
self.reg
.reg()
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
}
@ -71,19 +71,25 @@ macro_rules! pwm_common_func {
self.pwm_base.current_rst_val =
self.pwm_base.clock.raw() / self.pwm_base.current_period.raw();
self.reg
.reg()
.reg_block()
.rst_value()
.write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) });
}
#[inline]
pub fn disable(&mut self) {
self.reg.reg().ctrl().modify(|_, w| w.enable().clear_bit());
self.reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
}
#[inline]
pub fn enable(&mut self) {
self.reg.reg().ctrl().modify(|_, w| w.enable().set_bit());
self.reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().set_bit());
}
#[inline]
@ -120,7 +126,7 @@ macro_rules! pwmb_func {
* self.pwm_base.current_lower_limit as u64)
/ DUTY_MAX as u64;
self.reg
.reg()
.reg_block()
.pwmb_value()
.write(|w| unsafe { w.bits(pwmb_val as u32) });
}
@ -137,7 +143,7 @@ macro_rules! pwmb_func {
* self.pwm_base.current_duty as u64)
/ DUTY_MAX as u64;
self.reg
.reg()
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
}
@ -348,7 +354,7 @@ impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin {
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
/ DUTY_MAX as u64;
self.reg
.reg()
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
Ok(())
@ -368,7 +374,7 @@ impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::SetDutyCycle for PwmPin<Pin,
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
/ DUTY_MAX as u64;
self.reg
.reg()
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
Ok(())

View File

@ -29,16 +29,54 @@ use crate::{enable_interrupt, pac};
pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
pub const TIM_IRQ_OFFSET: usize = 48;
/// Get the peripheral block of a TIM peripheral given the index.
///
/// This function panics if the given index is greater than 23.
///
/// # Safety
///
/// This returns a direct handle to the peripheral block, which allows to circumvent ownership
/// rules for the peripheral block. You have to ensure that the retrieved peripheral block is not
/// used by any other software component.
#[inline(always)]
pub const unsafe fn get_tim_raw(tim_idx: usize) -> &'static pac::tim0::RegisterBlock {
match tim_idx {
0 => unsafe { &*pac::Tim0::ptr() },
1 => unsafe { &*pac::Tim1::ptr() },
2 => unsafe { &*pac::Tim2::ptr() },
3 => unsafe { &*pac::Tim3::ptr() },
4 => unsafe { &*pac::Tim4::ptr() },
5 => unsafe { &*pac::Tim5::ptr() },
6 => unsafe { &*pac::Tim6::ptr() },
7 => unsafe { &*pac::Tim7::ptr() },
8 => unsafe { &*pac::Tim8::ptr() },
9 => unsafe { &*pac::Tim9::ptr() },
10 => unsafe { &*pac::Tim10::ptr() },
11 => unsafe { &*pac::Tim11::ptr() },
12 => unsafe { &*pac::Tim12::ptr() },
13 => unsafe { &*pac::Tim13::ptr() },
14 => unsafe { &*pac::Tim14::ptr() },
15 => unsafe { &*pac::Tim15::ptr() },
16 => unsafe { &*pac::Tim16::ptr() },
17 => unsafe { &*pac::Tim17::ptr() },
18 => unsafe { &*pac::Tim18::ptr() },
19 => unsafe { &*pac::Tim19::ptr() },
20 => unsafe { &*pac::Tim20::ptr() },
21 => unsafe { &*pac::Tim21::ptr() },
22 => unsafe { &*pac::Tim22::ptr() },
23 => unsafe { &*pac::Tim23::ptr() },
_ => {
panic!("invalid alarm timer index")
}
}
}
//==================================================================================================
// Defintions
//==================================================================================================
/// Interrupt events
//pub enum Event {
/// Timer timed out / count down ended
//TimeOut,
//}
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
pub struct CascadeCtrl {
/// Enable Cascade 0 signal active as a requirement for counting
@ -143,11 +181,11 @@ pub trait TimPin {
pub trait ValidTim {
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
const TIM_ID: u8;
const ID: u8;
const IRQ: pac::Interrupt;
fn clock(clocks: &Clocks) -> Hertz {
if Self::TIM_ID <= 15 {
if Self::ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
@ -163,7 +201,7 @@ macro_rules! tim_markers {
) => {
$(
impl ValidTim for $TimX {
const TIM_ID: u8 = $id;
const ID: u8 = $id;
const IRQ: pac::Interrupt = $Irq;
}
)+
@ -171,7 +209,7 @@ macro_rules! tim_markers {
}
pub const fn const_clock<Tim: ValidTim + ?Sized>(_: &Tim, clocks: &Clocks) -> Hertz {
if Tim::TIM_ID <= 15 {
if Tim::ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
@ -371,7 +409,7 @@ pub type TimRegBlock = pac::tim0::RegisterBlock;
/// implementations should be overridden. The implementing type must also have
/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
/// pin ID is a singleton.
pub(super) unsafe trait TimRegInterface {
pub unsafe trait TimRegInterface {
fn tim_id(&self) -> u8;
const PORT_BASE: *const pac::tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
@ -379,7 +417,7 @@ pub(super) unsafe trait TimRegInterface {
/// All 24 TIM blocks are identical. This helper functions returns the correct
/// memory mapped peripheral depending on the TIM ID.
#[inline(always)]
fn reg(&self) -> &TimRegBlock {
fn reg_block(&self) -> &TimRegBlock {
unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) }
}
@ -406,6 +444,12 @@ pub(super) unsafe trait TimRegInterface {
}
}
unsafe impl<Tim: ValidTim> TimRegInterface for Tim {
fn tim_id(&self) -> u8 {
Tim::ID
}
}
/// Provide a safe register interface for [`ValidTimAndPin`]s
///
/// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to
@ -433,7 +477,7 @@ impl<TIM: ValidTim> TimRegister<TIM> {
unsafe impl<Tim: ValidTim> TimRegInterface for TimRegister<Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::TIM_ID
Tim::ID
}
}
@ -454,7 +498,7 @@ where
unsafe impl<Pin: TimPin, Tim: ValidTim> TimRegInterface for TimAndPinRegister<Pin, Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::TIM_ID
Tim::ID
}
}
@ -467,7 +511,7 @@ pub(super) struct TimDynRegister {
impl<Pin: TimPin, Tim: ValidTim> From<TimAndPinRegister<Pin, Tim>> for TimDynRegister {
fn from(_reg: TimAndPinRegister<Pin, Tim>) -> Self {
Self {
tim_id: Tim::TIM_ID,
tim_id: Tim::ID,
pin_id: Pin::DYN,
}
}
@ -504,10 +548,10 @@ pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
}
unsafe impl<TIM: ValidTim> TimRegInterface for CountdownTimer<TIM> {
unsafe impl<Tim: ValidTim> TimRegInterface for CountdownTimer<Tim> {
#[inline]
fn tim_id(&self) -> u8 {
TIM::TIM_ID
Tim::ID
}
}
@ -517,11 +561,11 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
/// You can use [Self::start] to start the countdown timer, and you may optionally call
/// [Self::listen] to enable interrupts for the TIM peripheral as well.
pub fn new(syscfg: &mut pac::Sysconfig, tim: Tim, clocks: &Clocks) -> Self {
enable_tim_clk(syscfg, Tim::TIM_ID);
assert_tim_reset(syscfg, Tim::TIM_ID);
enable_tim_clk(syscfg, Tim::ID);
assert_tim_reset(syscfg, Tim::ID);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_tim_reset(syscfg, Tim::TIM_ID);
deassert_tim_reset(syscfg, Tim::ID);
CountdownTimer {
tim: unsafe { TimRegister::new(tim) },
@ -551,7 +595,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
/// flag and restart the time if configured correctly
pub fn wait(&mut self) -> nb::Result<(), void::Void> {
let cnt = self.tim.reg().cnt_value().read().bits();
let cnt = self.tim.reg_block().cnt_value().read().bits();
if (cnt > self.last_cnt) || cnt == 0 {
self.last_cnt = self.rst_val;
Ok(())
@ -563,7 +607,10 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
#[inline]
pub fn stop(&mut self) {
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
}
#[inline]
@ -575,26 +622,38 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
#[inline(always)]
pub fn enable_interrupt(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().set_bit());
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.irq_enb().set_bit());
}
#[inline(always)]
pub fn disable_interrupt(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().clear_bit());
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.irq_enb().clear_bit());
}
#[inline]
pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim {
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::TIM_ID)) });
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::ID)) });
self.tim.release()
}
/// Load the count down timer with a timeout but do not start it.
pub fn load(&mut self, timeout: impl Into<Hertz>) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
self.curr_freq = timeout.into();
self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1;
self.set_reload(self.rst_val);
@ -604,27 +663,39 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
#[inline(always)]
pub fn set_reload(&mut self, val: u32) {
self.tim.reg().rst_value().write(|w| unsafe { w.bits(val) });
self.tim
.reg_block()
.rst_value()
.write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn set_count(&mut self, val: u32) {
self.tim.reg().cnt_value().write(|w| unsafe { w.bits(val) });
self.tim
.reg_block()
.cnt_value()
.write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn count(&self) -> u32 {
self.tim.reg().cnt_value().read().bits()
self.tim.reg_block().cnt_value().read().bits()
}
#[inline(always)]
pub fn enable(&mut self) {
self.tim.reg().enable().write(|w| unsafe { w.bits(1) });
self.tim
.reg_block()
.enable()
.write(|w| unsafe { w.bits(1) });
}
#[inline(always)]
pub fn disable(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
}
/// Disable the counter, setting both enable and active bit to 0
@ -632,12 +703,12 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn auto_disable(self, enable: bool) -> Self {
if enable {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().set_bit());
} else {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().clear_bit());
}
@ -652,12 +723,12 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn auto_deactivate(self, enable: bool) -> Self {
if enable {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().set_bit());
} else {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().clear_bit());
}
@ -667,7 +738,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
/// Configure the cascade parameters
#[inline]
pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
self.tim.reg().csd_ctrl().write(|w| {
self.tim.reg_block().csd_ctrl().write(|w| {
w.csden0().bit(ctrl.enb_start_src_csd0);
w.csdinv0().bit(ctrl.inv_csd0);
w.csden1().bit(ctrl.enb_start_src_csd1);
@ -685,7 +756,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.reg_block()
.cascade0()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
@ -695,7 +766,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.reg_block()
.cascade1()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
@ -705,7 +776,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.reg_block()
.cascade2()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())