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:
- uses: actions/checkout@v4
- 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:
name: Clippy

View File

@ -1,12 +1,14 @@
[workspace]
resolver = "2"
members = [
"va416xx",
"va416xx-hal",
"vorago-peb1",
"bootloader",
"flashloader",
"examples/simple",
"va416xx",
"va416xx-hal",
"vorago-peb1"
"examples/embassy",
"examples/rtic",
]
exclude = [
"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)
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.
- 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
@ -96,7 +100,9 @@ example.
### Using VS Code
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
`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"`
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') {
steps {
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"
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
critical-section = "1"
panic-rtt-target = { version = "0.1.3" }
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-nb = "1"
nb = "1"
@ -23,8 +23,16 @@ path = "../../va416xx-hal"
path = "../../vorago-peb1"
optional = true
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]
[features]
default = []
default = ["va41630"]
va41630 = ["va416xx-hal/va41630", "has-adc-dac"]
va41629 = ["va416xx-hal/va41629", "has-adc-dac"]
va41628 = ["va416xx-hal/va41628"]

View File

@ -4,13 +4,14 @@
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use critical_section::Mutex;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
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::{
pac::{self, interrupt},
@ -45,6 +46,7 @@ fn main() -> ! {
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.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
// statically.
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| {
src_buf[i] = i as u8;
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
// 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.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 8 bit transfer");
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;
});
}
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
// 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.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 16-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
@ -206,10 +208,10 @@ fn transfer_example_32_bit(
(0..16).for_each(|i| {
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);
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
// 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.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 32-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
@ -260,7 +262,7 @@ fn transfer_example_32_bit(
#[allow(non_snake_case)]
fn DMA_DONE0() {
// 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);
});
}
@ -269,7 +271,7 @@ fn DMA_DONE0() {
#[allow(non_snake_case)]
fn DMA_ACTIVE0() {
// 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);
});
}

View File

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

View File

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

View File

@ -1,12 +1,10 @@
#!/usr/bin/env python3
from spacepackets.ecss import RequestId
from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm
import toml
import struct
import logging
import argparse
import threading
import time
import enum
from tmtccmd.com.serial_base import SerialCfg
@ -45,6 +43,7 @@ ACTION_SERVICE = 8
RAW_MEMORY_WRITE_SUBSERVICE = 2
BOOT_NVM_MEMORY_ID = 1
PING_PAYLOAD_SIZE = 0
class ActionId(enum.IntEnum):
@ -104,6 +103,29 @@ def main() -> int:
com_if = SerialCobsComIF(serial_cfg)
com_if.open()
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 not args.corrupt:
if not args.path:
@ -113,15 +135,6 @@ def main() -> int:
if not file_path.exists():
_LOGGER.error("File does not exist")
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 not args.target:
_LOGGER.error("target for corruption command required")
@ -254,7 +267,7 @@ def main() -> int:
):
done = True
# Still keep a small delay
time.sleep(0.01)
# time.sleep(0.05)
verificator.remove_completed_entries()
if done:
break

View File

@ -18,13 +18,11 @@
#![no_main]
#![no_std]
use embedded_hal_nb::serial::Read;
use once_cell::sync::OnceCell;
use panic_rtt_target as _;
use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt};
const EXTCLK_FREQ: u32 = 40_000_000;
const COBS_FRAME_SEPARATOR: u8 = 0x0;
const MAX_TC_SIZE: usize = 1024;
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 UART_BAUDRATE: u32 = 115200;
const SERIAL_RX_WIRETAPPING: bool = false;
const COBS_RX_DEBUGGING: bool = false;
const BOOT_NVM_MEMORY_ID: u8 = 1;
const RX_DEBUGGING: bool = false;
pub enum ActionId {
CorruptImageA = 128,
@ -62,13 +58,24 @@ use ringbuf::{
CachingCons, StaticProd, StaticRb,
};
const BUF_RB_SIZE_TX: usize = 1024;
const SIZES_RB_SIZE_TX: usize = 16;
// Larger buffer for TC to be able to hold the possibly large memory write packets.
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>> =
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TX>::default);
static mut SIZES_RB_TX: Lazy<StaticRb<usize, SIZES_RB_SIZE_TX>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TX>::default);
const BUF_RB_SIZE_TM: usize = 512;
const SIZES_RB_SIZE_TM: usize = 16;
// 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 buf_prod: StaticProd<'static, u8, BUF_SIZE>,
@ -91,21 +98,17 @@ pub const APP_B_END_ADDR: u32 = 0x40000;
mod app {
use super::*;
use cortex_m::asm;
use embedded_hal_nb::nb;
use embedded_io::Write;
use panic_rtt_target as _;
use rtic::Mutex;
use rtic_monotonics::systick::prelude::*;
use rtic_sync::{
channel::{Receiver, Sender},
make_channel,
};
use rtt_target::rprintln;
use satrs::pus::verification::VerificationReportCreator;
use spacepackets::ecss::PusServiceId;
use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::{
clock::ClkgenExt,
edac,
@ -127,26 +130,24 @@ mod app {
#[local]
struct Local {
uart_rx: uart::Rx<pac::Uart0>,
uart_rx: uart::RxWithIrq<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>,
cobs_reader_state: CobsReaderStates,
tc_tx: TcTx,
tc_rx: TcRx,
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,
}
#[shared]
struct Shared {
decode_buffer_busy: bool,
decode_buf: [u8; MAX_TC_SIZE],
tx_prod: DataProducer<BUF_RB_SIZE_TX, SIZES_RB_SIZE_TX>,
// Having this shared allows multiple tasks to generate telemetry.
tm_prod: DataProducer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
}
pub type TcTx = Sender<'static, usize, 2>;
pub type TcRx = Receiver<'static, usize, 2>;
rtic_monotonics::systick_monotonic!(Mono, 10_000);
#[init]
@ -163,6 +164,7 @@ mod app {
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut cx.device.sysconfig)
.unwrap();
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
setup_edac(&mut cx.device.sysconfig);
let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
@ -176,38 +178,45 @@ mod app {
&mut cx.device.sysconfig,
&clocks,
);
let (tx, rx) = uart0.split();
let (tc_tx, tc_rx) = make_channel!(usize, 2);
let (tx, mut rx, _) = uart0.split_with_irq();
let verif_reporter = VerificationReportCreator::new(0).unwrap();
let (buf_prod, buf_cons) = unsafe { BUF_RB_TX.split_ref() };
let (sizes_prod, sizes_cons) = unsafe { SIZES_RB_TX.split_ref() };
let (buf_prod_tm, buf_cons_tm) = unsafe { BUF_RB_TM.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());
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();
uart_reader_task::spawn().unwrap();
pus_tm_tx_handler::spawn().unwrap();
(
Shared {
decode_buffer_busy: false,
decode_buf: [0; MAX_TC_SIZE],
tx_prod: DataProducer {
buf_prod,
sizes_prod,
tm_prod: DataProducer {
buf_prod: buf_prod_tm,
sizes_prod: sizes_prod_tm,
},
},
Local {
uart_rx: rx,
uart_tx: tx,
cobs_reader_state: CobsReaderStates::default(),
tc_tx,
tc_rx,
rom_spi: Some(cx.device.spi3),
tx_cons: DataConsumer {
buf_cons,
sizes_cons,
tm_cons: DataConsumer {
buf_cons: buf_cons_tm,
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,
},
@ -223,120 +232,62 @@ mod app {
}
#[task(
priority = 4,
local=[
read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
binds = UART0_RX,
local = [
cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
uart_rx,
cobs_reader_state,
tc_tx
tc_prod
],
shared=[decode_buffer_busy, decode_buf]
)]
async fn uart_reader_task(mut cx: uart_reader_task::Context) {
let mut current_idx = 0;
loop {
match cx.local.uart_rx.read() {
Ok(byte) => {
if SERIAL_RX_WIRETAPPING {
log::debug!("RX Byte: 0x{:x?}", byte);
}
handle_single_rx_byte(&mut cx, byte, &mut current_idx)
fn uart_rx_irq(cx: uart_rx_irq::Context) {
match cx.local.uart_rx.irq_handler(cx.local.rx_buf) {
Ok(result) => {
if RX_DEBUGGING {
log::debug!("RX Info: {:?}", cx.local.uart_rx.irq_info());
log::debug!("RX Result: {:?}", result);
}
Err(e) => {
match e {
nb::Error::Other(e) => {
log::warn!("UART error: {:?}", e);
match e {
uart::Error::Overrun => {
cx.local.uart_rx.clear_fifo();
}
uart::Error::FramingError => (),
uart::Error::ParityError => (),
uart::Error::BreakCondition => (),
uart::Error::TransferPending => (),
uart::Error::BufferTooShort => (),
if result.complete() {
// Check frame validity (must have COBS format) and decode the frame.
// Currently, we expect a full frame or a frame received through a timeout
// to be one COBS frame. We could parse for multiple COBS packets in one
// frame, but the additional complexity is not necessary here..
if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
let decoded_size =
cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
if decoded_size.is_err() {
log::warn!("COBS decoding failed");
} else {
let decoded_size = decoded_size.unwrap();
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 => {
// Delay for a short period before polling again.
Mono::delay(400.micros()).await;
}
} else {
log::warn!("COBS frame with invalid format, start and end bytes are not 0");
}
}
}
}
}
fn handle_single_rx_byte(
cx: &mut uart_reader_task::Context,
byte: u8,
current_idx: &mut usize,
) {
match cx.local.cobs_reader_state {
CobsReaderStates::WaitingForStart => {
if byte == COBS_FRAME_SEPARATOR {
if COBS_RX_DEBUGGING {
log::debug!("COBS start marker detected");
}
*cx.local.cobs_reader_state = CobsReaderStates::WatingForEnd;
*current_idx = 0;
// Initiate next transfer.
cx.local
.uart_rx
.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
.expect("read operation failed");
}
if result.error() {
log::warn!("UART error: {:?}", result.error());
}
}
CobsReaderStates::WatingForEnd => {
if byte == COBS_FRAME_SEPARATOR {
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;
}
Err(e) => {
log::warn!("UART error: {:?}", e);
}
}
}
@ -344,149 +295,167 @@ mod app {
#[task(
priority = 2,
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],
verif_buf: [u8; 32] = [0; 32],
tc_rx,
tc_cons,
rom_spi,
verif_reporter
],
shared=[decode_buffer_busy, decode_buf, tx_prod]
shared=[tm_prod]
)]
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
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);
// We still copy the data to a local buffer, so the exchange buffer can already be used
// for the next packet / decode process.
cx.shared
.decode_buf
.lock(|buf| cx.local.read_buf[0..buf.len()].copy_from_slice(buf));
cx.shared.decode_buffer_busy.lock(|busy| *busy = false);
match PusTcReader::new(cx.local.read_buf) {
Ok((pus_tc, _)) => {
let mut write_and_send = |tm: &PusTmCreator| {
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);
assert_eq!(
cx.local
.tc_cons
.buf_cons
.pop_slice(&mut cx.local.tc_buf[0..packet_len]),
packet_len
);
// Read a telecommand, now handle it.
handle_valid_pus_tc(&mut cx);
}
}
let (tm, started_token) = cx
.local
.verif_reporter
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
let pus_tc = PusTcReader::new(cx.local.tc_buf);
if pus_tc.is_err() {
log::warn!("PUS TC error: {}", pus_tc.unwrap_err());
return;
}
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 mut corrupt_image = |base_addr: u32| {
// 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(),
);
let mut buf = [0u8; 4];
nvm.read_data(base_addr + 32, &mut buf);
buf[0] += 1;
nvm.write_data(base_addr + 32, &buf);
*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);
};
if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
rprintln!("corrupting App Image A");
corrupt_image(APP_A_START_ADDR);
}
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
rprintln!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR);
}
}
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
log::info!(target: "TC Handler", "received ping TC");
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
let tm = cx
.local
.verif_reporter
.step_success(
cx.local.src_data_buf,
&started_token,
0,
0,
&[],
EcssEnumU8::new(0),
)
.expect("step success failed");
write_and_send(&tm);
// Raw memory write TC
if pus_tc.subservice() == 2 {
let app_data = pus_tc.app_data();
if app_data.len() < 10 {
log::warn!(
target: "TC Handler",
"app data for raw memory write is too short: {}",
app_data.len()
);
}
let memory_id = app_data[0];
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");
}
}
let (tm, started_token) = cx
.local
.verif_reporter
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
if pus_tc.service() == PusServiceId::Action as u8 {
let mut corrupt_image = |base_addr: u32| {
// 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(),
);
let mut buf = [0u8; 4];
nvm.read_data(base_addr + 32, &mut buf);
buf[0] += 1;
nvm.write_data(base_addr + 32, &buf);
*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);
};
if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
rprintln!("corrupting App Image A");
corrupt_image(APP_A_START_ADDR);
}
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
rprintln!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR);
}
}
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
log::info!(target: "TC Handler", "received ping TC");
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);
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
let tm = cx
.local
.verif_reporter
.step_success(
cx.local.src_data_buf,
&started_token,
0,
0,
&[],
EcssEnumU8::new(0),
)
.expect("step success failed");
write_and_send(&tm);
// Raw memory write TC
if pus_tc.subservice() == 2 {
let app_data = pus_tc.app_data();
if app_data.len() < 10 {
log::warn!(
target: "TC Handler",
"app data for raw memory write is too short: {}",
app_data.len()
);
}
Err(e) => {
log::warn!("PUS TC error: {}", e);
let memory_id = app_data[0];
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],
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
uart_tx,
tx_cons,
tm_cons
],
shared=[]
)]
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
loop {
while cx.local.tx_cons.sizes_cons.occupied_len() > 0 {
let next_size = cx.local.tx_cons.sizes_cons.try_pop().unwrap();
while cx.local.tm_cons.sizes_cons.occupied_len() > 0 {
let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap();
cx.local
.tx_cons
.tm_cons
.buf_cons
.pop_slice(&mut cx.local.read_buf[0..next_size]);
cx.local.encoded_buf[0] = 0;
@ -521,7 +490,7 @@ mod app {
.unwrap();
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]
# [v0.2.0]
# [v0.2.0] 2024-09-18
- Documentation improvements
- 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
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
- Small fixes and improvements for ADC drivers
- Fixes for the SPI implementation where the clock divider values were not calculated
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

View File

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

View File

@ -311,6 +311,12 @@ impl ClkgenCfgr {
self
}
#[inline]
pub fn pll_cfg(mut self, pll_cfg: PllCfg) -> Self {
self.pll_cfg = Some(pll_cfg);
self
}
#[inline]
pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self {
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
/// frozen clock.
/// frozen clocks.
///
/// 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
@ -494,33 +500,33 @@ pub struct Clocks {
impl Clocks {
/// Returns the frequency of the HBO clock
pub fn hbo(&self) -> Hertz {
pub const fn hbo(&self) -> Hertz {
HBO_FREQ
}
/// 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()
}
/// Returns system clock divied by 2.
pub fn apb1(&self) -> Hertz {
pub const fn apb1(&self) -> Hertz {
self.apb1
}
/// Returns system clock divied by 4.
pub fn apb2(&self) -> Hertz {
pub const fn apb2(&self) -> Hertz {
self.apb2
}
/// Returns the system (core) frequency
pub fn sysclk(&self) -> Hertz {
pub const fn sysclk(&self) -> Hertz {
self.sysclk
}
/// Returns the ADC clock frequency which has a separate divider.
#[cfg(not(feature = "va41628"))]
pub fn adc_clk(&self) -> Hertz {
pub const fn adc_clk(&self) -> Hertz {
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
/// [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)]
pub fn enable_sbe_irq() {
unsafe {
@ -31,7 +31,7 @@ pub fn enable_sbe_irq() {
}
/// 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)]
pub fn enable_mbe_irq() {
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.
#[inline(always)]
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.
#[inline(always)]
pub fn clear_mbe_irq() {

View File

@ -21,7 +21,6 @@
//! ## Examples
//!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(test)]
@ -22,7 +45,7 @@ pub mod dma;
pub mod edac;
pub mod gpio;
pub mod i2c;
pub mod nvm;
pub mod irq_router;
pub mod pwm;
pub mod spi;
pub mod time;
@ -31,6 +54,9 @@ pub mod typelevel;
pub mod uart;
pub mod wdt;
#[cfg(feature = "va41630")]
pub mod nvm;
#[cfg(not(feature = "va41628"))]
pub mod adc;
#[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 crate::clock::{Clocks, SyscfgExt};
@ -50,7 +57,7 @@ pub struct Nvm {
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", defmt::Format)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct VerifyError {
addr: u32,
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)
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m::asm;
use critical_section::Mutex;
use crate::clock::Clocks;
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!(
(pac::Tim0, 0, pac::Interrupt::TIM0),
(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
#[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
.tim_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) })
}
#[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
.tim_reset()
.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;
/// Register interface.
@ -481,7 +498,7 @@ pub struct CountdownTimer<TIM: ValidTim> {
}
#[inline]
fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
syscfg
.tim_clk_enable()
.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>) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
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_count(0);
// Decrementing counter, to set the reset value.
self.set_count(self.rst_val);
}
#[inline(always)]
@ -601,7 +619,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
#[inline(always)]
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)]
@ -778,7 +796,7 @@ pub fn set_up_ms_tick<Tim: ValidTim>(
/// This function can be called in a specified interrupt handler to increment
/// the MS counter
pub fn default_ms_irq_handler() {
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
let mut ms = MS_COUNTER.borrow(cs).get();
ms += 1;
MS_COUNTER.borrow(cs).set(ms);
@ -787,7 +805,7 @@ pub fn default_ms_irq_handler() {
/// Get the current MS tick count
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>);

View File

@ -3,6 +3,7 @@
//! ## Examples
//!
//! - [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 embedded_hal_nb::serial::Read;
@ -197,66 +198,36 @@ impl From<Hertz> for Config {
// IRQ Definitions
//==================================================================================================
struct IrqInfo {
#[derive(Debug)]
pub struct IrqInfo {
rx_len: usize,
rx_idx: usize,
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
#[derive(Debug, Default)]
pub struct IrqResult {
raw_res: u32,
complete: bool,
timeout: bool,
pub errors: IrqUartError,
pub bytes_read: usize,
}
impl IrqResult {
pub const fn new() -> Self {
pub fn new() -> Self {
IrqResult {
raw_res: 0,
complete: false,
timeout: false,
errors: IrqUartError::default(),
bytes_read: 0,
}
}
}
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]
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;
}
false
@ -264,34 +235,27 @@ impl IrqResult {
#[inline]
pub fn overflow_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Overflow as u32) & 0x01) == 0x01 {
return true;
}
false
self.errors.overflow
}
#[inline]
pub fn framing_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::FramingError as u32) & 0x01) == 0x01 {
return true;
}
false
self.errors.framing
}
#[inline]
pub fn parity_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::ParityError as u32) & 0x01) == 0x01 {
return true;
}
false
self.errors.parity
}
#[inline]
pub fn timeout(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Timeout as u32) & 0x01) == 0x01 {
return true;
}
false
self.timeout
}
#[inline]
pub fn complete(&self) -> bool {
self.complete
}
}
@ -317,41 +281,27 @@ pub struct Uart<UartInstance, Pins> {
pins: Pins,
}
/// UART using the IRQ capabilities of the peripheral. Can be created with the
/// [`Uart::into_uart_with_irq`] function. Currently, only the RX side for IRQ based reception
/// is implemented.
pub struct UartWithIrq<Uart, Pins> {
base: UartWithIrqBase<Uart>,
pins: Pins,
}
/// Serial receiver
pub struct Rx<Uart>(Uart);
/// Type-erased UART using the IRQ capabilities of the peripheral. Can be created with the
/// [`UartWithIrq::downgrade`] function. Currently, only the RX side for IRQ based reception
/// is implemented.
pub struct UartWithIrqBase<UART> {
pub inner: UartBase<UART>,
// Serial receiver, using interrupts to offload reading to the hardware.
pub struct RxWithIrq<Uart> {
inner: Rx<Uart>,
irq_info: IrqInfo,
}
/// Serial receiver
pub struct Rx<Uart> {
uart: Uart,
}
/// Serial transmitter
pub struct Tx<Uart> {
uart: Uart,
}
pub struct Tx<Uart>(Uart);
impl<Uart: Instance> Rx<Uart> {
fn new(uart: Uart) -> Self {
Self { uart }
Self(uart)
}
}
impl<Uart> Tx<Uart> {
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
/// with this function
pub fn into_uart_with_irq(self) -> UartWithIrq<UartInstance, (TxPinInst, RxPinInst)> {
/// with this function. Currently, IRQ abstractions are only implemented for the RX part
/// 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();
UartWithIrq {
pins,
base: UartWithIrqBase {
inner,
let (tx, rx) = inner.split();
(
tx,
RxWithIrq {
inner: rx,
irq_info: IrqInfo {
rx_len: 0,
rx_idx: 0,
mode: IrqReceptionMode::Idle,
},
},
}
pins,
)
}
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.
pub unsafe fn uart(&self) -> &Uart {
&self.uart
&self.0
}
#[inline]
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.
pub unsafe fn uart(&self) -> &Uart {
&self.uart
&self.0
}
#[inline]
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,
framing: bool,
parity: bool,
other: bool,
}
impl IrqUartError {
@ -715,7 +702,7 @@ pub enum IrqError {
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
/// peripheral.
///
@ -735,8 +722,7 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
self.irq_info.mode = IrqReceptionMode::Pending;
self.irq_info.rx_idx = 0;
self.irq_info.rx_len = max_len;
self.inner.enable_rx();
self.inner.enable_tx();
self.inner.enable();
self.enable_rx_irq_sources(enb_timeout_irq);
unsafe { enable_interrupt(Uart::IRQ_RX) };
Ok(())
@ -744,7 +730,7 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
#[inline]
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 {
w.irq_rx_to().set_bit();
}
@ -755,30 +741,24 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
#[inline]
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_status().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) {
self.disable_rx_irq_sources();
self.inner.clear_tx_fifo();
self.inner.clear_fifo();
self.irq_info.rx_idx = 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.
///
/// 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 possible_error = IrqUartError::default();
let rx_status = self.inner.uart.rxstatus().read();
res.raw_res = rx_status.bits();
let irq_end = self.inner.uart.irq_end().read();
let enb_status = self.inner.uart.enable().read();
let irq_end = self.inner.0.irq_end().read();
let enb_status = self.inner.0.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
let _tx_enabled = enb_status.txenable().bit_is_set();
let read_handler = |res: &mut IrqResult,
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
}
}
};
// Half-Full interrupt. We have a guaranteed amount of data we can read.
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.
// Read everything as fast as possible
for _ in 0..core::cmp::min(
self.inner.uart.rxfifoirqtrg().read().bits() as usize,
self.irq_info.rx_len,
) {
buf[self.irq_info.rx_idx] = (self.inner.uart.data().read().bits() & 0xff) as u8;
for _ in 0..bytes_to_read {
buf[self.irq_info.rx_idx] = (self.inner.0.data().read().bits() & 0xff) as u8;
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
loop {
if self.irq_info.rx_idx == self.irq_info.rx_len {
self.irq_completion_handler(&mut res);
return Ok(res);
break;
}
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;
self.irq_info.rx_idx += 1;
} else {
break;
}
}
self.irq_completion_handler(&mut res);
return Ok(res);
}
// RX transfer not complete, check for RX errors
if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled {
// Read status register again, might have changed since reading received data
let rx_status = self.inner.uart.rxstatus().read();
res.raw_res = rx_status.bits();
let rx_status = self.inner.0.rxstatus().read();
if rx_status.rxovr().bit_is_set() {
possible_error.overflow = true;
res.errors.overflow = true;
}
if rx_status.rxfrm().bit_is_set() {
possible_error.framing = true;
res.errors.framing = true;
}
if rx_status.rxpar().bit_is_set() {
possible_error.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);
res.errors.parity = true;
}
// If it is not a timeout, it's an error
if possible_error.error() {
if res.error() {
self.disable_rx_irq_sources();
return Err(IrqError::Uart(possible_error));
return Err(IrqError::Uart(res.errors));
}
}
// Clear the interrupt status bits
self.inner
.uart
.0
.irq_clr()
.write(|w| unsafe { w.bits(irq_end.bits()) });
Ok(res)
@ -896,48 +874,23 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
fn irq_completion_handler(&mut self, res: &mut IrqResult) {
self.disable_rx_irq_sources();
self.inner.disable_rx();
self.inner.disable();
res.bytes_read = self.irq_info.rx_idx;
res.clear_result();
res.set_result(IrqResultMask::Complete);
res.complete = true;
self.irq_info.mode = IrqReceptionMode::Idle;
self.irq_info.rx_idx = 0;
self.irq_info.rx_len = 0;
}
pub fn irq_info(&self) -> &IrqInfo {
&self.irq_info
}
pub fn release(self) -> Uart {
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 {
fn kind(&self) -> embedded_io::ErrorKind {
embedded_io::ErrorKind::Other

View File

@ -74,8 +74,7 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"decoders": [
{
"port": 0,
@ -104,8 +103,7 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"decoders": [
{
"port": 0,
@ -134,8 +132,7 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"decoders": [
{
"port": 0,
@ -164,11 +161,11 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
@ -194,8 +191,7 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"decoders": [
{
"port": 0,
@ -224,8 +220,7 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"decoders": [
{
"port": 0,
@ -255,8 +250,7 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"decoders": [
{
"port": 0,
@ -286,8 +280,187 @@
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"address": "auto",
"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": [
{
"port": 0,
@ -298,4 +471,4 @@
}
},
]
}
}

View File

@ -3,6 +3,32 @@
// for the documentation about the tasks.json format
"version": "2.0.0",
"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",
"type": "shell",
@ -29,6 +55,19 @@
"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",
"type": "shell",
@ -56,6 +95,19 @@
"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",
"type": "shell",
@ -108,5 +160,44 @@
"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",
}
},
]
}
}