1 Commits

Author SHA1 Message Date
7a525b0107 Bootloader and Flashloader App
Some checks failed
Rust/va416xx-rs/pipeline/pr-main There was a failure building this commit
2024-09-12 19:26:04 +02:00
48 changed files with 1394 additions and 2996 deletions

View File

@ -39,9 +39,7 @@ 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 -p vorago-peb1 - 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 va416xx-hal --features va41630
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx
clippy: clippy:
name: Clippy name: Clippy

View File

@ -1,14 +1,12 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"va416xx",
"va416xx-hal",
"vorago-peb1",
"bootloader", "bootloader",
"flashloader", "flashloader",
"examples/simple", "examples/simple",
"examples/embassy", "va416xx",
"examples/rtic", "va416xx-hal",
"vorago-peb1"
] ]
exclude = [ exclude = [
"flashloader/slot-a-blinky", "flashloader/slot-a-blinky",
@ -41,4 +39,4 @@ debug-assertions = false # <-
lto = true lto = true
opt-level = 'z' # <- opt-level = 'z' # <-
overflow-checks = false # <- overflow-checks = false # <-
strip = true # Automatically strip symbols from the binary. # strip = true # Automatically strip symbols from the binary.

View File

@ -24,11 +24,7 @@ It also contains the following helper crates:
- The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader) - The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
crate contains a sample flashloader which is able to update the redundant images in the NVM which crate contains a sample flashloader which is able to update the redundant images in the NVM which
is compatible to the provided bootloader as well. is compatible to the provided bootloader as well.
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples) - The `examples` folder contains various example applications crates for the HAL and the PAC.
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
@ -99,10 +95,8 @@ example.
### Using VS Code ### Using VS Code
Assuming a working debug connection to your VA416xx 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`
@ -117,5 +111,4 @@ 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`. In order for the RTT block address detection to via the terminal at `RTT Ch:0 console`.
work properly, `objdump-multiarch` and `nm-multiarch` need to be installed.

View File

@ -25,9 +25,7 @@ pipeline {
stage('Docs') { stage('Docs') {
steps { steps {
sh """ sh """
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 --all-features
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
""" """
} }
} }

View File

@ -8,15 +8,8 @@ cortex-m = "0.7"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
panic-halt = { version = "0.2" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
crc = "3" crc = "3"
static_assertions = "1"
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
path = "../va416xx-hal" path = "../va416xx-hal"
features = ["va41630"]
[features]
default = []
rtt-panic = []

View File

@ -11,12 +11,12 @@ The bootloader uses the following memory map:
| ------ | ---- | ---- | | ------ | ---- | ---- |
| 0x0 | Bootloader start | code up to 0x3FFC bytes | | 0x0 | Bootloader start | code up to 0x3FFC bytes |
| 0x3FFC | Bootloader CRC | word | | 0x3FFC | Bootloader CRC | word |
| 0x4000 | App image A start | code up to 0x1DFF8 (~120K) bytes | | 0x4000 | App image A start | code up to 0x1DFFC (~120K) bytes |
| 0x21FF8 | App image A CRC check length | word | | 0x21FFC | App image A CRC check length | word |
| 0x21FFC | App image A CRC check value | word | | 0x21FFE | App image A CRC check value | word |
| 0x22000 | App image B start | code up to 0x1DFF8 (~120K) bytes | | 0x22000 | App image B start | code up to 0x1DFFC (~120K) bytes |
| 0x3FFF8 | App image B CRC check length | word | | 0x3FFFC | App image B CRC check length | word |
| 0x3FFFC | App image B CRC check value | word | | 0x3FFFE | App image B CRC check value | word |
| 0x40000 | End of NVM | end | | 0x40000 | End of NVM | end |
## Additional Information ## Additional Information

View File

@ -1,5 +1,17 @@
//! Vorago bootloader which can boot from two images. //! Vorago bootloader which can boot from two images.
//! //!
//! Bootloader memory map
//!
//! * <0x0> Bootloader start <code up to 0x3FFE bytes>
//! * <0x3FFE> Bootloader CRC <halfword>
//! * <0x4000> App image A start <code up to 0x1DFFC (~120K) bytes>
//! * <0x21FFC> App image A CRC check length <halfword>
//! * <0x21FFE> App image A CRC check value <halfword>
//! * <0x22000> App image B start <code up to 0x1DFFC (~120K) bytes>
//! * <0x3FFFC> App image B CRC check length <halfword>
//! * <0x3FFFE> App image B CRC check value <halfword>
//! * <0x40000> <end>
//!
//! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock //! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
//! but does not scale that clock up. //! but does not scale that clock up.
#![no_main] #![no_main]
@ -7,9 +19,6 @@
use cortex_m_rt::entry; use cortex_m_rt::entry;
use crc::{Crc, CRC_32_ISO_HDLC}; use crc::{Crc, CRC_32_ISO_HDLC};
#[cfg(not(feature = "rtt-panic"))]
use panic_halt as _;
#[cfg(feature = "rtt-panic")]
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 va416xx_hal::{ use va416xx_hal::{
@ -33,42 +42,23 @@ const DEBUG_PRINTOUTS: bool = true;
// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash // 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. // loader to boot a bootloader without this feature.
const FLASH_SELF: bool = false; const FLASH_SELF: bool = false;
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
// the binary stays small enough.
const RTT_PRINTOUT: bool = true;
// Important bootloader addresses and offsets, vector table information. // Important bootloader addresses and offsets, vector table information.
const NVM_SIZE: u32 = 0x40000;
const BOOTLOADER_START_ADDR: u32 = 0x0; const BOOTLOADER_START_ADDR: u32 = 0x0;
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 4;
const BOOTLOADER_END_ADDR: u32 = 0x4000; const BOOTLOADER_END_ADDR: u32 = 0x4000;
const BOOTLOADER_CRC_ADDR: u32 = 0x3FFC;
// 0x4000 const APP_A_START_ADDR: u32 = 0x4000;
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR; pub const APP_A_END_ADDR: u32 = 0x22000;
// The actual size of the image which is relevant for CRC calculation will be store at this // The actual size of the image which is relevant for CRC calculation.
// address. const APP_A_SIZE_ADDR: u32 = 0x21FF8;
// 0x21FF8 const APP_A_CRC_ADDR: u32 = 0x21FFC;
const APP_A_SIZE_ADDR: u32 = APP_B_END_ADDR - 8; const APP_B_START_ADDR: u32 = 0x22000;
// 0x21FFC pub const APP_B_END_ADDR: u32 = 0x40000;
const APP_A_CRC_ADDR: u32 = APP_B_END_ADDR - 4; // The actual size of the image which is relevant for CRC calculation.
pub const APP_A_END_ADDR: u32 = BOOTLOADER_END_ADDR + APP_IMG_SZ; const APP_B_SIZE_ADDR: u32 = 0x3FFF8;
const APP_B_CRC_ADDR: u32 = 0x3FFFC;
// 0x22000 pub const APP_IMG_SZ: u32 = 0x1E000;
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
// The actual size of the image which is relevant for CRC calculation will be stored at this
// address.
// 0x3FFF8
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
// 0x3FFFC
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
// 0x40000
pub const APP_B_END_ADDR: u32 = NVM_SIZE;
pub const APP_IMG_SZ: u32 = APP_B_END_ADDR - APP_A_START_ADDR / 2;
static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
pub const VECTOR_TABLE_OFFSET: u32 = 0x0; pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
pub const VECTOR_TABLE_LEN: u32 = 0x350; pub const VECTOR_TABLE_LEN: u32 = 0x350;
@ -98,10 +88,8 @@ impl WdtInterface for OptWdt {
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
if RTT_PRINTOUT { rtt_init_print!();
rtt_init_print!(); rprintln!("-- VA416xx bootloader --");
rprintln!("-- VA416xx bootloader --");
}
let mut dp = pac::Peripherals::take().unwrap(); let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap(); let cp = cortex_m::Peripherals::take().unwrap();
// Disable ROM protection. // Disable ROM protection.
@ -145,24 +133,18 @@ fn main() -> ! {
nvm.write_data(0x0, &first_four_bytes); nvm.write_data(0x0, &first_four_bytes);
nvm.write_data(0x4, bootloader_data); nvm.write_data(0x4, bootloader_data);
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) { if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
if RTT_PRINTOUT { rprintln!("verification of self-flash to NVM failed: {:?}", e);
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
} }
if let Err(e) = nvm.verify_data(0x4, bootloader_data) { if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
if RTT_PRINTOUT { rprintln!("verification of self-flash to NVM failed: {:?}", e);
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
} }
nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()); nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes());
if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) { if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
if RTT_PRINTOUT { rprintln!(
rprintln!( "error: CRC verification for bootloader self-flash failed: {:?}",
"error: CRC verification for bootloader self-flash failed: {:?}", e
e );
);
}
} }
} }
@ -174,7 +156,7 @@ fn main() -> ! {
} else if check_app_crc(AppSel::B, &opt_wdt) { } else if check_app_crc(AppSel::B, &opt_wdt) {
boot_app(AppSel::B, &cp) boot_app(AppSel::B, &cp)
} else { } else {
if DEBUG_PRINTOUTS && RTT_PRINTOUT { if DEBUG_PRINTOUTS {
rprintln!("both images corrupt! booting image A"); rprintln!("both images corrupt! booting image A");
} }
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption. // TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
@ -202,7 +184,7 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
let crc_calc = digest.finalize(); let crc_calc = digest.finalize();
wdt.feed(); wdt.feed();
if crc_exp == 0x0000 || crc_exp == 0xffff { if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS && RTT_PRINTOUT { if DEBUG_PRINTOUTS {
rprintln!("BL CRC blank - prog new CRC"); rprintln!("BL CRC blank - prog new CRC");
} }
// Blank CRC, write it to NVM. // Blank CRC, write it to NVM.
@ -212,7 +194,7 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
// cortex_m::peripheral::SCB::sys_reset(); // cortex_m::peripheral::SCB::sys_reset();
} else if crc_exp != crc_calc { } else if crc_exp != crc_calc {
// Bootloader is corrupted. Try to run App A. // Bootloader is corrupted. Try to run App A.
if DEBUG_PRINTOUTS && RTT_PRINTOUT { if DEBUG_PRINTOUTS {
rprintln!( rprintln!(
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately", "bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
crc_calc, crc_calc,
@ -235,7 +217,7 @@ fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
} }
} }
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool { fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
if DEBUG_PRINTOUTS && RTT_PRINTOUT { if DEBUG_PRINTOUTS {
rprintln!("Checking image {:?}", app_sel); rprintln!("Checking image {:?}", app_sel);
} }
if app_sel == AppSel::A { if app_sel == AppSel::A {
@ -255,9 +237,7 @@ fn check_app_given_addr(
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() }; let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
// Sanity check. // Sanity check.
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 { if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
if RTT_PRINTOUT { rprintln!("detected invalid app size {}", image_size);
rprintln!("detected invalid app size {}", image_size);
}
return false; return false;
} }
wdt.feed(); wdt.feed();
@ -272,7 +252,7 @@ fn check_app_given_addr(
} }
fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! { fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! {
if DEBUG_PRINTOUTS && RTT_PRINTOUT { if DEBUG_PRINTOUTS {
rprintln!("booting app {:?}", app_sel); rprintln!("booting app {:?}", app_sel);
} }
let clkgen = unsafe { pac::Clkgen::steal() }; let clkgen = unsafe { pac::Clkgen::steal() };

View File

@ -1,25 +0,0 @@
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

@ -1,45 +0,0 @@
[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"
embedded-io = "0.6"
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.ringbuf]
version = "0.4"
default-features = false
[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

@ -1,161 +0,0 @@
//! This is an example of using the UART HAL abstraction with the IRQ support and embassy.
//!
//! It uses the UART0 for communication with another MCU or a host computer (recommended).
//! You can connect a USB-to-Serial converter to the UART0 pins and then use a serial terminal
//! application like picocom to send data to the microcontroller, which should be echoed
//! back to the sender.
//!
//! This application uses the interrupt support of the VA416xx to read the data arriving
//! on the UART without requiring polling.
#![no_std]
#![no_main]
use core::cell::RefCell;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use panic_rtt_target as _;
use ringbuf::{
traits::{Consumer, Observer, Producer},
StaticRb,
};
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
pac::{self, interrupt},
prelude::*,
time::Hertz,
uart,
};
pub type SharedUart = Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithIrq<pac::Uart0>>>>;
static RX: SharedUart = Mutex::new(RefCell::new(None));
const BAUDRATE: u32 = 115200;
// Ring buffer size.
const RING_BUF_SIZE: usize = 2048;
pub type SharedRingBuf =
Mutex<CriticalSectionRawMutex, RefCell<Option<StaticRb<u8, RING_BUF_SIZE>>>>;
// Ring buffers to handling variable sized telemetry
static RINGBUF: SharedRingBuf = Mutex::new(RefCell::new(None));
// See https://embassy.dev/book/#_sharing_using_a_mutex for background information about sharing
// a peripheral with embassy.
#[embassy_executor::main]
async fn main(spawner: Spawner) {
rtt_init_print!();
rprintln!("VA416xx UART-Embassy Example");
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 tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let uart0 = uart::Uart::new(
dp.uart0,
(tx, rx),
Hertz::from_raw(BAUDRATE),
&mut dp.sysconfig,
&clocks,
);
let (mut tx, rx) = uart0.split();
let mut rx = rx.into_rx_with_irq();
rx.start();
RX.lock(|static_rx| {
static_rx.borrow_mut().replace(rx);
});
RINGBUF.lock(|static_rb| {
static_rb.borrow_mut().replace(StaticRb::default());
});
let led = portg.pg5.into_readable_push_pull_output();
let mut ticker = Ticker::every(Duration::from_millis(50));
let mut processing_buf: [u8; RING_BUF_SIZE] = [0; RING_BUF_SIZE];
let mut read_bytes = 0;
spawner.spawn(blinky(led)).expect("failed to spawn blinky");
loop {
RINGBUF.lock(|static_rb| {
let mut rb_borrow = static_rb.borrow_mut();
let rb_mut = rb_borrow.as_mut().unwrap();
read_bytes = rb_mut.occupied_len();
rb_mut.pop_slice(&mut processing_buf[0..read_bytes]);
});
// Simply send back all received data.
tx.write_all(&processing_buf[0..read_bytes])
.expect("sending back read data failed");
ticker.next().await;
}
}
#[embassy_executor::task]
async fn blinky(mut led: Pin<PG5, OutputReadablePushPull>) {
let mut ticker = Ticker::every(Duration::from_millis(500));
loop {
led.toggle().ok();
ticker.next().await;
}
}
#[interrupt]
#[allow(non_snake_case)]
fn UART0_RX() {
let mut buf: [u8; 16] = [0; 16];
let mut read_len: usize = 0;
let mut errors = None;
RX.lock(|static_rx| {
let mut rx_borrow = static_rx.borrow_mut();
let rx_mut_ref = rx_borrow.as_mut().unwrap();
let result = rx_mut_ref.irq_handler(&mut buf);
read_len = result.bytes_read;
if result.errors.is_some() {
errors = result.errors;
}
});
let mut ringbuf_full = false;
if read_len > 0 {
// Send the received buffer to the main thread for processing via a ring buffer.
RINGBUF.lock(|static_rb| {
let mut rb_borrow = static_rb.borrow_mut();
let rb_mut_ref = rb_borrow.as_mut().unwrap();
if rb_mut_ref.vacant_len() < read_len {
ringbuf_full = true;
for _ in rb_mut_ref.pop_iter() {}
}
rb_mut_ref.push_slice(&buf[0..read_len]);
});
}
if errors.is_some() {
rprintln!("UART error: {:?}", errors);
}
if ringbuf_full {
rprintln!("ringbuffer is full, deleted oldest data");
}
}

View File

@ -1,6 +0,0 @@
#![no_std]
pub mod time_driver;
pub const EXTCLK_FREQ: u32 = 40_000_000;
pub use time_driver::init;

View File

@ -1,45 +0,0 @@
#![no_std]
#![no_main]
use embassy_example::EXTCLK_FREQ;
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};
// 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

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

View File

@ -1,24 +0,0 @@
[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"]

View File

@ -1,71 +0,0 @@
//! RTIC minimal blinky
#![no_main]
#![no_std]
use va416xx_hal::time::Hertz;
const EXTCLK_FREQ: Hertz = Hertz::from_raw(40_000_000);
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
mod app {
use super::*;
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,
prelude::*,
};
#[local]
struct Local {
led: Pin<PG5, OutputReadablePushPull>,
}
#[shared]
struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(mut cx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC example application --");
// Use the external clock connected to XTAL_N.
let clocks = cx
.device
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(EXTCLK_FREQ)
.freeze(&mut cx.device.sysconfig)
.unwrap();
Mono::start(cx.core.SYST, clocks.sysclk().raw());
let portg = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let led = portg.pg5.into_readable_push_pull_output();
blinky::spawn().ok();
(Shared {}, Local { led })
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
#[task(
priority = 3,
local=[led],
)]
async fn blinky(cx: blinky::Context) {
loop {
cx.local.led.toggle().ok();
Mono::delay(200.millis()).await;
}
}
}

View File

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

View File

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

View File

@ -11,8 +11,7 @@ use va416xx_hal::{
gpio::PinsA, gpio::PinsA,
pac, pac,
prelude::*, prelude::*,
pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin}, pwm::{self, get_duty_from_percent, CountdownTimer, PwmA, PwmB, ReducedPwmPin},
timer::CountdownTimer,
}; };
#[entry] #[entry]

View File

@ -3,12 +3,13 @@
//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board. //! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
#![no_main] #![no_main]
#![no_std] #![no_std]
use cortex_m_rt::entry; use cortex_m_rt::entry;
use embedded_hal::spi::{Mode, SpiBus, MODE_0}; use embedded_hal::spi::{Mode, SpiBus, MODE_0};
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::spi::{Spi, SpiClkConfig}; use va416xx_hal::spi::{clk_div_for_target_clock, Spi, TransferConfig};
use va416xx_hal::{ use va416xx_hal::{
gpio::{PinsB, PinsC}, gpio::{PinsB, PinsC},
pac, pac,
@ -21,8 +22,9 @@ use va416xx_hal::{
pub enum ExampleSelect { pub enum ExampleSelect {
// Enter loopback mode. It is not necessary to tie MOSI/MISO together for this // Enter loopback mode. It is not necessary to tie MOSI/MISO together for this
Loopback, Loopback,
// You need to tie together MOSI/MISO in this mode. // Send a test buffer and print everything received. You need to tie together MOSI/MISO in this
MosiMisoTiedTogether, // mode.
TestBuffer,
} }
const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback; const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback;
@ -48,23 +50,21 @@ fn main() -> ! {
let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb); let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb);
let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc); let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc);
// Configure SPI0 pins. // Configure SPI1 pins.
let (sck, miso, mosi) = ( let (sck, miso, mosi) = (
pins_b.pb15.into_funsel_1(), pins_b.pb15.into_funsel_1(),
pins_c.pc0.into_funsel_1(), pins_c.pc0.into_funsel_1(),
pins_c.pc1.into_funsel_1(), pins_c.pc1.into_funsel_1(),
); );
let mut spi_cfg = SpiConfig::default() let mut spi_cfg = SpiConfig::default().clk_div(
.clk_cfg( clk_div_for_target_clock(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
SpiClkConfig::from_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks) .expect("invalid target clock"),
.expect("invalid target clock"), );
)
.mode(SPI_MODE)
.blockmode(BLOCKMODE);
if EXAMPLE_SEL == ExampleSelect::Loopback { if EXAMPLE_SEL == ExampleSelect::Loopback {
spi_cfg = spi_cfg.loopback(true) spi_cfg = spi_cfg.loopback(true)
} }
let transfer_cfg = TransferConfig::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false);
// Create SPI peripheral. // Create SPI peripheral.
let mut spi0 = Spi::new( let mut spi0 = Spi::new(
&mut dp.sysconfig, &mut dp.sysconfig,
@ -72,27 +72,29 @@ fn main() -> ! {
dp.spi0, dp.spi0,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
); Some(&transfer_cfg.downgrade()),
)
.expect("creating SPI peripheral failed");
spi0.set_fill_word(FILL_WORD); spi0.set_fill_word(FILL_WORD);
loop { loop {
let tx_buf: [u8; 4] = [1, 2, 3, 0]; let mut tx_buf: [u8; 3] = [1, 2, 3];
let mut rx_buf: [u8; 4] = [0; 4]; let mut rx_buf: [u8; 3] = [0; 3];
// Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up. // Can't really verify correct reply here.
spi0.write(&[0x42, 0x43]).expect("write failed"); spi0.write(&[0x42]).expect("write failed");
// Need small delay.. otherwise we will read back the sent byte (which we don't want here).
// The write function will return as soon as all bytes were shifted out, ignoring the
// reply bytes.
delay_sysclk.delay_us(50);
// Because of the loopback mode, we should get back the fill word here.
spi0.read(&mut rx_buf[0..1]).unwrap();
assert_eq!(rx_buf[0], FILL_WORD);
// Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up. spi0.transfer_in_place(&mut tx_buf)
spi0.read(&mut rx_buf[0..2]).unwrap();
// If the pins are tied together, we should received exactly what we send.
let mut inplace_buf = tx_buf;
spi0.transfer_in_place(&mut inplace_buf)
.expect("SPI transfer_in_place failed"); .expect("SPI transfer_in_place failed");
assert_eq!([1, 2, 3, 0], inplace_buf); assert_eq!([1, 2, 3], tx_buf);
spi0.transfer(&mut rx_buf, &tx_buf) spi0.transfer(&mut rx_buf, &tx_buf)
.expect("SPI transfer failed"); .expect("SPI transfer failed");
assert_eq!(rx_buf, [1, 2, 3, 0]); assert_eq!(rx_buf, tx_buf);
delay_sysclk.delay_ms(500); delay_sysclk.delay_ms(500);
} }
} }

View File

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

View File

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

View File

@ -6,18 +6,12 @@ a simple PUS (CCSDS) interface to update the software. It also provides a Python
called the `image-loader.py` which can be used to upload compiled images to the flashloader called the `image-loader.py` which can be used to upload compiled images to the flashloader
application to write them to the NVM. application to write them to the NVM.
Please note that the both the application and the image loader are tailored towards usage
with the [bootloader provided by this repository](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader).
The software can quickly be adapted to interface with a real primary on-board software instead of The software can quickly be adapted to interface with a real primary on-board software instead of
the Python script provided here to upload images because it uses a low-level CCSDS based packet the Python script provided here to upload images because it uses a low-level CCSDS based packet
interface. interface.
## Using the Python image loader ## Using the Python image loader
The Python image loader communicates with the Rust flashload application using a dedicated serial
port with a baudrate of 115200.
It is recommended to run the script in a dedicated virtual environment. For example, on UNIX It is recommended to run the script in a dedicated virtual environment. For example, on UNIX
systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
and activate a virtual environment. and activate a virtual environment.

View File

@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from typing import List, Tuple from spacepackets.ecss import RequestId
from spacepackets.ecss.defs import PusService from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm from spacepackets.ecss.tm import PusTm
from tmtccmd.com import ComInterface
import toml import toml
import struct import struct
import logging import logging
import argparse import argparse
import threading
import time import time
import enum import enum
from tmtccmd.com.serial_base import SerialCfg from tmtccmd.com.serial_base import SerialCfg
@ -23,27 +23,20 @@ from elftools.elf.elffile import ELFFile
BAUD_RATE = 115200 BAUD_RATE = 115200
BOOTLOADER_START_ADDR = 0x0 BOOTLOADER_START_ADDR = 0x0
BOOTLOADER_END_ADDR = 0x4000 BOOTLOADER_END_ADDR = 0x4000
BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 4 BOOTLOADER_CRC_ADDR = 0x3FFC
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4
APP_A_START_ADDR = 0x4000 APP_A_START_ADDR = 0x4000
APP_A_END_ADDR = 0x22000 APP_A_END_ADDR = 0x22000
# The actual size of the image which is relevant for CRC calculation. # The actual size of the image which is relevant for CRC calculation.
APP_A_SIZE_ADDR = APP_A_END_ADDR - 8 APP_A_SIZE_ADDR = 0x21FF8
APP_A_CRC_ADDR = APP_A_END_ADDR - 4 APP_A_CRC_ADDR = 0x21FFC
APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_B_START_ADDR = 0x22000 APP_B_START_ADDR = 0x22000
APP_B_END_ADDR = 0x40000 APP_B_END_ADDR = 0x40000
# The actual size of the image which is relevant for CRC calculation. # The actual size of the image which is relevant for CRC calculation.
APP_B_SIZE_ADDR = APP_B_END_ADDR - 8 APP_B_SIZE_ADDR = 0x3FFF8
APP_B_CRC_ADDR = APP_B_END_ADDR - 4 APP_B_CRC_ADDR = 0x3FFFC
APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8 APP_IMG_SZ = 0x1E000
APP_IMG_SZ = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
CHUNK_SIZE = 896 CHUNK_SIZE = 896
@ -52,7 +45,6 @@ ACTION_SERVICE = 8
RAW_MEMORY_WRITE_SUBSERVICE = 2 RAW_MEMORY_WRITE_SUBSERVICE = 2
BOOT_NVM_MEMORY_ID = 1 BOOT_NVM_MEMORY_ID = 1
PING_PAYLOAD_SIZE = 0
class ActionId(enum.IntEnum): class ActionId(enum.IntEnum):
@ -61,7 +53,6 @@ class ActionId(enum.IntEnum):
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
@dataclasses.dataclass @dataclasses.dataclass
@ -72,174 +63,7 @@ class LoadableSegment:
data: bytes data: bytes
class Target(enum.Enum): SEQ_PROVIDER = SeqCountProvider(bit_width=14)
BOOTLOADER = 0
APP_A = 1
APP_B = 2
class ImageLoader:
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
self.com_if = com_if
self.verificator = verificator
def handle_ping_cmd(self):
_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),
)
self.verificator.add_tc(ping_tc)
self.com_if.send(bytes(ping_tc.pack()))
data_available = self.com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no ping reply received")
for reply in self.com_if.receive():
result = self.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")
def handle_corruption_cmd(self, target: Target):
if target == Target.BOOTLOADER:
_LOGGER.error("can not corrupt bootloader")
if target == Target.APP_A:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_A,
),
)
if target == Target.APP_B:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_B,
),
)
def handle_flash_cmd(self, target: Target, file_path: Path) -> int:
loadable_segments = []
_LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0
loadable_segments, total_size = create_loadable_segments(target, file_path)
segments_info_str(target, loadable_segments, total_size, file_path)
result = self._perform_flashing_algorithm(loadable_segments)
if result != 0:
return result
self._crc_and_app_size_postprocessing(target, total_size, loadable_segments)
return 0
def _perform_flashing_algorithm(
self,
loadable_segments: List[LoadableSegment],
) -> int:
# Perform the flashing algorithm.
for segment in loadable_segments:
segment_end = segment.offset + segment.size
current_addr = segment.offset
pos_in_segment = 0
while pos_in_segment < segment.size:
next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
data = segment.data[pos_in_segment : pos_in_segment + next_chunk_size]
next_packet = pack_memory_write_command(current_addr, data)
_LOGGER.info(
f"Sending memory write command for address {current_addr:#08x} and data with "
f"length {len(data)}"
)
self.verificator.add_tc(next_packet)
self.com_if.send(bytes(next_packet.pack()))
current_addr += next_chunk_size
pos_in_segment += next_chunk_size
start_time = time.time()
while True:
if time.time() - start_time > 1.0:
_LOGGER.error("Timeout while waiting for reply")
return -1
data_available = self.com_if.data_available(0.1)
done = False
if not data_available:
continue
replies = self.com_if.receive()
for reply in replies:
tm = PusTm.unpack(reply, 0)
if tm.service != 1:
continue
service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
check_result = self.verificator.add_tm(service_1_tm)
# We could send after we have received the step reply, but that can
# somehow lead to overrun errors. I think it's okay to do it like
# this as long as the flash loader only uses polling..
if (
check_result is not None
and check_result.status.completed == StatusField.SUCCESS
):
done = True
# This is an optimized variant, but I think the small delay is not an issue.
"""
if (
check_result is not None
and check_result.status.step == StatusField.SUCCESS
and len(check_result.status.step_list) == 1
):
done = True
"""
self.verificator.remove_completed_entries()
if done:
break
return 0
def _crc_and_app_size_postprocessing(
self,
target: Target,
total_size: int,
loadable_segments: List[LoadableSegment],
):
if target == Target.BOOTLOADER:
_LOGGER.info("Blanking the bootloader checksum")
# Blank the checksum. For the bootloader, the bootloader will calculate the
# checksum itself on the initial run.
checksum_write_packet = pack_memory_write_command(
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00, 0x00, 0x00])
)
self.send_tc(checksum_write_packet)
else:
crc_addr = None
size_addr = None
if target == Target.APP_A:
crc_addr = APP_A_CRC_ADDR
size_addr = APP_A_SIZE_ADDR
elif target == Target.APP_B:
crc_addr = APP_B_CRC_ADDR
size_addr = APP_B_SIZE_ADDR
assert crc_addr is not None
assert size_addr is not None
_LOGGER.info(f"Writing app size {total_size} at address {size_addr:#08x}")
size_write_packet = pack_memory_write_command(
size_addr, struct.pack("!I", total_size)
)
self.com_if.send(bytes(size_write_packet.pack()))
time.sleep(0.2)
crc_calc = PredefinedCrc("crc-32")
for segment in loadable_segments:
crc_calc.update(segment.data)
checksum = crc_calc.digest()
_LOGGER.info(
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
)
self.send_tc(pack_memory_write_command(crc_addr, checksum))
def send_tc(self, tc: PusTc):
self.com_if.send(bytes(tc.pack()))
def main() -> int: def main() -> int:
@ -279,134 +103,199 @@ def main() -> int:
verificator = PusVerificator() verificator = PusVerificator()
com_if = SerialCobsComIF(serial_cfg) com_if = SerialCobsComIF(serial_cfg)
com_if.open() com_if.open()
target = None
if args.target == "bl":
target = Target.BOOTLOADER
elif args.target == "a":
target = Target.APP_A
elif args.target == "b":
target = Target.APP_B
image_loader = ImageLoader(com_if, verificator)
file_path = None file_path = None
result = -1 if args.target:
if args.ping:
image_loader.handle_ping_cmd()
com_if.close()
return 0
if target:
if not args.corrupt: if not args.corrupt:
if not args.path: if not args.path:
_LOGGER.error("App Path needs to be specified for the flash process") _LOGGER.error("App Path needs to be specified for the flash process")
return -1
file_path = Path(args.path) file_path = Path(args.path)
if not file_path.exists(): if not file_path.exists():
_LOGGER.error("File does not exist") _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 args.corrupt:
if not target: if not args.target:
_LOGGER.error("target for corruption command required") _LOGGER.error("target for corruption command required")
com_if.close()
return -1 return -1
image_loader.handle_corruption_cmd(target) if args.target == "bl":
_LOGGER.error("can not corrupt bootloader")
if args.target == "a":
packet = PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_A,
)
com_if.send(packet.pack())
if args.target == "b":
packet = PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_B,
)
com_if.send(packet.pack())
else: else:
assert file_path is not None assert file_path is not None
assert target is not None loadable_segments = []
result = image_loader.handle_flash_cmd(target, file_path) _LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0
with open(file_path, "rb") as app_file:
elf_file = ELFFile(app_file)
com_if.close() for (idx, segment) in enumerate(elf_file.iter_segments("PT_LOAD")):
return result if segment.header.p_filesz == 0:
continue
# Basic validity checks of the base addresses.
def create_loadable_segments( if idx == 0:
target: Target, file_path: Path if (
) -> Tuple[List[LoadableSegment], int]: args.target == "bl"
loadable_segments = [] and segment.header.p_paddr != BOOTLOADER_START_ADDR
total_size = 0 ):
with open(file_path, "rb") as app_file: raise ValueError(
elf_file = ELFFile(app_file) f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"bootloader, expected {BOOTLOADER_START_ADDR}"
for idx, segment in enumerate(elf_file.iter_segments("PT_LOAD")): )
if segment.header.p_filesz == 0: if (
continue args.target == "a"
# Basic validity checks of the base addresses. and segment.header.p_paddr != APP_A_START_ADDR
if idx == 0: ):
if ( raise ValueError(
target == Target.BOOTLOADER f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
and segment.header.p_paddr != BOOTLOADER_START_ADDR f"App A, expected {APP_A_START_ADDR}"
): )
raise ValueError( if (
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for " args.target == "b"
f"bootloader, expected {BOOTLOADER_START_ADDR}" and segment.header.p_paddr != APP_B_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App B, expected {APP_B_START_ADDR}"
)
name = None
for section in elf_file.iter_sections():
if (
section.header.sh_offset == segment.header.p_offset
and section.header.sh_size > 0
):
name = section.name
if name is None:
_LOGGER.warning("no fitting section found for segment")
continue
# print(f"Segment Addr: {segment.header.p_paddr}")
# print(f"Segment Offset: {segment.header.p_offset}")
# print(f"Segment Filesize: {segment.header.p_filesz}")
loadable_segments.append(
LoadableSegment(
name=name,
offset=segment.header.p_paddr,
size=segment.header.p_filesz,
data=segment.data(),
) )
if (
target == Target.APP_A
and segment.header.p_paddr != APP_A_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App A, expected {APP_A_START_ADDR}"
)
if (
target == Target.APP_B
and segment.header.p_paddr != APP_B_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App B, expected {APP_B_START_ADDR}"
)
name = None
for section in elf_file.iter_sections():
if (
section.header.sh_offset == segment.header.p_offset
and section.header.sh_size > 0
):
name = section.name
if name is None:
_LOGGER.warning("no fitting section found for segment")
continue
# print(f"Segment Addr: {segment.header.p_paddr}")
# print(f"Segment Offset: {segment.header.p_offset}")
# print(f"Segment Filesize: {segment.header.p_filesz}")
loadable_segments.append(
LoadableSegment(
name=name,
offset=segment.header.p_paddr,
size=segment.header.p_filesz,
data=segment.data(),
) )
total_size += segment.header.p_filesz
context_str = None
if args.target == "bl":
context_str = "Bootloader"
elif args.target == "a":
context_str = "App Slot A"
elif args.target == "b":
context_str = "App Slot B"
_LOGGER.info(
f"Flashing {context_str} with image {file_path} (size {total_size})"
) )
total_size += segment.header.p_filesz for idx, segment in enumerate(loadable_segments):
return loadable_segments, total_size _LOGGER.info(
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and size {segment.size}"
)
def segments_info_str( for segment in loadable_segments:
target: Target, segment_end = segment.offset + segment.size
loadable_segments: List[LoadableSegment], current_addr = segment.offset
total_size: int, pos_in_segment = 0
file_path: Path, while pos_in_segment < segment.size:
): next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
# Set context string and perform basic sanity checks. data = segment.data[
if target == Target.BOOTLOADER: pos_in_segment : pos_in_segment + next_chunk_size
if total_size > BOOTLOADER_MAX_SIZE: ]
_LOGGER.error( next_packet = pack_memory_write_command(current_addr, data)
f"provided bootloader app larger than allowed {total_size} bytes" _LOGGER.info(
) f"Sending memory write command for address {current_addr:#08x} and data with "
return -1 f"length {len(data)}"
context_str = "Bootloader" )
elif target == Target.APP_A: verificator.add_tc(next_packet)
if total_size > APP_A_MAX_SIZE: com_if.send(next_packet.pack())
_LOGGER.error(f"provided App A larger than allowed {total_size} bytes") current_addr += next_chunk_size
return -1 pos_in_segment += next_chunk_size
context_str = "App Slot A" while True:
elif target == Target.APP_B: data_available = com_if.data_available(0.1)
if total_size > APP_B_MAX_SIZE: done = False
_LOGGER.error(f"provided App B larger than allowed {total_size} bytes") if not data_available:
return -1 continue
context_str = "App Slot B" replies = com_if.receive()
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})") for reply in replies:
for idx, segment in enumerate(loadable_segments): tm = PusTm.unpack(reply, 0)
_LOGGER.info( if tm.service != 1:
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and " continue
f"size {segment.size}" service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
) check_result = verificator.add_tm(service_1_tm)
# We could send after we have received the step reply, but that can
# somehow lead to overrun errors. I think it's okay to do it like
# this as long as the flash loader only uses polling..
if (
check_result is not None
and check_result.status.completed == StatusField.SUCCESS
):
done = True
# Still keep a small delay
time.sleep(0.01)
verificator.remove_completed_entries()
if done:
break
if args.target == "bl":
_LOGGER.info("Blanking the bootloader checksum")
# Blank the checksum. For the bootloader, the bootloader will calculate the
# checksum itself on the initial run.
checksum_write_packet = pack_memory_write_command(
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00, 0x00, 0x00])
)
com_if.send(checksum_write_packet.pack())
else:
crc_addr = None
size_addr = None
if args.target == "a":
crc_addr = APP_A_CRC_ADDR
size_addr = APP_A_SIZE_ADDR
elif args.target == "b":
crc_addr = APP_B_CRC_ADDR
size_addr = APP_B_SIZE_ADDR
assert crc_addr is not None
assert size_addr is not None
_LOGGER.info(
f"Writing app size {total_size} at address {size_addr:#08x}"
)
size_write_packet = pack_memory_write_command(
size_addr, struct.pack("!I", total_size)
)
com_if.send(size_write_packet.pack())
time.sleep(0.2)
crc_calc = PredefinedCrc("crc-32")
for segment in loadable_segments:
crc_calc.update(segment.data)
checksum = crc_calc.digest()
_LOGGER.info(
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
)
checksum_write_packet = pack_memory_write_command(crc_addr, checksum)
com_if.send(checksum_write_packet.pack())
com_if.close()
return 0
def pack_memory_write_command(addr: int, data: bytes) -> PusTc: def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
@ -422,7 +311,7 @@ def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
service=MEMORY_SERVICE, service=MEMORY_SERVICE,
subservice=RAW_MEMORY_WRITE_SUBSERVICE, subservice=RAW_MEMORY_WRITE_SUBSERVICE,
seq_count=SEQ_PROVIDER.get_and_increment(), seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes(app_data), app_data=app_data,
) )

View File

@ -7,11 +7,11 @@ edition = "2021"
[dependencies] [dependencies]
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
va416xx-hal = { path = "../../va416xx-hal" }
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
embedded-hal = "1" embedded-hal = "1"
va416xx-hal = { path = "../../va416xx-hal", features = ["va41630"] }
[profile.dev] [profile.dev]
codegen-units = 1 codegen-units = 1

View File

@ -7,11 +7,11 @@ edition = "2021"
[dependencies] [dependencies]
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
va416xx-hal = { path = "../../va416xx-hal" }
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
embedded-hal = "1" embedded-hal = "1"
va416xx-hal = { path = "../../va416xx-hal", features = ["va41630"] }
[profile.dev] [profile.dev]
codegen-units = 1 codegen-units = 1

View File

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

View File

@ -1,7 +1,7 @@
/* Special linker script for application slot A with an offset at address 0x4000 */ /* Special linker script for application slot A with an offset at address 0x4000 */
MEMORY MEMORY
{ {
FLASH : ORIGIN = 0x00004000, LENGTH = 0x1DFF8 FLASH : ORIGIN = 0x00004000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */ /* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K

View File

@ -1,7 +1,7 @@
/* Special linker script for application slot B with an offset at address 0x22000 */ /* Special linker script for application slot B with an offset at address 0x22000 */
MEMORY MEMORY
{ {
FLASH : ORIGIN = 0x00022000, LENGTH = 0x1DFF8 FLASH : ORIGIN = 0x00022000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */ /* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K

View File

@ -8,26 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.3.0] 2024-30-09 # [v0.2.0]
## Changed
- Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now
only expects a configuration structure and the transfer configuration needs to be applied in a
separate step.
- Added an additional way to read the UART RX with IRQs. The module documentation provides
more information.
- Made the UART with IRQ API more flexible for future additions.
- Improved UART API result and error handling, added low level API to read from and write
to the FIFO directly
## Fixed
- Fixes for SPI peripheral: Flush implementation was incorrect and should now flush properly.
- Fixes for SPI example
- Fixes for RTIC example
# [v0.2.0] 2024-09-18
- Documentation improvements - Documentation improvements
- Improved UART typing support: Validity of passed pins is now checked properly - Improved UART typing support: Validity of passed pins is now checked properly
@ -36,17 +17,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be - Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be
selected for HAL compilation to work properly selected for HAL compilation to work properly
- Adaptions for the UART IRQ feature which are now only implemented for the RX part of the UART.
## Fixed ## Fixed
- Small fixes and improvements for ADC drivers - Small fixes and improvements for ADC drivers
- Fixes for the SPI implementation where the clock divider values were not calculated - Fixes for the SPI implementation where the clock divider values were not calculated
correctly correctly
- Fixes for UART IRQ handler implementation
- Add new IRQ router initialization method `irq_router::enable_and_init_irq_router`. This method
also sets the initial values of some registers to 0 where the datasheet and the actual reset
value are inconsistent, which can lead to weird bugs like IRQs not being triggered properly.
## Added ## Added

View File

@ -1,6 +1,6 @@
[package] [package]
name = "va416xx-hal" name = "va416xx-hal"
version = "0.3.0" version = "0.2.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021" edition = "2021"
description = "HAL for the Vorago VA416xx family of MCUs" description = "HAL for the Vorago VA416xx family of MCUs"
@ -12,7 +12,6 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies] [dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
critical-section = "1"
nb = "1" nb = "1"
paste = "1" paste = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
@ -21,7 +20,7 @@ embedded-io = "0.6"
num_enum = { version = "0.7", default-features = false } num_enum = { version = "0.7", default-features = false }
typenum = "1" typenum = "1"
bitflags = "2" bitflags = "2"
bitfield = "0.17" bitfield = "0.15"
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
fugit = "0.3" fugit = "0.3"
delegate = "0.12" delegate = "0.12"

View File

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

View File

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

View File

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

View File

@ -113,6 +113,14 @@ pub(super) unsafe trait RegisterInterface {
/// this type. /// this type.
fn id(&self) -> DynPinId; fn id(&self) -> DynPinId;
const PORTA: *const PortRegisterBlock = Porta::ptr();
const PORTB: *const PortRegisterBlock = Portb::ptr();
const PORTC: *const PortRegisterBlock = Portc::ptr();
const PORTD: *const PortRegisterBlock = Portd::ptr();
const PORTE: *const PortRegisterBlock = Porte::ptr();
const PORTF: *const PortRegisterBlock = Portf::ptr();
const PORTG: *const PortRegisterBlock = Portg::ptr();
/// Change the pin mode /// Change the pin mode
#[inline] #[inline]
fn change_mode(&mut self, mode: DynPinMode) { fn change_mode(&mut self, mode: DynPinMode) {
@ -147,13 +155,13 @@ pub(super) unsafe trait RegisterInterface {
#[inline] #[inline]
fn port_reg(&self) -> &PortRegisterBlock { fn port_reg(&self) -> &PortRegisterBlock {
match self.id().group { match self.id().group {
DynGroup::A => unsafe { &(*Porta::ptr()) }, DynGroup::A => unsafe { &(*Self::PORTA) },
DynGroup::B => unsafe { &(*Portb::ptr()) }, DynGroup::B => unsafe { &(*Self::PORTB) },
DynGroup::C => unsafe { &(*Portc::ptr()) }, DynGroup::C => unsafe { &(*Self::PORTC) },
DynGroup::D => unsafe { &(*Portd::ptr()) }, DynGroup::D => unsafe { &(*Self::PORTD) },
DynGroup::E => unsafe { &(*Porte::ptr()) }, DynGroup::E => unsafe { &(*Self::PORTE) },
DynGroup::F => unsafe { &(*Portf::ptr()) }, DynGroup::F => unsafe { &(*Self::PORTF) },
DynGroup::G => unsafe { &(*Portg::ptr()) }, DynGroup::G => unsafe { &(*Self::PORTG) },
} }
} }

View File

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

View File

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

View File

@ -9,10 +9,8 @@ use core::convert::Infallible;
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::pac; use crate::pac;
use crate::time::Hertz;
pub use crate::timer::ValidTim;
use crate::timer::{TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTimAndPin};
use crate::{clock::Clocks, gpio::DynPinId}; use crate::{clock::Clocks, gpio::DynPinId};
pub use crate::{gpio::PinId, time::Hertz, timer::*};
const DUTY_MAX: u16 = u16::MAX; const DUTY_MAX: u16 = u16::MAX;

View File

@ -1,15 +1,11 @@
//! API for the SPI peripheral //! API for the SPI peripheral
//! //!
//! The main abstraction provided by this module are the [Spi] and the [SpiBase] structure.
//! These provide the [embedded_hal::spi] traits, but also offer a low level interface
//! via the [SpiLowLevel] trait.
//!
//! ## Examples //! ## Examples
//! //!
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs) //! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs)
use core::{convert::Infallible, marker::PhantomData, ops::Deref}; use core::{convert::Infallible, marker::PhantomData, ops::Deref};
use embedded_hal::spi::{Mode, MODE_0}; use embedded_hal::spi::Mode;
use crate::{ use crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt}, clock::{Clocks, PeripheralSelect, SyscfgExt},
@ -232,100 +228,100 @@ pub trait TransferConfigProvider {
fn sod(&mut self, sod: bool); fn sod(&mut self, sod: bool);
fn blockmode(&mut self, blockmode: bool); fn blockmode(&mut self, blockmode: bool);
fn mode(&mut self, mode: Mode); fn mode(&mut self, mode: Mode);
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig); fn clk_div(&mut self, clk_div: u16);
fn hw_cs_id(&self) -> u8; fn hw_cs_id(&self) -> u8;
} }
/// This struct contains all configuration parameter which are transfer specific /// This struct contains all configuration parameter which are transfer specific
/// and might change for transfers to different SPI slaves /// and might change for transfers to different SPI slaves
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone)]
pub struct TransferConfigWithHwcs<HwCs> { pub struct TransferConfig<HwCs> {
pub clk_div: Option<u16>,
pub mode: Option<Mode>,
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
/// false
pub hw_cs: Option<HwCs>, pub hw_cs: Option<HwCs>,
pub cfg: TransferConfig, pub sod: bool,
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
/// duration of multiple data words
pub blockmode: bool,
} }
/// Type erased variant of the transfer configuration. This is required to avoid generics in /// Type erased variant of the transfer configuration. This is required to avoid generics in
/// the SPI constructor. /// the SPI constructor.
#[derive(Copy, Clone, Debug)] pub struct ErasedTransferConfig {
pub struct TransferConfig { pub clk_div: Option<u16>,
pub clk_cfg: Option<SpiClkConfig>,
pub mode: Option<Mode>, pub mode: Option<Mode>,
pub sod: bool, pub sod: bool,
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless /// If this is enabled, all data in the FIFO is transmitted in a single frame unless
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
/// duration of multiple data words /// duration of multiple data words
pub blockmode: bool, pub blockmode: bool,
/// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
/// is set on a written word.
pub bmstall: bool,
pub hw_cs: HwChipSelectId, pub hw_cs: HwChipSelectId,
} }
impl TransferConfigWithHwcs<NoneT> { impl TransferConfig<NoneT> {
pub fn new_no_hw_cs( pub fn new_no_hw_cs(
clk_cfg: Option<SpiClkConfig>, clk_div: Option<u16>,
mode: Option<Mode>, mode: Option<Mode>,
blockmode: bool, blockmode: bool,
bmstall: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfigWithHwcs { TransferConfig {
clk_div,
mode,
hw_cs: None, hw_cs: None,
cfg: TransferConfig { sod,
clk_cfg, blockmode,
mode,
sod,
blockmode,
bmstall,
hw_cs: HwChipSelectId::Invalid,
},
} }
} }
} }
impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> { impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
pub fn new( pub fn new(
clk_cfg: Option<SpiClkConfig>, clk_div: Option<u16>,
mode: Option<Mode>, mode: Option<Mode>,
hw_cs: Option<HwCs>, hw_cs: Option<HwCs>,
blockmode: bool, blockmode: bool,
bmstall: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfigWithHwcs { TransferConfig {
clk_div,
mode,
hw_cs, hw_cs,
cfg: TransferConfig { sod,
clk_cfg, blockmode,
mode,
sod,
blockmode,
bmstall,
hw_cs: HwCs::CS_ID,
},
} }
} }
pub fn downgrade(self) -> TransferConfig { pub fn downgrade(self) -> ErasedTransferConfig {
self.cfg ErasedTransferConfig {
clk_div: self.clk_div,
mode: self.mode,
sod: self.sod,
blockmode: self.blockmode,
hw_cs: HwCs::CS_ID,
}
} }
} }
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfigWithHwcs<HwCs> { impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
/// Slave Output Disable /// Slave Output Disable
fn sod(&mut self, sod: bool) { fn sod(&mut self, sod: bool) {
self.cfg.sod = sod; self.sod = sod;
} }
fn blockmode(&mut self, blockmode: bool) { fn blockmode(&mut self, blockmode: bool) {
self.cfg.blockmode = blockmode; self.blockmode = blockmode;
} }
fn mode(&mut self, mode: Mode) { fn mode(&mut self, mode: Mode) {
self.cfg.mode = Some(mode); self.mode = Some(mode);
} }
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) { fn clk_div(&mut self, clk_div: u16) {
self.cfg.clk_cfg = Some(clk_cfg); self.clk_div = Some(clk_div);
} }
fn hw_cs_id(&self) -> u8 { fn hw_cs_id(&self) -> u8 {
@ -335,16 +331,7 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfigWithHwcs<HwCs>
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details /// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
pub struct SpiConfig { pub struct SpiConfig {
clk: SpiClkConfig, clk_div: u16,
// SPI mode configuration
pub init_mode: Mode,
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
/// duration of multiple data words. Defaults to true.
pub blockmode: bool,
/// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty.
/// Currently enabled by default.
pub bmstall: bool,
/// By default, configure SPI for master mode (ms == false) /// By default, configure SPI for master mode (ms == false)
ms: bool, ms: bool,
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
@ -358,11 +345,7 @@ pub struct SpiConfig {
impl Default for SpiConfig { impl Default for SpiConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
init_mode: MODE_0, clk_div: DEFAULT_CLK_DIV,
blockmode: true,
bmstall: true,
// Default value is definitely valid.
clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
ms: Default::default(), ms: Default::default(),
slave_output_disable: Default::default(), slave_output_disable: Default::default(),
loopback_mode: Default::default(), loopback_mode: Default::default(),
@ -377,23 +360,8 @@ impl SpiConfig {
self self
} }
pub fn blockmode(mut self, enable: bool) -> Self { pub fn clk_div(mut self, clk_div: u16) -> Self {
self.blockmode = enable; self.clk_div = clk_div;
self
}
pub fn bmstall(mut self, enable: bool) -> Self {
self.bmstall = enable;
self
}
pub fn mode(mut self, mode: Mode) -> Self {
self.init_mode = mode;
self
}
pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
self.clk = clk_cfg;
self self
} }
@ -487,36 +455,6 @@ impl Instance for pac::Spi3 {
// Spi // Spi
//================================================================================================== //==================================================================================================
/// Low level access trait for the SPI peripheral.
pub trait SpiLowLevel {
/// Low level function to write a word to the SPI FIFO but also checks whether
/// there is actually data in the FIFO.
///
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>;
/// Low level function to write a word to the SPI FIFO without checking whether
/// there FIFO is full.
///
/// This does not necesarily mean there is a space in the FIFO available.
/// Use [Self::write_fifo] function to write a word into the FIFO reliably.
fn write_fifo_unchecked(&self, data: u32);
/// Low level function to read a word from the SPI FIFO. Must be preceeded by a
/// [Self::write_fifo] call.
///
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
fn read_fifo(&self) -> nb::Result<u32, Infallible>;
/// Low level function to read a word from from the SPI FIFO.
///
/// This does not necesarily mean there is a word in the FIFO available.
/// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
/// API.
/// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
fn read_fifo_unchecked(&self) -> u32;
}
pub struct SpiBase<SpiInstance, Word = u8> { pub struct SpiBase<SpiInstance, Word = u8> {
spi: SpiInstance, spi: SpiInstance,
cfg: SpiConfig, cfg: SpiConfig,
@ -524,7 +462,6 @@ pub struct SpiBase<SpiInstance, Word = u8> {
/// Fill word for read-only SPI transactions. /// Fill word for read-only SPI transactions.
pub fill_word: Word, pub fill_word: Word,
blockmode: bool, blockmode: bool,
bmstall: bool,
word: PhantomData<Word>, word: PhantomData<Word>,
} }
@ -542,8 +479,7 @@ pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpiClkConfig { pub struct SpiClkConfig {
prescale_val: u16, prescale_val: u16,
scrdv: u8, scrdv: u8,
@ -558,23 +494,6 @@ impl SpiClkConfig {
} }
} }
impl SpiClkConfig {
pub fn new(prescale_val: u16, scrdv: u8) -> Self {
Self {
prescale_val,
scrdv,
}
}
pub fn from_div(div: u16) -> Result<Self, SpiClkConfigError> {
spi_clk_config_from_div(div)
}
pub fn from_clk(spi_clk: Hertz, clocks: &Clocks) -> Option<Self> {
clk_div_for_target_clock(spi_clk, clocks).map(|div| spi_clk_config_from_div(div).unwrap())
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum SpiClkConfigError { pub enum SpiClkConfigError {
DivIsZero, DivIsZero,
@ -646,23 +565,29 @@ impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word>
where where
<Word as TryFrom<u32>>::Error: core::fmt::Debug, <Word as TryFrom<u32>>::Error: core::fmt::Debug,
{ {
#[inline]
pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
self.spi
.ctrl0()
.modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) });
self.spi
.clkprescale()
.write(|w| unsafe { w.bits(cfg.prescale_val as u32) });
}
#[inline] #[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
let val = spi_clk_config_from_div(div)?; let val = spi_clk_config_from_div(div)?;
self.cfg_clock(val); self.spi_instance()
.ctrl0()
.modify(|_, w| unsafe { w.scrdv().bits(val.scrdv as u8) });
self.spi_instance()
.clkprescale()
.write(|w| unsafe { w.bits(val.prescale_val as u32) });
Ok(()) Ok(())
} }
/*
#[inline]
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
let clk_prescale =
self.apb1_clk.raw() / (spi_clk.into().raw() * (self.cfg.ser_clock_rate_div as u32 + 1));
self.spi
.clkprescale()
.write(|w| unsafe { w.bits(clk_prescale) });
}
*/
#[inline] #[inline]
pub fn cfg_mode(&mut self, mode: Mode) { pub fn cfg_mode(&mut self, mode: Mode) {
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
@ -673,7 +598,7 @@ where
} }
#[inline] #[inline]
pub fn spi(&self) -> &SpiInstance { pub fn spi_instance(&self) -> &SpiInstance {
&self.spi &self.spi
} }
@ -721,17 +646,17 @@ where
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>( pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
&mut self, &mut self,
transfer_cfg: &TransferConfigWithHwcs<HwCs>, transfer_cfg: &TransferConfig<HwCs>,
) { ) -> Result<(), SpiClkConfigError> {
if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg { if let Some(trans_clk_div) = transfer_cfg.clk_div {
self.cfg_clock(trans_clk_div); self.cfg_clock_from_div(trans_clk_div)?;
} }
if let Some(mode) = transfer_cfg.cfg.mode { if let Some(mode) = transfer_cfg.mode {
self.cfg_mode(mode); self.cfg_mode(mode);
} }
self.blockmode = transfer_cfg.cfg.blockmode; self.blockmode = transfer_cfg.blockmode;
self.spi.ctrl1().modify(|_, w| { self.spi.ctrl1().modify(|_, w| {
if transfer_cfg.cfg.sod { if transfer_cfg.sod {
w.sod().set_bit(); w.sod().set_bit();
} else if transfer_cfg.hw_cs.is_some() { } else if transfer_cfg.hw_cs.is_some() {
w.sod().clear_bit(); w.sod().clear_bit();
@ -741,264 +666,78 @@ where
} else { } else {
w.sod().clear_bit(); w.sod().clear_bit();
} }
w.blockmode().bit(transfer_cfg.cfg.blockmode); if transfer_cfg.blockmode {
w.bmstall().bit(transfer_cfg.cfg.bmstall) w.blockmode().set_bit();
} else {
w.blockmode().clear_bit();
}
w
}); });
}
/// Low level function to write a word to the SPI FIFO but also checks whether
/// there is actually data in the FIFO.
///
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
#[inline(always)]
pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
if self.spi.status().read().tnf().bit_is_clear() {
return Err(nb::Error::WouldBlock);
}
self.write_fifo_unchecked(data);
Ok(()) Ok(())
} }
/// Low level function to write a word to the SPI FIFO without checking whether /// Sends a word to the slave
/// there FIFO is full.
///
/// This does not necesarily mean there is a space in the FIFO available.
/// Use [Self::write_fifo] function to write a word into the FIFO reliably.
#[inline(always)] #[inline(always)]
pub fn write_fifo_unchecked(&self, data: u32) { fn send_blocking(&self, word: Word) {
self.spi.data().write(|w| unsafe { w.bits(data) }); // TODO: Upper limit for wait cycles to avoid complete hangups?
while self.spi.status().read().tnf().bit_is_clear() {}
self.send(word)
} }
/// Low level function to read a word from the SPI FIFO. Must be preceeded by a
/// [Self::write_fifo] call.
///
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
#[inline(always)] #[inline(always)]
pub fn read_fifo(&self) -> nb::Result<u32, Infallible> { fn send(&self, word: Word) {
if self.spi.status().read().rne().bit_is_clear() { self.spi.data().write(|w| unsafe { w.bits(word.into()) });
return Err(nb::Error::WouldBlock);
}
Ok(self.read_fifo_unchecked())
} }
/// Low level function to read a word from from the SPI FIFO. /// Read a word from the slave. Must be preceeded by a [`send`](Self::send) call
///
/// This does not necesarily mean there is a word in the FIFO available.
/// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
/// API.
/// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
#[inline(always)] #[inline(always)]
pub fn read_fifo_unchecked(&self) -> u32 { fn read_blocking(&self) -> Word {
self.spi.data().read().bits() // TODO: Upper limit for wait cycles to avoid complete hangups?
while self.spi.status().read().rne().bit_is_clear() {}
self.read_single_word()
} }
fn flush_internal(&self) { #[inline(always)]
let mut status_reg = self.spi.status().read(); fn read_single_word(&self) -> Word {
while status_reg.tfe().bit_is_clear() (self.spi.data().read().bits() & Word::MASK)
|| status_reg.rne().bit_is_set() .try_into()
|| status_reg.busy().bit_is_set() .unwrap()
{
if status_reg.rne().bit_is_set() {
self.read_fifo_unchecked();
}
status_reg = self.spi.status().read();
}
} }
fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> { fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
if words.is_empty() { if words.is_empty() {
return Ok(()); return Ok(());
} }
self.flush_internal(); let mut status_reg = self.spi.status().read();
Ok(()) // Wait until all bytes have been transferred.
} while status_reg.tfe().bit_is_clear() {
// Ignore all received read words.
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer if status_reg.rne().bit_is_set() {
// initialization. Returns the amount of written bytes.
fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize {
if self.blockmode {
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
}
// Fill the first half of the write FIFO
let mut current_write_idx = 0;
let smaller_idx = core::cmp::min(FILL_DEPTH, words.len());
for _ in 0..smaller_idx {
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK);
} else {
self.write_fifo_unchecked(words[current_write_idx].into());
}
current_write_idx += 1;
}
if self.blockmode {
self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit())
}
current_write_idx
}
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
// initialization.
fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize {
if self.blockmode {
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
}
// Fill the first half of the write FIFO
let mut current_write_idx = 0;
let smaller_idx = core::cmp::min(FILL_DEPTH, send_len);
for _ in 0..smaller_idx {
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK);
} else {
self.write_fifo_unchecked(self.fill_word.into());
}
current_write_idx += 1;
}
if self.blockmode {
self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit())
}
current_write_idx
}
}
impl<SpiInstance: Instance, Word: WordProvider> SpiLowLevel for SpiBase<SpiInstance, Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
#[inline(always)]
fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
if self.spi.status().read().tnf().bit_is_clear() {
return Err(nb::Error::WouldBlock);
}
self.write_fifo_unchecked(data);
Ok(())
}
#[inline(always)]
fn write_fifo_unchecked(&self, data: u32) {
self.spi.data().write(|w| unsafe { w.bits(data) });
}
#[inline(always)]
fn read_fifo(&self) -> nb::Result<u32, Infallible> {
if self.spi.status().read().rne().bit_is_clear() {
return Err(nb::Error::WouldBlock);
}
Ok(self.read_fifo_unchecked())
}
#[inline(always)]
fn read_fifo_unchecked(&self) -> u32 {
self.spi.data().read().bits()
}
}
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
type Error = Infallible;
}
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len());
loop {
if current_read_idx < words.len() {
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
.try_into()
.unwrap();
current_read_idx += 1;
}
if current_write_idx < words.len() {
if current_write_idx == words.len() - 1 && self.bmstall {
nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?;
} else {
nb::block!(self.write_fifo(self.fill_word.into()))?;
}
current_write_idx += 1;
}
if current_read_idx >= words.len() && current_write_idx >= words.len() {
break;
}
}
Ok(())
}
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
while current_write_idx < words.len() {
if current_write_idx == words.len() - 1 && self.bmstall {
nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?;
} else {
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
}
current_write_idx += 1;
// Ignore received words.
if self.spi.status().read().rne().bit_is_set() {
self.clear_rx_fifo(); self.clear_rx_fifo();
} }
status_reg = self.spi.status().read();
}
// Ignore all received read words.
if status_reg.rne().bit_is_set() {
self.clear_rx_fifo();
} }
Ok(()) Ok(())
} }
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize {
self.transfer_preparation(write)?; if self.blockmode {
let mut current_read_idx = 0; self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write);
while current_read_idx < read.len() || current_write_idx < write.len() {
if current_write_idx < write.len() {
if current_write_idx == write.len() - 1 && self.bmstall {
nb::block!(
self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK)
)?;
} else {
nb::block!(self.write_fifo(write[current_write_idx].into()))?;
}
current_write_idx += 1;
}
if current_read_idx < read.len() {
read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
.try_into()
.unwrap();
current_read_idx += 1;
}
} }
// Fill the first half of the write FIFO
Ok(()) let mut current_write_idx = 0;
} for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) {
self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx]));
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { current_write_idx += 1;
self.transfer_preparation(words)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
while current_read_idx < words.len() || current_write_idx < words.len() {
if current_write_idx < words.len() {
if current_write_idx == words.len() - 1 && self.bmstall {
nb::block!(
self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK)
)?;
} else {
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
}
current_write_idx += 1;
}
if current_read_idx < words.len() && current_read_idx < current_write_idx {
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
.try_into()
.unwrap();
current_read_idx += 1;
}
} }
Ok(()) if self.blockmode {
} self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit())
}
fn flush(&mut self) -> Result<(), Self::Error> { current_write_idx
self.flush_internal();
Ok(())
} }
} }
@ -1033,44 +772,55 @@ where
spi: SpiI, spi: SpiI,
pins: (Sck, Miso, Mosi), pins: (Sck, Miso, Mosi),
spi_cfg: SpiConfig, spi_cfg: SpiConfig,
) -> Self { transfer_cfg: Option<&ErasedTransferConfig>,
) -> Result<Self, SpiClkConfigError> {
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
// This is done in the C HAL. // This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL); syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
let SpiConfig { let SpiConfig {
clk, clk_div,
init_mode,
blockmode,
bmstall,
ms, ms,
slave_output_disable, slave_output_disable,
loopback_mode, loopback_mode,
master_delayer_capture, master_delayer_capture,
} = spi_cfg; } = spi_cfg;
let mut init_mode = embedded_hal::spi::MODE_0;
let mut ss = 0;
let mut init_blockmode = false;
let apb1_clk = clocks.apb1();
if let Some(transfer_cfg) = transfer_cfg {
if let Some(mode) = transfer_cfg.mode {
init_mode = mode;
}
//self.cfg_clock_from_div(transfer_cfg.clk_div);
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
ss = transfer_cfg.hw_cs as u8;
}
init_blockmode = transfer_cfg.blockmode;
}
let spi_clk_cfg = spi_clk_config_from_div(clk_div)?;
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode); let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
spi.ctrl0().write(|w| { spi.ctrl0().write(|w| {
unsafe { unsafe {
w.size().bits(Word::word_reg()); w.size().bits(Word::word_reg());
w.scrdv().bits(clk.scrdv); w.scrdv().bits(spi_clk_cfg.scrdv);
// Clear clock phase and polarity. Will be set to correct value for each // Clear clock phase and polarity. Will be set to correct value for each
// transfer // transfer
w.spo().bit(cpo_bit); w.spo().bit(cpo_bit);
w.sph().bit(cph_bit) w.sph().bit(cph_bit)
} }
}); });
spi.ctrl1().write(|w| { spi.ctrl1().write(|w| {
w.lbm().bit(loopback_mode); w.lbm().bit(loopback_mode);
w.sod().bit(slave_output_disable); w.sod().bit(slave_output_disable);
w.ms().bit(ms); w.ms().bit(ms);
w.mdlycap().bit(master_delayer_capture); w.mdlycap().bit(master_delayer_capture);
w.blockmode().bit(blockmode); w.blockmode().bit(init_blockmode);
w.bmstall().bit(bmstall); unsafe { w.ss().bits(ss) }
unsafe { w.ss().bits(0) }
}); });
spi.clkprescale() spi.clkprescale()
.write(|w| unsafe { w.bits(clk.prescale_val as u32) }); .write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val as u32) });
spi.fifo_clr().write(|w| { spi.fifo_clr().write(|w| {
w.rxfifo().set_bit(); w.rxfifo().set_bit();
@ -1079,30 +829,26 @@ where
// Enable the peripheral as the last step as recommended in the // Enable the peripheral as the last step as recommended in the
// programmers guide // programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit()); spi.ctrl1().modify(|_, w| w.enable().set_bit());
Spi { Ok(Spi {
inner: SpiBase { inner: SpiBase {
spi, spi,
cfg: spi_cfg, cfg: spi_cfg,
apb1_clk: clocks.apb1(), apb1_clk,
fill_word: Default::default(), fill_word: Default::default(),
bmstall, blockmode: init_blockmode,
blockmode,
word: PhantomData, word: PhantomData,
}, },
pins, pins,
} })
} }
delegate::delegate! { delegate::delegate! {
to self.inner { to self.inner {
#[inline]
pub fn cfg_clock(&mut self, cfg: SpiClkConfig);
#[inline] #[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>; pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
#[inline] #[inline]
pub fn spi(&self) -> &SpiI; pub fn spi_instance(&self) -> &SpiI;
#[inline] #[inline]
pub fn cfg_mode(&mut self, mode: Mode); pub fn cfg_mode(&mut self, mode: Mode);
@ -1111,8 +857,8 @@ where
pub fn perid(&self) -> u32; pub fn perid(&self) -> u32;
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>( pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
&mut self, transfer_cfg: &TransferConfigWithHwcs<HwCs> &mut self, transfer_cfg: &TransferConfig<HwCs>
); ) -> Result<(), SpiClkConfigError>;
} }
} }
@ -1136,58 +882,6 @@ where
} }
} }
impl<
SpiI: Instance,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
Word: WordProvider,
> SpiLowLevel for Spi<SpiI, (Sck, Miso, Mosi), Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
delegate::delegate! {
to self.inner {
fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>;
fn write_fifo_unchecked(&self, data: u32);
fn read_fifo(&self) -> nb::Result<u32, Infallible>;
fn read_fifo_unchecked(&self) -> u32;
}
}
}
impl<
SpiI: Instance,
Word: WordProvider,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
> embedded_hal::spi::ErrorType for Spi<SpiI, (Sck, Miso, Mosi), Word>
{
type Error = Infallible;
}
impl<
SpiI: Instance,
Word: WordProvider,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
> embedded_hal::spi::SpiBus<Word> for Spi<SpiI, (Sck, Miso, Mosi), Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
delegate::delegate! {
to self.inner {
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>;
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>;
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
fn flush(&mut self) -> Result<(), Self::Error>;
}
}
}
/// Changing the word size also requires a type conversion /// Changing the word size also requires a type conversion
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>> impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16> From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16>
@ -1203,7 +897,6 @@ impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>
spi: old_spi.inner.spi, spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg, cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode, blockmode: old_spi.inner.blockmode,
bmstall: old_spi.inner.bmstall,
fill_word: Default::default(), fill_word: Default::default(),
apb1_clk: old_spi.inner.apb1_clk, apb1_clk: old_spi.inner.apb1_clk,
word: PhantomData, word: PhantomData,
@ -1228,7 +921,6 @@ impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>
spi: old_spi.inner.spi, spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg, cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode, blockmode: old_spi.inner.blockmode,
bmstall: old_spi.inner.bmstall,
apb1_clk: old_spi.inner.apb1_clk, apb1_clk: old_spi.inner.apb1_clk,
fill_word: Default::default(), fill_word: Default::default(),
word: PhantomData, word: PhantomData,
@ -1237,3 +929,134 @@ impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>
} }
} }
} }
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
type Error = Infallible;
}
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping(None);
loop {
if current_write_idx < words.len() {
self.send_blocking(self.fill_word);
current_write_idx += 1;
}
if current_read_idx < words.len() {
words[current_read_idx] = self.read_blocking();
current_read_idx += 1;
}
if current_read_idx >= words.len() && current_write_idx >= words.len() {
break;
}
}
Ok(())
}
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
while current_write_idx < words.len() {
self.send_blocking(words[current_write_idx]);
current_write_idx += 1;
// Ignore received words.
if self.spi.status().read().rne().bit_is_set() {
self.clear_rx_fifo();
}
}
Ok(())
}
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
self.transfer_preparation(write)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping(Some(write));
while current_read_idx < read.len() || current_write_idx < write.len() {
if current_write_idx < write.len() {
self.send_blocking(write[current_write_idx]);
current_write_idx += 1;
}
if current_read_idx < read.len() {
read[current_read_idx] = self.read_blocking();
current_read_idx += 1;
}
}
Ok(())
}
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
while current_read_idx < words.len() || current_write_idx < words.len() {
if current_write_idx < words.len() {
self.send_blocking(words[current_write_idx]);
current_write_idx += 1;
}
if current_read_idx < words.len() && current_read_idx < current_write_idx {
words[current_read_idx] = self.read_blocking();
current_read_idx += 1;
}
}
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
let status_reg = self.spi.status().read();
while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() {
if status_reg.rne().bit_is_set() {
self.read_single_word();
}
}
Ok(())
}
}
impl<
SpiI: Instance,
Word: WordProvider,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
> embedded_hal::spi::ErrorType for Spi<SpiI, (Sck, Miso, Mosi), Word>
{
type Error = Infallible;
}
impl<
SpiI: Instance,
Word: WordProvider,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
> embedded_hal::spi::SpiBus<Word> for Spi<SpiI, (Sck, Miso, Mosi), Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.inner.read(words)
}
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
self.inner.write(words)
}
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
self.inner.transfer(read, write)
}
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.inner.transfer_in_place(words)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.inner.flush()
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
Change Log
=======
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.0] 2024-10-01
- Initial release

View File

@ -16,10 +16,14 @@ cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
path = "../va416xx-hal"
features = ["va41630"] features = ["va41630"]
version = ">=0.3, <0.4" version = "0.2.0"
[dependencies.lis2dh12] [dependencies.lis2dh12]
git = "https://github.com/us-irs/lis2dh12.git"
# path = "../../lis2dh12"
branch = "all-features"
version = "0.7" version = "0.7"
features = ["out_f32"] features = ["out_f32"]

View File

@ -1,12 +1,15 @@
[![Crates.io](https://img.shields.io/crates/v/vorago-peb1)](https://crates.io/crates/vorago-peb1)
[![docs.rs](https://img.shields.io/docsrs/vorago-peb1)](https://docs.rs/vorago-peb1)
# Rust BSP for the Vorago PEB1 development board # Rust BSP for the Vorago PEB1 development board
This is the Rust **B**oard **S**upport **P**ackage crate for the Vorago PEB1 development board. ## Using the `.cargo/config.toml` file
Its aim is to provide drivers for the board features of the PEB1 board.
The BSP builds on top of the [HAL crate for VA416xx devices](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx-hal). Use the following command to have a starting `config.toml` file
```sh
cp .cargo/def-config.toml .cargo/config.toml
```
You then can adapt the `config.toml` to your needs. For example, you can configure runners
to conveniently flash with `cargo run`.
## Notes on board revisions ## Notes on board revisions

View File

@ -5,10 +5,6 @@
pub use lis2dh12; pub use lis2dh12;
/// Support for the LIS2DH12 accelerometer on the GPIO board. /// Support for the LIS2DH12 accelerometer on the GPIO board.
///
/// # Example
///
/// - [PEB1 Accelerometer](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
pub mod accelerometer { pub mod accelerometer {
use lis2dh12::{self, detect_i2c_addr, AddrDetectionError, Lis2dh12}; use lis2dh12::{self, detect_i2c_addr, AddrDetectionError, Lis2dh12};

View File

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

View File

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