update package
All checks were successful
Rust/va108xx-rs/pipeline/pr-main This commit looks good

- Add embassy example
- improve timer API
- restructure examples
- restructure and improve SPI
This commit is contained in:
Robin Müller 2024-09-18 15:07:29 +02:00
parent 405cc089c3
commit ee383bbdfb
39 changed files with 2136 additions and 593 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 --all-features - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1
clippy: clippy:
name: Clippy name: Clippy

View File

@ -5,7 +5,10 @@ members = [
"va108xx", "va108xx",
"va108xx-hal", "va108xx-hal",
"examples/simple", "examples/simple",
"examples/rtic",
"examples/embassy",
"board-tests", "board-tests",
"bootloader",
] ]
exclude = [ exclude = [
@ -17,7 +20,8 @@ codegen-units = 1
debug = 2 debug = 2
debug-assertions = true # <- debug-assertions = true # <-
incremental = false incremental = false
opt-level = 'z' # <- # This is problematic for stepping..
# opt-level = 'z' # <-
overflow-checks = true # <- overflow-checks = true # <-
# cargo build/run --release # cargo build/run --release
@ -29,3 +33,12 @@ incremental = false
lto = 'fat' lto = 'fat'
opt-level = 3 # <- opt-level = 3 # <-
overflow-checks = false # <- overflow-checks = false # <-
[profile.small]
inherits = "release"
codegen-units = 1
debug-assertions = false # <-
lto = true
opt-level = 'z' # <-
overflow-checks = false # <-
strip = true # Automatically strip symbols from the binary.

View File

@ -19,9 +19,13 @@ This workspace contains the following released crates:
It also contains the following helper crates: It also contains the following helper crates:
- The `board-tests` contains an application which can be used to test the libraries on the - The [`board-tests`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/board-tests)
board. contains an application which can be used to test the libraries on the board.
- The `examples` crates contains various example applications for the HAL and the PAC. - The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va108xx-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
@ -94,6 +98,8 @@ example.
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`
@ -108,4 +114,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 va108xx
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1
""" """
} }
} }

View File

@ -4,10 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
cortex-m-rtic = "1"
panic-halt = "0.2"
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
panic-halt = "0.2"
rtt-target = "0.5" rtt-target = "0.5"
panic-rtt-target = "0.1.3" panic-rtt-target = "0.1.3"
embedded-hal = "1" embedded-hal = "1"

25
bootloader/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "bootloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-hal-bus = "0.2"
dummy-pin = "1"
panic-rtt-target = { version = "0.1.3" }
panic-halt = { version = "0.2" }
rtt-target = { version = "0.5" }
crc = "3"
[dependencies.va108xx-hal]
path = "../va108xx-hal"
[dependencies.vorago-reb1]
path = "../vorago-reb1"
[features]
default = []
rtt-panic = []

10
bootloader/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
#![no_std]
use core::convert::Infallible;
/// Simple trait which makes swapping the NVM easier. NVMs only need to implement this interface.
pub trait NvmInterface {
fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Infallible>;
fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), Infallible>;
fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, Infallible>;
}

296
bootloader/src/main.rs Normal file
View File

@ -0,0 +1,296 @@
//! Vorago bootloader which can boot from two images.
#![no_main]
#![no_std]
use bootloader::NvmInterface;
use cortex_m_rt::entry;
use crc::{Crc, CRC_16_IBM_3740};
#[cfg(not(feature = "rtt-panic"))]
use panic_halt as _;
#[cfg(feature = "rtt-panic")]
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{pac, time::Hertz};
use vorago_reb1::m95m01::M95M01;
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
// the binary stays small enough.
const RTT_PRINTOUT: bool = true;
const DEBUG_PRINTOUTS: bool = false;
// Dangerous option! An image with this option set to true will flash itself from RAM directly
// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM
// boot process. Please note that this will flash an image which will also always perform the
// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash
// loader to boot a bootloader without this feature.
const FLASH_SELF: bool = false;
// Register definitions for Cortex-M0 SCB register.
pub const SCB_AIRCR_VECTKEY_POS: u32 = 16;
pub const SCB_AIRCR_VECTKEY_MSK: u32 = 0xFFFF << SCB_AIRCR_VECTKEY_POS;
pub const SCB_AIRCR_SYSRESETREQ_POS: u32 = 2;
pub const SCB_AIRCR_SYSRESETREQ_MSK: u32 = 1 << SCB_AIRCR_SYSRESETREQ_POS;
const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
// Important bootloader addresses and offsets, vector table information.
const BOOTLOADER_START_ADDR: u32 = 0x0;
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
// This is also the maximum size of the bootloader.
const BOOTLOADER_END_ADDR: u32 = 0x3000;
const APP_A_START_ADDR: u32 = 0x3000;
const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
pub const APP_A_END_ADDR: u32 = 0x11000;
// The actual size of the image which is relevant for CRC calculation.
const APP_B_START_ADDR: u32 = 0x11000;
// The actual size of the image which is relevant for CRC calculation.
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
pub const APP_B_END_ADDR: u32 = 0x20000;
pub const APP_IMG_SZ: u32 = 0xE800;
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
pub const VECTOR_TABLE_LEN: u32 = 0xC0;
pub const RESET_VECTOR_OFFSET: u32 = 0x4;
const CRC_ALGO: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum AppSel {
A,
B,
}
pub struct NvmWrapper(pub M95M01);
// Newtype pattern. We could now more easily swap the used NVM type.
impl NvmInterface for NvmWrapper {
fn write(&mut self, address: u32, data: &[u8]) -> Result<(), core::convert::Infallible> {
self.0.write(address, data)
}
fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
self.0.read(address, buf)
}
fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, core::convert::Infallible> {
self.0.verify(address, data)
}
}
#[entry]
fn main() -> ! {
if RTT_PRINTOUT {
rtt_init_print!();
rprintln!("-- VA108xx bootloader --");
}
let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
if FLASH_SELF {
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let bootloader_data = {
unsafe {
&*core::ptr::slice_from_raw_parts(
(BOOTLOADER_START_ADDR + 4) as *const u8,
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 6) as usize,
)
}
};
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(bootloader_data);
let bootloader_crc = digest.finalize();
nvm.write(0x0, &first_four_bytes)
.expect("writing to NVM failed");
nvm.write(0x4, bootloader_data)
.expect("writing to NVM failed");
if let Err(e) = nvm.verify(0x0, &first_four_bytes) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
}
if let Err(e) = nvm.verify(0x4, bootloader_data) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
}
nvm.write(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes())
.expect("writing CRC failed");
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
if RTT_PRINTOUT {
rprintln!(
"error: CRC verification for bootloader self-flash failed: {:?}",
e
);
}
}
}
let mut nvm = NvmWrapper(nvm);
// Check bootloader's CRC (and write it if blank)
check_own_crc(&dp.sysconfig, &cp, &mut nvm);
if check_app_crc(AppSel::A) {
boot_app(&dp.sysconfig, &cp, AppSel::A)
} else if check_app_crc(AppSel::B) {
boot_app(&dp.sysconfig, &cp, AppSel::B)
} else {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("both images corrupt! booting image A");
}
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
// Both images seem to be corrupt. Boot default image A.
boot_app(&dp.sysconfig, &cp, AppSel::A)
}
}
fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut NvmWrapper) {
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() };
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
// because the address of the bootloader is 0x0, so the NULL check fails and the functions
// panics.
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(unsafe {
&*core::ptr::slice_from_raw_parts(
(BOOTLOADER_START_ADDR + 4) as *const u8,
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 6) as usize,
)
});
let crc_calc = digest.finalize();
if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("BL CRC blank - prog new CRC");
}
// Blank CRC, write it to NVM.
nvm.write(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes())
.expect("writing CRC failed");
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
// necessary because somehow the boot will not work if we just continue as usual.
// cortex_m::peripheral::SCB::sys_reset();
} else if crc_exp != crc_calc {
// Bootloader is corrupted. Try to run App A.
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!(
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
crc_calc,
crc_exp
);
}
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
boot_app(sysconfig, cp, AppSel::A);
}
}
// Reading from address 0x0 is problematic in Rust.
// See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/5.
// This solution falls back to assembler to deal with this.
fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
unsafe {
core::arch::asm!(
"ldr r0, [{0}]", // Load 4 bytes from src into r0 register
"str r0, [{1}]", // Store r0 register into first_four_bytes
in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0)
in(reg) buf as *mut [u8; 4], // Input: destination pointer
);
}
}
fn check_app_crc(app_sel: AppSel) -> bool {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("Checking image {:?}", app_sel);
}
if app_sel == AppSel::A {
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR)
} else {
check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR)
}
}
fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) -> bool {
let crc_exp = unsafe { (crc_addr as *const u16).read_unaligned().to_be() };
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
// Sanity check.
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
if RTT_PRINTOUT {
rprintln!("detected invalid app size {}", image_size);
}
return false;
}
let crc_calc = CRC_ALGO.checksum(unsafe {
core::slice::from_raw_parts(start_addr as *const u8, image_size as usize)
});
if crc_calc == crc_exp {
return true;
}
false
}
// The boot works by copying the interrupt vector table (IVT) of the respective app to the
// base address in code RAM (0x0) and then performing a soft reset.
fn boot_app(syscfg: &pac::Sysconfig, cp: &cortex_m::Peripherals, app_sel: AppSel) -> ! {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("booting app {:?}", app_sel);
}
// Disable ROM protection.
syscfg.rom_prot().write(|w| unsafe { w.bits(1) });
let base_addr = if app_sel == AppSel::A {
APP_A_START_ADDR
} else {
APP_B_START_ADDR
};
// Clear all interrupts set.
unsafe {
cp.NVIC.icer[0].write(0xFFFFFFFF);
cp.NVIC.icpr[0].write(0xFFFFFFFF);
// First 4 bytes done with inline assembly, writing to the physical address 0x0 can not
// be done without it. See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/2.
core::ptr::read(base_addr as *const u32);
core::arch::asm!(
"str {0}, [{1}]", // Load 4 bytes from src into r0 register
in(reg) base_addr, // Input: App vector table.
in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer
);
core::slice::from_raw_parts_mut(
(BOOTLOADER_START_ADDR + 4) as *mut u32,
(VECTOR_TABLE_LEN - 4) as usize,
)
.copy_from_slice(core::slice::from_raw_parts(
(base_addr + 4) as *const u32,
(VECTOR_TABLE_LEN - 4) as usize,
));
}
/* Disable re-loading from FRAM/code ROM on soft reset */
syscfg
.rst_cntl_rom()
.modify(|_, w| w.sysrstreq().clear_bit());
soft_reset(cp);
}
// Soft reset based on https://github.com/ARM-software/CMSIS_6/blob/5782d6f8057906d360f4b95ec08a2354afe5c9b9/CMSIS/Core/Include/core_cm0.h#L874.
fn soft_reset(cp: &cortex_m::Peripherals) -> ! {
// Ensure all outstanding memory accesses included buffered write are completed before reset.
cortex_m::asm::dsb();
unsafe {
cp.SCB
.aircr
.write((0x5FA << SCB_AIRCR_VECTKEY_POS) | SCB_AIRCR_SYSRESETREQ_MSK);
}
// Ensure completion of memory access.
cortex_m::asm::dsb();
unreachable!();
}

25
examples/README.md Normal file
View File

@ -0,0 +1,25 @@
VA108xx 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"
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]}
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.va108xx-hal]
path = "../../va108xx-hal"
[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,43 @@
#![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 va108xx_hal::{gpio::PinsA, pac, prelude::*};
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
// main is itself an async function.
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
rtt_init_print!();
rprintln!("-- VA108xx Embassy Demo --");
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
unsafe {
embassy_example::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
)
};
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
let mut led0 = porta.pa10.into_readable_push_pull_output();
let mut led1 = porta.pa7.into_readable_push_pull_output();
let mut led2 = porta.pa6.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());
led0.toggle().ok();
led1.toggle().ok();
led2.toggle().ok();
}
}

View File

@ -0,0 +1,334 @@
//! This is a sample time driver implementation for the VA108xx family of devices, supporting
//! one alarm and requiring/reserving 2 TIM peripherals. You could adapt this implementation to
//! support more alarms.
//!
//! This driver implementation reserves interrupts OC31 and OC30 for the timekeeping.
use core::{cell::Cell, mem, ptr};
use critical_section::CriticalSection;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use portable_atomic::{AtomicU32, AtomicU8, Ordering};
use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ};
use once_cell::sync::OnceCell;
use va108xx_hal::{
clock::enable_peripheral_clock,
enable_interrupt,
pac::{self, interrupt},
prelude::*,
timer::{enable_tim_clk, ValidTim},
PeripheralSelect,
};
pub type TimekeeperClk = pac::Tim23;
pub type AlarmClk0 = pac::Tim22;
pub type AlarmClk1 = pac::Tim21;
pub type AlarmClk2 = pac::Tim20;
const TIMEKEEPER_IRQ: pac::Interrupt = pac::Interrupt::OC31;
const ALARM_IRQ: pac::Interrupt = pac::Interrupt::OC30;
/// 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,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
timekeeper: TimekeeperClk,
alarm_tim: AlarmClk0,
) {
//enable_and_init_irq_router(syscfg, irq_router);
DRIVER.init(syscfg, irqsel, sysclk, timekeeper, alarm_tim)
}
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])
});
/// Timekeeper interrupt.
#[interrupt]
#[allow(non_snake_case)]
fn OC31() {
DRIVER.on_interrupt_timekeeping()
}
/// Alarm timer interrupt.
#[interrupt]
#[allow(non_snake_case)]
fn OC30() {
DRIVER.on_interrupt_alarm(0)
}
#[inline(always)]
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")
}
}
}
#[inline(always)]
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,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
timekeeper: TimekeeperClk,
alarm_tim: AlarmClk0,
) {
enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel);
enable_tim_clk(syscfg, TimekeeperClk::TIM_ID);
let sysclk = sysclk.into();
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
SCALE.set((sysclk.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.
irqsel
.tim0(TimekeeperClk::TIM_ID as usize)
.write(|w| unsafe { w.bits(TIMEKEEPER_IRQ as u32) });
unsafe {
enable_interrupt(TIMEKEEPER_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);
// 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(ALARM_IRQ);
}
irqsel
.tim0(AlarmClk0::TIM_ID as usize)
.write(|w| unsafe { w.bits(ALARM_IRQ as u32) });
}
// 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
})
}
}

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

@ -0,0 +1,33 @@
[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"
embedded-io = "0.6"
rtt-target = { version = "0.5" }
# Even though we do not use this directly, we need to activate this feature explicitely
# so that RTIC compiles because thumv6 does not have CAS operations natively.
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]}
panic-rtt-target = { version = "0.1" }
[dependencies.va108xx-hal]
path = "../../va108xx-hal"
[dependencies.vorago-reb1]
path = "../../vorago-reb1"
[dependencies.rtic]
version = "2"
features = ["thumbv6-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]
[dependencies.rtic-sync]
version = "1.3"
features = ["defmt-03"]

View File

@ -5,7 +5,7 @@
#[rtic::app(device = pac)] #[rtic::app(device = pac)]
mod app { mod app {
use panic_rtt_target as _; use panic_rtt_target as _;
use rtic_monotonics::systick::Systick; use rtic_example::SYSCLK_FREQ;
use rtt_target::{rprintln, rtt_init_default, set_print_channel}; use rtt_target::{rprintln, rtt_init_default, set_print_channel};
use va108xx_hal::{ use va108xx_hal::{
clock::{set_clk_div_register, FilterClkSel}, clock::{set_clk_div_register, FilterClkSel},
@ -17,6 +17,8 @@ mod app {
use vorago_reb1::button::Button; use vorago_reb1::button::Button;
use vorago_reb1::leds::Leds; use vorago_reb1::leds::Leds;
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum PressMode { pub enum PressMode {
Toggle, Toggle,
@ -44,17 +46,11 @@ mod app {
struct Shared {} struct Shared {}
#[init] #[init]
fn init(ctx: init::Context) -> (Shared, Local) { fn init(cx: init::Context) -> (Shared, Local) {
let channels = rtt_init_default!(); let channels = rtt_init_default!();
set_print_channel(channels.up.0); set_print_channel(channels.up.0);
rprintln!("-- Vorago Button IRQ Example --"); rprintln!("-- Vorago Button IRQ Example --");
// Initialize the systick interrupt & obtain the token to prove that we did Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(
ctx.core.SYST,
Hertz::from(50.MHz()).raw(),
systick_mono_token,
);
let mode = match CFG_MODE { let mode = match CFG_MODE {
// Ask mode from user via RTT // Ask mode from user via RTT
@ -64,7 +60,7 @@ mod app {
}; };
rprintln!("Using {:?} mode", mode); rprintln!("Using {:?} mode", mode);
let mut dp = ctx.device; let mut dp = cx.device;
let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
let edge_irq = match mode { let edge_irq = match mode {
PressMode::Toggle => InterruptEdge::HighToLow, PressMode::Toggle => InterruptEdge::HighToLow,
@ -117,12 +113,10 @@ mod app {
let mode = cx.local.mode; let mode = cx.local.mode;
if *mode == PressMode::Toggle { if *mode == PressMode::Toggle {
leds[0].toggle(); leds[0].toggle();
} else if button.released() {
leds[0].off();
} else { } else {
if button.released() { leds[0].on();
leds[0].off();
} else {
leds[0].on();
}
} }
} }
@ -138,14 +132,11 @@ mod app {
let mut read; let mut read;
loop { loop {
read = down_channel.read(&mut read_buf); read = down_channel.read(&mut read_buf);
for i in 0..read { for &byte in &read_buf[..read] {
let val = read_buf[i] as char; match byte as char {
if val == '0' || val == '1' { '0' => return PressMode::Toggle,
return if val == '0' { '1' => return PressMode::Keep,
PressMode::Toggle _ => continue, // Ignore other characters
} else {
PressMode::Keep
};
} }
} }
} }

View File

@ -14,14 +14,13 @@
mod app { mod app {
use embedded_io::Write; use embedded_io::Write;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtic_monotonics::systick::Systick; use rtic_example::SYSCLK_FREQ;
use rtic_sync::make_channel; use rtic_sync::make_channel;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{ use va108xx_hal::{
gpio::PinsB, gpio::PinsB,
pac, pac,
prelude::*, prelude::*,
time::Hertz,
uart::{self, IrqCfg, IrqResult, UartWithIrqBase}, uart::{self, IrqCfg, IrqResult, UartWithIrqBase},
}; };
@ -44,19 +43,14 @@ mod app {
pub timeout: bool, pub timeout: bool,
} }
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init] #[init]
fn init(cx: init::Context) -> (Shared, Local) { fn init(cx: init::Context) -> (Shared, Local) {
rtt_init_print!(); rtt_init_print!();
//set_print_channel(channels.up.0);
rprintln!("-- VA108xx UART IRQ example application--"); rprintln!("-- VA108xx UART IRQ example application--");
// Initialize the systick interrupt & obtain the token to prove that we did Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(
cx.core.SYST,
Hertz::from(50.MHz()).raw(),
systick_mono_token,
);
let mut dp = cx.device; let mut dp = cx.device;
let gpiob = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb); let gpiob = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
@ -74,7 +68,6 @@ mod app {
let (rx_info_tx, rx_info_rx) = make_channel!(RxInfo, 3); let (rx_info_tx, rx_info_rx) = make_channel!(RxInfo, 3);
let rx_buf: [u8; 64] = [0; 64]; let rx_buf: [u8; 64] = [0; 64];
//reply_handler::spawn().expect("spawning reply handler failed");
( (
Shared { irq_uart, rx_buf }, Shared { irq_uart, rx_buf },
Local { Local {
@ -112,8 +105,8 @@ mod app {
.expect("Read operation init failed"); .expect("Read operation init failed");
let mut end_idx = 0; let mut end_idx = 0;
for idx in 0..rx_buf.len() { for (idx, val) in rx_buf.iter().enumerate() {
if (rx_buf[idx] as char) == '\n' { if (*val as char) == '\n' {
end_idx = idx; end_idx = idx;
break; break;
} }

4
examples/rtic/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
#![no_std]
use va108xx_hal::time::Hertz;
pub const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);

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

@ -0,0 +1,71 @@
//! RTIC minimal blinky
#![no_main]
#![no_std]
#[rtic::app(device = pac, dispatchers = [OC31, OC30, OC29])]
mod app {
use cortex_m::asm;
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtic_example::SYSCLK_FREQ;
use rtic_monotonics::systick::prelude::*;
use rtic_monotonics::Monotonic;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsA, PA10, PA6, PA7},
pac,
};
#[local]
struct Local {
led0: Pin<PA10, OutputReadablePushPull>,
led1: Pin<PA7, OutputReadablePushPull>,
led2: Pin<PA6, OutputReadablePushPull>,
}
#[shared]
struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(mut cx: init::Context) -> (Shared, Local) {
rtt_init_print!();
rprintln!("-- Vorago VA108xx RTIC template --");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let porta = PinsA::new(
&mut cx.device.sysconfig,
Some(cx.device.ioconfig),
cx.device.porta,
);
let led0 = porta.pa10.into_readable_push_pull_output();
let led1 = porta.pa7.into_readable_push_pull_output();
let led2 = porta.pa6.into_readable_push_pull_output();
blinky::spawn().ok();
(Shared {}, Local { led0, led1, led2 })
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
#[task(
priority = 3,
local=[led0, led1, led2],
)]
async fn blinky(cx: blinky::Context) {
loop {
rprintln!("toggling LEDs");
cx.local.led0.toggle().ok();
cx.local.led1.toggle().ok();
cx.local.led2.toggle().ok();
Mono::delay(1000.millis()).await;
}
}
}

View File

@ -4,30 +4,20 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
panic-halt = "0.2"
cortex-m = {version = "0.7", features = ["critical-section-single-core"]} cortex-m = {version = "0.7", features = ["critical-section-single-core"]}
panic-rtt-target = "0.1"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
panic-halt = "0.2"
panic-rtt-target = "0.1"
critical-section = "1"
rtt-target = "0.5" rtt-target = "0.5"
rtic-sync = { version = "1.3", features = ["defmt-03"] }
embedded-hal = "1" embedded-hal = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
embedded-io = "0.6" embedded-io = "0.6"
cortex-m-semihosting = "0.5.0" cortex-m-semihosting = "0.5.0"
# I'd really like to use those, but it is tricky without probe-rs..
# defmt = "0.3"
# defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
# panic-probe = { version = "0.3", features = ["print-defmt"] }
[dependencies.rtic]
version = "2"
features = ["thumbv6-backend"]
[dependencies.rtic-monotonics]
version = "1"
features = ["cortex-m-systick"]
[dependencies.va108xx-hal] [dependencies.va108xx-hal]
version = "0.7"
path = "../../va108xx-hal" path = "../../va108xx-hal"
features = ["rt", "defmt"] features = ["rt", "defmt"]
[dependencies.vorago-reb1]
path = "../../vorago-reb1"

View File

@ -48,7 +48,7 @@ fn main() -> ! {
let mut cascade_target_1 = let mut cascade_target_1 =
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true); CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true);
cascade_target_1 cascade_target_1
.cascade_0_source(CascadeSource::TimBase, Some(3)) .cascade_0_source(CascadeSource::Tim(3))
.expect("Configuring cascade source for TIM4 failed"); .expect("Configuring cascade source for TIM4 failed");
let mut csd_cfg = CascadeCtrl { let mut csd_cfg = CascadeCtrl {
enb_start_src_csd0: true, enb_start_src_csd0: true,
@ -75,7 +75,7 @@ fn main() -> ! {
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true); CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true);
// Set TIM4 as cascade source // Set TIM4 as cascade source
cascade_target_2 cascade_target_2
.cascade_1_source(CascadeSource::TimBase, Some(4)) .cascade_1_source(CascadeSource::Tim(4))
.expect("Configuring cascade source for TIM5 failed"); .expect("Configuring cascade source for TIM5 failed");
csd_cfg = CascadeCtrl::default(); csd_cfg = CascadeCtrl::default();

View File

@ -16,7 +16,7 @@ use va108xx_hal::{
pac::{self, interrupt}, pac::{self, interrupt},
prelude::*, prelude::*,
pwm::{default_ms_irq_handler, set_up_ms_tick}, pwm::{default_ms_irq_handler, set_up_ms_tick},
spi::{self, Spi, SpiBase, TransferConfig}, spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs},
IrqCfg, IrqCfg,
}; };
@ -55,6 +55,8 @@ fn main() -> ! {
dp.tim0, dp.tim0,
); );
let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz())
.expect("creating SPI clock config failed");
let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None); let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None);
let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None); let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None);
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
@ -79,7 +81,6 @@ fn main() -> ! {
dp.spia, dp.spia,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
None,
); );
spia.set_fill_word(FILL_WORD); spia.set_fill_word(FILL_WORD);
spia_ref.borrow_mut().replace(spia.downgrade()); spia_ref.borrow_mut().replace(spia.downgrade());
@ -96,7 +97,6 @@ fn main() -> ! {
dp.spia, dp.spia,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
None,
); );
spia.set_fill_word(FILL_WORD); spia.set_fill_word(FILL_WORD);
spia_ref.borrow_mut().replace(spia.downgrade()); spia_ref.borrow_mut().replace(spia.downgrade());
@ -113,7 +113,6 @@ fn main() -> ! {
dp.spib, dp.spib,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
None,
); );
spib.set_fill_word(FILL_WORD); spib.set_fill_word(FILL_WORD);
spib_ref.borrow_mut().replace(spib.downgrade()); spib_ref.borrow_mut().replace(spib.downgrade());
@ -123,17 +122,21 @@ fn main() -> ! {
match SPI_BUS_SEL { match SPI_BUS_SEL {
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => { SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
if let Some(ref mut spi) = *spia_ref.borrow_mut() { if let Some(ref mut spi) = *spia_ref.borrow_mut() {
let transfer_cfg = let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs(
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false); Some(spi_clk_cfg),
Some(SPI_MODE),
BLOCKMODE,
false,
);
spi.cfg_transfer(&transfer_cfg); spi.cfg_transfer(&transfer_cfg);
} }
} }
SpiBusSelect::SpiBPortB => { SpiBusSelect::SpiBPortB => {
if let Some(ref mut spi) = *spib_ref.borrow_mut() { if let Some(ref mut spi) = *spib_ref.borrow_mut() {
let hw_cs_pin = pinsb.pb2.into_funsel_1(); let hw_cs_pin = pinsb.pb2.into_funsel_1();
let transfer_cfg = TransferConfig::new( let transfer_cfg = TransferConfigWithHwcs::new(
SPI_SPEED_KHZ.kHz(), Some(spi_clk_cfg),
SPI_MODE, Some(SPI_MODE),
Some(hw_cs_pin), Some(hw_cs_pin),
BLOCKMODE, BLOCKMODE,
false, false,

View File

@ -3,8 +3,8 @@
#![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 va108xx_hal::{ use va108xx_hal::{
@ -83,11 +83,12 @@ fn main() -> ! {
} }
} }
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); cortex_m::asm::delay(10000);
@ -110,7 +111,7 @@ fn OC0() {
#[interrupt] #[interrupt]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn OC1() { fn OC1() {
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

@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
- Improves `CascardSource` handling and general API when chosing cascade sources.
- Replaced `utility::unmask_irq` by `enable_interrupt` and `disable_interrupt` API.
## [v0.7.0] 2024-07-04 ## [v0.7.0] 2024-07-04
- Replace `uarta` and `uartb` `Uart` constructors by `new` constructor - Replace `uarta` and `uartb` `Uart` constructors by `new` constructor

View File

@ -19,7 +19,7 @@ embedded-hal-nb = "1"
embedded-io = "0.6" embedded-io = "0.6"
fugit = "0.3" fugit = "0.3"
typenum = "1" typenum = "1"
defmt = { version = "0.3", optional = true } critical-section = "1"
delegate = "0.12" delegate = "0.12"
[dependencies.va108xx] [dependencies.va108xx]
@ -38,9 +38,14 @@ default-features = false
version = "1.14" version = "1.14"
default-features = false default-features = false
[dependencies.defmt]
version = "0.3"
optional = true
[features] [features]
default = ["rt"] default = ["rt"]
rt = ["va108xx/rt"] rt = ["va108xx/rt"]
defmt = ["dep:defmt", "fugit/defmt"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -15,7 +15,6 @@ pub mod time;
pub mod timer; pub mod timer;
pub mod typelevel; pub mod typelevel;
pub mod uart; pub mod uart;
pub mod utility;
#[derive(Debug, Eq, Copy, Clone, PartialEq)] #[derive(Debug, Eq, Copy, Clone, PartialEq)]
pub enum FunSel { pub enum FunSel {
@ -98,3 +97,21 @@ pub fn port_mux(
} }
} }
} }
/// Enable a specific interrupt using the NVIC peripheral.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
#[inline]
pub unsafe fn enable_interrupt(irq: pac::Interrupt) {
unsafe {
cortex_m::peripheral::NVIC::unmask(irq);
}
}
/// Disable a specific interrupt using the NVIC peripheral.
#[inline]
pub fn disable_interrupt(irq: pac::Interrupt) {
cortex_m::peripheral::NVIC::mask(irq);
}

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
pub use crate::IrqCfg; pub use crate::IrqCfg;
use crate::{ use crate::{
clock::{enable_peripheral_clock, PeripheralClocks}, clock::{enable_peripheral_clock, PeripheralClocks},
enable_interrupt,
gpio::{ gpio::{
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14, AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
PA15, PA2, PA24, PA25, PA26, PA27, PA28, PA29, PA3, PA30, PA31, PA4, PA5, PA6, PA7, PA8, PA15, PA2, PA24, PA25, PA26, PA27, PA28, PA29, PA3, PA30, PA31, PA4, PA5, PA6, PA7, PA8,
@ -17,10 +18,9 @@ use crate::{
time::Hertz, time::Hertz,
timer, timer,
typelevel::Sealed, typelevel::Sealed,
utility::unmask_irq,
}; };
use core::cell::Cell; use core::cell::Cell;
use cortex_m::interrupt::Mutex; use critical_section::Mutex;
use fugit::RateExtU32; use fugit::RateExtU32;
const IRQ_DST_NONE: u32 = 0xffffffff; const IRQ_DST_NONE: u32 = 0xffffffff;
@ -72,25 +72,46 @@ pub enum CascadeSel {
Csd2 = 2, Csd2 = 2,
} }
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCascadeSourceId;
/// The numbers are the base numbers for bundles like PORTA, PORTB or TIM /// The numbers are the base numbers for bundles like PORTA, PORTB or TIM
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CascadeSource { pub enum CascadeSource {
PortABase = 0, PortA(u8),
PortBBase = 32, PortB(u8),
TimBase = 64, Tim(u8),
RamSbe = 96, RamSbe = 96,
RamMbe = 97, RamMbe = 97,
RomSbe = 98, RomSbe = 98,
RomMbe = 99, RomMbe = 99,
Txev = 100, Txev = 100,
ClockDividerBase = 120, ClockDivider(u8),
} }
#[derive(Debug, PartialEq, Eq)] impl CascadeSource {
pub enum TimerErrors { fn id(&self) -> Result<u8, InvalidCascadeSourceId> {
Canceled, let port_check = |base: u8, id: u8, len: u8| {
/// Invalid input for Cascade source if id > len - 1 {
InvalidCsdSourceInput, return Err(InvalidCascadeSourceId);
}
Ok(base + id)
};
match self {
CascadeSource::PortA(id) => port_check(0, *id, 32),
CascadeSource::PortB(id) => port_check(32, *id, 32),
CascadeSource::Tim(id) => port_check(64, *id, 24),
CascadeSource::RamSbe => Ok(96),
CascadeSource::RamMbe => Ok(97),
CascadeSource::RomSbe => Ok(98),
CascadeSource::RomMbe => Ok(99),
CascadeSource::Txev => Ok(100),
CascadeSource::ClockDivider(id) => port_check(120, *id, 8),
}
}
} }
//================================================================================================== //==================================================================================================
@ -360,89 +381,26 @@ pub struct CountDownTimer<TIM: ValidTim> {
listening: bool, listening: bool,
} }
fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { #[inline(always)]
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)) });
} }
#[inline(always)]
pub fn disable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << idx)) });
}
unsafe impl<TIM: ValidTim> TimRegInterface for CountDownTimer<TIM> { unsafe impl<TIM: ValidTim> TimRegInterface for CountDownTimer<TIM> {
fn tim_id(&self) -> u8 { fn tim_id(&self) -> u8 {
TIM::TIM_ID TIM::TIM_ID
} }
} }
macro_rules! csd_sel {
($func_name:ident, $csd_reg:ident) => {
/// Configure the Cascade sources
pub fn $func_name(
&mut self,
src: CascadeSource,
id: Option<u8>,
) -> Result<(), TimerErrors> {
let mut id_num = 0;
if let CascadeSource::PortABase
| CascadeSource::PortBBase
| CascadeSource::ClockDividerBase
| CascadeSource::TimBase = src
{
if id.is_none() {
return Err(TimerErrors::InvalidCsdSourceInput);
}
}
if id.is_some() {
id_num = id.unwrap();
}
match src {
CascadeSource::PortABase => {
if id_num > 55 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().$csd_reg().write(|w| unsafe {
w.cassel().bits(CascadeSource::PortABase as u8 + id_num)
});
Ok(())
}
CascadeSource::PortBBase => {
if id_num > 23 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().$csd_reg().write(|w| unsafe {
w.cassel().bits(CascadeSource::PortBBase as u8 + id_num)
});
Ok(())
}
CascadeSource::TimBase => {
if id_num > 23 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().$csd_reg().write(|w| unsafe {
w.cassel().bits(CascadeSource::TimBase as u8 + id_num)
});
Ok(())
}
CascadeSource::ClockDividerBase => {
if id_num > 7 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().cascade0().write(|w| unsafe {
w.cassel()
.bits(CascadeSource::ClockDividerBase as u8 + id_num)
});
Ok(())
}
_ => {
self.tim
.reg()
.$csd_reg()
.write(|w| unsafe { w.cassel().bits(src as u8) });
Ok(())
}
}
}
};
}
impl<TIM: ValidTim> CountDownTimer<TIM> { impl<TIM: ValidTim> CountDownTimer<TIM> {
/// Configures a TIM peripheral as a periodic count down timer /// Configures a TIM peripheral as a periodic count down timer
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, tim: TIM) -> Self { pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, tim: TIM) -> Self {
@ -554,18 +512,18 @@ 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());
if let Some(irq_cfg) = self.irq_cfg { if let Some(irq_cfg) = self.irq_cfg {
self.enable_interrupt(); self.enable_interrupt();
if irq_cfg.enable { if irq_cfg.enable {
unmask_irq(irq_cfg.irq); unsafe { enable_interrupt(irq_cfg.irq) };
} }
} }
self.tim.reg().enable().write(|w| unsafe { w.bits(1) });
} }
#[inline(always)] #[inline(always)]
pub fn disable(&mut self) { pub fn disable(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit()); self.tim.reg().enable().write(|w| unsafe { w.bits(0) });
} }
/// Disable the counter, setting both enable and active bit to 0 /// Disable the counter, setting both enable and active bit to 0
@ -619,9 +577,32 @@ impl<TIM: ValidTim> CountDownTimer<TIM> {
}); });
} }
csd_sel!(cascade_0_source, cascade0); pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
csd_sel!(cascade_1_source, cascade1); let id = src.id()?;
csd_sel!(cascade_2_source, cascade2); self.tim
.reg()
.cascade0()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.cascade1()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.cascade2()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
pub fn curr_freq(&self) -> Hertz { pub fn curr_freq(&self) -> Hertz {
self.curr_freq self.curr_freq
@ -656,12 +637,13 @@ impl<TIM: ValidTim> CountDownTimer<TIM> {
} }
} }
pub fn cancel(&mut self) -> Result<(), TimerErrors> { /// Returns [false] if the timer was not active, and true otherwise.
pub fn cancel(&mut self) -> bool {
if !self.tim.reg().ctrl().read().enable().bit_is_set() { if !self.tim.reg().ctrl().read().enable().bit_is_set() {
return Err(TimerErrors::Canceled); return false;
} }
self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
Ok(()) true
} }
} }
@ -747,7 +729,7 @@ pub fn set_up_ms_delay_provider<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);
@ -756,7 +738,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())
} }
//================================================================================================== //==================================================================================================

View File

@ -3,7 +3,7 @@
//! ## Examples //! ## Examples
//! //!
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs) //! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs)
//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart-irq-rtic.rs) //! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/rtic/bin/uart-rtic.rs)
use core::{marker::PhantomData, ops::Deref}; use core::{marker::PhantomData, ops::Deref};
use embedded_hal_nb::serial::Read; use embedded_hal_nb::serial::Read;
use fugit::RateExtU32; use fugit::RateExtU32;
@ -11,13 +11,13 @@ use fugit::RateExtU32;
pub use crate::IrqCfg; pub use crate::IrqCfg;
use crate::{ use crate::{
clock::{enable_peripheral_clock, PeripheralClocks}, clock::{enable_peripheral_clock, PeripheralClocks},
enable_interrupt,
gpio::pin::{ gpio::pin::{
AltFunc1, AltFunc2, AltFunc3, Pin, PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, PA30, AltFunc1, AltFunc2, AltFunc3, Pin, PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, PA30,
PA31, PA8, PA9, PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9, PA31, PA8, PA9, PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9,
}, },
pac::{self, uarta as uart_base}, pac::{self, uarta as uart_base},
time::Hertz, time::Hertz,
utility::unmask_irq,
PeripheralSelect, PeripheralSelect,
}; };
@ -638,7 +638,7 @@ impl Instance for pac::Uartb {
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1;
} }
impl<UART: Instance> UartWithIrqBase<UART> { impl<Uart: Instance> UartWithIrqBase<Uart> {
fn init(self, sys_cfg: Option<&mut pac::Sysconfig>, irq_sel: Option<&mut pac::Irqsel>) -> Self { fn init(self, sys_cfg: Option<&mut pac::Sysconfig>, irq_sel: Option<&mut pac::Irqsel>) -> Self {
if let Some(sys_cfg) = sys_cfg { if let Some(sys_cfg) = sys_cfg {
enable_peripheral_clock(sys_cfg, PeripheralClocks::Irqsel) enable_peripheral_clock(sys_cfg, PeripheralClocks::Irqsel)
@ -646,7 +646,7 @@ impl<UART: Instance> UartWithIrqBase<UART> {
if let Some(irq_sel) = irq_sel { if let Some(irq_sel) = irq_sel {
if self.irq_info.irq_cfg.route { if self.irq_info.irq_cfg.route {
irq_sel irq_sel
.uart0(UART::IDX as usize) .uart0(Uart::IDX as usize)
.write(|w| unsafe { w.bits(self.irq_info.irq_cfg.irq as u32) }); .write(|w| unsafe { w.bits(self.irq_info.irq_cfg.irq as u32) });
} }
} }
@ -676,7 +676,9 @@ impl<UART: Instance> UartWithIrqBase<UART> {
self.uart.enable_tx(); self.uart.enable_tx();
self.enable_rx_irq_sources(enb_timeout_irq); self.enable_rx_irq_sources(enb_timeout_irq);
if self.irq_info.irq_cfg.enable { if self.irq_info.irq_cfg.enable {
unmask_irq(self.irq_info.irq_cfg.irq); unsafe {
enable_interrupt(self.irq_info.irq_cfg.irq);
}
} }
Ok(()) Ok(())
} }
@ -839,7 +841,7 @@ impl<UART: Instance> UartWithIrqBase<UART> {
self.irq_info.rx_len = 0; self.irq_info.rx_len = 0;
} }
pub fn release(self) -> UART { pub fn release(self) -> Uart {
self.uart.release() self.uart.release()
} }
} }

View File

@ -1,16 +0,0 @@
//! # API for utility functions like the Error Detection and Correction (EDAC) block
//!
//! Some more information about the recommended scrub rates can be found on the
//! [Vorago White Paper website](https://www.voragotech.com/resources) in the
//! application note AN1212
use crate::pac;
/// Unmask and enable an IRQ with the given interrupt number
///
/// ## Safety
///
/// The unmask function can break mask-based critical sections
#[inline]
pub(crate) fn unmask_irq(irq: pac::Interrupt) {
unsafe { cortex_m::peripheral::NVIC::unmask(irq) };
}

View File

@ -14,12 +14,15 @@ categories = ["aerospace", "embedded", "no-std", "hardware-support"]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
nb = "1"
bitfield = "0.17"
[dependencies.max116xx-10bit] [dependencies.max116xx-10bit]
version = "0.3" version = "0.3"
[dependencies.va108xx-hal] [dependencies.va108xx-hal]
version = "0.7" version = ">=0.7, <0.8"
path = "../va108xx-hal"
features = ["rt"] features = ["rt"]
[features] [features]
@ -28,20 +31,10 @@ rt = ["va108xx-hal/rt"]
[dev-dependencies] [dev-dependencies]
panic-halt = "0.2" panic-halt = "0.2"
nb = "1" nb = "1"
rtt-target = "0.5"
[dev-dependencies.rtt-target] panic-rtt-target = "0.1"
version = "0.5" embedded-hal-bus = "0.2"
dummy-pin = "1"
[dev-dependencies.panic-rtt-target]
version = "0.1"
[dev-dependencies.rtic]
version = "2"
features = ["thumbv6-backend"]
[dev-dependencies.rtic-monotonics]
version = "1"
features = ["cortex-m-systick"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -5,15 +5,16 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use cortex_m_rt::entry; use cortex_m_rt::entry;
use embedded_hal::spi::SpiBus; use embedded_hal::spi::{SpiBus, MODE_3};
use embedded_hal::{delay::DelayNs, digital::OutputPin}; use embedded_hal::{delay::DelayNs, digital::OutputPin};
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 va108xx_hal::spi::SpiClkConfig;
use va108xx_hal::{ use va108xx_hal::{
gpio::PinsA, gpio::PinsA,
pac, pac,
prelude::*, prelude::*,
spi::{Spi, SpiConfig, TransferConfig}, spi::{Spi, SpiConfig},
timer::set_up_ms_delay_provider, timer::set_up_ms_delay_provider,
}; };
@ -31,7 +32,6 @@ fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap(); let mut dp = pac::Peripherals::take().unwrap();
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0); let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
let spi_cfg = SpiConfig::default();
let (sck, mosi, miso) = ( let (sck, mosi, miso) = (
pinsa.pa20.into_funsel_2(), pinsa.pa20.into_funsel_2(),
pinsa.pa19.into_funsel_2(), pinsa.pa19.into_funsel_2(),
@ -45,21 +45,20 @@ fn main() -> ! {
.set_high() .set_high()
.expect("Setting ADC chip select high failed"); .expect("Setting ADC chip select high failed");
let transfer_cfg = TransferConfig::new( let spi_cfg = SpiConfig::default()
1.MHz(), .clk_cfg(
embedded_hal::spi::MODE_3, SpiClkConfig::from_clk(50.MHz(), 1.MHz()).expect("creating SPI clock config failed"),
Some(cs_pin), )
false, .mode(MODE_3)
true, .slave_output_disable(true);
);
let mut spi = Spi::new( let mut spi = Spi::new(
&mut dp.sysconfig, &mut dp.sysconfig,
50.MHz(), 50.MHz(),
dp.spib, dp.spib,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
Some(&transfer_cfg.downgrade()),
); );
spi.cfg_hw_cs_with_pin(&cs_pin);
let mut tx_rx_buf: [u8; 3] = [0; 3]; let mut tx_rx_buf: [u8; 3] = [0; 3];
tx_rx_buf[0] = READ_MASK | DEVID_REG; tx_rx_buf[0] = READ_MASK | DEVID_REG;

View File

@ -9,19 +9,19 @@ use core::convert::Infallible;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use embedded_hal::digital::OutputPin; use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{SpiBus, SpiDevice}; use embedded_hal::spi::{SpiBus, SpiDevice, MODE_0};
use embedded_hal::{delay::DelayNs, spi}; use embedded_hal::{delay::DelayNs, spi};
use max116xx_10bit::VoltageRefMode; use max116xx_10bit::VoltageRefMode;
use max116xx_10bit::{AveragingConversions, AveragingResults}; use max116xx_10bit::{AveragingConversions, AveragingResults};
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 va108xx_hal::spi::{NoneT, OptionalHwCs}; use va108xx_hal::spi::{OptionalHwCs, SpiClkConfig};
use va108xx_hal::timer::CountDownTimer; use va108xx_hal::timer::CountDownTimer;
use va108xx_hal::{ use va108xx_hal::{
gpio::PinsA, gpio::PinsA,
pac::{self, interrupt}, pac::{self, interrupt},
prelude::*, prelude::*,
spi::{Spi, SpiBase, SpiConfig, TransferConfig}, spi::{Spi, SpiBase, SpiConfig},
timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, IrqCfg}, timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, IrqCfg},
}; };
use va108xx_hal::{port_mux, FunSel, PortSel}; use va108xx_hal::{port_mux, FunSel, PortSel};
@ -103,6 +103,8 @@ impl<Delay: DelayNs, HwCs: OptionalHwCs<pac::Spib>> SpiDevice for SpiWithHwCs<De
} }
} }
const SYS_CLK: Hertz = Hertz::from_raw(50_000_000);
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
rtt_init_print!(); rtt_init_print!();
@ -113,7 +115,7 @@ fn main() -> ! {
IrqCfg::new(pac::Interrupt::OC0, true, true), IrqCfg::new(pac::Interrupt::OC0, true, true),
&mut dp.sysconfig, &mut dp.sysconfig,
Some(&mut dp.irqsel), Some(&mut dp.irqsel),
50.MHz(), SYS_CLK,
dp.tim0, dp.tim0,
); );
let delay = DelayMs::new(tim0).unwrap(); let delay = DelayMs::new(tim0).unwrap();
@ -122,7 +124,10 @@ fn main() -> ! {
} }
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
let spi_cfg = SpiConfig::default(); let spi_cfg = SpiConfig::default()
.clk_cfg(SpiClkConfig::from_clk(SYS_CLK, 3.MHz()).unwrap())
.mode(MODE_0)
.blockmode(true);
let (sck, mosi, miso) = ( let (sck, mosi, miso) = (
pinsa.pa20.into_funsel_2(), pinsa.pa20.into_funsel_2(),
pinsa.pa19.into_funsel_2(), pinsa.pa19.into_funsel_2(),
@ -141,14 +146,12 @@ fn main() -> ! {
.set_high() .set_high()
.expect("Setting accelerometer chip select high failed"); .expect("Setting accelerometer chip select high failed");
let transfer_cfg = TransferConfig::<NoneT>::new(3.MHz(), spi::MODE_0, None, true, false);
let spi = Spi::new( let spi = Spi::new(
&mut dp.sysconfig, &mut dp.sysconfig,
50.MHz(), 50.MHz(),
dp.spib, dp.spib,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
Some(&transfer_cfg.downgrade()),
) )
.downgrade(); .downgrade();
let delay_provider = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1); let delay_provider = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);

View File

@ -0,0 +1,64 @@
//! Example application which interfaces with the boot EEPROM.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{pac, pwm::CountDownTimer, time::Hertz};
use vorago_reb1::m95m01::M95M01;
const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108XX REB1 NVM example --");
let mut dp = pac::Peripherals::take().unwrap();
let mut timer = CountDownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
let status_reg = nvm.read_status_reg().expect("reading status reg failed");
if status_reg.zero_segment() == 0b111 {
panic!("status register unexpected values");
}
let mut orig_content: [u8; 16] = [0; 16];
let mut read_buf: [u8; 16] = [0; 16];
let write_buf: [u8; 16] = [0; 16];
for (idx, val) in read_buf.iter_mut().enumerate() {
*val = idx as u8;
}
nvm.read(0x4000, &mut orig_content).unwrap();
// One byte write and read.
nvm.write(0x4000, &write_buf[0..1]).unwrap();
nvm.read(0x4000, &mut read_buf[0..1]).unwrap();
assert_eq!(write_buf[0], read_buf[0]);
read_buf.fill(0);
// Four bytes write and read.
nvm.write(0x4000, &write_buf[0..4]).unwrap();
nvm.read(0x4000, &mut read_buf[0..4]).unwrap();
assert_eq!(&read_buf[0..4], &write_buf[0..4]);
read_buf.fill(0);
// Full sixteen bytes
nvm.write(0x4000, &write_buf).unwrap();
nvm.read(0x4000, &mut read_buf).unwrap();
assert_eq!(&read_buf, &write_buf);
read_buf.fill(0);
// 3 bytes
nvm.write(0x4000, &write_buf[0..3]).unwrap();
nvm.read(0x4000, &mut read_buf[0..3]).unwrap();
assert_eq!(&read_buf[0..3], &write_buf[0..3]);
// Write back original content.
nvm.write(0x4000, &orig_content).unwrap();
loop {
timer.delay_ms(500);
}
}

View File

@ -3,5 +3,6 @@
pub mod button; pub mod button;
pub mod leds; pub mod leds;
pub mod m95m01;
pub mod max11619; pub mod max11619;
pub mod temp_sensor; pub mod temp_sensor;

172
vorago-reb1/src/m95m01.rs Normal file
View File

@ -0,0 +1,172 @@
//! Basic driver for the ST M95M01 EEPROM memory.
//!
//! This driver is used by the provided bootloader application for the REB1
//! board. It provides a convenient wrapper around the HAL SPI to interface
//! with the EEPROM memory of the REB1 board.
//!
//! # Example
//!
//! - [REB1 EEPROM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/nvm.rs)
use core::convert::Infallible;
use embedded_hal::spi::SpiBus;
bitfield::bitfield! {
pub struct StatusReg(u8);
impl Debug;
u8;
pub status_register_write_protect, _: 7;
pub zero_segment, _: 6, 4;
pub block_protection_bits, set_block_protection_bits: 3, 2;
pub write_enable_latch, _: 1;
pub write_in_progress, _: 0;
}
// Registers.
pub mod regs {
/// Write status register command.
pub const WRSR: u8 = 0x01;
// Write command.
pub const WRITE: u8 = 0x02;
// Read command.
pub const READ: u8 = 0x03;
/// Write disable command.
pub const WRDI: u8 = 0x04;
/// Read status register command.
pub const RDSR: u8 = 0x05;
/// Write enable command.
pub const WREN: u8 = 0x06;
}
use regs::*;
use va108xx_hal::{
pac,
prelude::*,
spi::{RomMiso, RomMosi, RomSck, Spi, SpiConfig, BMSTART_BMSTOP_MASK},
};
pub type RomSpi = Spi<pac::Spic, (RomSck, RomMiso, RomMosi), u8>;
/// Driver for the ST device M95M01 EEPROM memory.
///
/// Specialized for the requirements of the VA108XX MCUs.
pub struct M95M01 {
pub spi: RomSpi,
}
impl M95M01 {
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, spi: pac::Spic) -> Self {
let spi = RomSpi::new(
syscfg,
sys_clk,
spi,
(RomSck, RomMiso, RomMosi),
SpiConfig::default(),
);
let mut spi_dev = Self { spi };
spi_dev.clear_block_protection().unwrap();
spi_dev
}
pub fn release(mut self) -> pac::Spic {
self.set_block_protection().unwrap();
self.spi.release().0
}
// Wait until the write-in-progress state is cleared. This exposes a [nb] API, so this function
// will return [nb::Error::WouldBlock] if the EEPROM is still busy.
pub fn writes_are_done(&mut self) -> nb::Result<(), Infallible> {
let rdsr = self.read_status_reg()?;
if rdsr.write_in_progress() {
return Err(nb::Error::WouldBlock);
}
Ok(())
}
pub fn read_status_reg(&mut self) -> Result<StatusReg, Infallible> {
let mut write_read: [u8; 2] = [regs::RDSR, 0x00];
self.spi.transfer_in_place(&mut write_read)?;
Ok(StatusReg(write_read[1]))
}
pub fn write_enable(&mut self) -> Result<(), Infallible> {
self.spi.write(&[regs::WREN])
}
pub fn clear_block_protection(&mut self) -> Result<(), Infallible> {
// Has to be written separately.
self.write_enable()?;
self.spi.write(&[WRSR, 0x00])
}
pub fn set_block_protection(&mut self) -> Result<(), Infallible> {
let mut reg = StatusReg(0);
reg.set_block_protection_bits(0b11);
self.write_enable()?;
self.spi.write(&[WRSR, reg.0])
}
fn common_init_write_and_read(&mut self, address: u32, reg: u8) -> Result<(), Infallible> {
nb::block!(self.writes_are_done())?;
self.spi.flush()?;
if reg == WRITE {
self.write_enable()?;
self.spi.write_fifo_unchecked(WRITE as u32);
} else {
self.spi.write_fifo_unchecked(READ as u32);
}
self.spi.write_fifo_unchecked((address >> 16) & 0xff);
self.spi.write_fifo_unchecked((address >> 8) & 0xff);
self.spi.write_fifo_unchecked(address & 0xff);
Ok(())
}
fn common_read(&mut self, address: u32) -> Result<(), Infallible> {
self.common_init_write_and_read(address, READ)?;
for _ in 0..4 {
// Pump the FIFO.
self.spi.write_fifo_unchecked(0);
// Ignore the first 4 bytes.
self.spi.read_fifo_unchecked();
}
Ok(())
}
pub fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Infallible> {
self.common_init_write_and_read(address, WRITE)?;
for val in data.iter().take(data.len() - 1) {
nb::block!(self.spi.write_fifo(*val as u32))?;
self.spi.read_fifo_unchecked();
}
nb::block!(self
.spi
.write_fifo(*data.last().unwrap() as u32 | BMSTART_BMSTOP_MASK))?;
self.spi.flush()?;
nb::block!(self.writes_are_done())?;
Ok(())
}
pub fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), Infallible> {
self.common_read(address)?;
for val in buf.iter_mut() {
nb::block!(self.spi.write_fifo(0))?;
*val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8;
}
nb::block!(self.spi.write_fifo(BMSTART_BMSTOP_MASK))?;
self.spi.flush()?;
Ok(())
}
pub fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, Infallible> {
self.common_read(address)?;
for val in data.iter() {
nb::block!(self.spi.write_fifo(0))?;
let read_val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8;
if read_val != *val {
return Ok(false);
}
}
nb::block!(self.spi.write_fifo(BMSTART_BMSTOP_MASK))?;
self.spi.flush()?;
Ok(true)
}
}

View File

@ -4,7 +4,7 @@
//! //!
//! ## Examples //! ## Examples
//! //!
//! - [Temperature Sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs //! - [Temperature Sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs)
use embedded_hal::i2c::{I2c, SevenBitAddress}; use embedded_hal::i2c::{I2c, SevenBitAddress};
use va108xx_hal::{ use va108xx_hal::{
i2c::{Error, I2cMaster, I2cSpeed, InitError, MasterConfig}, i2c::{Error, I2cMaster, I2cSpeed, InitError, MasterConfig},

View File

@ -20,7 +20,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -44,7 +44,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -68,7 +68,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -92,7 +92,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -116,7 +116,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -129,7 +129,7 @@
{ {
"type": "cortex-debug", "type": "cortex-debug",
"request": "launch", "request": "launch",
"name": "Debug UART", "name": "UART Example",
"servertype": "jlink", "servertype": "jlink",
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"device": "Cortex-M0", "device": "Cortex-M0",
@ -140,7 +140,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -164,7 +164,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -188,7 +188,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -212,7 +212,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -236,7 +236,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -284,7 +284,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -321,7 +321,7 @@
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,
@ -340,12 +340,84 @@
"device": "Cortex-M0", "device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched", "svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "rust: cargo build uart irq", "preLaunchTask": "rust: cargo build uart irq",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/uart-irq-rtic", "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/uart-rtic",
"interface": "jtag", "interface": "jtag",
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
"enabled": true, "enabled": true,
"address": "0x10000000", "address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "REB1 NVM Example",
"servertype": "jlink",
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "reb1-nvm",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/nvm",
"interface": "jtag",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "RTIC Example",
"servertype": "jlink",
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "rtic-example",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/rtic-example",
"interface": "jtag",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Embassy Example",
"servertype": "jlink",
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "embassy-example",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/embassy-example",
"interface": "jtag",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [ "decoders": [
{ {
"port": 0, "port": 0,

View File

@ -67,8 +67,6 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"-p",
"va108xx-hal",
"--example", "--example",
"uart", "uart",
], ],
@ -129,10 +127,8 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"--example", "--bin",
"uart-irq-rtic", "uart-rtic",
"--features",
"rt"
], ],
"group": { "group": {
"kind": "build", "kind": "build",
@ -158,8 +154,6 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"-p",
"vorago-reb1",
"--example", "--example",
"blinky-leds", "blinky-leds",
], ],
@ -174,8 +168,6 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"-p",
"vorago-reb1",
"--example", "--example",
"blinky-button-irq", "blinky-button-irq",
], ],
@ -190,8 +182,6 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"-p",
"vorago-reb1",
"--example", "--example",
"adt75-temp-sensor", "adt75-temp-sensor",
], ],
@ -206,8 +196,6 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"-p",
"vorago-reb1",
"--example", "--example",
"blinky-button-rtic", "blinky-button-rtic",
], ],
@ -222,8 +210,6 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"-p",
"vorago-reb1",
"--example", "--example",
"adxl343-accelerometer" "adxl343-accelerometer"
], ],
@ -238,8 +224,6 @@
"command": "~/.cargo/bin/cargo", // note: full path to the cargo "command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [ "args": [
"build", "build",
"-p",
"vorago-reb1",
"--example", "--example",
"max11619-adc", "max11619-adc",
], ],
@ -248,5 +232,39 @@
"isDefault": true "isDefault": true
} }
}, },
{
"label": "reb1-nvm",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--example",
"nvm",
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "rtic-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"rtic-example",
],
},
{
"label": "embassy-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"embassy-example",
],
},
] ]
} }