Compare commits

...

24 Commits

Author SHA1 Message Date
969e5bbc42 Merge pull request 'use released HAL dependency' (#39) from prep-bsp-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #39
2024-10-01 10:56:23 +02:00
6960c09627 Merge branch 'main' into prep-bsp-release 2024-10-01 10:56:15 +02:00
25f7b79f28
use released HAL dependency 2024-10-01 10:55:55 +02:00
2cf7554cab Merge pull request 'README and docs update' (#38) from prep-bsp-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #38
2024-10-01 10:54:52 +02:00
ff58fb7b55 prepare release for BSP 2024-10-01 10:54:11 +02:00
b1f63b64ce Merge pull request 'prepare release for BSP' (#37) from prep-bsp-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #37
2024-10-01 10:46:28 +02:00
22cc40c095
prepare release for BSP
Some checks are pending
Rust/va416xx-rs/pipeline/head Build started...
2024-10-01 10:43:06 +02:00
1ca319b433 Merge pull request 'bump version' (#36) from prep-hal-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #36
2024-09-30 15:34:11 +02:00
3813c397f7 bump HAL version
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
2024-09-30 15:23:24 +02:00
9d8772bf1f Merge pull request 'prepare HAL release' (#35) from prep-hal-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #35
2024-09-30 14:45:54 +02:00
246b084429
prepare HAL release
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-09-30 14:13:11 +02:00
bea5a852a2 Merge pull request 'smaller improvements and fixes' (#34) from smaller-improvements-fixes into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #34
2024-09-30 13:50:52 +02:00
e1487c8969
smaller improvements and fixes
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-09-30 12:17:05 +02:00
7e7416efd1 Merge pull request 'Improve UART Impl' (#33) from improve-uart-impl into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #33
2024-09-24 17:47:47 +02:00
42e3cfde8a improve UART impl
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-24 17:22:00 +02:00
a50f7a947a Merge pull request 'UART with IRQ + Embassy example' (#32) from add-uart-embassy-echo-example into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #32
2024-09-24 11:07:00 +02:00
abede6057e UART with IRQ + Embassy example
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-24 10:59:41 +02:00
e04f4336cc Merge pull request 'Flashloader and UART update' (#31) from flashloader-and-uart-update into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #31
2024-09-23 12:00:23 +02:00
aae870c767 Flashloader and UART improvements
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
2024-09-23 11:57:32 +02:00
3e67749452 Merge pull request 'calculate most bootloader properties' (#30) from bootloader-improvements into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #30
2024-09-23 10:26:57 +02:00
5eb38f9c2a
fix
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-23 10:02:51 +02:00
5b336a2b41 calculate most bootloader properties
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-20 12:12:21 +02:00
051042ad1b Merge pull request 'Updates and fixes: SPI' (#29) from updates-and-fixes into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #29
2024-09-20 11:43:44 +02:00
aa1ed2a20d Updates and fixes
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
- Improve and fix SPI HAL and example
- Fix RTIC example
2024-09-20 11:41:24 +02:00
33 changed files with 1776 additions and 1009 deletions

View File

@ -41,4 +41,4 @@ debug-assertions = false # <-
lto = true
opt-level = 'z' # <-
overflow-checks = false # <-
# strip = true # Automatically strip symbols from the binary.
strip = true # Automatically strip symbols from the binary.

View File

@ -99,9 +99,9 @@ example.
### Using VS Code
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). Please make sure that
[`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146)
Assuming a working debug connection to your VA416xx board, you can debug using VS Code with
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

View File

@ -8,8 +8,15 @@ cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
panic-rtt-target = { version = "0.1.3" }
panic-halt = { version = "0.2" }
rtt-target = { version = "0.5" }
crc = "3"
static_assertions = "1"
[dependencies.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 |
| 0x3FFC | Bootloader CRC | word |
| 0x4000 | App image A start | code up to 0x1DFFC (~120K) bytes |
| 0x21FFC | App image A CRC check length | word |
| 0x21FFE | App image A CRC check value | word |
| 0x22000 | App image B start | code up to 0x1DFFC (~120K) bytes |
| 0x3FFFC | App image B CRC check length | word |
| 0x3FFFE | App image B CRC check value | word |
| 0x4000 | App image A start | code up to 0x1DFF8 (~120K) bytes |
| 0x21FF8 | App image A CRC check length | word |
| 0x21FFC | App image A CRC check value | word |
| 0x22000 | App image B start | code up to 0x1DFF8 (~120K) bytes |
| 0x3FFF8 | App image B CRC check length | word |
| 0x3FFFC | App image B CRC check value | word |
| 0x40000 | End of NVM | end |
## Additional Information

View File

@ -1,17 +1,5 @@
//! 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
//! but does not scale that clock up.
#![no_main]
@ -19,6 +7,9 @@
use cortex_m_rt::entry;
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 rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
@ -42,23 +33,42 @@ const DEBUG_PRINTOUTS: bool = true;
// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash
// loader to boot a bootloader without this feature.
const FLASH_SELF: bool = false;
// 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.
const NVM_SIZE: u32 = 0x40000;
const BOOTLOADER_START_ADDR: u32 = 0x0;
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 4;
const BOOTLOADER_END_ADDR: u32 = 0x4000;
const BOOTLOADER_CRC_ADDR: u32 = 0x3FFC;
const APP_A_START_ADDR: u32 = 0x4000;
pub const APP_A_END_ADDR: u32 = 0x22000;
// The actual size of the image which is relevant for CRC calculation.
const APP_A_SIZE_ADDR: u32 = 0x21FF8;
const APP_A_CRC_ADDR: u32 = 0x21FFC;
const APP_B_START_ADDR: u32 = 0x22000;
pub const APP_B_END_ADDR: u32 = 0x40000;
// The actual size of the image which is relevant for CRC calculation.
const APP_B_SIZE_ADDR: u32 = 0x3FFF8;
const APP_B_CRC_ADDR: u32 = 0x3FFFC;
pub const APP_IMG_SZ: u32 = 0x1E000;
// 0x4000
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR;
// The actual size of the image which is relevant for CRC calculation will be store at this
// address.
// 0x21FF8
const APP_A_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
// 0x21FFC
const APP_A_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
pub const APP_A_END_ADDR: u32 = BOOTLOADER_END_ADDR + APP_IMG_SZ;
// 0x22000
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_LEN: u32 = 0x350;
@ -88,8 +98,10 @@ impl WdtInterface for OptWdt {
#[entry]
fn main() -> ! {
if RTT_PRINTOUT {
rtt_init_print!();
rprintln!("-- VA416xx bootloader --");
}
let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// Disable ROM protection.
@ -133,20 +145,26 @@ fn main() -> ! {
nvm.write_data(0x0, &first_four_bytes);
nvm.write_data(0x4, bootloader_data);
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
}
if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
}
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 RTT_PRINTOUT {
rprintln!(
"error: CRC verification for bootloader self-flash failed: {:?}",
e
);
}
}
}
// Check bootloader's CRC (and write it if blank)
check_own_crc(&opt_wdt, &nvm, &cp);
@ -156,7 +174,7 @@ fn main() -> ! {
} else if check_app_crc(AppSel::B, &opt_wdt) {
boot_app(AppSel::B, &cp)
} else {
if DEBUG_PRINTOUTS {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("both images corrupt! booting image A");
}
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
@ -184,7 +202,7 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
let crc_calc = digest.finalize();
wdt.feed();
if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("BL CRC blank - prog new CRC");
}
// Blank CRC, write it to NVM.
@ -194,7 +212,7 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
// cortex_m::peripheral::SCB::sys_reset();
} else if crc_exp != crc_calc {
// Bootloader is corrupted. Try to run App A.
if DEBUG_PRINTOUTS {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!(
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
crc_calc,
@ -217,7 +235,7 @@ fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
}
}
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
if DEBUG_PRINTOUTS {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("Checking image {:?}", app_sel);
}
if app_sel == AppSel::A {
@ -237,7 +255,9 @@ fn check_app_given_addr(
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
// Sanity check.
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
if RTT_PRINTOUT {
rprintln!("detected invalid app size {}", image_size);
}
return false;
}
wdt.feed();
@ -252,7 +272,7 @@ fn check_app_given_addr(
}
fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! {
if DEBUG_PRINTOUTS {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("booting app {:?}", app_sel);
}
let clkgen = unsafe { pac::Clkgen::steal() };

View File

@ -7,6 +7,7 @@ edition = "2021"
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" }
@ -16,6 +17,10 @@ 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

View File

@ -0,0 +1,161 @@
//! 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,4 +1,6 @@
#![no_std]
pub mod time_driver;
pub const EXTCLK_FREQ: u32 = 40_000_000;
pub use time_driver::init;

View File

@ -1,5 +1,6 @@
#![no_std]
#![no_main]
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_hal::digital::StatefulOutputPin;
@ -7,8 +8,6 @@ use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
const EXTCLK_FREQ: u32 = 40_000_000;
// main is itself an async function.
#[embassy_executor::main]
async fn main(_spawner: Spawner) {

View File

@ -17,7 +17,7 @@ use va416xx_hal::{
enable_interrupt,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim},
timer::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim},
};
pub type TimekeeperClk = pac::Tim15;

View File

@ -2,8 +2,13 @@
#![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 _;
@ -13,6 +18,7 @@ mod app {
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
pac,
prelude::*,
};
#[local]
@ -23,14 +29,22 @@ mod app {
#[shared]
struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 10_000);
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(_ctx: init::Context) -> (Shared, Local) {
fn init(mut cx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC template --");
let mut dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
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 })

View File

@ -12,7 +12,7 @@ use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pwm::CountdownTimer;
use va416xx_hal::timer::CountdownTimer;
use va416xx_hal::{
pac::{self, interrupt},
prelude::*,

View File

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

View File

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

View File

@ -6,12 +6,18 @@ 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
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 Python script provided here to upload images because it uses a low-level CCSDS based packet
interface.
## 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
systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
and activate a virtual environment.

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python3
from typing import List, Tuple
from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm
from tmtccmd.com import ComInterface
import toml
import struct
import logging
@ -21,20 +23,27 @@ from elftools.elf.elffile import ELFFile
BAUD_RATE = 115200
BOOTLOADER_START_ADDR = 0x0
BOOTLOADER_END_ADDR = 0x4000
BOOTLOADER_CRC_ADDR = 0x3FFC
BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 4
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4
APP_A_START_ADDR = 0x4000
APP_A_END_ADDR = 0x22000
# The actual size of the image which is relevant for CRC calculation.
APP_A_SIZE_ADDR = 0x21FF8
APP_A_CRC_ADDR = 0x21FFC
APP_A_SIZE_ADDR = APP_A_END_ADDR - 8
APP_A_CRC_ADDR = APP_A_END_ADDR - 4
APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_B_START_ADDR = 0x22000
APP_B_END_ADDR = 0x40000
# The actual size of the image which is relevant for CRC calculation.
APP_B_SIZE_ADDR = 0x3FFF8
APP_B_CRC_ADDR = 0x3FFFC
APP_IMG_SZ = 0x1E000
APP_B_SIZE_ADDR = APP_B_END_ADDR - 8
APP_B_CRC_ADDR = APP_B_END_ADDR - 4
APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_IMG_SZ = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
CHUNK_SIZE = 896
@ -52,6 +61,7 @@ class ActionId(enum.IntEnum):
_LOGGER = logging.getLogger(__name__)
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
@dataclasses.dataclass
@ -62,7 +72,174 @@ class LoadableSegment:
data: bytes
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
class Target(enum.Enum):
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:
@ -102,74 +279,57 @@ def main() -> int:
verificator = PusVerificator()
com_if = SerialCobsComIF(serial_cfg)
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
result = -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(),
app_data=bytes(PING_PAYLOAD_SIZE),
)
verificator.add_tc(ping_tc)
com_if.send(ping_tc.pack())
data_available = com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no ping reply received")
for reply in com_if.receive():
result = verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info("received ping completion reply")
if not args.target:
image_loader.handle_ping_cmd()
com_if.close()
return 0
if args.target:
if target:
if not args.corrupt:
if not args.path:
_LOGGER.error("App Path needs to be specified for the flash process")
return -1
file_path = Path(args.path)
if not file_path.exists():
_LOGGER.error("File does not exist")
return -1
if args.corrupt:
if not args.target:
if not target:
_LOGGER.error("target for corruption command required")
com_if.close()
return -1
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())
image_loader.handle_corruption_cmd(target)
else:
assert file_path is not None
assert target is not None
result = image_loader.handle_flash_cmd(target, file_path)
com_if.close()
return result
def create_loadable_segments(
target: Target, file_path: Path
) -> Tuple[List[LoadableSegment], int]:
loadable_segments = []
_LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0
with open(file_path, "rb") as app_file:
elf_file = ELFFile(app_file)
for (idx, segment) in enumerate(elf_file.iter_segments("PT_LOAD")):
for idx, segment in enumerate(elf_file.iter_segments("PT_LOAD")):
if segment.header.p_filesz == 0:
continue
# Basic validity checks of the base addresses.
if idx == 0:
if (
args.target == "bl"
target == Target.BOOTLOADER
and segment.header.p_paddr != BOOTLOADER_START_ADDR
):
raise ValueError(
@ -177,7 +337,7 @@ def main() -> int:
f"bootloader, expected {BOOTLOADER_START_ADDR}"
)
if (
args.target == "a"
target == Target.APP_A
and segment.header.p_paddr != APP_A_START_ADDR
):
raise ValueError(
@ -185,7 +345,7 @@ def main() -> int:
f"App A, expected {APP_A_START_ADDR}"
)
if (
args.target == "b"
target == Target.APP_B
and segment.header.p_paddr != APP_B_START_ADDR
):
raise ValueError(
@ -214,101 +374,39 @@ def main() -> int:
)
)
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})"
return loadable_segments, total_size
def segments_info_str(
target: Target,
loadable_segments: List[LoadableSegment],
total_size: int,
file_path: Path,
):
# Set context string and perform basic sanity checks.
if target == Target.BOOTLOADER:
if total_size > BOOTLOADER_MAX_SIZE:
_LOGGER.error(
f"provided bootloader app larger than allowed {total_size} bytes"
)
return -1
context_str = "Bootloader"
elif target == Target.APP_A:
if total_size > APP_A_MAX_SIZE:
_LOGGER.error(f"provided App A larger than allowed {total_size} bytes")
return -1
context_str = "App Slot A"
elif target == Target.APP_B:
if total_size > APP_B_MAX_SIZE:
_LOGGER.error(f"provided App B larger than allowed {total_size} bytes")
return -1
context_str = "App Slot B"
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})")
for idx, segment in enumerate(loadable_segments):
_LOGGER.info(
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and size {segment.size}"
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and "
f"size {segment.size}"
)
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)}"
)
verificator.add_tc(next_packet)
com_if.send(next_packet.pack())
current_addr += next_chunk_size
pos_in_segment += next_chunk_size
while True:
data_available = com_if.data_available(0.1)
done = False
if not data_available:
continue
replies = 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 = 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.05)
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:
@ -324,7 +422,7 @@ def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
service=MEMORY_SERVICE,
subservice=RAW_MEMORY_WRITE_SUBSERVICE,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=app_data,
app_data=bytes(app_data),
)

View File

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

View File

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

View File

@ -109,6 +109,7 @@ mod app {
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::{
clock::ClkgenExt,
edac,
@ -132,6 +133,7 @@ mod app {
struct Local {
uart_rx: uart::RxWithIrq<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>,
rx_context: IrqContextTimeoutOrMaxSize,
rom_spi: Option<pac::Spi3>,
// We handle all TM in one task.
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
@ -167,9 +169,9 @@ mod app {
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
setup_edac(&mut cx.device.sysconfig);
let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let tx = gpiob.pg0.into_funsel_1();
let rx = gpiob.pg1.into_funsel_1();
let gpiog = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let tx = gpiog.pg0.into_funsel_1();
let rx = gpiog.pg1.into_funsel_1();
let uart0 = Uart::new(
cx.device.uart0,
@ -178,7 +180,7 @@ mod app {
&mut cx.device.sysconfig,
&clocks,
);
let (tx, mut rx, _) = uart0.split_with_irq();
let (tx, rx) = uart0.split();
let verif_reporter = VerificationReportCreator::new(0).unwrap();
@ -191,7 +193,9 @@ mod app {
Mono::start(cx.core.SYST, clocks.sysclk().raw());
CLOCKS.set(clocks).unwrap();
rx.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
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_tm_tx_handler::spawn().unwrap();
@ -205,6 +209,7 @@ mod app {
Local {
uart_rx: rx,
uart_tx: tx,
rx_context,
rom_spi: Some(cx.device.spi3),
tm_cons: DataConsumer {
buf_cons: buf_cons_tm,
@ -231,20 +236,26 @@ mod app {
}
}
// This is the interrupt handler to read all bytes received on the UART0.
#[task(
binds = UART0_RX,
local = [
cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
rx_context,
uart_rx,
tc_prod
],
)]
fn uart_rx_irq(cx: uart_rx_irq::Context) {
match cx.local.uart_rx.irq_handler(cx.local.rx_buf) {
match cx
.local
.uart_rx
.irq_handler_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
{
Ok(result) => {
if RX_DEBUGGING {
log::debug!("RX Info: {:?}", cx.local.uart_rx.irq_info());
log::debug!("RX Info: {:?}", cx.local.rx_context);
log::debug!("RX Result: {:?}", result);
}
if result.complete() {
@ -279,11 +290,11 @@ mod app {
// Initiate next transfer.
cx.local
.uart_rx
.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
.read_fixed_len_or_timeout_based_using_irq(cx.local.rx_context)
.expect("read operation failed");
}
if result.error() {
log::warn!("UART error: {:?}", result.error());
if result.has_errors() {
log::warn!("UART error: {:?}", result.errors.unwrap());
}
}
Err(e) => {
@ -438,7 +449,12 @@ mod app {
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
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() };
@ -455,7 +471,9 @@ mod app {
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
log::info!("NVM operation done");
log::info!(
target: "TC Handler",
"NVM operation done");
}
}
}

View File

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

View File

@ -8,6 +8,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.3.0] 2024-30-09
## 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

View File

@ -1,6 +1,6 @@
[package]
name = "va416xx-hal"
version = "0.2.0"
version = "0.3.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "HAL for the Vorago VA416xx family of MCUs"

View File

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

View File

@ -9,8 +9,10 @@ use core::convert::Infallible;
use core::marker::PhantomData;
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};
pub use crate::{gpio::PinId, time::Hertz, timer::*};
const DUTY_MAX: u16 = u16::MAX;

View File

@ -1,11 +1,15 @@
//! 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
//!
//! - [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 embedded_hal::spi::Mode;
use embedded_hal::spi::{Mode, MODE_0};
use crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt},
@ -228,100 +232,100 @@ pub trait TransferConfigProvider {
fn sod(&mut self, sod: bool);
fn blockmode(&mut self, blockmode: bool);
fn mode(&mut self, mode: Mode);
fn clk_div(&mut self, clk_div: u16);
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig);
fn hw_cs_id(&self) -> u8;
}
/// This struct contains all configuration parameter which are transfer specific
/// and might change for transfers to different SPI slaves
#[derive(Copy, Clone)]
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
#[derive(Copy, Clone, Debug)]
pub struct TransferConfigWithHwcs<HwCs> {
pub hw_cs: Option<HwCs>,
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,
pub cfg: TransferConfig,
}
/// Type erased variant of the transfer configuration. This is required to avoid generics in
/// the SPI constructor.
pub struct ErasedTransferConfig {
pub clk_div: Option<u16>,
#[derive(Copy, Clone, Debug)]
pub struct TransferConfig {
pub clk_cfg: Option<SpiClkConfig>,
pub mode: Option<Mode>,
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,
/// 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,
}
impl TransferConfig<NoneT> {
impl TransferConfigWithHwcs<NoneT> {
pub fn new_no_hw_cs(
clk_div: Option<u16>,
clk_cfg: Option<SpiClkConfig>,
mode: Option<Mode>,
blockmode: bool,
bmstall: bool,
sod: bool,
) -> Self {
TransferConfig {
clk_div,
mode,
TransferConfigWithHwcs {
hw_cs: None,
cfg: TransferConfig {
clk_cfg,
mode,
sod,
blockmode,
bmstall,
hw_cs: HwChipSelectId::Invalid,
},
}
}
}
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> {
pub fn new(
clk_div: Option<u16>,
clk_cfg: Option<SpiClkConfig>,
mode: Option<Mode>,
hw_cs: Option<HwCs>,
blockmode: bool,
bmstall: bool,
sod: bool,
) -> Self {
TransferConfig {
clk_div,
mode,
TransferConfigWithHwcs {
hw_cs,
cfg: TransferConfig {
clk_cfg,
mode,
sod,
blockmode,
}
}
pub fn downgrade(self) -> ErasedTransferConfig {
ErasedTransferConfig {
clk_div: self.clk_div,
mode: self.mode,
sod: self.sod,
blockmode: self.blockmode,
bmstall,
hw_cs: HwCs::CS_ID,
}
},
}
}
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
pub fn downgrade(self) -> TransferConfig {
self.cfg
}
}
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfigWithHwcs<HwCs> {
/// Slave Output Disable
fn sod(&mut self, sod: bool) {
self.sod = sod;
self.cfg.sod = sod;
}
fn blockmode(&mut self, blockmode: bool) {
self.blockmode = blockmode;
self.cfg.blockmode = blockmode;
}
fn mode(&mut self, mode: Mode) {
self.mode = Some(mode);
self.cfg.mode = Some(mode);
}
fn clk_div(&mut self, clk_div: u16) {
self.clk_div = Some(clk_div);
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) {
self.cfg.clk_cfg = Some(clk_cfg);
}
fn hw_cs_id(&self) -> u8 {
@ -331,7 +335,16 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
pub struct SpiConfig {
clk_div: u16,
clk: SpiClkConfig,
// 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)
ms: bool,
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
@ -345,7 +358,11 @@ pub struct SpiConfig {
impl Default for SpiConfig {
fn default() -> Self {
Self {
clk_div: DEFAULT_CLK_DIV,
init_mode: MODE_0,
blockmode: true,
bmstall: true,
// Default value is definitely valid.
clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
ms: Default::default(),
slave_output_disable: Default::default(),
loopback_mode: Default::default(),
@ -360,8 +377,23 @@ impl SpiConfig {
self
}
pub fn clk_div(mut self, clk_div: u16) -> Self {
self.clk_div = clk_div;
pub fn blockmode(mut self, enable: bool) -> Self {
self.blockmode = enable;
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
}
@ -455,6 +487,36 @@ impl Instance for pac::Spi3 {
// 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> {
spi: SpiInstance,
cfg: SpiConfig,
@ -462,6 +524,7 @@ pub struct SpiBase<SpiInstance, Word = u8> {
/// Fill word for read-only SPI transactions.
pub fill_word: Word,
blockmode: bool,
bmstall: bool,
word: PhantomData<Word>,
}
@ -479,7 +542,8 @@ pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
}
}
#[derive(Debug)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpiClkConfig {
prescale_val: u16,
scrdv: u8,
@ -494,6 +558,23 @@ 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)]
pub enum SpiClkConfigError {
DivIsZero,
@ -566,27 +647,21 @@ where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
#[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
let val = spi_clk_config_from_div(div)?;
self.spi_instance()
pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
self.spi
.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(())
}
/*
#[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));
.modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) });
self.spi
.clkprescale()
.write(|w| unsafe { w.bits(clk_prescale) });
.write(|w| unsafe { w.bits(cfg.prescale_val as u32) });
}
#[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
let val = spi_clk_config_from_div(div)?;
self.cfg_clock(val);
Ok(())
}
*/
#[inline]
pub fn cfg_mode(&mut self, mode: Mode) {
@ -598,7 +673,7 @@ where
}
#[inline]
pub fn spi_instance(&self) -> &SpiInstance {
pub fn spi(&self) -> &SpiInstance {
&self.spi
}
@ -646,17 +721,17 @@ where
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
&mut self,
transfer_cfg: &TransferConfig<HwCs>,
) -> Result<(), SpiClkConfigError> {
if let Some(trans_clk_div) = transfer_cfg.clk_div {
self.cfg_clock_from_div(trans_clk_div)?;
transfer_cfg: &TransferConfigWithHwcs<HwCs>,
) {
if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg {
self.cfg_clock(trans_clk_div);
}
if let Some(mode) = transfer_cfg.mode {
if let Some(mode) = transfer_cfg.cfg.mode {
self.cfg_mode(mode);
}
self.blockmode = transfer_cfg.blockmode;
self.blockmode = transfer_cfg.cfg.blockmode;
self.spi.ctrl1().modify(|_, w| {
if transfer_cfg.sod {
if transfer_cfg.cfg.sod {
w.sod().set_bit();
} else if transfer_cfg.hw_cs.is_some() {
w.sod().clear_bit();
@ -666,72 +741,93 @@ where
} else {
w.sod().clear_bit();
}
if transfer_cfg.blockmode {
w.blockmode().set_bit();
} else {
w.blockmode().clear_bit();
}
w
w.blockmode().bit(transfer_cfg.cfg.blockmode);
w.bmstall().bit(transfer_cfg.cfg.bmstall)
});
}
/// 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(())
}
/// Sends a word to the slave
/// 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.
#[inline(always)]
fn send_blocking(&self, word: Word) {
// TODO: Upper limit for wait cycles to avoid complete hangups?
while self.spi.status().read().tnf().bit_is_clear() {}
self.send(word)
pub fn write_fifo_unchecked(&self, data: u32) {
self.spi.data().write(|w| unsafe { w.bits(data) });
}
/// 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)]
fn send(&self, word: Word) {
self.spi.data().write(|w| unsafe { w.bits(word.into()) });
pub 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())
}
/// Read a word from the slave. Must be preceeded by a [`send`](Self::send) call
/// 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.
#[inline(always)]
fn read_blocking(&self) -> Word {
// TODO: Upper limit for wait cycles to avoid complete hangups?
while self.spi.status().read().rne().bit_is_clear() {}
self.read_single_word()
pub fn read_fifo_unchecked(&self) -> u32 {
self.spi.data().read().bits()
}
#[inline(always)]
fn read_single_word(&self) -> Word {
(self.spi.data().read().bits() & Word::MASK)
.try_into()
.unwrap()
fn flush_internal(&self) {
let mut status_reg = self.spi.status().read();
while status_reg.tfe().bit_is_clear()
|| status_reg.rne().bit_is_set()
|| status_reg.busy().bit_is_set()
{
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> {
if words.is_empty() {
return Ok(());
}
let mut status_reg = self.spi.status().read();
// Wait until all bytes have been transferred.
while status_reg.tfe().bit_is_clear() {
// Ignore all received read words.
if status_reg.rne().bit_is_set() {
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();
}
self.flush_internal();
Ok(())
}
fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize {
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
// 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;
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]));
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 {
@ -739,6 +835,171 @@ where
}
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();
}
}
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_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;
}
}
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_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(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.flush_internal();
Ok(())
}
}
impl<
@ -772,55 +1033,44 @@ where
spi: SpiI,
pins: (Sck, Miso, Mosi),
spi_cfg: SpiConfig,
transfer_cfg: Option<&ErasedTransferConfig>,
) -> Result<Self, SpiClkConfigError> {
) -> Self {
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
let SpiConfig {
clk_div,
clk,
init_mode,
blockmode,
bmstall,
ms,
slave_output_disable,
loopback_mode,
master_delayer_capture,
} = 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);
spi.ctrl0().write(|w| {
unsafe {
w.size().bits(Word::word_reg());
w.scrdv().bits(spi_clk_cfg.scrdv);
w.scrdv().bits(clk.scrdv);
// Clear clock phase and polarity. Will be set to correct value for each
// transfer
w.spo().bit(cpo_bit);
w.sph().bit(cph_bit)
}
});
spi.ctrl1().write(|w| {
w.lbm().bit(loopback_mode);
w.sod().bit(slave_output_disable);
w.ms().bit(ms);
w.mdlycap().bit(master_delayer_capture);
w.blockmode().bit(init_blockmode);
unsafe { w.ss().bits(ss) }
w.blockmode().bit(blockmode);
w.bmstall().bit(bmstall);
unsafe { w.ss().bits(0) }
});
spi.clkprescale()
.write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val as u32) });
.write(|w| unsafe { w.bits(clk.prescale_val as u32) });
spi.fifo_clr().write(|w| {
w.rxfifo().set_bit();
@ -829,26 +1079,30 @@ where
// Enable the peripheral as the last step as recommended in the
// programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit());
Ok(Spi {
Spi {
inner: SpiBase {
spi,
cfg: spi_cfg,
apb1_clk,
apb1_clk: clocks.apb1(),
fill_word: Default::default(),
blockmode: init_blockmode,
bmstall,
blockmode,
word: PhantomData,
},
pins,
})
}
}
delegate::delegate! {
to self.inner {
#[inline]
pub fn cfg_clock(&mut self, cfg: SpiClkConfig);
#[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
#[inline]
pub fn spi_instance(&self) -> &SpiI;
pub fn spi(&self) -> &SpiI;
#[inline]
pub fn cfg_mode(&mut self, mode: Mode);
@ -857,8 +1111,8 @@ where
pub fn perid(&self) -> u32;
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
&mut self, transfer_cfg: &TransferConfig<HwCs>
) -> Result<(), SpiClkConfigError>;
&mut self, transfer_cfg: &TransferConfigWithHwcs<HwCs>
);
}
}
@ -882,140 +1136,23 @@ where
}
}
/// Changing the word size also requires a type conversion
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>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> Self {
old_spi
.inner
.spi
.ctrl0()
.modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) });
Spi {
inner: SpiBase {
spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode,
fill_word: Default::default(),
apb1_clk: old_spi.inner.apb1_clk,
word: PhantomData,
},
pins: old_spi.pins,
}
}
}
/// Changing the word size also requires a type conversion
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> Self {
old_spi
.inner
.spi
.ctrl0()
.modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) });
Spi {
inner: SpiBase {
spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode,
apb1_clk: old_spi.inner.apb1_clk,
fill_word: Default::default(),
word: PhantomData,
},
pins: old_spi.pins,
}
}
}
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>
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,
{
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;
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;
}
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(())
}
}
@ -1040,23 +1177,63 @@ impl<
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.inner.read(words)
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>;
}
}
}
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
self.inner.write(words)
/// Changing the word size also requires a type conversion
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>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> Self {
old_spi
.inner
.spi
.ctrl0()
.modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) });
Spi {
inner: SpiBase {
spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode,
bmstall: old_spi.inner.bmstall,
fill_word: Default::default(),
apb1_clk: old_spi.inner.apb1_clk,
word: PhantomData,
},
pins: old_spi.pins,
}
}
}
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()
/// Changing the word size also requires a type conversion
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> Self {
old_spi
.inner
.spi
.ctrl0()
.modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) });
Spi {
inner: SpiBase {
spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode,
bmstall: old_spi.inner.bmstall,
apb1_clk: old_spi.inner.apb1_clk,
fill_word: Default::default(),
word: PhantomData,
},
pins: old_spi.pins,
}
}
}

File diff suppressed because it is too large Load Diff

13
vorago-peb1/CHANGELOG.md Normal file
View File

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

View File

@ -1,15 +1,12 @@
[![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
## Using the `.cargo/config.toml` file
This is the Rust **B**oard **S**upport **P**ackage crate for the Vorago PEB1 development board.
Its aim is to provide drivers for the board features of the PEB1 board.
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`.
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).
## Notes on board revisions

View File

@ -5,6 +5,10 @@
pub use lis2dh12;
/// 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 {
use lis2dh12::{self, detect_i2c_addr, AddrDetectionError, Lis2dh12};

View File

@ -350,6 +350,36 @@
]
}
},
{
"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",

View File

@ -95,6 +95,19 @@
"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",