Skip to content

Commit

Permalink
Merge pull request #18 from decaday/feat/systick-time-driver
Browse files Browse the repository at this point in the history
systick-time-driver support
  • Loading branch information
decaday authored Dec 4, 2024
2 parents 1fcf0e5 + 53f3681 commit 2c3577b
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 6 deletions.
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ defmt-rtt = { version = "0.4", optional = true }
log = { version = "0.4", optional = true }
critical-section = "1.2"
cfg-if = "1.0.0"

portable-atomic = { version = "1", features = ["unsafe-assume-single-core", "require-cas"], optional = true }

futures-util = { version = "0.3.30", default-features = false }
embassy-hal-internal = { version = "0.2.0", features = [
Expand Down Expand Up @@ -98,6 +98,12 @@ time-driver-tim1 = ["_time-driver"]
time-driver-tim3 = ["_time-driver"]
time-driver-tim15 = ["_time-driver"]

time-driver-systick = ["portable-atomic"]

# td == time-driver, to avoid confliction
# By default, only one alarm is provided (similar to a 2-channel timer). Enabling this feature provides three alarms (similar to a 4-channel timer).
# Of course, this will also increase the execution time of the interrupt handler.
td-systick-multi-alarms = ["time-driver-systick"]

_time-driver = []

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ For a full list of chip capabilities and peripherals, check the [py32-data](http
- TODO: I haven't got a dev board yet, help-wanted
- N/A: Not available

### TODOs
## TODOs

Too many...

Expand All @@ -88,7 +88,9 @@ Too many...

This crate provides an implementation of the Embassy `time-driver`.

Embassy requires that any TIM used as a time-driver has at least two channels, so only TIM1 and TIM3 are available for the PY32F030, 003, and 002A series. You can select either `time-driver-tim3` or `time-driver-tim1` to specify the TIM to use.
Embassy requires that any TIM used as a time-driver has at least two channels, so only TIM1 and TIM3 are available for the PY32F030, 003, and 002A series. You can select either `time-driver-tim3` or `time-driver-tim1` to specify the TIM to use.

`time-driver-systick`: Although we do not recommend using it and there are some shortcomings, it does work. For details, please see [systick-demo](examples/systick-time-driver-f030/README.md)

## Minimum supported Rust version(MSRV)

Expand Down
3 changes: 2 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ fn main() {
.to_ascii_lowercase(),
),
Err(GetOneError::None) => None,
Err(GetOneError::Multiple) => panic!("Multiple py32xx Cargo features enabled"),
Err(GetOneError::Multiple) => panic!("Multiple time-driver-xxx Cargo features enabled"),
};

let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) {
Expand All @@ -212,6 +212,7 @@ fn main() {
Some("tim22") => "TIM22",
Some("tim23") => "TIM23",
Some("tim24") => "TIM24",
Some("systick") => "",
Some("any") => {
// Order of TIM candidators:
// 1. 2CH -> 2CH_CMP -> GP16 -> GP32 -> ADV
Expand Down
11 changes: 11 additions & 0 deletions examples/systick-time-driver-f030/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[target.thumbv6m-none-eabi]
# probe-rs chip list | grep -i PY32
runner = 'probe-rs run --chip PY32F030x6'

[build]
target = "thumbv6m-none-eabi"

[env]
DEFMT_LOG = "info"

# rustflags = ["-C", "link-arg=-Tlink.x"]
46 changes: 46 additions & 0 deletions examples/systick-time-driver-f030/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "systick-time-driver-f030"
version = "0.1.1"
edition = "2021"

[dependencies]
panic-halt = "1.0.0"
cortex-m = { version = "0.7.7", features = [
"critical-section-single-core",
"critical-section",
] }
cortex-m-rt = "0.7.3"
cortex-m-semihosting = { version = "0.5" }
panic-probe = { version = "0.3", features = ["print-defmt"] }

embassy-sync = { version = "0.6.0", features = ["defmt"] }
embassy-executor = { version = "0.6.1", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-1_000"] }

embedded-io = { version = "0.6.0" }
embedded-io-async = { version = "0.6.1" }

py32-hal = { path = "../../", features = [ "time-driver-systick", "py32f030k28"]}

defmt = "0.3"
defmt-rtt = "0.4"

# cargo build/run
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
opt-level = 'z' # <-
overflow-checks = true # <-

# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = 'fat'
opt-level = 3 # <-
overflow-checks = false # <-

42 changes: 42 additions & 0 deletions examples/systick-time-driver-f030/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Systick Time-Driver Demo

Although we do not recommend using it and there are some shortcomings, it does work (after all, some chips lack sufficient multi-channel timers).

Currently, the `systick-time-driver` implementation in `py32-hal` uses a retry loop. The reload value register (RVR) of the Systick timer is set to a fixed value, causing high-frequency interrupts. During these interrupts, it updates the tick count and checks whether any alarm has expired (since Systick lacks compare-capture functionality).

The interrupt frequency of Systick matches the tick frequency configured for `embassy-time`. It is **not recommended** to use excessively high frequencies because `time-driver-systick` generates frequent interrupts, unlike `time-driver-timx`. Additionally, `time-driver-systick` updates the tick count and wakes up alarms in software, which takes longer.

### Usage

Enable the `time-driver-systick` feature. Then, before initializing `py32-hal`, add the following:

```rust
use cortex_m::Peripherals;
let cp = Peripherals::take().unwrap();
let systick = cp.SYST;
```

Pass the `systick` instance during initialization:

```rust
let p = py32_hal::init(Default::default(), systick);
```

Complete code demo can be found in the same directory as this documentation.

### Feature: `td-systick-multi-alarms`

By default, only one alarm is provided (similar to a 2-channel timer). Enabling this feature provides three alarms (similar to a 4-channel timer).

Of course, this will also increase the execution time of the interrupt handler.

### More

[SysTick time driver · Issue #786 · embassy-rs/embassy](https://github.com/embassy-rs/embassy/issues/786)

Here are the key disadvantages of using **SysTick** as a time-driver:

- **Clock Source Uncertainty**: SysTick supports two clock sources, but their speeds are not known at compile time, requiring vendor-specific runtime translation.
- **Interrupt Limitation**: SysTick can only trigger an interrupt at zero, complicating setting alarms for specific points in the cycle.
- **Dual-Core Challenges**: On dual-core devices like the RP2040, each core has an independent SysTick timer with no cross-access.
- **Perturbation Issues**: Modifying the counter register while counting can perturb the overall tick count, affecting timing accuracy.
10 changes: 10 additions & 0 deletions examples/systick-time-driver-f030/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fn main() {
// `--nmagic` is required if memory section addresses are not aligned to 0x10000,
// for example the FLASH and RAM sections in your `memory.x`.
// See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
println!("cargo:rustc-link-arg=--nmagic");

println!("cargo:rustc-link-arg=-Tlink.x");

println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
}
42 changes: 42 additions & 0 deletions examples/systick-time-driver-f030/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#![no_std]
#![no_main]
#![feature(impl_trait_in_assoc_type)]

use embassy_time::Timer;
use py32_hal::gpio::{Level, Output, Speed};
use py32_hal::rcc::{Pll, PllSource, Sysclk};
use py32_hal::time::Hertz;
use embassy_executor::Spawner;

use cortex_m::Peripherals;
use defmt::*;
use {defmt_rtt as _, panic_halt as _};


#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let cp = Peripherals::take().unwrap();
let systick = cp.SYST;

let mut cfg: py32_hal::Config = Default::default();
cfg.rcc.hsi = Some(Hertz::mhz(24));
cfg.rcc.pll = Some(Pll {
src: PllSource::HSI,
});
cfg.rcc.sys = Sysclk::PLL;
let p = py32_hal::init(cfg, systick);

info!("Hello World!");

let mut led = Output::new(p.PB1, Level::High, Speed::Low);

loop {
info!("high");
led.set_high();
Timer::after_millis(1000).await;

info!("low");
led.set_low();
Timer::after_millis(1000).await;
}
}
23 changes: 21 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,22 @@ pub mod usart;
pub mod timer;
#[cfg(feature = "_time-driver")]
pub mod time_driver;

#[cfg(feature = "time-driver-systick")]
pub mod systick_time_driver;
pub mod gpio;

#[cfg(feature = "exti")]
pub mod exti;

#[cfg(all(feature = "_time-driver", feature = "time-driver-systick"))]
compile_error!(
"The `time-driver-systick` feature is incompatible with the `time-driver-timxx` feature. "
);

#[cfg(feature = "time-driver-systick")]
use cortex_m::peripheral::SYST;

/// `py32-hal` global configuration.
#[non_exhaustive]
#[derive(Clone, Copy)]
Expand Down Expand Up @@ -104,17 +115,25 @@ impl Default for Config {
/// This returns the peripheral singletons that can be used for creating drivers.
///
/// This should only be called once at startup, otherwise it panics.
pub fn init(config: Config) -> Peripherals {
pub fn init(
config: Config,
#[cfg(feature = "time-driver-systick")]
systick: SYST,
) -> Peripherals {
critical_section::with(|cs| {
let p = Peripherals::take_with_cs(cs);
unsafe {
rcc::init(config.rcc);
gpio::init(cs);

#[cfg(feature = "_time-driver")]
// must be after rcc init
#[cfg(feature = "_time-driver")]
time_driver::init(cs);

#[cfg(feature = "time-driver-systick")]
systick_time_driver::init(cs, systick);


#[cfg(feature = "exti")]
exti::init(cs);
};
Expand Down
Loading

0 comments on commit 2c3577b

Please sign in to comment.