6 Commits

Author SHA1 Message Date
6cd2e809d7 DMA experimentation
Some checks failed
Rust/va416xx-rs/pipeline/head There was a failure building this commit
2024-07-03 11:21:48 +02:00
16d2856fb2 static DMA ctrl block placement
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-03 00:01:33 +02:00
01341edc91 some more improvements 2024-07-02 23:32:41 +02:00
d3deb8a467 DMA example working 2024-07-02 23:28:07 +02:00
988d6adcdc come on dma, do something..
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-02 20:09:21 +02:00
c78c90b60d start adding DMA support
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-02 16:03:54 +02:00
50 changed files with 301 additions and 2810 deletions

View File

@ -35,8 +35,6 @@ target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
[alias] [alias]
rb = "run --bin" rb = "run --bin"
rrb = "run --release --bin" rrb = "run --release --bin"
ut = "test --target=x86_64-unknown-linux-gnu"
genbin = "objcopy --release -- -O binary app.bin"
[env] [env]
DEFMT_LOG = "info" DEFMT_LOG = "info"

View File

@ -21,7 +21,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- name: Install nextest - name: Install nextest
uses: taiki-e/install-action@nextest uses: taiki-e/install-action@nextest
- run: cargo nextest run --features va41630 -p va416xx-hal - run: cargo nextest run --all-features -p va416xx-hal
# I think we can skip those on an embedded crate.. # I think we can skip those on an embedded crate..
# - run: cargo test --doc -p va108xx-hal # - run: cargo test --doc -p va108xx-hal
@ -39,7 +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 --features va41630 - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
clippy: clippy:
name: Clippy name: Clippy

1
.gitignore vendored
View File

@ -14,4 +14,3 @@ Cargo.lock
**/*.rs.bk **/*.rs.bk
/app.map /app.map
/app.bin

View File

@ -1,17 +1,11 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"bootloader",
"flashloader",
"examples/simple", "examples/simple",
"va416xx", "va416xx",
"va416xx-hal", "va416xx-hal",
"vorago-peb1" "vorago-peb1"
] ]
exclude = [
"flashloader/slot-a-blinky",
"flashloader/slot-b-blinky",
]
[profile.dev] [profile.dev]
codegen-units = 1 codegen-units = 1
@ -31,12 +25,3 @@ incremental = false
lto = 'fat' lto = 'fat'
opt-level = 3 # <- opt-level = 3 # <-
overflow-checks = false # <- overflow-checks = false # <-
[profile.small]
inherits = "release"
codegen-units = 1
debug-assertions = false # <-
lto = true
opt-level = 'z' # <-
overflow-checks = false # <-
# strip = true # Automatically strip symbols from the binary.

View File

@ -3,7 +3,7 @@
Vorago VA416xx Rust Support Vorago VA416xx Rust Support
========= =========
This crate collection provides support to write Rust applications for the VA416XX family This crate collection provided support to write Rust applications for the VA416XX family
of devices. of devices.
## List of crates ## List of crates
@ -19,12 +19,7 @@ This workspace contains the following crates:
It also contains the following helper crates: It also contains the following helper crates:
- The [`bootloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader) - The `examples` crates contains various example applications for the HAL and the PAC.
crate contains a sample bootloader strongly based on the one provided by Vorago.
- The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
crate contains a sample flashloader which is able to update the redundant images in the NVM which
is compatible to the provided bootloader as well.
- The `examples` folder contains various example applications crates for the HAL and the PAC.
## Using the `.cargo/config.toml` file ## Using the `.cargo/config.toml` file

View File

@ -36,9 +36,7 @@ pipeline {
} }
stage('Check Examples') { stage('Check Examples') {
steps { steps {
sh """ sh 'cargo check --target thumbv7em-none-eabihf --examples'
cargo check --target thumbv7em-none-eabihf --examples
"""
} }
} }
} }

View File

@ -1,15 +0,0 @@
[package]
name = "bootloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" }
crc = "3"
[dependencies.va416xx-hal]
path = "../va416xx-hal"

View File

@ -1,47 +0,0 @@
VA416xx Bootloader Application
=======
This is the Rust version of the bootloader supplied by Vorago.
## Memory Map
The bootloader uses the following memory map:
| Address | Notes | Size |
| ------ | ---- | ---- |
| 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 |
| 0x40000 | End of NVM | end |
## Additional Information
As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
but does not scale that clock up. It also uses a word (4 bytes) instead of a half-word for the CRC
and uses the ISO 3309 CRC32 standard checksum.
This bootloader does not provide tools to flash the NVM memories by itself. Instead, you can use
the [flashloader](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
application to perform this task using a CCSDS interface via a UART.
The bootloader performs the following steps:
1. The application will calculate the checksum of itself if the bootloader CRC is blank (all zeroes
or all ones). If the CRC is not blank and the checksum check fails, it will immediately boot
application image A. Otherwise, it proceeds to the next step.
2. Check the checksum of App A. If that checksum is valid, it will boot App A. If not, it will
proceed to the next step.
3. Check the checksum of App B. If that checksum is valid, it will boot App B. If not, it will
boot App A as the fallback image.
You could adapt and combine this bootloader with a non-volatile memory to select a prefered app
image, which would be a first step towards an updatable flight software.
Please note that you *MUST* compile the application at slot A and slot B with an appropriate
`memory.x` file where the base address of the `FLASH` was adapted according to the base address
shown in the memory map above. The memory files to do this were provided in the `scripts` folder.

View File

@ -1,339 +0,0 @@
//! 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]
#![no_std]
use cortex_m_rt::entry;
use crc::{Crc, CRC_32_ISO_HDLC};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
clock::{pll_setup_delay, ClkDivSel, ClkselSys},
edac,
nvm::Nvm,
pac::{self, interrupt},
prelude::*,
time::Hertz,
wdt::Wdt,
};
const EXTCLK_FREQ: u32 = 40_000_000;
const WITH_WDT: bool = false;
const WDT_FREQ_MS: u32 = 50;
const DEBUG_PRINTOUTS: bool = true;
// Dangerous option! An image with this option set to true will flash itself from RAM directly
// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM
// boot process. Please note that this will flash an image which will also always perform the
// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash
// loader to boot a bootloader without this feature.
const FLASH_SELF: bool = false;
// Important bootloader addresses and offsets, vector table information.
const BOOTLOADER_START_ADDR: u32 = 0x0;
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;
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
pub const VECTOR_TABLE_LEN: u32 = 0x350;
pub const RESET_VECTOR_OFFSET: u32 = 0x4;
const CRC_ALGO: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum AppSel {
A,
B,
}
pub trait WdtInterface {
fn feed(&self);
}
pub struct OptWdt(Option<Wdt>);
impl WdtInterface for OptWdt {
fn feed(&self) {
if self.0.is_some() {
self.0.as_ref().unwrap().feed();
}
}
}
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA416xx bootloader --");
let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// Disable ROM protection.
dp.sysconfig.rom_prot().write(|w| unsafe { w.bits(1) });
setup_edac(&mut dp.sysconfig);
// 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();
let mut opt_wdt = OptWdt(None);
if WITH_WDT {
opt_wdt.0 = Some(Wdt::start(
&mut dp.sysconfig,
dp.watch_dog,
&clocks,
WDT_FREQ_MS,
));
}
let nvm = Nvm::new(&mut dp.sysconfig, dp.spi3, &clocks);
if FLASH_SELF {
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let bootloader_data = {
unsafe {
&*core::ptr::slice_from_raw_parts(
(BOOTLOADER_START_ADDR + 4) as *const u8,
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
)
}
};
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(bootloader_data);
let bootloader_crc = digest.finalize();
nvm.write_data(0x0, &first_four_bytes);
nvm.write_data(0x4, bootloader_data);
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
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()) {
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);
if check_app_crc(AppSel::A, &opt_wdt) {
boot_app(AppSel::A, &cp)
} else if check_app_crc(AppSel::B, &opt_wdt) {
boot_app(AppSel::B, &cp)
} else {
if DEBUG_PRINTOUTS {
rprintln!("both images corrupt! booting image A");
}
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
// Both images seem to be corrupt. Boot default image A.
boot_app(AppSel::A, &cp)
}
}
fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u32).read_unaligned().to_be() };
wdt.feed();
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
// because the address of the bootloader is 0x0, so the NULL check fails and the functions
// panics.
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(unsafe {
&*core::ptr::slice_from_raw_parts(
(BOOTLOADER_START_ADDR + 4) as *const u8,
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
)
});
let crc_calc = digest.finalize();
wdt.feed();
if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS {
rprintln!("BL CRC blank - prog new CRC");
}
// Blank CRC, write it to NVM.
nvm.write_data(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes());
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
// necessary because somehow the boot will not work if we just continue as usual.
// cortex_m::peripheral::SCB::sys_reset();
} else if crc_exp != crc_calc {
// Bootloader is corrupted. Try to run App A.
if DEBUG_PRINTOUTS {
rprintln!(
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
crc_calc,
crc_exp
);
}
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
boot_app(AppSel::A, cp);
}
}
fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
unsafe {
core::arch::asm!(
"ldr r0, [{0}]", // Load 4 bytes from src into r0 register
"str r0, [{1}]", // Store r0 register into first_four_bytes
in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0)
in(reg) buf as *mut [u8; 4], // Input: destination pointer
);
}
}
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
if DEBUG_PRINTOUTS {
rprintln!("Checking image {:?}", app_sel);
}
if app_sel == AppSel::A {
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR, wdt)
} else {
check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR, wdt)
}
}
fn check_app_given_addr(
crc_addr: u32,
start_addr: u32,
image_size_addr: u32,
wdt: &OptWdt,
) -> bool {
let crc_exp = unsafe { (crc_addr as *const u32).read_unaligned().to_be() };
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
// Sanity check.
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
rprintln!("detected invalid app size {}", image_size);
return false;
}
wdt.feed();
let crc_calc = CRC_ALGO.checksum(unsafe {
core::slice::from_raw_parts(start_addr as *const u8, image_size as usize)
});
wdt.feed();
if crc_calc == crc_exp {
return true;
}
false
}
fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! {
if DEBUG_PRINTOUTS {
rprintln!("booting app {:?}", app_sel);
}
let clkgen = unsafe { pac::Clkgen::steal() };
clkgen
.ctrl0()
.modify(|_, w| unsafe { w.clksel_sys().bits(ClkselSys::Hbo as u8) });
pll_setup_delay();
clkgen
.ctrl0()
.modify(|_, w| unsafe { w.clk_div_sel().bits(ClkDivSel::Div1 as u8) });
// Clear all interrupts set.
unsafe {
cp.NVIC.icer[0].write(0xFFFFFFFF);
cp.NVIC.icpr[0].write(0xFFFFFFFF);
}
cortex_m::asm::dsb();
cortex_m::asm::isb();
unsafe {
if app_sel == AppSel::A {
cp.SCB.vtor.write(APP_A_START_ADDR);
} else {
cp.SCB.vtor.write(APP_B_START_ADDR);
}
}
cortex_m::asm::dsb();
cortex_m::asm::isb();
vector_reset();
}
pub fn vector_reset() -> ! {
unsafe {
// Set R0 to VTOR address (0xE000ED08)
let vtor_address: u32 = 0xE000ED08;
// Load VTOR
let vtor: u32 = *(vtor_address as *const u32);
// Load initial MSP value
let initial_msp: u32 = *(vtor as *const u32);
// Set SP value (assume MSP is selected)
core::arch::asm!("mov sp, {0}", in(reg) initial_msp);
// Load reset vector
let reset_vector: u32 = *((vtor + 4) as *const u32);
// Branch to reset handler
core::arch::asm!("bx {0}", in(reg) reset_vector);
}
unreachable!();
}
fn setup_edac(syscfg: &mut pac::Sysconfig) {
// The scrub values are based on the Vorago provided bootloader.
edac::enable_rom_scrub(syscfg, 125);
edac::enable_ram0_scrub(syscfg, 1000);
edac::enable_ram1_scrub(syscfg, 1000);
edac::enable_sbe_irq();
edac::enable_mbe_irq();
}
#[interrupt]
#[allow(non_snake_case)]
fn WATCHDOG() {
let wdt = unsafe { pac::WatchDog::steal() };
// Clear interrupt.
wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
}
#[interrupt]
#[allow(non_snake_case)]
fn EDAC_SBE() {
// TODO: Send some command via UART for notification purposes. Also identify the problematic
// memory.
edac::clear_sbe_irq();
}
#[interrupt]
#[allow(non_snake_case)]
fn EDAC_MBE() {
// TODO: Send some command via UART for notification purposes.
edac::clear_mbe_irq();
// TODO: Reset like the vorago example?
}

View File

@ -5,39 +5,14 @@ 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"] }
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"
embedded-io = "0.6" embedded-io = "0.6"
panic-halt = "0.2" panic-halt = "0.2"
vorago-peb1 = { path = "../../vorago-peb1" }
accelerometer = "0.12" accelerometer = "0.12"
[dependencies.va416xx-hal]
path = "../../va416xx-hal"
[dependencies.vorago-peb1]
path = "../../vorago-peb1"
optional = true
[features]
default = []
va41630 = ["va416xx-hal/va41630", "has-adc-dac"]
va41629 = ["va416xx-hal/va41629", "has-adc-dac"]
va41628 = ["va416xx-hal/va41628"]
has-adc-dac = []
[[example]]
name = "peb1-accelerometer"
required-features = ["vorago-peb1"]
[[example]]
name = "dac-adc"
required-features = ["has-adc-dac"]
[[example]]
name = "adc"
required-features = ["has-adc-dac"]

View File

@ -36,12 +36,9 @@ fn main() -> ! {
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8]; let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
loop { loop {
let single_value = adc let single_value = adc
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::TempSensor) .trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
.expect("reading single channel value failed"); .expect("reading single channel value failed");
rprintln!( rprintln!("Read single ADC value on channel 0: {:?}", single_value);
"Read single ADC value on temperature sensor channel: {:?}",
single_value
);
let read_num = adc let read_num = adc
.sweep_and_read_range(0, 7, &mut read_buf) .sweep_and_read_range(0, 7, &mut read_buf)
.expect("ADC range read failed"); .expect("ADC range read failed");

View File

@ -25,13 +25,6 @@ static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
#[link_section = ".sram1"] #[link_section = ".sram1"]
static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new(); static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new();
// We can use statically allocated buffers for DMA transfers as well, and we can also place
// those into SRAM1.
#[link_section = ".sram1"]
static mut DMA_SRC_BUF: [u16; 36] = [0; 36];
#[link_section = ".sram1"]
static mut DMA_DEST_BUF: [u16; 36] = [0; 36];
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
rtt_init_print!(); rtt_init_print!();
@ -55,10 +48,11 @@ fn main() -> ! {
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks); let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut src_buf_8_bit: [u8; 65] = [0; 65]; let mut src_buf_8_bit: [u8; 65] = [0; 65];
let mut dest_buf_8_bit: [u8; 65] = [0; 65]; let mut dest_buf_8_bit: [u8; 65] = [0; 65];
let mut src_buf_16_bit: [u16; 33] = [0; 33];
let mut dest_buf_16_bit: [u16; 33] = [0; 33];
let mut src_buf_32_bit: [u32; 17] = [0; 17]; let mut src_buf_32_bit: [u32; 17] = [0; 17];
let mut dest_buf_32_bit: [u32; 17] = [0; 17]; let mut dest_buf_32_bit: [u32; 17] = [0; 17];
loop { loop {
// This example uses stack-allocated buffers.
transfer_example_8_bit( transfer_example_8_bit(
&mut src_buf_8_bit, &mut src_buf_8_bit,
&mut dest_buf_8_bit, &mut dest_buf_8_bit,
@ -66,8 +60,12 @@ fn main() -> ! {
&mut delay_ms, &mut delay_ms,
); );
delay_ms.delay_ms(500); delay_ms.delay_ms(500);
// This example uses statically allocated buffers. transfer_example_16_bit(
transfer_example_16_bit(&mut dma0, &mut delay_ms); &mut src_buf_16_bit,
&mut dest_buf_16_bit,
&mut dma0,
&mut delay_ms,
);
delay_ms.delay_ms(500); delay_ms.delay_ms(500);
transfer_example_32_bit( transfer_example_32_bit(
&mut src_buf_32_bit, &mut src_buf_32_bit,
@ -94,11 +92,9 @@ fn transfer_example_8_bit(
cortex_m::interrupt::free(|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. let dma_transfer = dma0
unsafe { .prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
dma0.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf) .expect("error preparing transfer");
.expect("error preparing transfer");
}
// Enable all interrupts. // Enable all interrupts.
// Safety: Not using mask based critical sections. // Safety: Not using mask based critical sections.
unsafe { unsafe {
@ -109,6 +105,7 @@ fn transfer_example_8_bit(
dma0.enable(); dma0.enable();
// We still need to manually trigger the DMA request. // We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request(); dma0.trigger_with_sw_request();
let dest_buf;
// Use polling for completion status. // Use polling for completion status.
loop { loop {
let mut dma_done = false; let mut dma_done = false;
@ -123,6 +120,8 @@ fn transfer_example_8_bit(
}); });
if dma_done { if dma_done {
rprintln!("8-bit transfer done"); rprintln!("8-bit transfer done");
// Safety: We checked for transfer completion.
dest_buf = unsafe { dma_transfer.release() };
break; break;
} }
delay_ms.delay_ms(1); delay_ms.delay_ms(1);
@ -135,28 +134,26 @@ fn transfer_example_8_bit(
dest_buf.fill(0); dest_buf.fill(0);
} }
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<pac::Tim0>) { fn transfer_example_16_bit(
let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..33]) }; src_buf: &mut [u16; 33],
unsafe { dest_buf: &mut [u16; 33],
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer. dma0: &mut DmaChannel,
(0..32).for_each(|i| { delay_ms: &mut CountdownTimer<pac::Tim0>,
DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16; ) {
}); // Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
} (0..32).for_each(|i| {
src_buf[i] = (i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16;
});
cortex_m::interrupt::free(|cs| { cortex_m::interrupt::free(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false); DMA_DONE_FLAG.borrow(cs).set(false);
}); });
cortex_m::interrupt::free(|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. let dma_transfer = dma0
unsafe { .prepare_mem_to_mem_transfer_16_bit(src_buf, dest_buf)
dma0.prepare_mem_to_mem_transfer_16_bit(
&*core::ptr::addr_of!(DMA_SRC_BUF[0..32]),
&mut dest_buf_ref[0..32],
)
.expect("error preparing transfer"); .expect("error preparing transfer");
} dest_buf[5] = 2;
// Enable all interrupts. // Enable all interrupts.
// Safety: Not using mask based critical sections. // Safety: Not using mask based critical sections.
unsafe { unsafe {
@ -187,13 +184,13 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<
} }
(0..32).for_each(|i| { (0..32).for_each(|i| {
assert_eq!( assert_eq!(
dest_buf_ref[i], dest_buf[i],
(i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16 (i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16
); );
}); });
// Sentinel value, should be 0. // Sentinel value, should be 0.
assert_eq!(dest_buf_ref[32], 0); assert_eq!(dest_buf[32], 0);
dest_buf_ref.fill(0); dest_buf.fill(0);
} }
fn transfer_example_32_bit( fn transfer_example_32_bit(
@ -212,11 +209,8 @@ fn transfer_example_32_bit(
cortex_m::interrupt::free(|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. dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
unsafe { .expect("error preparing transfer");
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
.expect("error preparing transfer");
}
// Enable all interrupts. // Enable all interrupts.
// Safety: Not using mask based critical sections. // Safety: Not using mask based critical sections.
unsafe { unsafe {

View File

@ -1,30 +0,0 @@
//! Empty RTIC project template
#![no_main]
#![no_std]
#[rtic::app(device = pac)]
mod app {
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_default};
use va416xx_hal::pac;
#[local]
struct Local {}
#[shared]
struct Shared {}
#[init]
fn init(_ctx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC template --");
(Shared {}, Local {})
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
#[allow(clippy::empty_loop)]
loop {}
}
}

View File

@ -9,13 +9,12 @@ 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::{clk_div_for_target_clock, Spi, TransferConfig}; use va416xx_hal::spi::{Spi, TransferConfig};
use va416xx_hal::{ use va416xx_hal::{
gpio::{PinsB, PinsC}, gpio::{PinsB, PinsC},
pac, pac,
prelude::*, prelude::*,
spi::SpiConfig, spi::SpiConfig,
time::Hertz,
}; };
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -57,24 +56,21 @@ fn main() -> ! {
pins_c.pc1.into_funsel_1(), pins_c.pc1.into_funsel_1(),
); );
let mut spi_cfg = SpiConfig::default().clk_div( let mut spi_cfg = SpiConfig::default();
clk_div_for_target_clock(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
.expect("invalid target clock"),
);
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); let transfer_cfg =
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false);
// Create SPI peripheral. // Create SPI peripheral.
let mut spi0 = Spi::new( let mut spi0 = Spi::new(
&mut dp.sysconfig,
&clocks,
dp.spi0, dp.spi0,
(sck, miso, mosi), (sck, miso, mosi),
&clocks,
spi_cfg, spi_cfg,
&mut dp.sysconfig,
Some(&transfer_cfg.downgrade()), Some(&transfer_cfg.downgrade()),
) );
.expect("creating SPI peripheral failed");
spi0.set_fill_word(FILL_WORD); spi0.set_fill_word(FILL_WORD);
loop { loop {
let mut tx_buf: [u8; 3] = [1, 2, 3]; let mut tx_buf: [u8; 3] = [1, 2, 3];

View File

@ -10,7 +10,7 @@ use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
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::WdtController;
static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0)); static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
@ -43,7 +43,8 @@ fn main() -> ! {
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;
let mut wdt_ctrl = Wdt::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS); let mut wdt_ctrl =
WdtController::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
wdt_ctrl.enable_reset(); wdt_ctrl.enable_reset();
loop { loop {
if TEST_MODE != TestMode::AllowReset { if TEST_MODE != TestMode::AllowReset {

View File

@ -1 +0,0 @@
/venv

View File

@ -1,51 +0,0 @@
[package]
name = "flashloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" }
rtt-log = "0.3"
log = "0.4"
crc = "3"
rtic-sync = "1"
[dependencies.satrs]
version = "0.2"
default-features = false
[dependencies.ringbuf]
version = "0.4"
default-features = false
[dependencies.once_cell]
version = "1"
default-features = false
features = ["critical-section"]
[dependencies.spacepackets]
version = "0.11"
default-features = false
[dependencies.cobs]
git = "https://github.com/robamu/cobs.rs.git"
branch = "all_features"
default-features = false
[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,60 +0,0 @@
VA416xx Flashloader Application
========
This flashloader shows a minimal example for a self-updatable Rust software which exposes
a simple PUS (CCSDS) interface to update the software. It also provides a Python application
called the `image-loader.py` which can be used to upload compiled images to the flashloader
application to write them to the NVM.
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
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.
After that, you can use
```sh
pip install -r requirements.txt
```
to install all required dependencies.
After that, it is recommended to use `./image-load.py -h` to get an overview of some options.
The flash loader uses the UART0 interface of the VA416xx board to perform CCSDS based
communication. The Python image loader application will search for a file named `loader.toml` and
use the `serial_port` key to determine the serial port to use for serial communication.
### Examples
You can use
```sh
./image-loader.py -p
```
to send a ping an verify the connection.
You can use
```sh
cd flashloader/slot-a-blinky
cargo build --release
cd ../..
./image-loader.py -t a ./slot-a-blinky/target/thumbv7em-none-eabihf/release/slot-a-blinky
```
to build the slot A sample application and upload it to a running flash loader application
to write it to slot A.
You can use
```sh
./image-loader.py -c -t a
```
to corrupt the image A and test that it switches to image B after a failed CRC check instead.

View File

@ -1,319 +0,0 @@
#!/usr/bin/env python3
from spacepackets.ecss import RequestId
from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm
import toml
import struct
import logging
import argparse
import threading
import time
import enum
from tmtccmd.com.serial_base import SerialCfg
from tmtccmd.com.serial_cobs import SerialCobsComIF
from tmtccmd.com.ser_utils import prompt_com_port
from crcmod.predefined import PredefinedCrc
from spacepackets.ecss.tc import PusTc
from spacepackets.ecss.pus_verificator import PusVerificator, StatusField
from spacepackets.ecss.pus_1_verification import Service1Tm, UnpackParams
from spacepackets.seqcount import SeqCountProvider
from pathlib import Path
import dataclasses
from elftools.elf.elffile import ELFFile
BAUD_RATE = 115200
BOOTLOADER_START_ADDR = 0x0
BOOTLOADER_END_ADDR = 0x4000
BOOTLOADER_CRC_ADDR = 0x3FFC
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_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
CHUNK_SIZE = 896
MEMORY_SERVICE = 6
ACTION_SERVICE = 8
RAW_MEMORY_WRITE_SUBSERVICE = 2
BOOT_NVM_MEMORY_ID = 1
class ActionId(enum.IntEnum):
CORRUPT_APP_A = 128
CORRUPT_APP_B = 129
_LOGGER = logging.getLogger(__name__)
@dataclasses.dataclass
class LoadableSegment:
name: str
offset: int
size: int
data: bytes
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
def main() -> int:
print("Python VA416XX Image Loader Application")
logging.basicConfig(
format="[%(asctime)s] [%(levelname)s] %(message)s", level=logging.DEBUG
)
parser = argparse.ArgumentParser(
prog="image-loader", description="Python VA416XX Image Loader Application"
)
parser.add_argument("-p", "--ping", action="store_true", help="Send ping command")
parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target")
parser.add_argument(
"-t",
"--target",
choices=["bl", "a", "b"],
help="Target (Bootloader or slot A or B)",
)
parser.add_argument(
"path", nargs="?", default=None, help="Path to the App to flash"
)
args = parser.parse_args()
serial_port = None
if Path("loader.toml").exists():
with open("loader.toml", "r") as toml_file:
parsed_toml = toml.loads(toml_file.read())
if "serial_port" in parsed_toml:
serial_port = parsed_toml["serial_port"]
if serial_port is None:
serial_port = prompt_com_port()
serial_cfg = SerialCfg(
com_if_id="ser_cobs",
serial_port=serial_port,
baud_rate=BAUD_RATE,
serial_timeout=0.1,
)
verificator = PusVerificator()
com_if = SerialCobsComIF(serial_cfg)
com_if.open()
file_path = None
if args.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.ping:
_LOGGER.info("Sending ping command")
ping_tc = PusTc(
apid=0x00,
service=PusService.S17_TEST,
subservice=1,
seq_count=SEQ_PROVIDER.get_and_increment(),
)
com_if.send(ping_tc.pack())
if args.corrupt:
if not args.target:
_LOGGER.error("target for corruption command required")
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())
else:
assert file_path is not None
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")):
if segment.header.p_filesz == 0:
continue
# Basic validity checks of the base addresses.
if idx == 0:
if (
args.target == "bl"
and segment.header.p_paddr != BOOTLOADER_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"bootloader, expected {BOOTLOADER_START_ADDR}"
)
if (
args.target == "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 (
args.target == "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})"
)
for idx, segment in enumerate(loadable_segments):
_LOGGER.info(
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and 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.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:
app_data = bytearray()
app_data.append(BOOT_NVM_MEMORY_ID)
# N parameter is always 1 here.
app_data.append(1)
app_data.extend(struct.pack("!I", addr))
app_data.extend(struct.pack("!I", len(data)))
app_data.extend(data)
return PusTc(
apid=0,
service=MEMORY_SERVICE,
subservice=RAW_MEMORY_WRITE_SUBSERVICE,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=app_data,
)
if __name__ == "__main__":
main()

View File

@ -1 +0,0 @@
serial_port = "/dev/ttyUSB0"

View File

@ -1,5 +0,0 @@
spacepackets == 0.24
tmtccmd == 8.0.2
toml == 0.10
pyelftools == 0.31
crcmod == 1.7

View File

@ -1,2 +0,0 @@
/target
/app.map

View File

@ -1,42 +0,0 @@
[package]
name = "slot-a-blinky"
version = "0.1.0"
edition = "2021"
[workspace]
[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"
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
# This is problematic for stepping..
# opt-level = 'z' # <-
overflow-checks = true # <-
# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = 'fat'
opt-level = 3 # <-
overflow-checks = false # <-
[profile.small]
inherits = "release"
codegen-units = 1
debug-assertions = false # <-
lto = true
opt-level = 'z' # <-
overflow-checks = false # <-
# strip = true # Automatically strip symbols from the binary.

View File

@ -1,24 +0,0 @@
/* Special linker script for application slot A with an offset at address 0x4000 */
MEMORY
{
FLASH : ORIGIN = 0x00004000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
/* SRAM_0 can be used for all busses: Instruction, Data and System */
/* SRAM_1 only supports the system bus */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
/* Define sections for placing symbols into the extra memory regions above. */
/* This makes them accessible from code. */
SECTIONS {
.sram1 (NOLOAD) : ALIGN(8) {
*(.sram1 .sram1.*);
. = ALIGN(4);
} > SRAM_1
};

View File

@ -1,23 +0,0 @@
//! Simple blinky example using the HAL
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx HAL blinky example for App Slot A");
let mut dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
loop {
cortex_m::asm::delay(1_000_000);
led.toggle().ok();
}
}

View File

@ -1,2 +0,0 @@
/target
/app.map

View File

@ -1,42 +0,0 @@
[package]
name = "slot-b-blinky"
version = "0.1.0"
edition = "2021"
[workspace]
[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"
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
# This is problematic for stepping..
# opt-level = 'z' # <-
overflow-checks = true # <-
# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = 'fat'
opt-level = 3 # <-
overflow-checks = false # <-
[profile.small]
inherits = "release"
codegen-units = 1
debug-assertions = false # <-
lto = true
opt-level = 'z' # <-
overflow-checks = false # <-
# strip = true # Automatically strip symbols from the binary.

View File

@ -1,24 +0,0 @@
/* Special linker script for application slot B with an offset at address 0x22000 */
MEMORY
{
FLASH : ORIGIN = 0x00022000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
/* SRAM_0 can be used for all busses: Instruction, Data and System */
/* SRAM_1 only supports the system bus */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
/* Define sections for placing symbols into the extra memory regions above. */
/* This makes them accessible from code. */
SECTIONS {
.sram1 (NOLOAD) : ALIGN(8) {
*(.sram1 .sram1.*);
. = ALIGN(4);
} > SRAM_1
};

View File

@ -1,23 +0,0 @@
//! Simple blinky example using the HAL
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx HAL blinky example for App Slot B");
let mut dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
loop {
cortex_m::asm::delay(8_000_000);
led.toggle().ok();
}
}

View File

@ -1,9 +0,0 @@
#![no_std]
#[cfg(test)]
mod tests {
#[test]
fn simple() {
assert_eq!(1 + 1, 2);
}
}

View File

@ -1,557 +0,0 @@
//! Vorago flashloader which can be used to flash image A and image B via a simple
//! low-level CCSDS memory interface via a UART wire.
//!
//! This flash loader can be used after the bootloader was flashed to flash the images.
//! You can also use this as an starting application for a software update mechanism.
//!
//! 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>
#![no_main]
#![no_std]
use embedded_hal_nb::serial::Read;
use once_cell::sync::OnceCell;
use panic_rtt_target as _;
use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt};
const EXTCLK_FREQ: u32 = 40_000_000;
const COBS_FRAME_SEPARATOR: u8 = 0x0;
const MAX_TC_SIZE: usize = 1024;
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
const MAX_TM_SIZE: usize = 128;
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
const UART_BAUDRATE: u32 = 115200;
const SERIAL_RX_WIRETAPPING: bool = false;
const COBS_RX_DEBUGGING: bool = false;
const BOOT_NVM_MEMORY_ID: u8 = 1;
pub enum ActionId {
CorruptImageA = 128,
CorruptImageB = 129,
}
pub trait WdtInterface {
fn feed(&self);
}
pub struct OptWdt(Option<Wdt>);
impl WdtInterface for OptWdt {
fn feed(&self) {
if self.0.is_some() {
self.0.as_ref().unwrap().feed();
}
}
}
use once_cell::sync::Lazy;
use ringbuf::{
traits::{Consumer, Observer, Producer, SplitRef},
CachingCons, StaticProd, StaticRb,
};
const BUF_RB_SIZE_TX: usize = 1024;
const SIZES_RB_SIZE_TX: usize = 16;
static mut BUF_RB_TX: Lazy<StaticRb<u8, BUF_RB_SIZE_TX>> =
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TX>::default);
static mut SIZES_RB_TX: Lazy<StaticRb<usize, SIZES_RB_SIZE_TX>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TX>::default);
pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf_prod: StaticProd<'static, u8, BUF_SIZE>,
pub sizes_prod: StaticProd<'static, usize, SIZES_LEN>,
}
pub struct DataConsumer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf_cons: CachingCons<&'static StaticRb<u8, BUF_SIZE>>,
pub sizes_cons: CachingCons<&'static StaticRb<usize, SIZES_LEN>>,
}
static CLOCKS: OnceCell<Clocks> = OnceCell::new();
pub const APP_A_START_ADDR: u32 = 0x4000;
pub const APP_A_END_ADDR: u32 = 0x22000;
pub const APP_B_START_ADDR: u32 = 0x22000;
pub const APP_B_END_ADDR: u32 = 0x40000;
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
mod app {
use super::*;
use cortex_m::asm;
use embedded_hal_nb::nb;
use embedded_io::Write;
use panic_rtt_target as _;
use rtic::Mutex;
use rtic_monotonics::systick::prelude::*;
use rtic_sync::{
channel::{Receiver, Sender},
make_channel,
};
use rtt_target::rprintln;
use satrs::pus::verification::VerificationReportCreator;
use spacepackets::ecss::PusServiceId;
use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use va416xx_hal::{
clock::ClkgenExt,
edac,
gpio::PinsG,
nvm::Nvm,
pac,
uart::{self, Uart},
};
use crate::{setup_edac, EXTCLK_FREQ};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum CobsReaderStates {
#[default]
WaitingForStart,
WatingForEnd,
FrameOverflow,
}
#[local]
struct Local {
uart_rx: uart::Rx<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>,
cobs_reader_state: CobsReaderStates,
tc_tx: TcTx,
tc_rx: TcRx,
rom_spi: Option<pac::Spi3>,
tx_cons: DataConsumer<BUF_RB_SIZE_TX, SIZES_RB_SIZE_TX>,
verif_reporter: VerificationReportCreator,
}
#[shared]
struct Shared {
decode_buffer_busy: bool,
decode_buf: [u8; MAX_TC_SIZE],
tx_prod: DataProducer<BUF_RB_SIZE_TX, SIZES_RB_SIZE_TX>,
}
pub type TcTx = Sender<'static, usize, 2>;
pub type TcRx = Receiver<'static, usize, 2>;
rtic_monotonics::systick_monotonic!(Mono, 10_000);
#[init]
fn init(mut cx: init::Context) -> (Shared, Local) {
//rtt_init_default!();
rtt_log::init();
rprintln!("-- Vorago flashloader --");
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = cx
.device
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut cx.device.sysconfig)
.unwrap();
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 uart0 = Uart::new(
cx.device.uart0,
(tx, rx),
Hertz::from_raw(UART_BAUDRATE),
&mut cx.device.sysconfig,
&clocks,
);
let (tx, rx) = uart0.split();
let (tc_tx, tc_rx) = make_channel!(usize, 2);
let verif_reporter = VerificationReportCreator::new(0).unwrap();
let (buf_prod, buf_cons) = unsafe { BUF_RB_TX.split_ref() };
let (sizes_prod, sizes_cons) = unsafe { SIZES_RB_TX.split_ref() };
Mono::start(cx.core.SYST, clocks.sysclk().raw());
CLOCKS.set(clocks).unwrap();
pus_tc_handler::spawn().unwrap();
uart_reader_task::spawn().unwrap();
pus_tm_tx_handler::spawn().unwrap();
(
Shared {
decode_buffer_busy: false,
decode_buf: [0; MAX_TC_SIZE],
tx_prod: DataProducer {
buf_prod,
sizes_prod,
},
},
Local {
uart_rx: rx,
uart_tx: tx,
cobs_reader_state: CobsReaderStates::default(),
tc_tx,
tc_rx,
rom_spi: Some(cx.device.spi3),
tx_cons: DataConsumer {
buf_cons,
sizes_cons,
},
verif_reporter,
},
)
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
#[task(
priority = 4,
local=[
read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
uart_rx,
cobs_reader_state,
tc_tx
],
shared=[decode_buffer_busy, decode_buf]
)]
async fn uart_reader_task(mut cx: uart_reader_task::Context) {
let mut current_idx = 0;
loop {
match cx.local.uart_rx.read() {
Ok(byte) => {
if SERIAL_RX_WIRETAPPING {
log::debug!("RX Byte: 0x{:x?}", byte);
}
handle_single_rx_byte(&mut cx, byte, &mut current_idx)
}
Err(e) => {
match e {
nb::Error::Other(e) => {
log::warn!("UART error: {:?}", e);
match e {
uart::Error::Overrun => {
cx.local.uart_rx.clear_fifo();
}
uart::Error::FramingError => (),
uart::Error::ParityError => (),
uart::Error::BreakCondition => (),
uart::Error::TransferPending => (),
uart::Error::BufferTooShort => (),
}
}
nb::Error::WouldBlock => {
// Delay for a short period before polling again.
Mono::delay(400.micros()).await;
}
}
}
}
}
}
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;
}
}
}
}
#[task(
priority = 2,
local=[
read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
src_data_buf: [u8; 16] = [0; 16],
verif_buf: [u8; 32] = [0; 32],
tc_rx,
rom_spi,
verif_reporter
],
shared=[decode_buffer_busy, decode_buf, tx_prod]
)]
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
loop {
let packet_len = cx.local.tc_rx.recv().await.expect("all senders down");
log::info!(target: "TC Handler", "received packet with length {}", packet_len);
// We still copy the data to a local buffer, so the exchange buffer can already be used
// for the next packet / decode process.
cx.shared
.decode_buf
.lock(|buf| cx.local.read_buf[0..buf.len()].copy_from_slice(buf));
cx.shared.decode_buffer_busy.lock(|busy| *busy = false);
match PusTcReader::new(cx.local.read_buf) {
Ok((pus_tc, _)) => {
let mut write_and_send = |tm: &PusTmCreator| {
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
cx.shared.tx_prod.lock(|prod| {
prod.sizes_prod.try_push(tm.len_written()).unwrap();
prod.buf_prod
.push_slice(&cx.local.verif_buf[0..written_size]);
});
};
let token = cx.local.verif_reporter.add_tc(&pus_tc);
let (tm, accepted_token) = cx
.local
.verif_reporter
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
let (tm, started_token) = cx
.local
.verif_reporter
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
if pus_tc.service() == PusServiceId::Action as u8 {
let mut corrupt_image = |base_addr: u32| {
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
let mut buf = [0u8; 4];
nvm.read_data(base_addr + 32, &mut buf);
buf[0] += 1;
nvm.write_data(base_addr + 32, &buf);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
};
if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
rprintln!("corrupting App Image A");
corrupt_image(APP_A_START_ADDR);
}
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
rprintln!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR);
}
}
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
log::info!(target: "TC Handler", "received ping TC");
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
let tm = cx
.local
.verif_reporter
.step_success(
cx.local.src_data_buf,
&started_token,
0,
0,
&[],
EcssEnumU8::new(0),
)
.expect("step success failed");
write_and_send(&tm);
// Raw memory write TC
if pus_tc.subservice() == 2 {
let app_data = pus_tc.app_data();
if app_data.len() < 10 {
log::warn!(
target: "TC Handler",
"app data for raw memory write is too short: {}",
app_data.len()
);
}
let memory_id = app_data[0];
if memory_id != BOOT_NVM_MEMORY_ID {
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id);
// TODO: Error reporting
return;
}
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap());
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap());
if 10 + data_len as usize > app_data.len() {
log::warn!(
target: "TC Handler",
"invalid data length {} for raw mem write detected",
data_len
);
// TODO: Error reporting
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
log::info!("NVM operation done");
}
}
}
Err(e) => {
log::warn!("PUS TC error: {}", e);
}
}
}
}
#[task(
priority = 1,
local=[
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
uart_tx,
tx_cons,
],
shared=[]
)]
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
loop {
while cx.local.tx_cons.sizes_cons.occupied_len() > 0 {
let next_size = cx.local.tx_cons.sizes_cons.try_pop().unwrap();
cx.local
.tx_cons
.buf_cons
.pop_slice(&mut cx.local.read_buf[0..next_size]);
cx.local.encoded_buf[0] = 0;
let send_size = cobs::encode(
&cx.local.read_buf[0..next_size],
&mut cx.local.encoded_buf[1..],
);
cx.local.encoded_buf[send_size + 1] = 0;
cx.local
.uart_tx
.write(&cx.local.encoded_buf[0..send_size + 2])
.unwrap();
Mono::delay(2.millis()).await;
}
Mono::delay(30.millis()).await;
}
}
#[task(binds = EDAC_SBE, priority = 1)]
fn edac_sbe_isr(_cx: edac_sbe_isr::Context) {
// TODO: Send some command via UART for notification purposes. Also identify the problematic
// memory.
edac::clear_sbe_irq();
}
#[task(binds = EDAC_MBE, priority = 1)]
fn edac_mbe_isr(_cx: edac_mbe_isr::Context) {
// TODO: Send some command via UART for notification purposes.
edac::clear_mbe_irq();
// TODO: Reset like the vorago example?
}
#[task(binds = WATCHDOG, priority = 1)]
fn watchdog_isr(_cx: watchdog_isr::Context) {
let wdt = unsafe { pac::WatchDog::steal() };
// Clear interrupt.
wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
}
}
fn setup_edac(syscfg: &mut pac::Sysconfig) {
// The scrub values are based on the Vorago provided bootloader.
edac::enable_rom_scrub(syscfg, 125);
edac::enable_ram0_scrub(syscfg, 1000);
edac::enable_ram1_scrub(syscfg, 1000);
edac::enable_sbe_irq();
edac::enable_mbe_irq();
}

View File

@ -1,23 +0,0 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
/* SRAM_0 can be used for all busses: Instruction, Data and System */
/* SRAM_1 only supports the system bus */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
/* Define sections for placing symbols into the extra memory regions above. */
/* This makes them accessible from code. */
SECTIONS {
.sram1 (NOLOAD) : ALIGN(8) {
*(.sram1 .sram1.*);
. = ALIGN(4);
} > SRAM_1
};

View File

@ -1,24 +0,0 @@
/* Special linker script for application slot A with an offset at address 0x4000 */
MEMORY
{
FLASH : ORIGIN = 0x00004000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
/* SRAM_0 can be used for all busses: Instruction, Data and System */
/* SRAM_1 only supports the system bus */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
/* Define sections for placing symbols into the extra memory regions above. */
/* This makes them accessible from code. */
SECTIONS {
.sram1 (NOLOAD) : ALIGN(8) {
*(.sram1 .sram1.*);
. = ALIGN(4);
} > SRAM_1
};

View File

@ -1,24 +0,0 @@
/* Special linker script for application slot B with an offset at address 0x22000 */
MEMORY
{
FLASH : ORIGIN = 0x00022000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
/* SRAM_0 can be used for all busses: Instruction, Data and System */
/* SRAM_1 only supports the system bus */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
/* Define sections for placing symbols into the extra memory regions above. */
/* This makes them accessible from code. */
SECTIONS {
.sram1 (NOLOAD) : ALIGN(8) {
*(.sram1 .sram1.*);
. = ALIGN(4);
} > SRAM_1
};

View File

@ -1,36 +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.2.0]
- Documentation improvements
- Improved UART typing support: Validity of passed pins is now checked properly
## Changed
- Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be
selected for HAL compilation to work properly
## Fixed
- Small fixes and improvements for ADC drivers
- Fixes for the SPI implementation where the clock divider values were not calculated
correctly
## Added
- Added basic DMA driver
- Added basic EDAC module
- Added bootloader and flashloader example application
- Added NVM module which exposes a simple API to write to the NVM memory used for the boot process
# [v0.1.0] 2024-07-01
- Initial release with basic HAL drivers

View File

@ -1,6 +1,6 @@
[package] [package]
name = "va416xx-hal" name = "va416xx-hal"
version = "0.2.0" version = "0.1.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"
@ -17,6 +17,7 @@ paste = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
embedded-hal = "1" embedded-hal = "1"
embedded-io = "0.6" embedded-io = "0.6"
embedded-dma = "0.2"
num_enum = { version = "0.7", default-features = false } num_enum = { version = "0.7", default-features = false }
typenum = "1" typenum = "1"
bitflags = "2" bitflags = "2"
@ -38,16 +39,8 @@ features = ["critical-section"]
default = ["rt", "revb"] default = ["rt", "revb"]
rt = ["va416xx/rt"] rt = ["va416xx/rt"]
defmt = ["dep:defmt", "fugit/defmt"] defmt = ["dep:defmt", "fugit/defmt"]
va41630 = ["device-selected"]
va41620 = ["device-selected"]
va41629 = ["device-selected"]
va41628 = ["device-selected"]
device-selected = []
revb = [] revb = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["va41630", "defmt"] all-features = true
rustdoc-args = ["--generate-link-to-definition"] rustdoc-args = ["--generate-link-to-definition"]

View File

@ -11,14 +11,6 @@ raw PAC. This crate also implements traits specified by the
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with [embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
various drivers in the embedded rust ecosystem. 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`
## Building ## Building
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain. Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
@ -64,7 +56,7 @@ is contained within the
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
version = "<Most Recent Version>" version = "<Most Recent Version>"
features = ["va41630"] features = ["rt"]
``` ```
6. Build the application with `cargo build` 6. Build the application with `cargo build`

View File

@ -1,9 +1,3 @@
//! Analog to Digital Converter (ADC) driver.
//!
//! ## Examples
//!
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
//! - [ADC](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/adc.rs)
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::clock::Clocks; use crate::clock::Clocks;
@ -52,8 +46,6 @@ pub enum ChannelSelect {
} }
bitflags::bitflags! { bitflags::bitflags! {
/// This structure is used by the ADC multi-select API to
/// allow selecting multiple channels in a convenient manner.
pub struct MultiChannelSelect: u16 { pub struct MultiChannelSelect: u16 {
const AnIn0 = 1; const AnIn0 = 1;
const AnIn1 = 1 << 1; const AnIn1 = 1 << 1;
@ -137,18 +129,6 @@ impl ChannelValue {
pub enum ChannelTagEnabled {} pub enum ChannelTagEnabled {}
pub enum ChannelTagDisabled {} pub enum ChannelTagDisabled {}
/// ADC driver structure.
///
/// Currently, this structure supports three primary ways to measure channel value(s):
///
/// * Trigger and read a single value
/// * Trigger and read a range of ADC values using the sweep functionality
/// * Trigger and read multiple ADC values using the sweep functionality
///
/// The ADC channel tag feature is enabled or disabled at compile time using the
/// [ChannelTagEnabled] and [ChannelTagDisabled]. The [Adc::new] method returns a driver instance
/// with the channel tag enabled, while the [Adc::new_with_channel_tag] method can be used to
/// return an instance with the channel tag enabled.
pub struct Adc<TagEnabled = ChannelTagDisabled> { pub struct Adc<TagEnabled = ChannelTagDisabled> {
adc: pac::Adc, adc: pac::Adc,
phantom: PhantomData<TagEnabled>, phantom: PhantomData<TagEnabled>,
@ -174,44 +154,34 @@ impl Adc<ChannelTagDisabled> {
lower_bound_idx: u8, lower_bound_idx: u8,
upper_bound_idx: u8, upper_bound_idx: u8,
rx_buf: &mut [u16], rx_buf: &mut [u16],
) -> Result<usize, AdcRangeReadError> { ) -> Result<(), AdcRangeReadError> {
self.generic_prepare_range_sweep_and_wait_until_ready( self.generic_prepare_range_sweep_and_wait_until_ready(
lower_bound_idx, lower_bound_idx,
upper_bound_idx, upper_bound_idx,
rx_buf.len(), rx_buf.len(),
)?; )?;
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits(); for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff; rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
} }
Ok(fifo_entry_count as usize) Ok(())
} }
/// Perform a sweep for selected ADC channels.
///
/// Returns the number of read values which were written to the passed RX buffer.
pub fn sweep_and_read_multiselect( pub fn sweep_and_read_multiselect(
&self, &self,
ch_select: MultiChannelSelect, ch_select: MultiChannelSelect,
rx_buf: &mut [u16], rx_buf: &mut [u16],
) -> Result<usize, BufferTooSmallError> { ) -> Result<(), BufferTooSmallError> {
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?; self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits(); for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff; rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
} }
Ok(fifo_entry_count as usize) Ok(())
} }
pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> { pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
self.generic_try_read_single_value() self.generic_try_read_single_value()
.map(|v| v.map(|v| v & 0xfff)) .map(|v| v.map(|v| v & 0xfff))
} }
#[inline(always)]
pub fn channel_tag_enabled(&self) -> bool {
false
}
} }
impl Adc<ChannelTagEnabled> { impl Adc<ChannelTagEnabled> {
@ -260,21 +230,17 @@ impl Adc<ChannelTagEnabled> {
Ok(fifo_entry_count as usize) Ok(fifo_entry_count as usize)
} }
/// Perform a sweep for selected ADC channels.
///
/// Returns the number of read values which were written to the passed RX buffer.
pub fn sweep_and_read_multiselect( pub fn sweep_and_read_multiselect(
&self, &self,
ch_select: MultiChannelSelect, ch_select: MultiChannelSelect,
rx_buf: &mut [ChannelValue], rx_buf: &mut [ChannelValue],
) -> Result<usize, BufferTooSmallError> { ) -> Result<(), BufferTooSmallError> {
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?; self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits(); for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
rx_buf[i as usize] = rx_buf[i as usize] =
self.create_channel_value(self.adc.fifo_data().read().bits() as u16); self.create_channel_value(self.adc.fifo_data().read().bits() as u16);
} }
Ok(fifo_entry_count as usize) Ok(())
} }
#[inline] #[inline]
@ -284,11 +250,6 @@ impl Adc<ChannelTagEnabled> {
channel: ChannelSelect::try_from(((raw_value >> 12) & 0xf) as u8).unwrap(), channel: ChannelSelect::try_from(((raw_value >> 12) & 0xf) as u8).unwrap(),
} }
} }
#[inline(always)]
pub fn channel_tag_enabled(&self) -> bool {
true
}
} }
impl<TagEnabled> Adc<TagEnabled> { impl<TagEnabled> Adc<TagEnabled> {
@ -313,6 +274,11 @@ impl<TagEnabled> Adc<TagEnabled> {
self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit()); self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit());
} }
#[inline(always)]
pub fn channel_tag_enabled(&self) -> bool {
self.adc.ctrl().read().chan_tag_en().bit_is_set()
}
#[inline(always)] #[inline(always)]
pub fn clear_fifo(&self) { pub fn clear_fifo(&self) {
self.adc.fifo_clr().write(|w| unsafe { w.bits(1) }); self.adc.fifo_clr().write(|w| unsafe { w.bits(1) });
@ -360,6 +326,8 @@ impl<TagEnabled> Adc<TagEnabled> {
ch_select |= 1 << i; ch_select |= 1 << i;
} }
self.generic_trigger_sweep(ch_select); self.generic_trigger_sweep(ch_select);
cortex_m::asm::nop();
cortex_m::asm::nop();
while self.adc.status().read().adc_busy().bit_is_set() { while self.adc.status().read().adc_busy().bit_is_set() {
cortex_m::asm::nop(); cortex_m::asm::nop();
} }

View File

@ -10,7 +10,6 @@
//! # Examples //! # Examples
//! //!
//! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs) //! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
#[cfg(not(feature = "va41628"))]
use crate::adc::ADC_MAX_CLK; use crate::adc::ADC_MAX_CLK;
use crate::pac; use crate::pac;
@ -448,22 +447,11 @@ impl ClkgenCfgr {
.ctrl0() .ctrl0()
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) }); .modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
Ok(Clocks {
sysclk: final_sysclk,
apb1: final_sysclk / 2,
apb2: final_sysclk / 4,
#[cfg(not(feature = "va41628"))]
adc_clk: self.cfg_adc_clk_div(final_sysclk),
})
}
#[cfg(not(feature = "va41628"))]
fn cfg_adc_clk_div(&self, final_sysclk: Hertz) -> Hertz {
// I will just do the ADC stuff like Vorago does it. // I will just do the ADC stuff like Vorago does it.
// ADC clock (must be 2-12.5 MHz) // ADC clock (must be 2-12.5 MHz)
// NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue // NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue
// For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range) // For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range)
if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 { let adc_clk = if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
self.clkgen self.clkgen
.ctrl1() .ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) }); .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
@ -473,7 +461,14 @@ impl ClkgenCfgr {
.ctrl1() .ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) }); .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
final_sysclk / 8 final_sysclk / 8
} };
Ok(Clocks {
sysclk: final_sysclk,
apb1: final_sysclk / 2,
apb2: final_sysclk / 4,
adc_clk,
})
} }
} }
@ -488,7 +483,6 @@ pub struct Clocks {
sysclk: Hertz, sysclk: Hertz,
apb1: Hertz, apb1: Hertz,
apb2: Hertz, apb2: Hertz,
#[cfg(not(feature = "va41628"))]
adc_clk: Hertz, adc_clk: Hertz,
} }
@ -519,7 +513,6 @@ impl Clocks {
} }
/// Returns the ADC clock frequency which has a separate divider. /// Returns the ADC clock frequency which has a separate divider.
#[cfg(not(feature = "va41628"))]
pub fn adc_clk(&self) -> Hertz { pub fn adc_clk(&self) -> Hertz {
self.adc_clk self.adc_clk
} }

View File

@ -1,8 +1,3 @@
//! Digital to Analog Converter (DAC) driver.
//!
//! ## Examples
//!
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
use core::ops::Deref; use core::ops::Deref;
use crate::{ use crate::{

View File

@ -3,6 +3,8 @@
//! ## Examples //! ## Examples
//! //!
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs) //! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
use embedded_dma::WriteBuffer;
use crate::{ use crate::{
clock::{PeripheralClock, PeripheralSelect}, clock::{PeripheralClock, PeripheralSelect},
enable_interrupt, pac, enable_interrupt, pac,
@ -203,6 +205,28 @@ pub struct DmaChannel {
pub ch_ctrl_alt: &'static mut DmaChannelControl, pub ch_ctrl_alt: &'static mut DmaChannelControl,
} }
/// This transfer structure takes ownership of the mutable destination slice.
///
/// This avoids accidental violation of the ownership rules because the DMA now has mutable
/// access to that slice as well. The mutable slice can be retrieved after DMA transfer completion
/// by using the [Self::release] method.
pub struct DmaTransfer<W> {
buf: W,
//ch: DmaChannel
}
impl<W: WriteBuffer> DmaTransfer<W> {
/// Retrieve the mutable destination slice once the DMA transfer has completed.
///
/// # Safety
///
/// - The user MUST ensure that the DMA transfer has completed, for example by polling a
/// completion flag set by the DMA_DONE ISR.
pub unsafe fn release(self) -> W {
self.buf
}
}
impl DmaChannel { impl DmaChannel {
#[inline(always)] #[inline(always)]
pub fn channel(&self) -> u8 { pub fn channel(&self) -> u8 {
@ -281,35 +305,25 @@ impl DmaChannel {
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt] /// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to /// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
/// start the DMA transfer. /// start the DMA transfer.
/// pub fn prepare_mem_to_mem_transfer_8_bit<W: WriteBuffer<Word = u8>>(
/// # Safety
///
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
/// is safe for DMA reads. The specific requirements can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
pub unsafe fn prepare_mem_to_mem_transfer_8_bit(
&mut self, &mut self,
source: &[u8], source: &[u8],
dest: &mut [u8], mut dest: W,
) -> Result<(), DmaTransferInitError> { ) -> Result<DmaTransfer<W>, DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?; let (write_ptr, len) = unsafe { dest.write_buffer() };
let len = Self::common_mem_transfer_checks(source.len(), len)?;
self.generic_mem_to_mem_transfer_init( self.generic_mem_to_mem_transfer_init(
len, len,
(source.as_ptr() as u32) (source.as_ptr() as u32)
.checked_add(len as u32) .checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?, .ok_or(DmaTransferInitError::AddrOverflow)?,
(dest.as_ptr() as u32) (write_ptr as u32)
.checked_add(len as u32) .checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?, .ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Byte, DataSize::Byte,
AddrIncrement::Byte, AddrIncrement::Byte,
); );
Ok(()) Ok(DmaTransfer { buf: dest })
} }
/// Prepares a 16-bit DMA transfer from memory to memory. /// Prepares a 16-bit DMA transfer from memory to memory.
@ -321,21 +335,10 @@ impl DmaChannel {
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt] /// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to /// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
/// start the DMA transfer. /// start the DMA transfer.
/// pub fn prepare_mem_to_mem_transfer_16_bit<'dest>(
/// # Safety
///
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
/// is safe for DMA reads. The specific requirements can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
pub unsafe fn prepare_mem_to_mem_transfer_16_bit(
&mut self, &mut self,
source: &[u16], source: &[u16],
dest: &mut [u16], dest: &'dest mut [u16],
) -> Result<(), DmaTransferInitError> { ) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?; let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init( self.generic_mem_to_mem_transfer_init(
@ -361,21 +364,10 @@ impl DmaChannel {
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt] /// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to /// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
/// start the DMA transfer. /// start the DMA transfer.
/// pub fn prepare_mem_to_mem_transfer_32_bit<'dest>(
/// # Safety
///
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
/// is safe for DMA reads. The specific requirements can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
pub unsafe fn prepare_mem_to_mem_transfer_32_bit(
&mut self, &mut self,
source: &[u32], source: &[u32],
dest: &mut [u32], dest: &'dest mut [u32],
) -> Result<(), DmaTransferInitError> { ) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?; let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init( self.generic_mem_to_mem_transfer_init(
@ -392,54 +384,6 @@ impl DmaChannel {
Ok(()) Ok(())
} }
/// Prepares a 8-bit DMA transfer from memory to a peripheral.
///
/// It is assumed that a peripheral with a 16-byte FIFO is used here and that the
/// transfer is activated by an IRQ trigger when the half-full interrupt of the peripheral
/// is fired. Therefore, this function configured the DMA in [CycleControl::Basic] mode with
/// rearbitration happening every 8 DMA cycles. It also configures the primary channel control
/// structure to perform the transfer.
///
/// # Safety
///
/// You must ensure that the source buffer is safe for DMA reads. The specific requirements
/// can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
///
/// The destination address must be the pointer address of a peripheral FIFO register address.
/// You must also ensure that the regular synchronous transfer API of the peripheral is NOT
/// used to perform transfers.
pub unsafe fn prepare_mem_to_periph_transfer_8_bit(
&mut self,
source: &[u8],
dest: *mut u32,
) -> Result<(), DmaTransferInitError> {
if source.len() > MAX_DMA_TRANSFERS_PER_CYCLE {
return Err(DmaTransferInitError::TransferSizeTooLarge(source.len()));
}
let len = source.len() - 1;
self.ch_ctrl_pri.cfg.set_raw(0);
self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?;
self.ch_ctrl_pri.dest_end_ptr = dest as u32;
self.ch_ctrl_pri
.cfg
.set_cycle_ctr(CycleControl::Basic as u8);
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte as u8);
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte as u8);
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte as u8);
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None as u8);
self.ch_ctrl_pri.cfg.set_n_minus_1(len as u16);
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8 as u8);
self.select_primary_structure();
Ok(())
}
// This function performs common checks and returns the source length minus one which is // This function performs common checks and returns the source length minus one which is
// relevant for further configuration of the DMA. This is because the DMA API expects N minus // relevant for further configuration of the DMA. This is because the DMA API expects N minus
// 1 and the source and end pointer need to point to the last transfer address. // 1 and the source and end pointer need to point to the last transfer address.

View File

@ -1,66 +0,0 @@
use crate::{enable_interrupt, pac};
#[inline(always)]
pub fn enable_rom_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
syscfg
.rom_scrub()
.write(|w| unsafe { w.bits(counter_reset as u32) })
}
#[inline(always)]
pub fn enable_ram0_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
syscfg
.ram0_scrub()
.write(|w| unsafe { w.bits(counter_reset as u32) })
}
#[inline(always)]
pub fn enable_ram1_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
syscfg
.ram1_scrub()
.write(|w| unsafe { w.bits(counter_reset as u32) })
}
/// This function enables the SBE related interrupts. The user should also provide a
/// [pac::EDAC_SBE] ISR and use [clear_sbe_irq] inside that ISR at the very least.
#[inline(always)]
pub fn enable_sbe_irq() {
unsafe {
enable_interrupt(pac::Interrupt::EDAC_SBE);
}
}
/// This function enables the SBE related interrupts. The user should also provide a
/// [pac::EDAC_MBE] ISR and use [clear_mbe_irq] inside that ISR at the very least.
#[inline(always)]
pub fn enable_mbe_irq() {
unsafe {
enable_interrupt(pac::Interrupt::EDAC_MBE);
}
}
/// This function should be called in the user provided [pac::EDAC_SBE] interrupt-service routine
/// to clear the SBE related interrupts.
#[inline(always)]
pub fn clear_sbe_irq() {
// Safety: This function only clears SBE related IRQs
let syscfg = unsafe { pac::Sysconfig::steal() };
syscfg.irq_clr().write(|w| {
w.romsbe().set_bit();
w.ram0sbe().set_bit();
w.ram1sbe().set_bit()
});
}
/// This function should be called in the user provided [pac::EDAC_MBE] interrupt-service routine
/// to clear the MBE related interrupts.
#[inline(always)]
pub fn clear_mbe_irq() {
// Safety: This function only clears SBE related IRQs
let syscfg = unsafe { pac::Sysconfig::steal() };
syscfg.irq_clr().write(|w| {
w.rommbe().set_bit();
w.ram0mbe().set_bit();
w.ram1mbe().set_bit()
});
}

View File

@ -295,17 +295,12 @@ pub trait PinId: Sealed {
} }
macro_rules! pin_id { macro_rules! pin_id {
($Group:ident, $Id:ident, $NUM:literal $(, $meta: meta)?) => { ($Group:ident, $Id:ident, $NUM:literal) => {
// Need paste macro to use ident in doc attribute // Need paste macro to use ident in doc attribute
paste::paste! { paste::paste! {
$(#[$meta])?
#[doc = "Pin ID representing pin " $Id] #[doc = "Pin ID representing pin " $Id]
pub enum $Id {} pub enum $Id {}
$(#[$meta])?
impl Sealed for $Id {} impl Sealed for $Id {}
$(#[$meta])?
impl PinId for $Id { impl PinId for $Id {
const DYN: DynPinId = DynPinId { const DYN: DynPinId = DynPinId {
group: DynGroup::$Group, group: DynGroup::$Group,
@ -694,14 +689,13 @@ impl<I: PinId> Registers<I> {
macro_rules! pins { macro_rules! pins {
( (
$Port:ident, $PinsName:ident, $($Id:ident $(, $meta:meta)?)+, $Port:ident, $PinsName:ident, $($Id:ident,)+,
) => { ) => {
paste::paste!( paste::paste!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB) /// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
pub struct $PinsName { pub struct $PinsName {
port: $Port, port: $Port,
$( $(
$(#[$meta])?
#[doc = "Pin " $Id] #[doc = "Pin " $Id]
pub [<$Id:lower>]: Pin<$Id, Reset>, pub [<$Id:lower>]: Pin<$Id, Reset>,
)+ )+
@ -724,7 +718,6 @@ macro_rules! pins {
port, port,
// Safe because we only create one `Pin` per `PinId` // Safe because we only create one `Pin` per `PinId`
$( $(
$(#[$meta])?
[<$Id:lower>]: unsafe { Pin::new() }, [<$Id:lower>]: unsafe { Pin::new() },
)+ )+
} }
@ -746,15 +739,13 @@ macro_rules! pins {
} }
} }
//$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
//$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal, $meta: meta),)+]
macro_rules! declare_pins { macro_rules! declare_pins {
( (
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+] $Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal),)+]
) => { ) => {
pins!($Port, $PinsName, $($Id $(, $meta)?)+,); pins!($Port, $PinsName, $($Id,)+,);
$( $(
pin_id!($Group, $Id, $NUM $(, $meta)?); pin_id!($Group, $Id, $NUM);
)+ )+
} }
} }
@ -779,7 +770,7 @@ declare_pins!(
(PA12, 12), (PA12, 12),
(PA13, 13), (PA13, 13),
(PA14, 14), (PA14, 14),
(PA15, 15) (PA15, 15),
] ]
); );
@ -793,17 +784,17 @@ declare_pins!(
(PB2, 2), (PB2, 2),
(PB3, 3), (PB3, 3),
(PB4, 4), (PB4, 4),
(PB5, 5, cfg(not(feature = "va41628"))), (PB5, 5),
(PB6, 6, cfg(not(feature = "va41628"))), (PB6, 6),
(PB7, 7, cfg(not(feature = "va41628"))), (PB7, 7),
(PB8, 8, cfg(not(feature = "va41628"))), (PB8, 8),
(PB9, 9, cfg(not(feature = "va41628"))), (PB9, 9),
(PB10, 10, cfg(not(feature = "va41628"))), (PB10, 10),
(PB11, 11, cfg(not(feature = "va41628"))), (PB11, 11),
(PB12, 12), (PB12, 12),
(PB13, 13), (PB13, 13),
(PB14, 14), (PB14, 14),
(PB15, 15) (PB15, 15),
] ]
); );
@ -825,9 +816,9 @@ declare_pins!(
(PC10, 10), (PC10, 10),
(PC11, 11), (PC11, 11),
(PC12, 12), (PC12, 12),
(PC13, 13, cfg(not(feature = "va41628"))), (PC13, 13),
(PC14, 14), (PC14, 14),
(PC15, 15, cfg(not(feature = "va41628"))) (PC15, 15),
] ]
); );
@ -836,22 +827,22 @@ declare_pins!(
PinsD, PinsD,
Portd, Portd,
[ [
(PD0, 0, cfg(not(feature = "va41628"))), (PD0, 0),
(PD1, 1, cfg(not(feature = "va41628"))), (PD1, 1),
(PD2, 2, cfg(not(feature = "va41628"))), (PD2, 2),
(PD3, 3, cfg(not(feature = "va41628"))), (PD3, 3),
(PD4, 4, cfg(not(feature = "va41628"))), (PD4, 4),
(PD5, 5, cfg(not(feature = "va41628"))), (PD5, 5),
(PD6, 6, cfg(not(feature = "va41628"))), (PD6, 6),
(PD7, 7, cfg(not(feature = "va41628"))), (PD7, 7),
(PD8, 8, cfg(not(feature = "va41628"))), (PD8, 8),
(PD9, 9, cfg(not(feature = "va41628"))), (PD9, 9),
(PD10, 10), (PD10, 10),
(PD11, 11), (PD11, 11),
(PD12, 12), (PD12, 12),
(PD13, 13), (PD13, 13),
(PD14, 14), (PD14, 14),
(PD15, 15) (PD15, 15),
] ]
); );
@ -870,12 +861,12 @@ declare_pins!(
(PE7, 7), (PE7, 7),
(PE8, 8), (PE8, 8),
(PE9, 9), (PE9, 9),
(PE10, 10, cfg(not(feature = "va41628"))), (PE10, 10),
(PE11, 11, cfg(not(feature = "va41628"))), (PE11, 11),
(PE12, 12), (PE12, 12),
(PE13, 13), (PE13, 13),
(PE14, 14), (PE14, 14),
(PE15, 15) (PE15, 15),
] ]
); );
@ -886,20 +877,20 @@ declare_pins!(
[ [
(PF0, 0), (PF0, 0),
(PF1, 1), (PF1, 1),
(PF2, 2, cfg(not(feature = "va41628"))), (PF2, 2),
(PF3, 3, cfg(not(feature = "va41628"))), (PF3, 3),
(PF4, 4, cfg(not(feature = "va41628"))), (PF4, 4),
(PF5, 5, cfg(not(feature = "va41628"))), (PF5, 5),
(PF6, 6, cfg(not(feature = "va41628"))), (PF6, 6),
(PF7, 7, cfg(not(feature = "va41628"))), (PF7, 7),
(PF8, 8, cfg(not(feature = "va41628"))), (PF8, 8),
(PF9, 9), (PF9, 9),
(PF10, 10, cfg(not(feature = "va41628"))), (PF10, 10),
(PF11, 11), (PF11, 11),
(PF12, 12), (PF12, 12),
(PF13, 13), (PF13, 13),
(PF14, 14), (PF14, 14),
(PF15, 15) (PF15, 15),
] ]
); );
@ -915,6 +906,6 @@ declare_pins!(
(PG4, 4), (PG4, 4),
(PG5, 5), (PG5, 5),
(PG6, 6), (PG6, 6),
(PG7, 7) (PG7, 7),
] ]
); );

View File

@ -3,26 +3,17 @@
#[cfg(test)] #[cfg(test)]
extern crate std; extern crate std;
#[cfg(not(feature = "device-selected"))]
compile_error!(
"This crate requires one of the following device features enabled:
va41630
va41629
va41628
"
);
pub use va416xx as device; pub use va416xx as device;
pub use va416xx as pac; pub use va416xx as pac;
pub mod prelude; pub mod prelude;
pub mod adc;
pub mod clock; pub mod clock;
pub mod dac;
pub mod dma; pub mod dma;
pub mod edac;
pub mod gpio; pub mod gpio;
pub mod i2c; pub mod i2c;
pub mod nvm;
pub mod pwm; pub mod pwm;
pub mod spi; pub mod spi;
pub mod time; pub mod time;
@ -31,11 +22,6 @@ pub mod typelevel;
pub mod uart; pub mod uart;
pub mod wdt; pub mod wdt;
#[cfg(not(feature = "va41628"))]
pub mod adc;
#[cfg(not(feature = "va41628"))]
pub mod dac;
#[derive(Debug, Eq, Copy, Clone, PartialEq)] #[derive(Debug, Eq, Copy, Clone, PartialEq)]
pub enum FunSel { pub enum FunSel {
Sel0 = 0b00, Sel0 = 0b00,

View File

@ -1,267 +0,0 @@
use embedded_hal::spi::MODE_0;
use crate::clock::{Clocks, SyscfgExt};
use crate::pac;
use crate::spi::{
mode_to_cpo_cph_bit, spi_clk_config_from_div, Instance, WordProvider, BMSTART_BMSTOP_MASK,
};
const NVM_CLOCK_DIV: u16 = 2;
// Commands. The internal FRAM is based on the Cypress FM25V20A device.
/// Write enable register.
pub const FRAM_WREN: u8 = 0x06;
pub const FRAM_WRDI: u8 = 0x04;
pub const FRAM_RDSR: u8 = 0x05;
/// Write single status register
pub const FRAM_WRSR: u8 = 0x01;
pub const FRAM_READ: u8 = 0x03;
pub const FRAM_WRITE: u8 = 0x02;
pub const FRAM_RDID: u8 = 0x9F;
pub const FRAM_SLEEP: u8 = 0xB9;
/* Address Masks */
const ADDR_MSB_MASK: u32 = 0xFF0000;
const ADDR_MID_MASK: u32 = 0x00FF00;
const ADDR_LSB_MASK: u32 = 0x0000FF;
#[inline(always)]
const fn msb_addr_byte(addr: u32) -> u8 {
((addr & ADDR_MSB_MASK) >> 16) as u8
}
#[inline(always)]
const fn mid_addr_byte(addr: u32) -> u8 {
((addr & ADDR_MID_MASK) >> 8) as u8
}
#[inline(always)]
const fn lsb_addr_byte(addr: u32) -> u8 {
(addr & ADDR_LSB_MASK) as u8
}
pub const WPEN_ENABLE_MASK: u8 = 1 << 7;
pub const BP_0_ENABLE_MASK: u8 = 1 << 2;
pub const BP_1_ENABLE_MASK: u8 = 1 << 3;
pub struct Nvm {
spi: Option<pac::Spi3>,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", defmt::Format)]
pub struct VerifyError {
addr: u32,
found: u8,
expected: u8,
}
impl Nvm {
pub fn new(syscfg: &mut pac::Sysconfig, spi: pac::Spi3, _clocks: &Clocks) -> Self {
crate::clock::enable_peripheral_clock(syscfg, pac::Spi3::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(pac::Spi3::PERIPH_SEL);
let spi_clk_cfg = spi_clk_config_from_div(NVM_CLOCK_DIV).unwrap();
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(MODE_0);
spi.ctrl0().write(|w| {
unsafe {
w.size().bits(u8::word_reg());
w.scrdv().bits(spi_clk_cfg.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.blockmode().set_bit();
unsafe { w.ss().bits(0) };
w.bmstart().set_bit();
w.bmstall().set_bit()
});
spi.clkprescale()
.write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val() as u32) });
spi.fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
// Enable the peripheral as the last step as recommended in the
// programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit());
let mut nvm = Self { spi: Some(spi) };
nvm.disable_write_prot();
nvm
}
pub fn disable_write_prot(&mut self) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(FRAM_WRSR);
self.write_with_bmstop(0x00);
self.wait_for_tx_idle();
}
pub fn read_rdsr(&self) -> u8 {
self.write_single(FRAM_RDSR);
self.write_with_bmstop(0x00);
self.wait_for_rx_available();
self.read_single_word();
self.wait_for_rx_available();
(self.read_single_word() & 0xff) as u8
}
pub fn enable_write_prot(&mut self) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(FRAM_WRSR);
self.write_with_bmstop(0x00);
}
#[inline(always)]
pub fn spi(&self) -> &pac::Spi3 {
self.spi.as_ref().unwrap()
}
#[inline(always)]
pub fn write_single(&self, word: u8) {
self.spi().data().write(|w| unsafe { w.bits(word as u32) })
}
#[inline(always)]
pub fn write_with_bmstop(&self, word: u8) {
self.spi()
.data()
.write(|w| unsafe { w.bits(BMSTART_BMSTOP_MASK | word as u32) })
}
#[inline(always)]
pub fn wait_for_tx_idle(&self) {
while self.spi().status().read().tfe().bit_is_clear() {
cortex_m::asm::nop();
}
while self.spi().status().read().busy().bit_is_set() {
cortex_m::asm::nop();
}
self.clear_fifos()
}
#[inline(always)]
pub fn clear_fifos(&self) {
self.spi().fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
})
}
#[inline(always)]
pub fn wait_for_rx_available(&self) {
while !self.spi().status().read().rne().bit_is_set() {
cortex_m::asm::nop();
}
}
#[inline(always)]
pub fn read_single_word(&self) -> u32 {
self.spi().data().read().bits()
}
pub fn write_data(&self, addr: u32, data: &[u8]) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(FRAM_WRITE);
self.write_single(msb_addr_byte(addr));
self.write_single(mid_addr_byte(addr));
self.write_single(lsb_addr_byte(addr));
for byte in data.iter().take(data.len() - 1) {
while self.spi().status().read().tnf().bit_is_clear() {
cortex_m::asm::nop();
}
self.write_single(*byte);
self.read_single_word();
}
while self.spi().status().read().tnf().bit_is_clear() {
cortex_m::asm::nop();
}
self.write_with_bmstop(*data.last().unwrap());
self.wait_for_tx_idle();
}
pub fn read_data(&self, addr: u32, buf: &mut [u8]) {
self.common_read_start(addr);
for byte in buf {
// Pump the SPI.
self.write_single(0);
self.wait_for_rx_available();
*byte = self.read_single_word() as u8;
}
self.write_with_bmstop(0);
self.wait_for_tx_idle();
}
pub fn verify_data(&self, addr: u32, comp_buf: &[u8]) -> Result<(), VerifyError> {
self.common_read_start(addr);
for (idx, byte) in comp_buf.iter().enumerate() {
// Pump the SPI.
self.write_single(0);
self.wait_for_rx_available();
let next_word = self.read_single_word() as u8;
if next_word != *byte {
self.write_with_bmstop(0);
self.wait_for_tx_idle();
return Err(VerifyError {
addr: addr + idx as u32,
found: next_word,
expected: *byte,
});
}
}
self.write_with_bmstop(0);
self.wait_for_tx_idle();
Ok(())
}
/// Enable write-protection and disables the peripheral clock.
pub fn shutdown(&mut self, sys_cfg: &mut pac::Sysconfig) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK);
crate::clock::disable_peripheral_clock(sys_cfg, pac::Spi3::PERIPH_SEL);
}
/// This function calls [Self::shutdown] and gives back the peripheral structure.
pub fn release(mut self, sys_cfg: &mut pac::Sysconfig) -> pac::Spi3 {
self.shutdown(sys_cfg);
self.spi.take().unwrap()
}
fn common_read_start(&self, addr: u32) {
self.wait_for_tx_idle();
self.write_single(FRAM_READ);
self.write_single(msb_addr_byte(addr));
self.write_single(mid_addr_byte(addr));
self.write_single(lsb_addr_byte(addr));
for _ in 0..4 {
// Pump the SPI.
self.write_single(0);
self.wait_for_rx_available();
// The first 4 data bytes received need to be ignored.
self.read_single_word();
}
}
}
/// Call [Self::shutdown] on drop.
impl Drop for Nvm {
fn drop(&mut self) {
if self.spi.is_some() {
self.shutdown(unsafe { &mut pac::Sysconfig::steal() });
}
}
}

View File

@ -8,20 +8,18 @@ use core::{convert::Infallible, marker::PhantomData, ops::Deref};
use embedded_hal::spi::Mode; use embedded_hal::spi::Mode;
use crate::{ use crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt}, clock::{PeripheralSelect, SyscfgExt},
gpio::{ gpio::{
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0, AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1, PC10, PC11, PC7, PC8, PC9, PE12, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1,
PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9, PF0, PF1, PG2, PG3, PG4, PC10, PC11, PC7, PC8, PC9, PE10, PE11, PE12, PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9,
PF0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PG2, PG3, PG4,
}, },
pac, pac,
time::Hertz, time::Hertz,
typelevel::{NoneT, Sealed}, typelevel::{NoneT, Sealed},
}; };
#[cfg(not(feature = "va41628"))]
use crate::gpio::{PB10, PB11, PB5, PB6, PB7, PB8, PB9, PE10, PE11, PF2, PF3, PF4, PF5, PF6, PF7};
//================================================================================================== //==================================================================================================
// Defintions // Defintions
//================================================================================================== //==================================================================================================
@ -29,11 +27,6 @@ use crate::gpio::{PB10, PB11, PB5, PB6, PB7, PB8, PB9, PE10, PE11, PF2, PF3, PF4
// FIFO has a depth of 16. // FIFO has a depth of 16.
const FILL_DEPTH: usize = 12; const FILL_DEPTH: usize = 12;
pub const DEFAULT_CLK_DIV: u16 = 2;
pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
pub const BMSKIPDATA_MASK: u32 = 1 << 30;
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum HwChipSelectId { pub enum HwChipSelectId {
Id0 = 0, Id0 = 0,
@ -82,20 +75,15 @@ pub trait OptionalHwCs<Spi>: HwCsProvider + Sealed {}
macro_rules! hw_cs_pins { macro_rules! hw_cs_pins {
($SPIx:path, $portId: path: ($SPIx:path, $portId: path:
$( $(
($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident $(, $meta: meta)?), ($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident),
)+ )+
) => { ) => {
$( $(
$(#[$meta])?
impl HwCsProvider for Pin<$PXx, $AFx> { impl HwCsProvider for Pin<$PXx, $AFx> {
const CS_ID: HwChipSelectId = $HwCsIdent; const CS_ID: HwChipSelectId = $HwCsIdent;
const SPI_ID: SpiId = $portId; const SPI_ID: SpiId = $portId;
} }
$(#[$meta])?
impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {} impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {}
$(#[$meta])?
pub type $typedef = Pin<$PXx, $AFx>; pub type $typedef = Pin<$PXx, $AFx>;
)+ )+
}; };
@ -111,14 +99,6 @@ impl OptionalHwCs<pac::Spi1> for NoneT {}
impl OptionalHwCs<pac::Spi2> for NoneT {} impl OptionalHwCs<pac::Spi2> for NoneT {}
impl OptionalHwCs<pac::Spi3> for NoneT {} impl OptionalHwCs<pac::Spi3> for NoneT {}
pub struct RomSpiSck;
pub struct RomSpiMiso;
pub struct RomSpiMosi;
impl Sealed for RomSpiSck {}
impl Sealed for RomSpiMosi {}
impl Sealed for RomSpiMiso {}
// SPI 0 // SPI 0
impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {} impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {}
@ -126,11 +106,9 @@ impl PinMosi<pac::Spi0> for Pin<PC1, AltFunc1> {}
impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {} impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {}
// SPI 1 // SPI 1
#[cfg(not(feature = "va41628"))]
impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {} impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {}
#[cfg(not(feature = "va41628"))]
impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {} impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {}
#[cfg(not(feature = "va41628"))]
impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {} impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {}
impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {} impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {}
@ -144,11 +122,8 @@ impl PinSck<pac::Spi1> for Pin<PE13, AltFunc2> {}
impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {} impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {}
impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {} impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {}
#[cfg(not(feature = "va41628"))]
impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {} impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {}
#[cfg(not(feature = "va41628"))]
impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {} impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {}
#[cfg(not(feature = "va41628"))]
impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {} impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {}
// SPI 2 // SPI 2
@ -157,18 +132,11 @@ impl PinSck<pac::Spi2> for Pin<PA5, AltFunc2> {}
impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {} impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {}
impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {} impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {}
#[cfg(not(feature = "va41628"))]
impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {} impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {}
#[cfg(not(feature = "va41628"))]
impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {} impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {}
#[cfg(not(feature = "va41628"))]
impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {} impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {}
// SPI3 is shared with the ROM SPI pins and has its own dedicated pins. // SPI3 is shared with the ROM SPI pins and has its own dedicated pins.
//
impl PinSck<pac::Spi3> for RomSpiSck {}
impl PinMosi<pac::Spi3> for RomSpiMosi {}
impl PinMiso<pac::Spi3> for RomSpiMiso {}
// SPI 0 HW CS pins // SPI 0 HW CS pins
@ -177,14 +145,14 @@ hw_cs_pins!(
(PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0), (PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0),
(PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0), (PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0),
(PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0), (PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0),
(PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0, cfg(not(feature="va41628"))), (PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0),
); );
hw_cs_pins!( hw_cs_pins!(
pac::Spi1, SpiId::Spi1: pac::Spi1, SpiId::Spi1:
(PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb, cfg(not(feature="va41628"))), (PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb),
(PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb, cfg(not(feature="va41628"))), (PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb),
(PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb, cfg(not(feature="va41628"))), (PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb),
(PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb), (PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb),
(PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb), (PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb),
(PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb), (PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb),
@ -193,14 +161,14 @@ hw_cs_pins!(
(PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc), (PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc),
(PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc), (PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc),
(PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe), (PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe),
(PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe, cfg(not(feature="va41628"))), (PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe),
(PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe, cfg(not(feature="va41628"))), (PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe),
(PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe), (PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe),
(PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe), (PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe),
(PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe), (PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe),
(PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe), (PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe),
(PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe), (PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe),
(PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf, cfg(not(feature="va41628"))), (PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf),
(PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg), (PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
); );
@ -215,9 +183,9 @@ hw_cs_pins!(
(PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa), (PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa),
(PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf), (PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf),
(PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf), (PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf),
(PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf, cfg(not(feature="va41628"))), (PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf),
(PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf, cfg(not(feature="va41628"))), (PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf),
(PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf, cfg(not(feature="va41628"))), (PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf),
); );
//================================================================================================== //==================================================================================================
@ -228,7 +196,7 @@ 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_div(&mut self, clk_div: u16); fn frequency(&mut self, spi_clk: Hertz);
fn hw_cs_id(&self) -> u8; fn hw_cs_id(&self) -> u8;
} }
@ -236,8 +204,8 @@ pub trait TransferConfigProvider {
/// and might change for transfers to different SPI slaves /// and might change for transfers to different SPI slaves
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct TransferConfig<HwCs> { pub struct TransferConfig<HwCs> {
pub clk_div: Option<u16>, pub spi_clk: Hertz,
pub mode: Option<Mode>, pub mode: Mode,
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to /// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
/// false /// false
pub hw_cs: Option<HwCs>, pub hw_cs: Option<HwCs>,
@ -251,8 +219,8 @@ pub struct TransferConfig<HwCs> {
/// 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.
pub struct ErasedTransferConfig { pub struct ErasedTransferConfig {
pub clk_div: Option<u16>, pub spi_clk: Hertz,
pub mode: Option<Mode>, pub mode: 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
@ -262,14 +230,9 @@ pub struct ErasedTransferConfig {
} }
impl TransferConfig<NoneT> { impl TransferConfig<NoneT> {
pub fn new_no_hw_cs( pub fn new_no_hw_cs(spi_clk: impl Into<Hertz>, mode: Mode, blockmode: bool, sod: bool) -> Self {
clk_div: Option<u16>,
mode: Option<Mode>,
blockmode: bool,
sod: bool,
) -> Self {
TransferConfig { TransferConfig {
clk_div, spi_clk: spi_clk.into(),
mode, mode,
hw_cs: None, hw_cs: None,
sod, sod,
@ -280,14 +243,14 @@ impl TransferConfig<NoneT> {
impl<HwCs: HwCsProvider> TransferConfig<HwCs> { impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
pub fn new( pub fn new(
clk_div: Option<u16>, spi_clk: impl Into<Hertz>,
mode: Option<Mode>, mode: Mode,
hw_cs: Option<HwCs>, hw_cs: Option<HwCs>,
blockmode: bool, blockmode: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfig { TransferConfig {
clk_div, spi_clk: spi_clk.into(),
mode, mode,
hw_cs, hw_cs,
sod, sod,
@ -297,7 +260,7 @@ impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
pub fn downgrade(self) -> ErasedTransferConfig { pub fn downgrade(self) -> ErasedTransferConfig {
ErasedTransferConfig { ErasedTransferConfig {
clk_div: self.clk_div, spi_clk: self.spi_clk,
mode: self.mode, mode: self.mode,
sod: self.sod, sod: self.sod,
blockmode: self.blockmode, blockmode: self.blockmode,
@ -317,11 +280,11 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
} }
fn mode(&mut self, mode: Mode) { fn mode(&mut self, mode: Mode) {
self.mode = Some(mode); self.mode = mode;
} }
fn clk_div(&mut self, clk_div: u16) { fn frequency(&mut self, spi_clk: Hertz) {
self.clk_div = Some(clk_div); self.spi_clk = spi_clk;
} }
fn hw_cs_id(&self) -> u8 { fn hw_cs_id(&self) -> u8 {
@ -329,9 +292,13 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
} }
} }
#[derive(Default)]
/// 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_div: u16, /// Serial clock rate divider. Together with the CLKPRESCALE register, it determines
/// the SPI clock rate in master mode. 0 by default. Specifying a higher value
/// limits the maximum attainable SPI speed
pub ser_clock_rate_div: u8,
/// 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
@ -342,29 +309,12 @@ pub struct SpiConfig {
pub master_delayer_capture: bool, pub master_delayer_capture: bool,
} }
impl Default for SpiConfig {
fn default() -> Self {
Self {
clk_div: DEFAULT_CLK_DIV,
ms: Default::default(),
slave_output_disable: Default::default(),
loopback_mode: Default::default(),
master_delayer_capture: Default::default(),
}
}
}
impl SpiConfig { impl SpiConfig {
pub fn loopback(mut self, enable: bool) -> Self { pub fn loopback(mut self, enable: bool) -> Self {
self.loopback_mode = enable; self.loopback_mode = enable;
self self
} }
pub fn clk_div(mut self, clk_div: u16) -> Self {
self.clk_div = clk_div;
self
}
pub fn master_mode(mut self, master: bool) -> Self { pub fn master_mode(mut self, master: bool) -> Self {
self.ms = !master; self.ms = !master;
self self
@ -441,16 +391,6 @@ impl Instance for pac::Spi2 {
} }
} }
impl Instance for pac::Spi3 {
const IDX: u8 = 3;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi3;
#[inline(always)]
fn ptr() -> *const SpiRegBlock {
Self::ptr()
}
}
//================================================================================================== //==================================================================================================
// Spi // Spi
//================================================================================================== //==================================================================================================
@ -470,7 +410,7 @@ pub struct Spi<SpiInstance, Pins, Word = u8> {
pins: Pins, pins: Pins,
} }
pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
match mode { match mode {
embedded_hal::spi::MODE_0 => (false, false), embedded_hal::spi::MODE_0 => (false, false),
embedded_hal::spi::MODE_1 => (false, true), embedded_hal::spi::MODE_1 => (false, true),
@ -479,105 +419,10 @@ pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
} }
} }
#[derive(Debug)]
pub struct SpiClkConfig {
prescale_val: u16,
scrdv: u8,
}
impl SpiClkConfig {
pub fn prescale_val(&self) -> u16 {
self.prescale_val
}
pub fn scrdv(&self) -> u8 {
self.scrdv
}
}
#[derive(Debug)]
pub enum SpiClkConfigError {
DivIsZero,
DivideValueNotEven,
ScrdvValueTooLarge,
}
#[inline]
pub fn spi_clk_config_from_div(mut div: u16) -> Result<SpiClkConfig, SpiClkConfigError> {
if div == 0 {
return Err(SpiClkConfigError::DivIsZero);
}
if div % 2 != 0 {
return Err(SpiClkConfigError::DivideValueNotEven);
}
let mut prescale_val = 0;
// find largest (even) prescale value that divides into div
for i in (2..=0xfe).rev().step_by(2) {
if div % i == 0 {
prescale_val = i;
break;
}
}
if prescale_val == 0 {
return Err(SpiClkConfigError::DivideValueNotEven);
}
div /= prescale_val;
if div > u8::MAX as u16 + 1 {
return Err(SpiClkConfigError::ScrdvValueTooLarge);
}
Ok(SpiClkConfig {
prescale_val,
scrdv: (div - 1) as u8,
})
}
#[inline]
pub fn clk_div_for_target_clock(spi_clk: impl Into<Hertz>, clocks: &Clocks) -> Option<u16> {
let spi_clk = spi_clk.into();
if spi_clk > clocks.apb1() {
return None;
}
// Step 1: Calculate raw divider.
let raw_div = clocks.apb1().raw() / spi_clk.raw();
let remainder = clocks.apb1().raw() % spi_clk.raw();
// Step 2: Round up if necessary.
let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
raw_div + 1
} else {
raw_div
};
if rounded_div % 2 != 0 {
// Take slower clock conservatively.
rounded_div += 1;
}
if rounded_div > u16::MAX as u32 {
return None;
}
Some(rounded_div as u16)
}
impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word> 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_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
let val = spi_clk_config_from_div(div)?;
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(())
}
/*
#[inline] #[inline]
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) { pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
let clk_prescale = let clk_prescale =
@ -586,7 +431,6 @@ where
.clkprescale() .clkprescale()
.write(|w| unsafe { w.bits(clk_prescale) }); .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) {
@ -597,11 +441,6 @@ where
}); });
} }
#[inline]
pub fn spi_instance(&self) -> &SpiInstance {
&self.spi
}
#[inline] #[inline]
pub fn clear_tx_fifo(&self) { pub fn clear_tx_fifo(&self) {
self.spi.fifo_clr().write(|w| w.txfifo().set_bit()); self.spi.fifo_clr().write(|w| w.txfifo().set_bit());
@ -647,13 +486,9 @@ where
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>( pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
&mut self, &mut self,
transfer_cfg: &TransferConfig<HwCs>, transfer_cfg: &TransferConfig<HwCs>,
) -> Result<(), SpiClkConfigError> { ) {
if let Some(trans_clk_div) = transfer_cfg.clk_div { self.cfg_clock(transfer_cfg.spi_clk);
self.cfg_clock_from_div(trans_clk_div)?; self.cfg_mode(transfer_cfg.mode);
}
if let Some(mode) = transfer_cfg.mode {
self.cfg_mode(mode);
}
self.blockmode = transfer_cfg.blockmode; self.blockmode = transfer_cfg.blockmode;
self.spi.ctrl1().modify(|_, w| { self.spi.ctrl1().modify(|_, w| {
if transfer_cfg.sod { if transfer_cfg.sod {
@ -673,7 +508,6 @@ where
} }
w w
}); });
Ok(())
} }
/// Sends a word to the slave /// Sends a word to the slave
@ -767,44 +601,43 @@ where
/// to be done once. /// to be done once.
/// * `syscfg` - Can be passed optionally to enable the peripheral clock /// * `syscfg` - Can be passed optionally to enable the peripheral clock
pub fn new( pub fn new(
syscfg: &mut pac::Sysconfig,
clocks: &crate::clock::Clocks,
spi: SpiI, spi: SpiI,
pins: (Sck, Miso, Mosi), pins: (Sck, Miso, Mosi),
clocks: &crate::clock::Clocks,
spi_cfg: SpiConfig, spi_cfg: SpiConfig,
syscfg: &mut pac::Sysconfig,
transfer_cfg: Option<&ErasedTransferConfig>, transfer_cfg: Option<&ErasedTransferConfig>,
) -> Result<Self, SpiClkConfigError> { ) -> Self {
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_div, ser_clock_rate_div,
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 mode = embedded_hal::spi::MODE_0;
let mut clk_prescale = 0x02;
let mut ss = 0; let mut ss = 0;
let mut init_blockmode = false; let mut init_blockmode = false;
let apb1_clk = clocks.apb1(); let apb1_clk = clocks.apb1();
if let Some(transfer_cfg) = transfer_cfg { if let Some(transfer_cfg) = transfer_cfg {
if let Some(mode) = transfer_cfg.mode { mode = transfer_cfg.mode;
init_mode = mode; clk_prescale =
} apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1));
//self.cfg_clock_from_div(transfer_cfg.clk_div);
if transfer_cfg.hw_cs != HwChipSelectId::Invalid { if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
ss = transfer_cfg.hw_cs as u8; ss = transfer_cfg.hw_cs as u8;
} }
init_blockmode = transfer_cfg.blockmode; 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(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(spi_clk_cfg.scrdv); w.scrdv().bits(ser_clock_rate_div);
// 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);
@ -819,17 +652,16 @@ where
w.blockmode().bit(init_blockmode); w.blockmode().bit(init_blockmode);
unsafe { w.ss().bits(ss) } unsafe { w.ss().bits(ss) }
}); });
spi.clkprescale()
.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();
w.txfifo().set_bit() w.txfifo().set_bit()
}); });
spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) });
// 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());
Ok(Spi { Spi {
inner: SpiBase { inner: SpiBase {
spi, spi,
cfg: spi_cfg, cfg: spi_cfg,
@ -839,39 +671,36 @@ where
word: PhantomData, word: PhantomData,
}, },
pins, pins,
})
}
delegate::delegate! {
to self.inner {
#[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
#[inline]
pub fn spi_instance(&self) -> &SpiI;
#[inline]
pub fn cfg_mode(&mut self, mode: Mode);
#[inline]
pub fn perid(&self) -> u32;
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
&mut self, transfer_cfg: &TransferConfig<HwCs>
) -> Result<(), SpiClkConfigError>;
} }
} }
#[inline] #[inline]
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
self.inner.cfg_clock(spi_clk);
}
#[inline]
pub fn cfg_mode(&mut self, mode: Mode) {
self.inner.cfg_mode(mode);
}
pub fn set_fill_word(&mut self, fill_word: Word) { pub fn set_fill_word(&mut self, fill_word: Word) {
self.inner.fill_word = fill_word; self.inner.fill_word = fill_word;
} }
#[inline]
pub fn fill_word(&self) -> Word { pub fn fill_word(&self) -> Word {
self.inner.fill_word self.inner.fill_word
} }
#[inline]
pub fn perid(&self) -> u32 {
self.inner.perid()
}
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(&mut self, transfer_cfg: &TransferConfig<HwCs>) {
self.inner.cfg_transfer(transfer_cfg);
}
/// Releases the SPI peripheral and associated pins /// Releases the SPI peripheral and associated pins
pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) { pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) {
(self.inner.spi, self.pins, self.inner.cfg) (self.inner.spi, self.pins, self.inner.cfg)

View File

@ -2,7 +2,7 @@
//! //!
//! ## Examples //! ## Examples
//! //!
//! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs) //! TODO.
use core::cell::Cell; use core::cell::Cell;
use cortex_m::interrupt::Mutex; use cortex_m::interrupt::Mutex;
@ -10,17 +10,12 @@ use cortex_m::interrupt::Mutex;
use crate::clock::Clocks; use crate::clock::Clocks;
use crate::gpio::{ use crate::gpio::{
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14, AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1, PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3,
PD10, PD11, PD12, PD13, PD14, PD15, PE0, PE1, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE6, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, PD0, PD1, PD10, PD11, PD12, PD13, PD14, PD15, PD2, PD3,
PE7, PE8, PE9, PF0, PF1, PF11, PF12, PF13, PF14, PF15, PF9, PG0, PG1, PG2, PG3, PG6, PD4, PD5, PD6, PD7, PD8, PD9, PE0, PE1, PE10, PE11, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5,
PE6, PE7, PE8, PE9, PF0, PF1, PF10, PF11, PF12, PF13, PF14, PF15, PF2, PF3, PF4, PF5, PF6, PF7,
PF8, PF9, PG0, PG1, PG2, PG3, PG6,
}; };
#[cfg(not(feature = "va41628"))]
use crate::gpio::{
PB10, PB11, PB5, PB6, PB7, PB8, PB9, PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PE10,
PE11, PF10, PF2, PF3, PF4, PF5, PF6, PF7, PF8,
};
use crate::time::Hertz; use crate::time::Hertz;
use crate::typelevel::Sealed; use crate::typelevel::Sealed;
use crate::{disable_interrupt, prelude::*}; use crate::{disable_interrupt, prelude::*};
@ -201,11 +196,10 @@ pub trait ValidTimAndPin<Pin: TimPin, Tim: ValidTim>: Sealed {}
macro_rules! valid_pin_and_tims { macro_rules! valid_pin_and_tims {
( (
$( $(
($PinX:ident, $AltFunc:ident, $TimX:path $(, $meta: meta)?), ($PinX:ident, $AltFunc:ident, $TimX:path),
)+ )+
) => { ) => {
$( $(
$(#[$meta])?
impl TimPin for Pin<$PinX, $AltFunc> impl TimPin for Pin<$PinX, $AltFunc>
where where
$PinX: PinId, $PinX: PinId,
@ -213,7 +207,6 @@ macro_rules! valid_pin_and_tims {
const DYN: DynPinId = $PinX::DYN; const DYN: DynPinId = $PinX::DYN;
} }
$(#[$meta])?
impl< impl<
PinInstance: TimPin, PinInstance: TimPin,
Tim: ValidTim Tim: ValidTim
@ -224,7 +217,6 @@ macro_rules! valid_pin_and_tims {
{ {
} }
$(#[$meta])?
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {} impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
)+ )+
}; };
@ -250,29 +242,29 @@ valid_pin_and_tims!(
(PB2, AltFunc2, pac::Tim15), (PB2, AltFunc2, pac::Tim15),
(PB3, AltFunc2, pac::Tim14), (PB3, AltFunc2, pac::Tim14),
(PB4, AltFunc2, pac::Tim13), (PB4, AltFunc2, pac::Tim13),
(PB5, AltFunc2, pac::Tim12, cfg(not(feature = "va41628"))), (PB5, AltFunc2, pac::Tim12),
(PB6, AltFunc2, pac::Tim11, cfg(not(feature = "va41628"))), (PB6, AltFunc2, pac::Tim11),
(PB7, AltFunc2, pac::Tim10, cfg(not(feature = "va41628"))), (PB7, AltFunc2, pac::Tim10),
(PB8, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))), (PB8, AltFunc2, pac::Tim9),
(PB9, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))), (PB9, AltFunc2, pac::Tim8),
(PB10, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))), (PB10, AltFunc2, pac::Tim7),
(PB11, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))), (PB11, AltFunc2, pac::Tim6),
(PB12, AltFunc2, pac::Tim5), (PB12, AltFunc2, pac::Tim5),
(PB13, AltFunc2, pac::Tim4), (PB13, AltFunc2, pac::Tim4),
(PB14, AltFunc2, pac::Tim3), (PB14, AltFunc2, pac::Tim3),
(PB15, AltFunc2, pac::Tim2), (PB15, AltFunc2, pac::Tim2),
(PC0, AltFunc2, pac::Tim1), (PC0, AltFunc2, pac::Tim1),
(PC1, AltFunc2, pac::Tim0), (PC1, AltFunc2, pac::Tim0),
(PD0, AltFunc2, pac::Tim0, cfg(not(feature = "va41628"))), (PD0, AltFunc2, pac::Tim0),
(PD1, AltFunc2, pac::Tim1, cfg(not(feature = "va41628"))), (PD1, AltFunc2, pac::Tim1),
(PD2, AltFunc2, pac::Tim2, cfg(not(feature = "va41628"))), (PD2, AltFunc2, pac::Tim2),
(PD3, AltFunc2, pac::Tim3, cfg(not(feature = "va41628"))), (PD3, AltFunc2, pac::Tim3),
(PD4, AltFunc2, pac::Tim4, cfg(not(feature = "va41628"))), (PD4, AltFunc2, pac::Tim4),
(PD5, AltFunc2, pac::Tim5, cfg(not(feature = "va41628"))), (PD5, AltFunc2, pac::Tim5),
(PD6, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))), (PD6, AltFunc2, pac::Tim6),
(PD7, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))), (PD7, AltFunc2, pac::Tim7),
(PD8, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))), (PD8, AltFunc2, pac::Tim8),
(PD9, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))), (PD9, AltFunc2, pac::Tim9),
(PD10, AltFunc2, pac::Tim10), (PD10, AltFunc2, pac::Tim10),
(PD11, AltFunc2, pac::Tim11), (PD11, AltFunc2, pac::Tim11),
(PD12, AltFunc2, pac::Tim12), (PD12, AltFunc2, pac::Tim12),
@ -289,23 +281,23 @@ valid_pin_and_tims!(
(PE7, AltFunc2, pac::Tim23), (PE7, AltFunc2, pac::Tim23),
(PE8, AltFunc3, pac::Tim16), (PE8, AltFunc3, pac::Tim16),
(PE9, AltFunc3, pac::Tim17), (PE9, AltFunc3, pac::Tim17),
(PE10, AltFunc3, pac::Tim18, cfg(not(feature = "va41628"))), (PE10, AltFunc3, pac::Tim18),
(PE11, AltFunc3, pac::Tim19, cfg(not(feature = "va41628"))), (PE11, AltFunc3, pac::Tim19),
(PE12, AltFunc3, pac::Tim20), (PE12, AltFunc3, pac::Tim20),
(PE13, AltFunc3, pac::Tim21), (PE13, AltFunc3, pac::Tim21),
(PE14, AltFunc3, pac::Tim22), (PE14, AltFunc3, pac::Tim22),
(PE15, AltFunc3, pac::Tim23), (PE15, AltFunc3, pac::Tim23),
(PF0, AltFunc3, pac::Tim0), (PF0, AltFunc3, pac::Tim0),
(PF1, AltFunc3, pac::Tim1), (PF1, AltFunc3, pac::Tim1),
(PF2, AltFunc3, pac::Tim2, cfg(not(feature = "va41628"))), (PF2, AltFunc3, pac::Tim2),
(PF3, AltFunc3, pac::Tim3, cfg(not(feature = "va41628"))), (PF3, AltFunc3, pac::Tim3),
(PF4, AltFunc3, pac::Tim4, cfg(not(feature = "va41628"))), (PF4, AltFunc3, pac::Tim4),
(PF5, AltFunc3, pac::Tim5, cfg(not(feature = "va41628"))), (PF5, AltFunc3, pac::Tim5),
(PF6, AltFunc3, pac::Tim6, cfg(not(feature = "va41628"))), (PF6, AltFunc3, pac::Tim6),
(PF7, AltFunc3, pac::Tim7, cfg(not(feature = "va41628"))), (PF7, AltFunc3, pac::Tim7),
(PF8, AltFunc3, pac::Tim8, cfg(not(feature = "va41628"))), (PF8, AltFunc3, pac::Tim8),
(PF9, AltFunc3, pac::Tim9), (PF9, AltFunc3, pac::Tim9),
(PF10, AltFunc3, pac::Tim10, cfg(not(feature = "va41628"))), (PF10, AltFunc3, pac::Tim10),
(PF11, AltFunc3, pac::Tim11), (PF11, AltFunc3, pac::Tim11),
(PF12, AltFunc3, pac::Tim12), (PF12, AltFunc3, pac::Tim12),
(PF13, AltFunc2, pac::Tim19), (PF13, AltFunc2, pac::Tim19),
@ -467,10 +459,7 @@ unsafe impl TimRegInterface for TimDynRegister {
// Timers // Timers
//================================================================================================== //==================================================================================================
/// Hardware timers. /// Hardware timers
///
/// These timers also implement the [embedded_hal::delay::DelayNs] trait and can be used to delay
/// with a higher resolution compared to the Cortex-M systick delays.
pub struct CountdownTimer<TIM: ValidTim> { pub struct CountdownTimer<TIM: ValidTim> {
tim: TimRegister<TIM>, tim: TimRegister<TIM>,
curr_freq: Hertz, curr_freq: Hertz,

View File

@ -3,61 +3,38 @@
//! ## Examples //! ## Examples
//! //!
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs) //! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
use core::marker::PhantomData;
use core::ops::Deref; use core::ops::Deref;
use embedded_hal_nb::serial::Read; use embedded_hal_nb::serial::Read;
use fugit::RateExtU32; use fugit::RateExtU32;
use crate::clock::{Clocks, PeripheralSelect, SyscfgExt}; use crate::clock::{Clocks, PeripheralSelect, SyscfgExt};
use crate::gpio::PF13; use crate::gpio::{AltFunc1, Pin, PD11, PD12, PE2, PE3, PF11, PF12, PF8, PF9, PG0, PG1};
use crate::time::Hertz; use crate::time::Hertz;
use crate::{disable_interrupt, enable_interrupt}; use crate::{disable_interrupt, enable_interrupt};
use crate::{ use crate::{
gpio::{ gpio::{AltFunc2, AltFunc3, PA2, PA3, PB14, PB15, PC14, PC15, PC4, PC5},
AltFunc1, AltFunc2, AltFunc3, Pin, PA2, PA3, PB14, PB15, PC14, PC4, PC5, PD11, PD12, PE2,
PE3, PF12, PF9, PG0, PG1,
},
pac::{self, uart0 as uart_base, Uart0, Uart1, Uart2}, pac::{self, uart0 as uart_base, Uart0, Uart1, Uart2},
}; };
#[cfg(not(feature = "va41628"))]
use crate::gpio::{PC15, PF8};
//================================================================================================== //==================================================================================================
// Type-Level support // Type-Level support
//================================================================================================== //==================================================================================================
pub trait RxPin<Uart> {} pub trait TxRxPins<Uart> {}
pub trait TxPin<Uart> {}
impl TxPin<Uart0> for Pin<PA2, AltFunc3> {} impl TxRxPins<Uart0> for (Pin<PA2, AltFunc3>, Pin<PA3, AltFunc3>) {}
impl RxPin<Uart0> for Pin<PA3, AltFunc3> {} impl TxRxPins<Uart0> for (Pin<PC4, AltFunc2>, Pin<PC5, AltFunc2>) {}
impl TxRxPins<Uart0> for (Pin<PE2, AltFunc3>, Pin<PE3, AltFunc3>) {}
impl TxRxPins<Uart0> for (Pin<PG0, AltFunc1>, Pin<PG1, AltFunc1>) {}
impl TxPin<Uart0> for Pin<PC4, AltFunc2> {} impl TxRxPins<Uart1> for (Pin<PB14, AltFunc3>, Pin<PB15, AltFunc3>) {}
impl RxPin<Uart0> for Pin<PC5, AltFunc2> {} impl TxRxPins<Uart1> for (Pin<PD11, AltFunc3>, Pin<PD12, AltFunc3>) {}
impl TxRxPins<Uart1> for (Pin<PF11, AltFunc1>, Pin<PF12, AltFunc1>) {}
impl TxPin<Uart0> for Pin<PE2, AltFunc3> {} impl TxRxPins<Uart2> for (Pin<PC14, AltFunc2>, Pin<PC15, AltFunc2>) {}
impl RxPin<Uart0> for Pin<PE3, AltFunc3> {} impl TxRxPins<Uart2> for (Pin<PF8, AltFunc1>, Pin<PF9, AltFunc1>) {}
impl TxPin<Uart0> for Pin<PG0, AltFunc1> {}
impl RxPin<Uart0> for Pin<PG1, AltFunc1> {}
impl TxPin<Uart1> for Pin<PB14, AltFunc3> {}
impl RxPin<Uart1> for Pin<PB15, AltFunc3> {}
impl TxPin<Uart1> for Pin<PD11, AltFunc3> {}
impl RxPin<Uart1> for Pin<PD12, AltFunc3> {}
impl TxPin<Uart1> for Pin<PF12, AltFunc1> {}
impl RxPin<Uart1> for Pin<PF13, AltFunc1> {}
impl TxPin<Uart2> for Pin<PC14, AltFunc2> {}
#[cfg(not(feature = "va41628"))]
impl RxPin<Uart2> for Pin<PC15, AltFunc2> {}
#[cfg(not(feature = "va41628"))]
impl TxPin<Uart2> for Pin<PF8, AltFunc1> {}
impl RxPin<Uart2> for Pin<PF9, AltFunc1> {}
//================================================================================================== //==================================================================================================
// Regular Definitions // Regular Definitions
@ -335,23 +312,27 @@ pub struct UartWithIrqBase<UART> {
/// Serial receiver /// Serial receiver
pub struct Rx<Uart> { pub struct Rx<Uart> {
uart: Uart, _usart: PhantomData<Uart>,
} }
/// Serial transmitter /// Serial transmitter
pub struct Tx<Uart> { pub struct Tx<Uart> {
uart: Uart, _usart: PhantomData<Uart>,
} }
impl<Uart: Instance> Rx<Uart> { impl<Uart> Rx<Uart> {
fn new(uart: Uart) -> Self { fn new() -> Self {
Self { uart } Self {
_usart: PhantomData,
}
} }
} }
impl<Uart> Tx<Uart> { impl<Uart> Tx<Uart> {
fn new(uart: Uart) -> Self { fn new() -> Self {
Self { uart } Self {
_usart: PhantomData,
}
} }
} }
@ -361,12 +342,6 @@ pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
const IRQ_RX: pac::Interrupt; const IRQ_RX: pac::Interrupt;
const IRQ_TX: pac::Interrupt; const IRQ_TX: pac::Interrupt;
/// Retrieve the peripheral structure.
///
/// # Safety
///
/// This circumvents the safety guarantees of the HAL.
unsafe fn steal() -> Self;
fn ptr() -> *const uart_base::RegisterBlock; fn ptr() -> *const uart_base::RegisterBlock;
} }
@ -376,9 +351,6 @@ impl Instance for Uart0 {
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX; const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX; const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
unsafe fn steal() -> Self {
pac::Peripherals::steal().uart0
}
fn ptr() -> *const uart_base::RegisterBlock { fn ptr() -> *const uart_base::RegisterBlock {
Uart0::ptr() as *const _ Uart0::ptr() as *const _
} }
@ -390,9 +362,6 @@ impl Instance for Uart1 {
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX; const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX; const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
unsafe fn steal() -> Self {
pac::Peripherals::steal().uart1
}
fn ptr() -> *const uart_base::RegisterBlock { fn ptr() -> *const uart_base::RegisterBlock {
Uart1::ptr() as *const _ Uart1::ptr() as *const _
} }
@ -404,9 +373,6 @@ impl Instance for Uart2 {
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX; const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX; const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
unsafe fn steal() -> Self {
pac::Peripherals::steal().uart2
}
fn ptr() -> *const uart_base::RegisterBlock { fn ptr() -> *const uart_base::RegisterBlock {
Uart2::ptr() as *const _ Uart2::ptr() as *const _
} }
@ -545,12 +511,10 @@ impl<Uart: Instance> UartBase<Uart> {
} }
} }
impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstance: Instance> impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
Uart<UartInstance, (TxPinInst, RxPinInst)>
{
pub fn new( pub fn new(
uart: UartInstance, uart: UartInstance,
pins: (TxPinInst, RxPinInst), pins: Pins,
config: impl Into<Config>, config: impl Into<Config>,
syscfg: &mut va416xx::Sysconfig, syscfg: &mut va416xx::Sysconfig,
clocks: &Clocks, clocks: &Clocks,
@ -561,8 +525,8 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
Uart { Uart {
inner: UartBase { inner: UartBase {
uart, uart,
tx: Tx::new(unsafe { UartInstance::steal() }), tx: Tx::new(),
rx: Rx::new(unsafe { UartInstance::steal() }), rx: Rx::new(),
}, },
pins, pins,
} }
@ -571,7 +535,7 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
pub fn new_with_clock_freq( pub fn new_with_clock_freq(
uart: UartInstance, uart: UartInstance,
pins: (TxPinInst, RxPinInst), pins: Pins,
config: impl Into<Config>, config: impl Into<Config>,
syscfg: &mut va416xx::Sysconfig, syscfg: &mut va416xx::Sysconfig,
clock: impl Into<Hertz>, clock: impl Into<Hertz>,
@ -580,8 +544,8 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
Uart { Uart {
inner: UartBase { inner: UartBase {
uart, uart,
tx: Tx::new(unsafe { UartInstance::steal() }), tx: Tx::new(),
rx: Rx::new(unsafe { UartInstance::steal() }), rx: Rx::new(),
}, },
pins, pins,
} }
@ -603,7 +567,7 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
/// If the IRQ capabilities of the peripheral are used, the UART needs to be converted /// If the IRQ capabilities of the peripheral are used, the UART needs to be converted
/// with this function /// with this function
pub fn into_uart_with_irq(self) -> UartWithIrq<UartInstance, (TxPinInst, RxPinInst)> { pub fn into_uart_with_irq(self) -> UartWithIrq<UartInstance, Pins> {
let (inner, pins) = self.downgrade_internal(); let (inner, pins) = self.downgrade_internal();
UartWithIrq { UartWithIrq {
pins, pins,
@ -644,7 +608,7 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
} }
} }
fn downgrade_internal(self) -> (UartBase<UartInstance>, (TxPinInst, RxPinInst)) { fn downgrade_internal(self) -> (UartBase<UartInstance>, Pins) {
let base = UartBase { let base = UartBase {
uart: self.inner.uart, uart: self.inner.uart,
tx: self.inner.tx, tx: self.inner.tx,
@ -661,41 +625,11 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
} }
} }
pub fn release(self) -> (UartInstance, (TxPinInst, RxPinInst)) { pub fn release(self) -> (UartInstance, Pins) {
(self.inner.release(), self.pins) (self.inner.release(), self.pins)
} }
} }
impl<Uart: Instance> Rx<Uart> {
/// Direct access to the peripheral structure.
///
/// # Safety
///
/// You must ensure that only registers related to the operation of the RX side are used.
pub unsafe fn uart(&self) -> &Uart {
&self.uart
}
pub fn clear_fifo(&self) {
self.uart.fifo_clr().write(|w| w.rxfifo().set_bit());
}
}
impl<Uart: Instance> Tx<Uart> {
/// Direct access to the peripheral structure.
///
/// # Safety
///
/// You must ensure that only registers related to the operation of the TX side are used.
pub unsafe fn uart(&self) -> &Uart {
&self.uart
}
pub fn clear_fifo(&self) {
self.uart.fifo_clr().write(|w| w.txfifo().set_bit());
}
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct IrqUartError { pub struct IrqUartError {
overflow: bool, overflow: bool,

View File

@ -13,16 +13,11 @@ use crate::{disable_interrupt, enable_interrupt};
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551; pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
/// Watchdog peripheral driver. pub struct WdtController {
pub struct Wdt {
clock_freq: Hertz, clock_freq: Hertz,
wdt: pac::WatchDog, wdt: pac::WatchDog,
} }
/// Type alias for backwards compatibility
#[deprecated(since = "0.2.0", note = "Please use `Wdt` instead")]
pub type WdtController = Wdt;
/// Enable the watchdog interrupt /// Enable the watchdog interrupt
/// ///
/// # Safety /// # Safety
@ -38,8 +33,9 @@ pub fn disable_wdt_interrupts() {
disable_interrupt(pac::Interrupt::WATCHDOG) disable_interrupt(pac::Interrupt::WATCHDOG)
} }
impl Wdt { impl WdtController {
pub fn new( pub fn new(
&self,
syscfg: &mut pac::Sysconfig, syscfg: &mut pac::Sysconfig,
wdt: pac::WatchDog, wdt: pac::WatchDog,
clocks: &Clocks, clocks: &Clocks,

View File

@ -17,8 +17,7 @@ embedded-hal = "1"
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
path = "../va416xx-hal" path = "../va416xx-hal"
features = ["va41630"] version = "0.1.0"
version = "0.2.0"
[dependencies.lis2dh12] [dependencies.lis2dh12]
git = "https://github.com/us-irs/lis2dh12.git" git = "https://github.com/us-irs/lis2dh12.git"