added and created embassy library
This commit is contained in:
parent
13a86ac291
commit
69f8671412
@ -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",
|
||||
|
@ -3,6 +3,7 @@ resolver = "2"
|
||||
members = [
|
||||
"va416xx",
|
||||
"va416xx-hal",
|
||||
"va416xx-embassy",
|
||||
"vorago-peb1",
|
||||
"bootloader",
|
||||
"flashloader",
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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"]
|
||||
|
@ -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,
|
||||
|
@ -1,6 +1,2 @@
|
||||
#![no_std]
|
||||
pub mod time_driver;
|
||||
|
||||
pub const EXTCLK_FREQ: u32 = 40_000_000;
|
||||
|
||||
pub use time_driver::init;
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
|
13
va416xx-embassy/CHANGELOG.md
Normal file
13
va416xx-embassy/CHANGELOG.md
Normal 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
|
37
va416xx-embassy/Cargo.toml
Normal file
37
va416xx-embassy/Cargo.toml
Normal 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
398
va416xx-embassy/src/lib.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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(())
|
||||
|
@ -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(())
|
||||
|
Loading…
x
Reference in New Issue
Block a user