12 Commits

Author SHA1 Message Date
dfab81a813 Merge pull request 'smaller improvements' (#28) from more-smaller-improvements into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #28
2024-09-18 12:33:57 +02:00
2c0728102a smaller improvements
Some checks are pending
Rust/va416xx-rs/pipeline/head Build started...
2024-09-18 12:31:02 +02:00
5ce2dae236 Merge pull request 'prepare next HAL release' (#27) from prep-hal-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #27
2024-09-18 12:13:15 +02:00
19c64a8257 prepare next HAL release
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-09-18 12:01:27 +02:00
4ed0a806a6 Merge pull request 'Start adding embassy example' (#24) from add-embassy-example into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #24
2024-09-17 22:08:31 +02:00
a1a83700f8 Add embassy example
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-17 21:30:57 +02:00
ed175a03fc Merge pull request 'update VS Code settings for RTT block detection' (#26) from add-rtt-addr-detection-update-vscode into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #26
2024-09-17 10:55:14 +02:00
2465248223 update VS Code settings for RTT block detection 2024-09-17 10:54:46 +02:00
1603de11bf Merge pull request 'Make flashload COM more reliable' (#25) from flashloader-use-uart-irq into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #25
2024-09-13 18:27:14 +02:00
78dd7ee5c3 Make flashload COM more reliable
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...
Fixes for UART RX with IRQ implementation
2024-09-13 18:26:12 +02:00
cad968342a Merge pull request 'bootloader and flashloader' (#21) from bootloader-flashloader into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #21
2024-09-12 19:48:24 +02:00
d386e5f6a1 Bootloader and Flashloader App
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-12 19:41:54 +02:00
30 changed files with 1403 additions and 568 deletions

View File

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

View File

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

View File

@ -24,7 +24,11 @@ It also contains the following helper crates:
- The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader) - The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
crate contains a sample flashloader which is able to update the redundant images in the NVM which crate contains a sample flashloader which is able to update the redundant images in the NVM which
is compatible to the provided bootloader as well. is compatible to the provided bootloader as well.
- The `examples` folder contains various example applications crates for the HAL and the PAC. - The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples)
folder contains various example applications crates using the HAL and the PAC.
This folder also contains dedicated example applications using the
[`RTIC`](https://rtic.rs/2/book/en/) and [`embassy`](https://github.com/embassy-rs/embassy)
native Rust RTOSes.
## Using the `.cargo/config.toml` file ## Using the `.cargo/config.toml` file
@ -96,7 +100,9 @@ example.
### Using VS Code ### Using VS Code
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). Please make sure that
[`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146)
are installed as well.
Some sample configuration files for VS code were provided and can be used by running Some sample configuration files for VS code were provided and can be used by running
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug` `cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
@ -111,4 +117,5 @@ configuration variables in your `settings.json`:
- `"cortex-debug.gdbPath.osx"` - `"cortex-debug.gdbPath.osx"`
The provided VS Code configurations also provide an integrated RTT logger, which you can access The provided VS Code configurations also provide an integrated RTT logger, which you can access
via the terminal at `RTT Ch:0 console`. 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.

View File

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

25
examples/README.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

@ -4,11 +4,11 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
critical-section = "1"
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
rtic-sync = { version = "1.3", features = ["defmt-03"] }
embedded-hal = "1" embedded-hal = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
nb = "1" nb = "1"
@ -23,8 +23,16 @@ path = "../../va416xx-hal"
path = "../../vorago-peb1" path = "../../vorago-peb1"
optional = true optional = true
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]
[features] [features]
default = [] default = ["va41630"]
va41630 = ["va416xx-hal/va41630", "has-adc-dac"] va41630 = ["va416xx-hal/va41630", "has-adc-dac"]
va41629 = ["va416xx-hal/va41629", "has-adc-dac"] va41629 = ["va416xx-hal/va41629", "has-adc-dac"]
va41628 = ["va416xx-hal/va41628"] va41628 = ["va416xx-hal/va41628"]

View File

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

View File

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

View File

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

View File

@ -1,12 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from spacepackets.ecss import RequestId
from spacepackets.ecss.defs import PusService from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm from spacepackets.ecss.tm import PusTm
import toml import toml
import struct import struct
import logging import logging
import argparse import argparse
import threading
import time import time
import enum import enum
from tmtccmd.com.serial_base import SerialCfg from tmtccmd.com.serial_base import SerialCfg
@ -45,6 +43,7 @@ ACTION_SERVICE = 8
RAW_MEMORY_WRITE_SUBSERVICE = 2 RAW_MEMORY_WRITE_SUBSERVICE = 2
BOOT_NVM_MEMORY_ID = 1 BOOT_NVM_MEMORY_ID = 1
PING_PAYLOAD_SIZE = 0
class ActionId(enum.IntEnum): class ActionId(enum.IntEnum):
@ -104,6 +103,29 @@ def main() -> int:
com_if = SerialCobsComIF(serial_cfg) com_if = SerialCobsComIF(serial_cfg)
com_if.open() com_if.open()
file_path = None file_path = None
if args.ping:
_LOGGER.info("Sending ping command")
ping_tc = PusTc(
apid=0x00,
service=PusService.S17_TEST,
subservice=1,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes(PING_PAYLOAD_SIZE),
)
verificator.add_tc(ping_tc)
com_if.send(ping_tc.pack())
data_available = com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no ping reply received")
for reply in com_if.receive():
result = verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info("received ping completion reply")
if not args.target:
return 0
if args.target: if args.target:
if not args.corrupt: if not args.corrupt:
if not args.path: if not args.path:
@ -113,15 +135,6 @@ def main() -> int:
if not file_path.exists(): if not file_path.exists():
_LOGGER.error("File does not exist") _LOGGER.error("File does not exist")
return -1 return -1
if args.ping:
_LOGGER.info("Sending ping command")
ping_tc = PusTc(
apid=0x00,
service=PusService.S17_TEST,
subservice=1,
seq_count=SEQ_PROVIDER.get_and_increment(),
)
com_if.send(ping_tc.pack())
if args.corrupt: if args.corrupt:
if not args.target: if not args.target:
_LOGGER.error("target for corruption command required") _LOGGER.error("target for corruption command required")
@ -254,7 +267,7 @@ def main() -> int:
): ):
done = True done = True
# Still keep a small delay # Still keep a small delay
time.sleep(0.01) # time.sleep(0.05)
verificator.remove_completed_entries() verificator.remove_completed_entries()
if done: if done:
break break

View File

@ -18,13 +18,11 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use embedded_hal_nb::serial::Read;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use panic_rtt_target as _; use panic_rtt_target as _;
use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt}; use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt};
const EXTCLK_FREQ: u32 = 40_000_000; const EXTCLK_FREQ: u32 = 40_000_000;
const COBS_FRAME_SEPARATOR: u8 = 0x0;
const MAX_TC_SIZE: usize = 1024; const MAX_TC_SIZE: usize = 1024;
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE); const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
@ -33,10 +31,8 @@ const MAX_TM_SIZE: usize = 128;
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE); const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
const UART_BAUDRATE: u32 = 115200; const UART_BAUDRATE: u32 = 115200;
const SERIAL_RX_WIRETAPPING: bool = false;
const COBS_RX_DEBUGGING: bool = false;
const BOOT_NVM_MEMORY_ID: u8 = 1; const BOOT_NVM_MEMORY_ID: u8 = 1;
const RX_DEBUGGING: bool = false;
pub enum ActionId { pub enum ActionId {
CorruptImageA = 128, CorruptImageA = 128,
@ -62,13 +58,24 @@ use ringbuf::{
CachingCons, StaticProd, StaticRb, CachingCons, StaticProd, StaticRb,
}; };
const BUF_RB_SIZE_TX: usize = 1024; // Larger buffer for TC to be able to hold the possibly large memory write packets.
const SIZES_RB_SIZE_TX: usize = 16; const BUF_RB_SIZE_TC: usize = 2048;
const SIZES_RB_SIZE_TC: usize = 16;
static mut BUF_RB_TX: Lazy<StaticRb<u8, BUF_RB_SIZE_TX>> = const BUF_RB_SIZE_TM: usize = 512;
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TX>::default); const SIZES_RB_SIZE_TM: usize = 16;
static mut SIZES_RB_TX: Lazy<StaticRb<usize, SIZES_RB_SIZE_TX>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TX>::default); // Ring buffers to handling variable sized telemetry
static mut BUF_RB_TM: Lazy<StaticRb<u8, BUF_RB_SIZE_TM>> =
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TM>::default);
static mut SIZES_RB_TM: Lazy<StaticRb<usize, SIZES_RB_SIZE_TM>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TM>::default);
// Ring buffers to handling variable sized telecommands
static mut BUF_RB_TC: Lazy<StaticRb<u8, BUF_RB_SIZE_TC>> =
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TC>::default);
static mut SIZES_RB_TC: Lazy<StaticRb<usize, SIZES_RB_SIZE_TC>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TC>::default);
pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> { pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf_prod: StaticProd<'static, u8, BUF_SIZE>, pub buf_prod: StaticProd<'static, u8, BUF_SIZE>,
@ -91,21 +98,17 @@ pub const APP_B_END_ADDR: u32 = 0x40000;
mod app { mod app {
use super::*; use super::*;
use cortex_m::asm; use cortex_m::asm;
use embedded_hal_nb::nb;
use embedded_io::Write; use embedded_io::Write;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtic::Mutex; use rtic::Mutex;
use rtic_monotonics::systick::prelude::*; use rtic_monotonics::systick::prelude::*;
use rtic_sync::{
channel::{Receiver, Sender},
make_channel,
};
use rtt_target::rprintln; use rtt_target::rprintln;
use satrs::pus::verification::VerificationReportCreator; use satrs::pus::verification::VerificationReportCreator;
use spacepackets::ecss::PusServiceId; use spacepackets::ecss::PusServiceId;
use spacepackets::ecss::{ use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket, tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
}; };
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::{ use va416xx_hal::{
clock::ClkgenExt, clock::ClkgenExt,
edac, edac,
@ -127,26 +130,24 @@ mod app {
#[local] #[local]
struct Local { struct Local {
uart_rx: uart::Rx<pac::Uart0>, uart_rx: uart::RxWithIrq<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>, uart_tx: uart::Tx<pac::Uart0>,
cobs_reader_state: CobsReaderStates,
tc_tx: TcTx,
tc_rx: TcRx,
rom_spi: Option<pac::Spi3>, rom_spi: Option<pac::Spi3>,
tx_cons: DataConsumer<BUF_RB_SIZE_TX, SIZES_RB_SIZE_TX>, // We handle all TM in one task.
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
// We consume all TC in one task.
tc_cons: DataConsumer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
// We produce all TC in one task.
tc_prod: DataProducer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
verif_reporter: VerificationReportCreator, verif_reporter: VerificationReportCreator,
} }
#[shared] #[shared]
struct Shared { struct Shared {
decode_buffer_busy: bool, // Having this shared allows multiple tasks to generate telemetry.
decode_buf: [u8; MAX_TC_SIZE], tm_prod: DataProducer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
tx_prod: DataProducer<BUF_RB_SIZE_TX, SIZES_RB_SIZE_TX>,
} }
pub type TcTx = Sender<'static, usize, 2>;
pub type TcRx = Receiver<'static, usize, 2>;
rtic_monotonics::systick_monotonic!(Mono, 10_000); rtic_monotonics::systick_monotonic!(Mono, 10_000);
#[init] #[init]
@ -163,6 +164,7 @@ mod app {
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ)) .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut cx.device.sysconfig) .freeze(&mut cx.device.sysconfig)
.unwrap(); .unwrap();
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
setup_edac(&mut cx.device.sysconfig); setup_edac(&mut cx.device.sysconfig);
let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
@ -176,38 +178,45 @@ mod app {
&mut cx.device.sysconfig, &mut cx.device.sysconfig,
&clocks, &clocks,
); );
let (tx, rx) = uart0.split(); let (tx, mut rx, _) = uart0.split_with_irq();
let (tc_tx, tc_rx) = make_channel!(usize, 2);
let verif_reporter = VerificationReportCreator::new(0).unwrap(); let verif_reporter = VerificationReportCreator::new(0).unwrap();
let (buf_prod, buf_cons) = unsafe { BUF_RB_TX.split_ref() }; let (buf_prod_tm, buf_cons_tm) = unsafe { BUF_RB_TM.split_ref() };
let (sizes_prod, sizes_cons) = unsafe { SIZES_RB_TX.split_ref() }; let (sizes_prod_tm, sizes_cons_tm) = unsafe { SIZES_RB_TM.split_ref() };
let (buf_prod_tc, buf_cons_tc) = unsafe { BUF_RB_TC.split_ref() };
let (sizes_prod_tc, sizes_cons_tc) = unsafe { SIZES_RB_TC.split_ref() };
Mono::start(cx.core.SYST, clocks.sysclk().raw()); Mono::start(cx.core.SYST, clocks.sysclk().raw());
CLOCKS.set(clocks).unwrap(); CLOCKS.set(clocks).unwrap();
rx.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
.expect("initiating UART RX failed");
pus_tc_handler::spawn().unwrap(); pus_tc_handler::spawn().unwrap();
uart_reader_task::spawn().unwrap();
pus_tm_tx_handler::spawn().unwrap(); pus_tm_tx_handler::spawn().unwrap();
( (
Shared { Shared {
decode_buffer_busy: false, tm_prod: DataProducer {
decode_buf: [0; MAX_TC_SIZE], buf_prod: buf_prod_tm,
tx_prod: DataProducer { sizes_prod: sizes_prod_tm,
buf_prod,
sizes_prod,
}, },
}, },
Local { Local {
uart_rx: rx, uart_rx: rx,
uart_tx: tx, uart_tx: tx,
cobs_reader_state: CobsReaderStates::default(),
tc_tx,
tc_rx,
rom_spi: Some(cx.device.spi3), rom_spi: Some(cx.device.spi3),
tx_cons: DataConsumer { tm_cons: DataConsumer {
buf_cons, buf_cons: buf_cons_tm,
sizes_cons, sizes_cons: sizes_cons_tm,
},
tc_cons: DataConsumer {
buf_cons: buf_cons_tc,
sizes_cons: sizes_cons_tc,
},
tc_prod: DataProducer {
buf_prod: buf_prod_tc,
sizes_prod: sizes_prod_tc,
}, },
verif_reporter, verif_reporter,
}, },
@ -223,120 +232,62 @@ mod app {
} }
#[task( #[task(
priority = 4, binds = UART0_RX,
local=[ local = [
read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
uart_rx, uart_rx,
cobs_reader_state, tc_prod
tc_tx
], ],
shared=[decode_buffer_busy, decode_buf]
)] )]
async fn uart_reader_task(mut cx: uart_reader_task::Context) { fn uart_rx_irq(cx: uart_rx_irq::Context) {
let mut current_idx = 0; match cx.local.uart_rx.irq_handler(cx.local.rx_buf) {
loop { Ok(result) => {
match cx.local.uart_rx.read() { if RX_DEBUGGING {
Ok(byte) => { log::debug!("RX Info: {:?}", cx.local.uart_rx.irq_info());
if SERIAL_RX_WIRETAPPING { log::debug!("RX Result: {:?}", result);
log::debug!("RX Byte: 0x{:x?}", byte);
}
handle_single_rx_byte(&mut cx, byte, &mut current_idx)
} }
Err(e) => { if result.complete() {
match e { // Check frame validity (must have COBS format) and decode the frame.
nb::Error::Other(e) => { // Currently, we expect a full frame or a frame received through a timeout
log::warn!("UART error: {:?}", e); // to be one COBS frame. We could parse for multiple COBS packets in one
match e { // frame, but the additional complexity is not necessary here..
uart::Error::Overrun => { if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
cx.local.uart_rx.clear_fifo(); let decoded_size =
} cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
uart::Error::FramingError => (), if decoded_size.is_err() {
uart::Error::ParityError => (), log::warn!("COBS decoding failed");
uart::Error::BreakCondition => (), } else {
uart::Error::TransferPending => (), let decoded_size = decoded_size.unwrap();
uart::Error::BufferTooShort => (), if cx.local.tc_prod.sizes_prod.vacant_len() >= 1
&& cx.local.tc_prod.buf_prod.vacant_len() >= decoded_size
{
// Should never fail, we checked there is enough space.
cx.local.tc_prod.sizes_prod.try_push(decoded_size).unwrap();
cx.local
.tc_prod
.buf_prod
.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
} else {
log::warn!("COBS TC queue full");
} }
} }
nb::Error::WouldBlock => { } else {
// Delay for a short period before polling again. log::warn!("COBS frame with invalid format, start and end bytes are not 0");
Mono::delay(400.micros()).await;
}
} }
}
}
}
}
fn handle_single_rx_byte( // Initiate next transfer.
cx: &mut uart_reader_task::Context, cx.local
byte: u8, .uart_rx
current_idx: &mut usize, .read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
) { .expect("read operation failed");
match cx.local.cobs_reader_state { }
CobsReaderStates::WaitingForStart => { if result.error() {
if byte == COBS_FRAME_SEPARATOR { log::warn!("UART error: {:?}", result.error());
if COBS_RX_DEBUGGING {
log::debug!("COBS start marker detected");
}
*cx.local.cobs_reader_state = CobsReaderStates::WatingForEnd;
*current_idx = 0;
} }
} }
CobsReaderStates::WatingForEnd => { Err(e) => {
if byte == COBS_FRAME_SEPARATOR { log::warn!("UART error: {:?}", e);
if COBS_RX_DEBUGGING {
log::debug!("COBS end marker detected");
}
let mut sending_failed = false;
let mut decoding_error = false;
let mut decode_buffer_busy = false;
cx.shared.decode_buffer_busy.lock(|busy| {
if *busy {
decode_buffer_busy = true;
} else {
cx.shared.decode_buf.lock(|buf| {
match cobs::decode(&cx.local.read_buf[..*current_idx], buf) {
Ok(packet_len) => {
if COBS_RX_DEBUGGING {
log::debug!(
"COBS decoded packet with length {}",
packet_len
);
}
if cx.local.tc_tx.try_send(packet_len).is_err() {
sending_failed = true;
}
*busy = true;
}
Err(_) => {
decoding_error = true;
}
}
});
}
});
if sending_failed {
log::warn!("sending TC packet failed, queue full");
}
if decoding_error {
log::warn!("decoding error");
}
if decode_buffer_busy {
log::warn!("decode buffer busy. data arriving too fast");
}
*cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart;
} else if *current_idx >= cx.local.read_buf.len() {
*cx.local.cobs_reader_state = CobsReaderStates::FrameOverflow;
} else {
cx.local.read_buf[*current_idx] = byte;
*current_idx += 1;
}
}
CobsReaderStates::FrameOverflow => {
if byte == COBS_FRAME_SEPARATOR {
*cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart;
*current_idx = 0;
}
} }
} }
} }
@ -344,149 +295,167 @@ mod app {
#[task( #[task(
priority = 2, priority = 2,
local=[ local=[
read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
src_data_buf: [u8; 16] = [0; 16], src_data_buf: [u8; 16] = [0; 16],
verif_buf: [u8; 32] = [0; 32], verif_buf: [u8; 32] = [0; 32],
tc_rx, tc_cons,
rom_spi, rom_spi,
verif_reporter verif_reporter
], ],
shared=[decode_buffer_busy, decode_buf, tx_prod] shared=[tm_prod]
)] )]
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) { async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
loop { loop {
let packet_len = cx.local.tc_rx.recv().await.expect("all senders down"); // Try to read a TC from the ring buffer.
let packet_len = cx.local.tc_cons.sizes_cons.try_pop();
if packet_len.is_none() {
// Small delay, TCs might arrive very quickly.
Mono::delay(20.millis()).await;
continue;
}
let packet_len = packet_len.unwrap();
log::info!(target: "TC Handler", "received packet with length {}", packet_len); log::info!(target: "TC Handler", "received packet with length {}", packet_len);
// We still copy the data to a local buffer, so the exchange buffer can already be used assert_eq!(
// for the next packet / decode process. cx.local
cx.shared .tc_cons
.decode_buf .buf_cons
.lock(|buf| cx.local.read_buf[0..buf.len()].copy_from_slice(buf)); .pop_slice(&mut cx.local.tc_buf[0..packet_len]),
cx.shared.decode_buffer_busy.lock(|busy| *busy = false); packet_len
match PusTcReader::new(cx.local.read_buf) { );
Ok((pus_tc, _)) => { // Read a telecommand, now handle it.
let mut write_and_send = |tm: &PusTmCreator| { handle_valid_pus_tc(&mut cx);
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap(); }
cx.shared.tx_prod.lock(|prod| { }
prod.sizes_prod.try_push(tm.len_written()).unwrap();
prod.buf_prod
.push_slice(&cx.local.verif_buf[0..written_size]);
});
};
let token = cx.local.verif_reporter.add_tc(&pus_tc);
let (tm, accepted_token) = cx
.local
.verif_reporter
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
let (tm, started_token) = cx fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
.local let pus_tc = PusTcReader::new(cx.local.tc_buf);
.verif_reporter if pus_tc.is_err() {
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[]) log::warn!("PUS TC error: {}", pus_tc.unwrap_err());
.expect("acceptance success failed"); return;
write_and_send(&tm); }
let (pus_tc, _) = pus_tc.unwrap();
let mut write_and_send = |tm: &PusTmCreator| {
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
cx.shared.tm_prod.lock(|prod| {
prod.sizes_prod.try_push(tm.len_written()).unwrap();
prod.buf_prod
.push_slice(&cx.local.verif_buf[0..written_size]);
});
};
let token = cx.local.verif_reporter.add_tc(&pus_tc);
let (tm, accepted_token) = cx
.local
.verif_reporter
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
if pus_tc.service() == PusServiceId::Action as u8 { let (tm, started_token) = cx
let mut corrupt_image = |base_addr: u32| { .local
// Safety: We only use this for NVM handling and we only do NVM .verif_reporter
// handling here. .start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
let mut sys_cfg = unsafe { pac::Sysconfig::steal() }; .expect("acceptance success failed");
let nvm = Nvm::new( write_and_send(&tm);
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(), if pus_tc.service() == PusServiceId::Action as u8 {
CLOCKS.get().as_ref().unwrap(), let mut corrupt_image = |base_addr: u32| {
); // Safety: We only use this for NVM handling and we only do NVM
let mut buf = [0u8; 4]; // handling here.
nvm.read_data(base_addr + 32, &mut buf); let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
buf[0] += 1; let nvm = Nvm::new(
nvm.write_data(base_addr + 32, &buf); &mut sys_cfg,
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg)); cx.local.rom_spi.take().unwrap(),
let tm = cx CLOCKS.get().as_ref().unwrap(),
.local );
.verif_reporter let mut buf = [0u8; 4];
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) nvm.read_data(base_addr + 32, &mut buf);
.expect("completion success failed"); buf[0] += 1;
write_and_send(&tm); nvm.write_data(base_addr + 32, &buf);
}; *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
if pus_tc.subservice() == ActionId::CorruptImageA as u8 { let tm = cx
rprintln!("corrupting App Image A"); .local
corrupt_image(APP_A_START_ADDR); .verif_reporter
} .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
if pus_tc.subservice() == ActionId::CorruptImageB as u8 { .expect("completion success failed");
rprintln!("corrupting App Image B"); write_and_send(&tm);
corrupt_image(APP_B_START_ADDR); };
} if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
} rprintln!("corrupting App Image A");
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 { corrupt_image(APP_A_START_ADDR);
log::info!(target: "TC Handler", "received ping TC"); }
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 { if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
let tm = cx rprintln!("corrupting App Image B");
.local corrupt_image(APP_B_START_ADDR);
.verif_reporter }
.step_success( }
cx.local.src_data_buf, if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
&started_token, log::info!(target: "TC Handler", "received ping TC");
0, let tm = cx
0, .local
&[], .verif_reporter
EcssEnumU8::new(0), .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
) .expect("completion success failed");
.expect("step success failed"); write_and_send(&tm);
write_and_send(&tm); } else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
// Raw memory write TC let tm = cx
if pus_tc.subservice() == 2 { .local
let app_data = pus_tc.app_data(); .verif_reporter
if app_data.len() < 10 { .step_success(
log::warn!( cx.local.src_data_buf,
target: "TC Handler", &started_token,
"app data for raw memory write is too short: {}", 0,
app_data.len() 0,
); &[],
} EcssEnumU8::new(0),
let memory_id = app_data[0]; )
if memory_id != BOOT_NVM_MEMORY_ID { .expect("step success failed");
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id); write_and_send(&tm);
// TODO: Error reporting // Raw memory write TC
return; if pus_tc.subservice() == 2 {
} let app_data = pus_tc.app_data();
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap()); if app_data.len() < 10 {
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap()); log::warn!(
if 10 + data_len as usize > app_data.len() { target: "TC Handler",
log::warn!( "app data for raw memory write is too short: {}",
target: "TC Handler", app_data.len()
"invalid data length {} for raw mem write detected", );
data_len
);
// TODO: Error reporting
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
log::info!("NVM operation done");
}
}
} }
Err(e) => { let memory_id = app_data[0];
log::warn!("PUS TC error: {}", e); if memory_id != BOOT_NVM_MEMORY_ID {
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id);
// TODO: Error reporting
return;
} }
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap());
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap());
if 10 + data_len as usize > app_data.len() {
log::warn!(
target: "TC Handler",
"invalid data length {} for raw mem write detected",
data_len
);
// TODO: Error reporting
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
log::info!("NVM operation done");
} }
} }
} }
@ -497,16 +466,16 @@ mod app {
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE], read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE], encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
uart_tx, uart_tx,
tx_cons, tm_cons
], ],
shared=[] shared=[]
)] )]
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) { async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
loop { loop {
while cx.local.tx_cons.sizes_cons.occupied_len() > 0 { while cx.local.tm_cons.sizes_cons.occupied_len() > 0 {
let next_size = cx.local.tx_cons.sizes_cons.try_pop().unwrap(); let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap();
cx.local cx.local
.tx_cons .tm_cons
.buf_cons .buf_cons
.pop_slice(&mut cx.local.read_buf[0..next_size]); .pop_slice(&mut cx.local.read_buf[0..next_size]);
cx.local.encoded_buf[0] = 0; cx.local.encoded_buf[0] = 0;
@ -521,7 +490,7 @@ mod app {
.unwrap(); .unwrap();
Mono::delay(2.millis()).await; Mono::delay(2.millis()).await;
} }
Mono::delay(30.millis()).await; Mono::delay(50.millis()).await;
} }
} }

View File

@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.2.0] # [v0.2.0] 2024-09-18
- Documentation improvements - Documentation improvements
- Improved UART typing support: Validity of passed pins is now checked properly - Improved UART typing support: Validity of passed pins is now checked properly
@ -17,12 +17,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be - Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be
selected for HAL compilation to work properly selected for HAL compilation to work properly
- Adaptions for the UART IRQ feature which are now only implemented for the RX part of the UART.
## Fixed ## Fixed
- Small fixes and improvements for ADC drivers - Small fixes and improvements for ADC drivers
- Fixes for the SPI implementation where the clock divider values were not calculated - Fixes for the SPI implementation where the clock divider values were not calculated
correctly correctly
- Fixes for UART IRQ handler implementation
- Add new IRQ router initialization method `irq_router::enable_and_init_irq_router`. This method
also sets the initial values of some registers to 0 where the datasheet and the actual reset
value are inconsistent, which can lead to weird bugs like IRQs not being triggered properly.
## Added ## Added

View File

@ -12,6 +12,7 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies] [dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
critical-section = "1"
nb = "1" nb = "1"
paste = "1" paste = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
@ -20,7 +21,7 @@ embedded-io = "0.6"
num_enum = { version = "0.7", default-features = false } num_enum = { version = "0.7", default-features = false }
typenum = "1" typenum = "1"
bitflags = "2" bitflags = "2"
bitfield = "0.15" bitfield = "0.17"
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
fugit = "0.3" fugit = "0.3"
delegate = "0.12" delegate = "0.12"

View File

@ -311,6 +311,12 @@ impl ClkgenCfgr {
self self
} }
#[inline]
pub fn pll_cfg(mut self, pll_cfg: PllCfg) -> Self {
self.pll_cfg = Some(pll_cfg);
self
}
#[inline] #[inline]
pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self { pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self {
self.ref_clk_sel = ref_clk_sel; self.ref_clk_sel = ref_clk_sel;
@ -318,7 +324,7 @@ impl ClkgenCfgr {
} }
/// Configures all clocks and return a clock configuration structure containing the final /// Configures all clocks and return a clock configuration structure containing the final
/// frozen clock. /// frozen clocks.
/// ///
/// Internal implementation details: This implementation is based on the HAL implementation /// Internal implementation details: This implementation is based on the HAL implementation
/// which performs a lot of delays. I do not know if all of those are necessary, but /// which performs a lot of delays. I do not know if all of those are necessary, but
@ -494,33 +500,33 @@ pub struct Clocks {
impl Clocks { impl Clocks {
/// Returns the frequency of the HBO clock /// Returns the frequency of the HBO clock
pub fn hbo(&self) -> Hertz { pub const fn hbo(&self) -> Hertz {
HBO_FREQ HBO_FREQ
} }
/// Returns the frequency of the APB0 which is equal to the system clock. /// Returns the frequency of the APB0 which is equal to the system clock.
pub fn apb0(&self) -> Hertz { pub const fn apb0(&self) -> Hertz {
self.sysclk() self.sysclk()
} }
/// Returns system clock divied by 2. /// Returns system clock divied by 2.
pub fn apb1(&self) -> Hertz { pub const fn apb1(&self) -> Hertz {
self.apb1 self.apb1
} }
/// Returns system clock divied by 4. /// Returns system clock divied by 4.
pub fn apb2(&self) -> Hertz { pub const fn apb2(&self) -> Hertz {
self.apb2 self.apb2
} }
/// Returns the system (core) frequency /// Returns the system (core) frequency
pub fn sysclk(&self) -> Hertz { pub const fn sysclk(&self) -> Hertz {
self.sysclk self.sysclk
} }
/// Returns the ADC clock frequency which has a separate divider. /// Returns the ADC clock frequency which has a separate divider.
#[cfg(not(feature = "va41628"))] #[cfg(not(feature = "va41628"))]
pub fn adc_clk(&self) -> Hertz { pub const fn adc_clk(&self) -> Hertz {
self.adc_clk self.adc_clk
} }
} }

View File

@ -22,7 +22,7 @@ pub fn enable_ram1_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
} }
/// This function enables the SBE related interrupts. The user should also provide a /// This function enables the SBE related interrupts. The user should also provide a
/// [pac::EDAC_SBE] ISR and use [clear_sbe_irq] inside that ISR at the very least. /// `EDAC_SBE` ISR and use [clear_sbe_irq] inside that ISR at the very least.
#[inline(always)] #[inline(always)]
pub fn enable_sbe_irq() { pub fn enable_sbe_irq() {
unsafe { unsafe {
@ -31,7 +31,7 @@ pub fn enable_sbe_irq() {
} }
/// This function enables the SBE related interrupts. The user should also provide a /// This function enables the SBE related interrupts. The user should also provide a
/// [pac::EDAC_MBE] ISR and use [clear_mbe_irq] inside that ISR at the very least. /// `EDAC_MBE` ISR and use [clear_mbe_irq] inside that ISR at the very least.
#[inline(always)] #[inline(always)]
pub fn enable_mbe_irq() { pub fn enable_mbe_irq() {
unsafe { unsafe {
@ -39,7 +39,7 @@ pub fn enable_mbe_irq() {
} }
} }
/// This function should be called in the user provided [pac::EDAC_SBE] interrupt-service routine /// This function should be called in the user provided `EDAC_SBE` interrupt-service routine
/// to clear the SBE related interrupts. /// to clear the SBE related interrupts.
#[inline(always)] #[inline(always)]
pub fn clear_sbe_irq() { pub fn clear_sbe_irq() {
@ -52,7 +52,7 @@ pub fn clear_sbe_irq() {
}); });
} }
/// This function should be called in the user provided [pac::EDAC_MBE] interrupt-service routine /// This function should be called in the user provided `EDAC_MBE` interrupt-service routine
/// to clear the MBE related interrupts. /// to clear the MBE related interrupts.
#[inline(always)] #[inline(always)]
pub fn clear_mbe_irq() { pub fn clear_mbe_irq() {

View File

@ -21,7 +21,6 @@
//! ## Examples //! ## Examples
//! //!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs) //! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct IsMaskedError; pub struct IsMaskedError;

View File

@ -0,0 +1,26 @@
//! IRQ Router peripheral support.
use crate::{
clock::{PeripheralSelect, SyscfgExt},
pac,
};
/// This enables and initiates the peripheral.
///
/// Please note that this method also writes 0 to the registers which do not have 0 as the default
/// reset value. The programmers guide v1.2 and the actual values inspected using a SVD viewer
/// are inconsistent here, and the registers being non-zero can actually lead to weird bugs
/// when working with interrupts. Registers DMASELx and ADCSEL/DMASELx will reset to 0x7f and 0x1f
/// respectively instead of 0x00.
pub fn enable_and_init_irq_router(sysconfig: &mut pac::Sysconfig, irq_router: &pac::IrqRouter) {
sysconfig.enable_peripheral_clock(PeripheralSelect::IrqRouter);
sysconfig.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter);
unsafe {
irq_router.dmasel0().write_with_zero(|w| w);
irq_router.dmasel1().write_with_zero(|w| w);
irq_router.dmasel2().write_with_zero(|w| w);
irq_router.dmasel3().write_with_zero(|w| w);
irq_router.adcsel().write_with_zero(|w| w);
irq_router.dacsel0().write_with_zero(|w| w);
irq_router.dacsel1().write_with_zero(|w| w);
}
}

View File

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

View File

@ -1,3 +1,10 @@
//! Non-volatile memory (NVM) driver.
//!
//! Provides a basic API to work with the internal NVM of the VA41630 MCU.
//!
//! # Examples
//!
//! - [Flashloader application](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
use embedded_hal::spi::MODE_0; use embedded_hal::spi::MODE_0;
use crate::clock::{Clocks, SyscfgExt}; use crate::clock::{Clocks, SyscfgExt};
@ -50,7 +57,7 @@ pub struct Nvm {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", defmt::Format)] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct VerifyError { pub struct VerifyError {
addr: u32, addr: u32,
found: u8, found: u8,

View File

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

View File

@ -3,6 +3,7 @@
//! ## Examples //! ## Examples
//! //!
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs) //! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
//! - [Flashloader app using UART with IRQs](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
use core::ops::Deref; use core::ops::Deref;
use embedded_hal_nb::serial::Read; use embedded_hal_nb::serial::Read;
@ -197,66 +198,36 @@ impl From<Hertz> for Config {
// IRQ Definitions // IRQ Definitions
//================================================================================================== //==================================================================================================
struct IrqInfo { #[derive(Debug)]
pub struct IrqInfo {
rx_len: usize, rx_len: usize,
rx_idx: usize, rx_idx: usize,
mode: IrqReceptionMode, mode: IrqReceptionMode,
} }
pub enum IrqResultMask {
Complete = 0,
Overflow = 1,
FramingError = 2,
ParityError = 3,
Break = 4,
Timeout = 5,
Addr9 = 6,
/// Should not happen
Unknown = 7,
}
/// This struct is used to return the default IRQ handler result to the user /// This struct is used to return the default IRQ handler result to the user
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct IrqResult { pub struct IrqResult {
raw_res: u32, complete: bool,
timeout: bool,
pub errors: IrqUartError,
pub bytes_read: usize, pub bytes_read: usize,
} }
impl IrqResult { impl IrqResult {
pub const fn new() -> Self { pub fn new() -> Self {
IrqResult { IrqResult {
raw_res: 0, complete: false,
timeout: false,
errors: IrqUartError::default(),
bytes_read: 0, bytes_read: 0,
} }
} }
} }
impl IrqResult { impl IrqResult {
#[inline]
pub fn raw_result(&self) -> u32 {
self.raw_res
}
#[inline]
pub(crate) fn clear_result(&mut self) {
self.raw_res = 0;
}
#[inline]
pub(crate) fn set_result(&mut self, flag: IrqResultMask) {
self.raw_res |= 1 << flag as u32;
}
#[inline]
pub fn complete(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x01 {
return true;
}
false
}
#[inline] #[inline]
pub fn error(&self) -> bool { pub fn error(&self) -> bool {
if self.overflow_error() || self.framing_error() || self.parity_error() { if self.errors.overflow || self.errors.parity || self.errors.framing {
return true; return true;
} }
false false
@ -264,34 +235,27 @@ impl IrqResult {
#[inline] #[inline]
pub fn overflow_error(&self) -> bool { pub fn overflow_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Overflow as u32) & 0x01) == 0x01 { self.errors.overflow
return true;
}
false
} }
#[inline] #[inline]
pub fn framing_error(&self) -> bool { pub fn framing_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::FramingError as u32) & 0x01) == 0x01 { self.errors.framing
return true;
}
false
} }
#[inline] #[inline]
pub fn parity_error(&self) -> bool { pub fn parity_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::ParityError as u32) & 0x01) == 0x01 { self.errors.parity
return true;
}
false
} }
#[inline] #[inline]
pub fn timeout(&self) -> bool { pub fn timeout(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Timeout as u32) & 0x01) == 0x01 { self.timeout
return true; }
}
false #[inline]
pub fn complete(&self) -> bool {
self.complete
} }
} }
@ -317,41 +281,27 @@ pub struct Uart<UartInstance, Pins> {
pins: Pins, pins: Pins,
} }
/// UART using the IRQ capabilities of the peripheral. Can be created with the /// Serial receiver
/// [`Uart::into_uart_with_irq`] function. Currently, only the RX side for IRQ based reception pub struct Rx<Uart>(Uart);
/// is implemented.
pub struct UartWithIrq<Uart, Pins> {
base: UartWithIrqBase<Uart>,
pins: Pins,
}
/// Type-erased UART using the IRQ capabilities of the peripheral. Can be created with the // Serial receiver, using interrupts to offload reading to the hardware.
/// [`UartWithIrq::downgrade`] function. Currently, only the RX side for IRQ based reception pub struct RxWithIrq<Uart> {
/// is implemented. inner: Rx<Uart>,
pub struct UartWithIrqBase<UART> {
pub inner: UartBase<UART>,
irq_info: IrqInfo, irq_info: IrqInfo,
} }
/// Serial receiver
pub struct Rx<Uart> {
uart: Uart,
}
/// Serial transmitter /// Serial transmitter
pub struct Tx<Uart> { pub struct Tx<Uart>(Uart);
uart: Uart,
}
impl<Uart: Instance> Rx<Uart> { impl<Uart: Instance> Rx<Uart> {
fn new(uart: Uart) -> Self { fn new(uart: Uart) -> Self {
Self { uart } Self(uart)
} }
} }
impl<Uart> Tx<Uart> { impl<Uart> Tx<Uart> {
fn new(uart: Uart) -> Self { fn new(uart: Uart) -> Self {
Self { uart } Self(uart)
} }
} }
@ -602,20 +552,30 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
} }
/// If the IRQ capabilities of the peripheral are used, the UART needs to be converted /// If the IRQ capabilities of the peripheral are used, the UART needs to be converted
/// with this function /// with this function. Currently, IRQ abstractions are only implemented for the RX part
pub fn into_uart_with_irq(self) -> UartWithIrq<UartInstance, (TxPinInst, RxPinInst)> { /// of the UART, so this function will release a TX and RX handle as well as the pin
/// instances.
pub fn split_with_irq(
self,
) -> (
Tx<UartInstance>,
RxWithIrq<UartInstance>,
(TxPinInst, RxPinInst),
) {
let (inner, pins) = self.downgrade_internal(); let (inner, pins) = self.downgrade_internal();
UartWithIrq { let (tx, rx) = inner.split();
pins, (
base: UartWithIrqBase { tx,
inner, RxWithIrq {
inner: rx,
irq_info: IrqInfo { irq_info: IrqInfo {
rx_len: 0, rx_len: 0,
rx_idx: 0, rx_idx: 0,
mode: IrqReceptionMode::Idle, mode: IrqReceptionMode::Idle,
}, },
}, },
} pins,
)
} }
delegate::delegate! { delegate::delegate! {
@ -673,11 +633,26 @@ impl<Uart: Instance> Rx<Uart> {
/// ///
/// You must ensure that only registers related to the operation of the RX side are used. /// You must ensure that only registers related to the operation of the RX side are used.
pub unsafe fn uart(&self) -> &Uart { pub unsafe fn uart(&self) -> &Uart {
&self.uart &self.0
} }
#[inline]
pub fn clear_fifo(&self) { pub fn clear_fifo(&self) {
self.uart.fifo_clr().write(|w| w.rxfifo().set_bit()); self.0.fifo_clr().write(|w| w.rxfifo().set_bit());
}
#[inline]
pub fn enable(&mut self) {
self.0.enable().modify(|_, w| w.rxenable().set_bit());
}
#[inline]
pub fn disable(&mut self) {
self.0.enable().modify(|_, w| w.rxenable().clear_bit());
}
pub fn release(self) -> Uart {
self.0
} }
} }
@ -688,11 +663,22 @@ impl<Uart: Instance> Tx<Uart> {
/// ///
/// You must ensure that only registers related to the operation of the TX side are used. /// You must ensure that only registers related to the operation of the TX side are used.
pub unsafe fn uart(&self) -> &Uart { pub unsafe fn uart(&self) -> &Uart {
&self.uart &self.0
} }
#[inline]
pub fn clear_fifo(&self) { pub fn clear_fifo(&self) {
self.uart.fifo_clr().write(|w| w.txfifo().set_bit()); self.0.fifo_clr().write(|w| w.txfifo().set_bit());
}
#[inline]
pub fn enable(&mut self) {
self.0.enable().modify(|_, w| w.txenable().set_bit());
}
#[inline]
pub fn disable(&mut self) {
self.0.enable().modify(|_, w| w.txenable().clear_bit());
} }
} }
@ -701,6 +687,7 @@ pub struct IrqUartError {
overflow: bool, overflow: bool,
framing: bool, framing: bool,
parity: bool, parity: bool,
other: bool,
} }
impl IrqUartError { impl IrqUartError {
@ -715,7 +702,7 @@ pub enum IrqError {
Uart(IrqUartError), Uart(IrqUartError),
} }
impl<Uart: Instance> UartWithIrqBase<Uart> { impl<Uart: Instance> RxWithIrq<Uart> {
/// This initializes a non-blocking read transfer using the IRQ capabilities of the UART /// This initializes a non-blocking read transfer using the IRQ capabilities of the UART
/// peripheral. /// peripheral.
/// ///
@ -735,8 +722,7 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
self.irq_info.mode = IrqReceptionMode::Pending; self.irq_info.mode = IrqReceptionMode::Pending;
self.irq_info.rx_idx = 0; self.irq_info.rx_idx = 0;
self.irq_info.rx_len = max_len; self.irq_info.rx_len = max_len;
self.inner.enable_rx(); self.inner.enable();
self.inner.enable_tx();
self.enable_rx_irq_sources(enb_timeout_irq); self.enable_rx_irq_sources(enb_timeout_irq);
unsafe { enable_interrupt(Uart::IRQ_RX) }; unsafe { enable_interrupt(Uart::IRQ_RX) };
Ok(()) Ok(())
@ -744,7 +730,7 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
#[inline] #[inline]
fn enable_rx_irq_sources(&mut self, timeout: bool) { fn enable_rx_irq_sources(&mut self, timeout: bool) {
self.inner.uart.irq_enb().modify(|_, w| { self.inner.0.irq_enb().modify(|_, w| {
if timeout { if timeout {
w.irq_rx_to().set_bit(); w.irq_rx_to().set_bit();
} }
@ -755,30 +741,24 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
#[inline] #[inline]
fn disable_rx_irq_sources(&mut self) { fn disable_rx_irq_sources(&mut self) {
self.inner.uart.irq_enb().modify(|_, w| { self.inner.0.irq_enb().modify(|_, w| {
w.irq_rx_to().clear_bit(); w.irq_rx_to().clear_bit();
w.irq_rx_status().clear_bit(); w.irq_rx_status().clear_bit();
w.irq_rx().clear_bit() w.irq_rx().clear_bit()
}); });
} }
#[inline]
pub fn enable_tx(&mut self) {
self.inner.enable_tx()
}
#[inline]
pub fn disable_tx(&mut self) {
self.inner.disable_tx()
}
pub fn cancel_transfer(&mut self) { pub fn cancel_transfer(&mut self) {
self.disable_rx_irq_sources(); self.disable_rx_irq_sources();
self.inner.clear_tx_fifo(); self.inner.clear_fifo();
self.irq_info.rx_idx = 0; self.irq_info.rx_idx = 0;
self.irq_info.rx_len = 0; self.irq_info.rx_len = 0;
} }
pub fn uart(&self) -> &Uart {
&self.inner.0
}
/// Default IRQ handler which can be used to read the packets arriving on the UART peripheral. /// Default IRQ handler which can be used to read the packets arriving on the UART peripheral.
/// ///
/// If passed buffer is equal to or larger than the specified maximum length, an /// If passed buffer is equal to or larger than the specified maximum length, an
@ -791,104 +771,102 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
}); });
} }
let mut res = IrqResult::default(); let mut res = IrqResult::default();
let mut possible_error = IrqUartError::default();
let rx_status = self.inner.uart.rxstatus().read(); let irq_end = self.inner.0.irq_end().read();
res.raw_res = rx_status.bits(); let enb_status = self.inner.0.enable().read();
let irq_end = self.inner.uart.irq_end().read();
let enb_status = self.inner.uart.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set(); let rx_enabled = enb_status.rxenable().bit_is_set();
let _tx_enabled = enb_status.txenable().bit_is_set();
let read_handler = |res: &mut IrqResult, // Half-Full interrupt. We have a guaranteed amount of data we can read.
possible_error: &mut IrqUartError,
read_res: nb::Result<u8, Error>|
-> Option<u8> {
match read_res {
Ok(byte) => Some(byte),
Err(nb::Error::WouldBlock) => None,
Err(nb::Error::Other(e)) => {
match e {
Error::Overrun => {
possible_error.overflow = true;
}
Error::FramingError => {
possible_error.framing = true;
}
Error::ParityError => {
possible_error.parity = true;
}
_ => {
res.set_result(IrqResultMask::Unknown);
}
}
None
}
}
};
if irq_end.irq_rx().bit_is_set() { if irq_end.irq_rx().bit_is_set() {
// Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO.
// We use this trick/hack because the timeout feature of the peripheral relies on data
// being in the RX FIFO. If data continues arriving, another half-full IRQ will fire.
// If not, the last byte(s) is/are emptied by the timeout interrupt.
let available_bytes = self.inner.0.rxfifoirqtrg().read().bits() as usize;
let bytes_to_read = core::cmp::min(
available_bytes.saturating_sub(1),
self.irq_info.rx_len - self.irq_info.rx_idx,
);
// If this interrupt bit is set, the trigger level is available at the very least. // If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible // Read everything as fast as possible
for _ in 0..core::cmp::min( for _ in 0..bytes_to_read {
self.inner.uart.rxfifoirqtrg().read().bits() as usize, buf[self.irq_info.rx_idx] = (self.inner.0.data().read().bits() & 0xff) as u8;
self.irq_info.rx_len,
) {
buf[self.irq_info.rx_idx] = (self.inner.uart.data().read().bits() & 0xff) as u8;
self.irq_info.rx_idx += 1; self.irq_info.rx_idx += 1;
} }
// On high-baudrates, data might be available immediately, and we possible have to
// read continuosly? Then again, the CPU should always be faster than that. I'd rather
// rely on the hardware firing another IRQ. I have not tried baudrates higher than
// 115200 so far.
}
let read_handler =
|possible_error: &mut IrqUartError, read_res: nb::Result<u8, Error>| -> Option<u8> {
match read_res {
Ok(byte) => Some(byte),
Err(nb::Error::WouldBlock) => None,
Err(nb::Error::Other(e)) => {
match e {
Error::Overrun => {
possible_error.overflow = true;
}
Error::FramingError => {
possible_error.framing = true;
}
Error::ParityError => {
possible_error.parity = true;
}
_ => {
possible_error.other = true;
}
}
None
}
}
};
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer // While there is data in the FIFO, write it into the reception buffer
loop { loop {
if self.irq_info.rx_idx == self.irq_info.rx_len { if self.irq_info.rx_idx == self.irq_info.rx_len {
self.irq_completion_handler(&mut res); break;
return Ok(res);
} }
if let Some(byte) = read_handler(&mut res, &mut possible_error, self.inner.read()) { if let Some(byte) = read_handler(&mut res.errors, self.inner.read()) {
buf[self.irq_info.rx_idx] = byte; buf[self.irq_info.rx_idx] = byte;
self.irq_info.rx_idx += 1; self.irq_info.rx_idx += 1;
} else { } else {
break; break;
} }
} }
self.irq_completion_handler(&mut res);
return Ok(res);
} }
// RX transfer not complete, check for RX errors // RX transfer not complete, check for RX errors
if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled { if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled {
// Read status register again, might have changed since reading received data // Read status register again, might have changed since reading received data
let rx_status = self.inner.uart.rxstatus().read(); let rx_status = self.inner.0.rxstatus().read();
res.raw_res = rx_status.bits();
if rx_status.rxovr().bit_is_set() { if rx_status.rxovr().bit_is_set() {
possible_error.overflow = true; res.errors.overflow = true;
} }
if rx_status.rxfrm().bit_is_set() { if rx_status.rxfrm().bit_is_set() {
possible_error.framing = true; res.errors.framing = true;
} }
if rx_status.rxpar().bit_is_set() { if rx_status.rxpar().bit_is_set() {
possible_error.parity = true; res.errors.parity = true;
}
if rx_status.rxto().bit_is_set() {
// A timeout has occured but there might be some leftover data in the FIFO,
// so read that data as well
while let Some(byte) =
read_handler(&mut res, &mut possible_error, self.inner.read())
{
buf[self.irq_info.rx_idx] = byte;
self.irq_info.rx_idx += 1;
}
self.irq_completion_handler(&mut res);
res.set_result(IrqResultMask::Timeout);
return Ok(res);
} }
// If it is not a timeout, it's an error // If it is not a timeout, it's an error
if possible_error.error() { if res.error() {
self.disable_rx_irq_sources(); self.disable_rx_irq_sources();
return Err(IrqError::Uart(possible_error)); return Err(IrqError::Uart(res.errors));
} }
} }
// Clear the interrupt status bits // Clear the interrupt status bits
self.inner self.inner
.uart .0
.irq_clr() .irq_clr()
.write(|w| unsafe { w.bits(irq_end.bits()) }); .write(|w| unsafe { w.bits(irq_end.bits()) });
Ok(res) Ok(res)
@ -896,48 +874,23 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
fn irq_completion_handler(&mut self, res: &mut IrqResult) { fn irq_completion_handler(&mut self, res: &mut IrqResult) {
self.disable_rx_irq_sources(); self.disable_rx_irq_sources();
self.inner.disable_rx(); self.inner.disable();
res.bytes_read = self.irq_info.rx_idx; res.bytes_read = self.irq_info.rx_idx;
res.clear_result(); res.complete = true;
res.set_result(IrqResultMask::Complete);
self.irq_info.mode = IrqReceptionMode::Idle; self.irq_info.mode = IrqReceptionMode::Idle;
self.irq_info.rx_idx = 0; self.irq_info.rx_idx = 0;
self.irq_info.rx_len = 0; self.irq_info.rx_len = 0;
} }
pub fn irq_info(&self) -> &IrqInfo {
&self.irq_info
}
pub fn release(self) -> Uart { pub fn release(self) -> Uart {
self.inner.release() self.inner.release()
} }
} }
impl<Uart: Instance, Pins> UartWithIrq<Uart, Pins> {
/// See [`UartWithIrqBase::read_fixed_len_using_irq`] doc
pub fn read_fixed_len_using_irq(
&mut self,
max_len: usize,
enb_timeout_irq: bool,
) -> Result<(), Error> {
self.base.read_fixed_len_using_irq(max_len, enb_timeout_irq)
}
pub fn cancel_transfer(&mut self) {
self.base.cancel_transfer()
}
/// See [`UartWithIrqBase::irq_handler`] doc
pub fn irq_handler(&mut self, buf: &mut [u8]) -> Result<IrqResult, IrqError> {
self.base.irq_handler(buf)
}
pub fn release(self) -> (Uart, Pins) {
(self.base.release(), self.pins)
}
pub fn downgrade(self) -> (UartWithIrqBase<Uart>, Pins) {
(self.base, self.pins)
}
}
impl embedded_io::Error for Error { impl embedded_io::Error for Error {
fn kind(&self) -> embedded_io::ErrorKind { fn kind(&self) -> embedded_io::ErrorKind {
embedded_io::ErrorKind::Other embedded_io::ErrorKind::Other

View File

@ -74,8 +74,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -104,8 +103,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -134,8 +132,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -164,11 +161,11 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
"timestamp": true,
"type": "console" "type": "console"
} }
] ]
@ -194,8 +191,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -224,8 +220,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -255,8 +250,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -286,8 +280,187 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason.. "address": "auto",
"address": "0x1fff8000", "decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Debug PWM Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "pwm-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/pwm",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Debug DMA Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "dma-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/dma",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Bootloader",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "bootloader",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/bootloader",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Flashloader",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "flashloader",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/flashloader",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Embassy Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "embassy-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/embassy-example",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "RTIC Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "embassy-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/rtic-example",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -298,4 +471,4 @@
} }
}, },
] ]
} }

View File

@ -3,6 +3,32 @@
// for the documentation about the tasks.json format // for the documentation about the tasks.json format
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{
"label": "bootloader",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"bootloader"
],
"group": {
"kind": "build",
}
},
{
"label": "flashloader",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"flashloader"
],
"group": {
"kind": "build",
}
},
{ {
"label": "blinky-pac-example", "label": "blinky-pac-example",
"type": "shell", "type": "shell",
@ -29,6 +55,19 @@
"kind": "build", "kind": "build",
} }
}, },
{
"label": "timer-ticks-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--example",
"timer-ticks"
],
"group": {
"kind": "build",
}
},
{ {
"label": "blinky-example", "label": "blinky-example",
"type": "shell", "type": "shell",
@ -56,6 +95,19 @@
"kind": "build", "kind": "build",
} }
}, },
{
"label": "pwm-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--example",
"pwm"
],
"group": {
"kind": "build",
}
},
{ {
"label": "wdt-example", "label": "wdt-example",
"type": "shell", "type": "shell",
@ -108,5 +160,44 @@
"kind": "build", "kind": "build",
} }
}, },
{
"label": "dma-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--example",
"dma"
],
"group": {
"kind": "build",
}
},
{
"label": "embassy-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"embassy-example"
],
"group": {
"kind": "build",
}
},
{
"label": "rtic-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"rtic-example"
],
"group": {
"kind": "build",
}
},
] ]
} }