28 Commits

Author SHA1 Message Date
ce89f9a10d changelog
Some checks failed
Rust/va416xx-rs/pipeline/pr-main There was a failure building this commit
2024-09-11 20:49:42 +02:00
896fdb839f Bootloader and Flashloader App
Some checks failed
Rust/va416xx-rs/pipeline/pr-main There was a failure building this commit
2024-09-11 20:44:10 +02:00
deebc88042 Merge pull request 'Device specific support and UART Improvements' (#23) from device-specific-support into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #23
2024-07-19 06:47:25 +02:00
a326e5d058 Device specific support and UART Improvements
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-18 21:32:36 -07:00
dd5beb47f0 README typo
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-04 18:37:47 +02:00
8d84c61327 Merge pull request 'bugfix for updated DMA example' (#20) from dma-bugfix into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #20
2024-07-04 15:52:13 +02:00
5b1e30ea15 bugfix for updated DMA example
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-04 15:34:09 +02:00
baf900107d Merge pull request 'Basic DMA HAL' (#19) from dma-support into main
Some checks failed
Rust/va416xx-rs/pipeline/head There was a failure building this commit
Reviewed-on: #19
2024-07-03 22:54:10 +02:00
f47ba0828e changelog
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...
2024-07-03 22:52:07 +02:00
d6efff6832 Merge branch 'main' into dma-support
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
2024-07-03 22:48:33 +02:00
895ad438aa Merge pull request 'small improvements and fixes' (#18) from smaller-improvements-and-fixes into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #18
2024-07-03 22:48:08 +02:00
1c7affc4c5 Basic DMA HAL
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-03 15:40:14 +02:00
a2a4b5ff01 small improvements and fixes
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-01 16:02:10 +02:00
a2b43bff06 Merge pull request 'use released version of the PAC' (#17) from prep-hal-release into main
Some checks failed
Rust/va416xx-rs/pipeline/head There was a failure building this commit
Reviewed-on: #17
2024-07-01 15:03:13 +02:00
081d978360 Merge branch 'prep-hal-release' of egit.irs.uni-stuttgart.de:rust/va416xx-rs into prep-hal-release
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...
2024-07-01 15:00:25 +02:00
a2bb6b9227 add badges for HAL 2024-07-01 14:59:51 +02:00
5575e04b91 Merge branch 'main' into prep-hal-release
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...
2024-07-01 14:58:43 +02:00
6a83d2e507 use releases version of the PAC
Some checks are pending
Rust/va416xx-rs/pipeline/head Build queued...
2024-07-01 14:58:18 +02:00
aee7e8aa12 Merge pull request 'prepare HAL release' (#16) from prep-hal-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #16
2024-07-01 14:55:56 +02:00
1bd39624ff prepare HAL release
Some checks are pending
Rust/va416xx-rs/pipeline/head Build queued...
2024-07-01 14:54:47 +02:00
3517fb6ec2 Merge pull request 'finished basic ADC and DAC HAL' (#15) from adc-and-dac into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #15
2024-07-01 14:50:05 +02:00
e1011e3600 delete commented code
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...
2024-07-01 14:49:34 +02:00
5f50892d8a finished basic ADC and DAC HAL
Some checks are pending
Rust/va416xx-rs/pipeline/head Build started...
2024-07-01 14:47:08 +02:00
78e6c52835 Merge pull request 'adapt dev variant' (#13) from adapt-dev-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #13
2024-07-01 10:17:16 +02:00
d458926e29 Merge branch 'main' into adapt-dev-release 2024-07-01 10:17:05 +02:00
8fa8137d79 Merge pull request 'remove reset in jlink.gdb script, add Embed.toml' (#14) from flashing-update into main
Reviewed-on: #14
2024-07-01 10:16:58 +02:00
2b6c45dbb7 adapt dev variant
Some checks are pending
Rust/va416xx-rs/pipeline/head Build started...
2024-07-01 10:15:01 +02:00
118341cabe remove reset in jlink.gdb script, add Embed.toml
Some checks are pending
Rust/va416xx-rs/pipeline/head Build queued...
2024-06-30 18:17:00 +02:00
60 changed files with 4138 additions and 468 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@ -1,18 +1,25 @@
[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
debug = 2 debug = 2
debug-assertions = true # <- debug-assertions = true # <-
incremental = false incremental = false
opt-level = 'z' # <- # This is problematic for stepping..
# opt-level = 'z' # <-
overflow-checks = true # <- overflow-checks = true # <-
# cargo build/run --release # cargo build/run --release
@ -24,3 +31,12 @@ incremental = false
lto = 'fat' lto = 'fat'
opt-level = 3 # <- opt-level = 3 # <-
overflow-checks = false # <- overflow-checks = false # <-
[profile.small]
inherits = "release"
codegen-units = 1
debug-assertions = false # <-
lto = true
opt-level = 'z' # <-
overflow-checks = false # <-
# strip = true # Automatically strip symbols from the binary.

5
Embed.toml Normal file
View File

@ -0,0 +1,5 @@
[default.general]
chip = "VA416xx"
[default.rtt]
enabled = true

View File

@ -3,7 +3,7 @@
Vorago VA416xx Rust Support Vorago VA416xx Rust Support
========= =========
This crate collection provided support to write Rust applications for the VA416XX family This crate collection provides support to write Rust applications for the VA416XX family
of devices. of devices.
## List of crates ## List of crates

View File

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

16
bootloader/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[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"
version = "0.1.0"

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

@ -0,0 +1,339 @@
//! 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,14 +5,39 @@ 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

@ -0,0 +1,73 @@
//! Simple ADC example.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::{
adc::{Adc, ChannelSelect, ChannelValue, MultiChannelSelect},
pac,
prelude::*,
timer::CountdownTimer,
};
// Quite spammy and disabled by default.
const ENABLE_BUF_PRINTOUT: bool = false;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx ADC example");
let mut dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.unwrap();
let adc = Adc::new_with_channel_tag(&mut dp.sysconfig, dp.adc, &clocks);
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
loop {
let single_value = adc
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::TempSensor)
.expect("reading single channel value failed");
rprintln!(
"Read single ADC value on temperature sensor channel: {:?}",
single_value
);
let read_num = adc
.sweep_and_read_range(0, 7, &mut read_buf)
.expect("ADC range read failed");
if ENABLE_BUF_PRINTOUT {
rprintln!("ADC Range Read (0-8) read {} values", read_num);
rprintln!("ADC Range Read (0-8): {:?}", read_buf);
}
assert_eq!(read_num, 8);
for (idx, ch_val) in read_buf.iter().enumerate() {
assert_eq!(
ch_val.channel(),
ChannelSelect::try_from(idx as u8).unwrap()
);
}
adc.sweep_and_read_multiselect(
MultiChannelSelect::AnIn0 | MultiChannelSelect::AnIn2 | MultiChannelSelect::TempSensor,
&mut read_buf[0..3],
)
.expect("ADC multiselect read failed");
if ENABLE_BUF_PRINTOUT {
rprintln!("ADC Multiselect Read(0, 2 and 10): {:?}", &read_buf[0..3]);
}
assert_eq!(read_buf[0].channel(), ChannelSelect::AnIn0);
assert_eq!(read_buf[1].channel(), ChannelSelect::AnIn2);
assert_eq!(read_buf[2].channel(), ChannelSelect::TempSensor);
delay_provider.delay_ms(500);
}
}

View File

@ -16,7 +16,6 @@ fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap(); let mut dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(&mut dp.sysconfig, dp.portg); let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output(); let mut led = portg.pg5.into_readable_push_pull_output();
//let mut delay = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0);
loop { loop {
cortex_m::asm::delay(2_000_000); cortex_m::asm::delay(2_000_000);
led.toggle().ok(); led.toggle().ok();

View File

@ -0,0 +1,78 @@
//! Simple DAC-ADC example.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::{adc::Adc, dac::Dac, pac, prelude::*, timer::CountdownTimer};
const DAC_INCREMENT: u16 = 256;
#[derive(Debug, PartialEq, Eq)]
pub enum AppMode {
// Measurements on AIN0.
AdcOnly,
// AOUT0. You can use a multi-meter to measure the changing voltage on the pin.
DacOnly,
/// AOUT0 needs to be wired to AIN0.
DacAndAdc,
}
const APP_MODE: AppMode = AppMode::DacAndAdc;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx DAC/ADC example");
let mut dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.unwrap();
let mut dac = None;
if APP_MODE == AppMode::DacOnly || APP_MODE == AppMode::DacAndAdc {
dac = Some(Dac::new(
&mut dp.sysconfig,
dp.dac0,
va416xx_hal::dac::DacSettling::Apb2Times100,
&clocks,
));
}
let mut adc = None;
if APP_MODE == AppMode::AdcOnly || APP_MODE == AppMode::DacAndAdc {
adc = Some(Adc::new(&mut dp.sysconfig, dp.adc, &clocks));
}
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut current_val = 0;
loop {
if let Some(dac) = &dac {
rprintln!("loading DAC with value {}", current_val);
dac.load_and_trigger_manually(current_val)
.expect("loading DAC value failed");
if current_val + DAC_INCREMENT >= 4096 {
current_val = 0;
} else {
current_val += DAC_INCREMENT;
}
}
if let Some(dac) = &dac {
// This should never block.
nb::block!(dac.is_settled()).unwrap();
}
if let Some(adc) = &adc {
let ch_value = adc
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
.expect("reading ADC channel 0 failed");
rprintln!("Received channel value {:?}", ch_value);
}
delay_provider.delay_ms(500);
}
}

View File

@ -0,0 +1,275 @@
//! Simple DMA example
#![no_main]
#![no_std]
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
use va416xx_hal::pwm::CountdownTimer;
use va416xx_hal::{
pac::{self, interrupt},
prelude::*,
};
static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
// Place the DMA control block into SRAM1 statically. This section needs to be defined in
// memory.x
#[link_section = ".sram1"]
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]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx DMA example");
let mut dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
// statically.
let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe {
core::ptr::addr_of_mut!(DMA_CTRL_BLOCK)
})
.expect("error creating DMA");
let (mut dma0, _, _, _) = dma.split();
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut src_buf_8_bit: [u8; 65] = [0; 65];
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
let mut src_buf_32_bit: [u32; 17] = [0; 17];
let mut dest_buf_32_bit: [u32; 17] = [0; 17];
loop {
// This example uses stack-allocated buffers.
transfer_example_8_bit(
&mut src_buf_8_bit,
&mut dest_buf_8_bit,
&mut dma0,
&mut delay_ms,
);
delay_ms.delay_ms(500);
// This example uses statically allocated buffers.
transfer_example_16_bit(&mut dma0, &mut delay_ms);
delay_ms.delay_ms(500);
transfer_example_32_bit(
&mut src_buf_32_bit,
&mut dest_buf_32_bit,
&mut dma0,
&mut delay_ms,
);
delay_ms.delay_ms(500);
}
}
fn transfer_example_8_bit(
src_buf: &mut [u8; 65],
dest_buf: &mut [u8; 65],
dma0: &mut DmaChannel,
delay_ms: &mut CountdownTimer<pac::Tim0>,
) {
(0..64).for_each(|i| {
src_buf[i] = i as u8;
});
cortex_m::interrupt::free(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
cortex_m::interrupt::free(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
unsafe {
dma0.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
.expect("error preparing transfer");
}
// Enable all interrupts.
// Safety: Not using mask based critical sections.
unsafe {
dma0.enable_done_interrupt();
dma0.enable_active_interrupt();
};
// Enable the individual channel.
dma0.enable();
// We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request();
// Use polling for completion status.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 8 bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
dma_done = true;
}
});
if dma_done {
rprintln!("8-bit transfer done");
break;
}
delay_ms.delay_ms(1);
}
(0..64).for_each(|i| {
assert_eq!(dest_buf[i], i as u8);
});
// Sentinel value, should be 0.
assert_eq!(dest_buf[64], 0);
dest_buf.fill(0);
}
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<pac::Tim0>) {
let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..33]) };
unsafe {
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
(0..32).for_each(|i| {
DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16;
});
}
cortex_m::interrupt::free(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
cortex_m::interrupt::free(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
unsafe {
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");
}
// Enable all interrupts.
// Safety: Not using mask based critical sections.
unsafe {
dma0.enable_done_interrupt();
dma0.enable_active_interrupt();
};
// Enable the individual channel.
dma0.enable();
// We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request();
// Use polling for completion status.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 16-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
dma_done = true;
}
});
if dma_done {
rprintln!("16-bit transfer done");
break;
}
delay_ms.delay_ms(1);
}
(0..32).for_each(|i| {
assert_eq!(
dest_buf_ref[i],
(i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16
);
});
// Sentinel value, should be 0.
assert_eq!(dest_buf_ref[32], 0);
dest_buf_ref.fill(0);
}
fn transfer_example_32_bit(
src_buf: &mut [u32; 17],
dest_buf: &mut [u32; 17],
dma0: &mut DmaChannel,
delay_ms: &mut CountdownTimer<pac::Tim0>,
) {
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
(0..16).for_each(|i| {
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
});
cortex_m::interrupt::free(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
cortex_m::interrupt::free(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
unsafe {
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
.expect("error preparing transfer");
}
// Enable all interrupts.
// Safety: Not using mask based critical sections.
unsafe {
dma0.enable_done_interrupt();
dma0.enable_active_interrupt();
};
// Enable the individual channel.
dma0.enable();
// We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request();
// Use polling for completion status.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 32-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
dma_done = true;
}
});
if dma_done {
rprintln!("32-bit transfer done");
break;
}
delay_ms.delay_ms(1);
}
(0..16).for_each(|i| {
assert_eq!(
dest_buf[i],
(i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32
);
});
// Sentinel value, should be 0.
assert_eq!(dest_buf[16], 0);
dest_buf.fill(0);
}
#[interrupt]
#[allow(non_snake_case)]
fn DMA_DONE0() {
// Notify the main loop that the DMA transfer is finished.
cortex_m::interrupt::free(|cs| {
DMA_DONE_FLAG.borrow(cs).set(true);
});
}
#[interrupt]
#[allow(non_snake_case)]
fn DMA_ACTIVE0() {
// Notify the main loop that the DMA 0 is active now.
cortex_m::interrupt::free(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(true);
});
}

View File

@ -0,0 +1,30 @@
//! 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,12 +9,13 @@ use embedded_hal::spi::{Mode, SpiBus, MODE_0};
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::spi::{Spi, TransferConfig}; use va416xx_hal::spi::{clk_div_for_target_clock, Spi, TransferConfig};
use va416xx_hal::{ use va416xx_hal::{
gpio::{PinsB, PinsC}, gpio::{PinsB, PinsC},
pac, pac,
prelude::*, prelude::*,
spi::SpiConfig, spi::SpiConfig,
time::Hertz,
}; };
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -56,21 +57,24 @@ fn main() -> ! {
pins_c.pc1.into_funsel_1(), pins_c.pc1.into_funsel_1(),
); );
let mut spi_cfg = SpiConfig::default(); let mut spi_cfg = SpiConfig::default().clk_div(
clk_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 = let transfer_cfg = TransferConfig::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false);
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::WdtController; use va416xx_hal::wdt::Wdt;
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,8 +43,7 @@ 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 = let mut wdt_ctrl = Wdt::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
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 {

1
flashloader/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/venv

42
flashloader/Cargo.toml Normal file
View File

@ -0,0 +1,42 @@
[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"
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.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"
version = "0.1.0"
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]

279
flashloader/image-loader.py Executable file
View File

@ -0,0 +1,279 @@
#!/usr/bin/env python3
from spacepackets.ecss.defs import PusService
import toml
import struct
import logging
import argparse
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 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
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,
)
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)
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
]
_LOGGER.info(
f"Sending memory write command for address {current_addr:#08x} and data with "
f"length {len(data)}"
)
next_packet = pack_memory_write_command(current_addr, data)
com_if.send(next_packet.pack())
current_addr += next_chunk_size
pos_in_segment += next_chunk_size
time.sleep(0.2)
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())
while True:
data_available = com_if.data_available(0.4)
if data_available:
reply = com_if.receive()
# TODO: Parse replies
print("Received replies: {}", reply)
break
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,
app_data=app_data,
)
if __name__ == "__main__":
main()

1
flashloader/loader.toml Normal file
View File

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

View File

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

2
flashloader/slot-a-blinky/.gitignore vendored Normal file
View File

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

View File

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

@ -0,0 +1,24 @@
/* 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

@ -0,0 +1,23 @@
//! 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();
}
}

2
flashloader/slot-b-blinky/.gitignore vendored Normal file
View File

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

View File

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

@ -0,0 +1,24 @@
/* 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

@ -0,0 +1,23 @@
//! 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();
}
}

9
flashloader/src/lib.rs Normal file
View File

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

435
flashloader/src/main.rs Normal file
View File

@ -0,0 +1,435 @@
//! 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_PACKET_SIZE: usize = 1024;
const MAX_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_PACKET_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();
}
}
}
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 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 spacepackets::ecss::PusServiceId;
use spacepackets::ecss::{tc::PusTcReader, PusPacket};
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>,
}
#[shared]
struct Shared {
decode_buffer_busy: bool,
decode_buf: [u8; MAX_PACKET_SIZE],
}
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);
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_PACKET_SIZE],
},
Local {
uart_rx: rx,
uart_tx: tx,
cobs_reader_state: CobsReaderStates::default(),
tc_tx,
tc_rx,
rom_spi: Some(cx.device.spi3),
},
)
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
#[task(
priority = 3,
local=[
read_buf: [u8;MAX_FRAME_SIZE] = [0; MAX_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 => todo!(),
uart::Error::ParityError => todo!(),
uart::Error::BreakCondition => todo!(),
uart::Error::TransferPending => todo!(),
uart::Error::BufferTooShort => todo!(),
}
}
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_FRAME_SIZE] = [0; MAX_FRAME_SIZE],
tc_rx,
rom_spi
],
shared=[decode_buffer_busy, decode_buf]
)]
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, _)) => {
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));
};
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 {
// 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));
log::info!("NVM operation done");
}
}
}
Err(e) => {
log::warn!("PUS TC error: {}", e);
}
}
}
}
#[task(
priority = 1,
local=[
uart_tx,
],
shared=[]
)]
async fn pus_tm_tx_handler(_cx: pus_tm_tx_handler::Context) {
loop {
Mono::delay(500.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,8 +1,6 @@
target remote localhost:2331 target remote localhost:2331
monitor halt monitor halt
# Reset is problematic on RevA, okay for RevB
monitor reset
# *try* to stop at the user entry point (it might be gone due to inlining) # *try* to stop at the user entry point (it might be gone due to inlining)
break main break main

View File

@ -22,7 +22,8 @@ variants:
range: range:
start: 0x0 start: 0x0
end: 0x40000 end: 0x40000
is_boot_memory: true access:
boot: true
cores: cores:
- main - main
- !Generic - !Generic

View File

@ -11,4 +11,13 @@ MEMORY
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ /* 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_0 can be used for all busses: Instruction, Data and System */
/* SRAM_1 only supports the system bus */ /* SRAM_1 only supports the system bus */
_stack_start = ORIGIN(RAM) + LENGTH(RAM) - 4; _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
};

24
scripts/memory_app_a.x Normal file
View File

@ -0,0 +1,24 @@
/* 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
};

24
scripts/memory_app_b.x Normal file
View File

@ -0,0 +1,24 @@
/* 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,38 +0,0 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# uncomment ONE of these three option to make `cargo run` start a GDB session
# which option to pick depends on your system
# If the RevA board is used, replace jlink.gdb with jlink-reva.gdb
# runner = "arm-none-eabi-gdb -q -x jlink.gdb"
# runner = "gdb-multiarch -q -x jlink.gdb"
# runner = "gdb -q -x openocd.gdb"
# runner = "gdb-multiarch -q -x jlink.gdb"
rustflags = [
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
"-C", "link-arg=--nmagic",
# LLD (shipped with the Rust toolchain) is used as the default linker
"-C", "link-arg=-Tlink.x",
# if you run into problems with LLD switch to the GNU linker by commenting out
# this line
# "-C", "linker=arm-none-eabi-ld",
# if you need to link to pre-compiled C libraries provided by a C toolchain
# use GCC as the linker by commenting out both lines above and then
# uncommenting the three lines below
# "-C", "linker=arm-none-eabi-gcc",
# "-C", "link-arg=-Wl,-Tlink.x",
# "-C", "link-arg=-nostartfiles",
]
[build]
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
# target = "thumbv8m.base-none-eabi" # Cortex-M23
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)

View File

@ -1,39 +0,0 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# uncomment ONE of these three option to make `cargo run` start a GDB session
# which option to pick depends on your system
# If the RevA board is used, replace jlink.gdb with jlink-reva.gdb
# runner = "arm-none-eabi-gdb -q -x jlink/jlink.gdb"
# runner = "gdb-multiarch -q -x jlink/jlink.gdb"
# runner = "arm-none-eabi-gdb -q -x jlink/jlink-reva.gdb"
# runner = "gdb-multiarch -q -x jlink/jlink-reva.gdb"
rustflags = [
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
"-C", "link-arg=--nmagic",
# LLD (shipped with the Rust toolchain) is used as the default linker
"-C", "link-arg=-Tlink.x",
# if you run into problems with LLD switch to the GNU linker by commenting out
# this line
# "-C", "linker=arm-none-eabi-ld",
# if you need to link to pre-compiled C libraries provided by a C toolchain
# use GCC as the linker by commenting out both lines above and then
# uncommenting the three lines below
# "-C", "linker=arm-none-eabi-gcc",
# "-C", "link-arg=-Wl,-Tlink.x",
# "-C", "link-arg=-nostartfiles",
]
[build]
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
# target = "thumbv8m.base-none-eabi" # Cortex-M23
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)

36
va416xx-hal/CHANGELOG.md Normal file
View File

@ -0,0 +1,36 @@
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,11 +1,11 @@
[package] [package]
name = "va416xx-hal" name = "va416xx-hal"
version = "0.1.0" version = "0.2.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021" edition = "2021"
description = "HAL for the Vorago VA416xx family of MCUs" description = "HAL for the Vorago VA416xx family of MCUs"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx-hal" homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx-hal" repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
license = "Apache-2.0" license = "Apache-2.0"
keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"] keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
categories = ["embedded", "no-std", "hardware-support"] categories = ["embedded", "no-std", "hardware-support"]
@ -17,7 +17,10 @@ paste = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
embedded-hal = "1" embedded-hal = "1"
embedded-io = "0.6" embedded-io = "0.6"
num_enum = { version = "0.7", default-features = false }
typenum = "1" typenum = "1"
bitflags = "2"
bitfield = "0.15"
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
fugit = "0.3" fugit = "0.3"
delegate = "0.12" delegate = "0.12"
@ -27,17 +30,24 @@ version = "1"
default-features = false default-features = false
[dependencies.va416xx] [dependencies.va416xx]
path = "../va416xx"
default-features = false default-features = false
version = "0.2.0" version = "0.2"
features = ["critical-section"] features = ["critical-section"]
[features] [features]
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]
all-features = true features = ["va41630", "defmt"]
rustdoc-args = ["--generate-link-to-definition"] rustdoc-args = ["--generate-link-to-definition"]

View File

@ -1,3 +1,6 @@
[![Crates.io](https://img.shields.io/crates/v/va416xx-hal)](https://crates.io/crates/va416xx-hal)
[![docs.rs](https://img.shields.io/docsrs/va416xx-hal)](https://docs.rs/va416xx-hal)
# HAL for the Vorago VA416xx MCU family # HAL for the Vorago VA416xx MCU family
This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
@ -8,11 +11,13 @@ 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.
## Supported Boards You have to enable one of the following device features to use this crate depending on
which chip you are using:
The first way to use this HAL will probably be with the - `va41630`
[PEB1 development board](https://www.voragotech.com/products/peb1va416x0-development-kit). - `va41629`
The BSP provided for this board also contains instructions how to flash the board. - `va41628`
- `va41620`
## Building ## Building
@ -44,7 +49,8 @@ is contained within the
1. Set up your Rust cross-compiler if you have not done so yet. See more in the [build chapter](#Building) 1. Set up your Rust cross-compiler if you have not done so yet. See more in the [build chapter](#Building)
2. Create a new binary crate with `cargo init` 2. Create a new binary crate with `cargo init`
3. To ensure that `cargo build` cross-compiles, it is recommended to create a `.cargo/config.toml` 3. To ensure that `cargo build` cross-compiles, it is recommended to create a `.cargo/config.toml`
file. A sample `.cargo/config.toml` file is provided in this repository as well file. You can use [this](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/.cargo/def-config.toml)
sample file as a starting point.
4. Copy the `memory.x` file into your project. This file contains information required by the linker. 4. Copy the `memory.x` file into your project. This file contains information required by the linker.
5. Copy the `blinky.rs` file to the `src/main.rs` file in your binary crate 5. Copy the `blinky.rs` file to the `src/main.rs` file in your binary crate
6. You need to add some dependencies to your `Cargo.toml` file 6. You need to add some dependencies to your `Cargo.toml` file
@ -58,7 +64,7 @@ is contained within the
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
version = "<Most Recent Version>" version = "<Most Recent Version>"
features = ["rt"] features = ["va41630"]
``` ```
6. Build the application with `cargo build` 6. Build the application with `cargo build`

434
va416xx-hal/src/adc.rs Normal file
View File

@ -0,0 +1,434 @@
//! 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 crate::clock::Clocks;
use crate::pac;
use crate::prelude::*;
use crate::time::Hertz;
use num_enum::{IntoPrimitive, TryFromPrimitive};
pub const ADC_MIN_CLK: Hertz = Hertz::from_raw(2_000_000);
pub const ADC_MAX_CLK: Hertz = Hertz::from_raw(12_500_000);
#[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum ChannelSelect {
/// Analogue Input 0 external channel
AnIn0 = 0,
/// Analogue Input 1 external channel
AnIn1 = 1,
/// Analogue Input 2 external channel
AnIn2 = 2,
/// Analogue Input 3 external channel
AnIn3 = 3,
/// Analogue Input 4 external channel
AnIn4 = 4,
/// Analogue Input 5 external channel
AnIn5 = 5,
/// Analogue Input 6 external channel
AnIn6 = 6,
/// Analogue Input 7 external channel
AnIn7 = 7,
/// DAC 0 internal channel
Dac0 = 8,
/// DAC 1 internal channel
Dac1 = 9,
/// Internal temperature sensor
TempSensor = 10,
/// Internal bandgap 1 V reference
Bandgap1V = 11,
/// Internal bandgap 1.5 V reference
Bandgap1_5V = 12,
Avdd1_5 = 13,
Dvdd1_5 = 14,
/// Internally generated Voltage equal to VREFH / 2
Vrefp5 = 15,
}
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 {
const AnIn0 = 1;
const AnIn1 = 1 << 1;
const AnIn2 = 1 << 2;
const AnIn3 = 1 << 3;
const AnIn4 = 1 << 4;
const AnIn5 = 1 << 5;
const AnIn6 = 1 << 6;
const AnIn7 = 1 << 7;
const Dac0 = 1 << 8;
const Dac1 = 1 << 9;
const TempSensor = 1 << 10;
const Bandgap1V = 1 << 11;
const Bandgap1_5V = 1 << 12;
const Avdd1_5 = 1 << 13;
const Dvdd1_5 = 1 << 14;
const Vrefp5 = 1 << 15;
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AdcEmptyError;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidChannelRangeError;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BufferTooSmallError;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AdcRangeReadError {
InvalidChannelRange(InvalidChannelRangeError),
BufferTooSmall(BufferTooSmallError),
}
impl From<InvalidChannelRangeError> for AdcRangeReadError {
fn from(value: InvalidChannelRangeError) -> Self {
AdcRangeReadError::InvalidChannelRange(value)
}
}
impl From<BufferTooSmallError> for AdcRangeReadError {
fn from(value: BufferTooSmallError) -> Self {
AdcRangeReadError::BufferTooSmall(value)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChannelValue {
/// If the channel tag is enabled, this field will contain the determined channel tag.
channel: ChannelSelect,
/// Raw value.
value: u16,
}
impl Default for ChannelValue {
fn default() -> Self {
Self {
channel: ChannelSelect::AnIn0,
value: Default::default(),
}
}
}
impl ChannelValue {
#[inline]
pub fn value(&self) -> u16 {
self.value
}
#[inline]
pub fn channel(&self) -> ChannelSelect {
self.channel
}
}
pub enum ChannelTagEnabled {}
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> {
adc: pac::Adc,
phantom: PhantomData<TagEnabled>,
}
impl Adc<ChannelTagEnabled> {}
impl Adc<ChannelTagDisabled> {
pub fn new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, clocks: &Clocks) -> Self {
Self::generic_new(syscfg, adc, clocks)
}
pub fn trigger_and_read_single_channel(&self, ch: ChannelSelect) -> Result<u16, AdcEmptyError> {
self.generic_trigger_and_read_single_channel(ch)
.map(|v| v & 0xfff)
}
/// Perform a sweep for a specified range of ADC channels.
///
/// Returns the number of read values which were written to the passed RX buffer.
pub fn sweep_and_read_range(
&self,
lower_bound_idx: u8,
upper_bound_idx: u8,
rx_buf: &mut [u16],
) -> Result<usize, AdcRangeReadError> {
self.generic_prepare_range_sweep_and_wait_until_ready(
lower_bound_idx,
upper_bound_idx,
rx_buf.len(),
)?;
let fifo_entry_count = 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;
}
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(
&self,
ch_select: MultiChannelSelect,
rx_buf: &mut [u16],
) -> Result<usize, BufferTooSmallError> {
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..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;
}
Ok(fifo_entry_count as usize)
}
pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
self.generic_try_read_single_value()
.map(|v| v.map(|v| v & 0xfff))
}
#[inline(always)]
pub fn channel_tag_enabled(&self) -> bool {
false
}
}
impl Adc<ChannelTagEnabled> {
pub fn new_with_channel_tag(
syscfg: &mut pac::Sysconfig,
adc: pac::Adc,
clocks: &Clocks,
) -> Self {
let mut adc = Self::generic_new(syscfg, adc, clocks);
adc.enable_channel_tag();
adc
}
pub fn trigger_and_read_single_channel(
&self,
ch: ChannelSelect,
) -> Result<ChannelValue, AdcEmptyError> {
self.generic_trigger_and_read_single_channel(ch)
.map(|v| self.create_channel_value(v))
}
pub fn try_read_single_value(&self) -> nb::Result<Option<ChannelValue>, ()> {
self.generic_try_read_single_value()
.map(|v| v.map(|v| self.create_channel_value(v)))
}
/// Perform a sweep for a specified range of ADC channels.
///
/// Returns the number of read values which were written to the passed RX buffer.
pub fn sweep_and_read_range(
&self,
lower_bound_idx: u8,
upper_bound_idx: u8,
rx_buf: &mut [ChannelValue],
) -> Result<usize, AdcRangeReadError> {
self.generic_prepare_range_sweep_and_wait_until_ready(
lower_bound_idx,
upper_bound_idx,
rx_buf.len(),
)?;
let fifo_entry_count = 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.create_channel_value(self.adc.fifo_data().read().bits() as u16);
}
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(
&self,
ch_select: MultiChannelSelect,
rx_buf: &mut [ChannelValue],
) -> Result<usize, BufferTooSmallError> {
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..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
rx_buf[i as usize] =
self.create_channel_value(self.adc.fifo_data().read().bits() as u16);
}
Ok(fifo_entry_count as usize)
}
#[inline]
pub fn create_channel_value(&self, raw_value: u16) -> ChannelValue {
ChannelValue {
value: raw_value & 0xfff,
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> {
fn generic_new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, _clocks: &Clocks) -> Self {
syscfg.enable_peripheral_clock(crate::clock::PeripheralSelect::Adc);
adc.ctrl().write(|w| unsafe { w.bits(0) });
let adc = Self {
adc,
phantom: PhantomData,
};
adc.clear_fifo();
adc
}
#[inline(always)]
fn enable_channel_tag(&mut self) {
self.adc.ctrl().modify(|_, w| w.chan_tag_en().set_bit());
}
#[inline(always)]
fn disable_channel_tag(&mut self) {
self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit());
}
#[inline(always)]
pub fn clear_fifo(&self) {
self.adc.fifo_clr().write(|w| unsafe { w.bits(1) });
}
pub fn generic_try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
if self.adc.status().read().adc_busy().bit_is_set() {
return Err(nb::Error::WouldBlock);
}
if self.adc.status().read().fifo_entry_cnt().bits() == 0 {
return Ok(None);
}
Ok(Some(self.adc.fifo_data().read().bits() as u16))
}
fn generic_trigger_single_channel(&self, ch: ChannelSelect) {
self.adc.ctrl().modify(|_, w| {
w.ext_trig_en().clear_bit();
unsafe {
// N + 1 conversions, so set set 0 here.
w.conv_cnt().bits(0);
w.chan_en().bits(1 << ch as u8)
}
});
self.clear_fifo();
self.adc.ctrl().modify(|_, w| w.manual_trig().set_bit());
}
fn generic_prepare_range_sweep_and_wait_until_ready(
&self,
lower_bound_idx: u8,
upper_bound_idx: u8,
buf_len: usize,
) -> Result<(), AdcRangeReadError> {
if (lower_bound_idx > 15 || upper_bound_idx > 15) || lower_bound_idx > upper_bound_idx {
return Err(InvalidChannelRangeError.into());
}
let ch_count = upper_bound_idx - lower_bound_idx + 1;
if buf_len < ch_count as usize {
return Err(BufferTooSmallError.into());
}
let mut ch_select = 0;
for i in lower_bound_idx..upper_bound_idx + 1 {
ch_select |= 1 << i;
}
self.generic_trigger_sweep(ch_select);
while self.adc.status().read().adc_busy().bit_is_set() {
cortex_m::asm::nop();
}
Ok(())
}
fn generic_prepare_multiselect_sweep_and_wait_until_ready(
&self,
ch_select: MultiChannelSelect,
buf_len: usize,
) -> Result<(), BufferTooSmallError> {
let ch_select = ch_select.bits();
let ch_count = ch_select.count_ones();
if buf_len < ch_count as usize {
return Err(BufferTooSmallError);
}
self.generic_trigger_sweep(ch_select);
while self.adc.status().read().adc_busy().bit_is_set() {
cortex_m::asm::nop();
}
Ok(())
}
fn generic_trigger_sweep(&self, ch_select: u16) {
let ch_num = ch_select.count_ones() as u8;
assert!(ch_num > 0);
self.adc.ctrl().modify(|_, w| {
w.ext_trig_en().clear_bit();
unsafe {
// N + 1 conversions.
w.conv_cnt().bits(0);
w.chan_en().bits(ch_select);
w.sweep_en().set_bit()
}
});
self.clear_fifo();
self.adc.ctrl().modify(|_, w| w.manual_trig().set_bit());
}
fn generic_trigger_and_read_single_channel(
&self,
ch: ChannelSelect,
) -> Result<u16, AdcEmptyError> {
self.generic_trigger_single_channel(ch);
nb::block!(self.generic_try_read_single_value())
.unwrap()
.ok_or(AdcEmptyError)
}
}
impl From<Adc<ChannelTagDisabled>> for Adc<ChannelTagEnabled> {
fn from(value: Adc<ChannelTagDisabled>) -> Self {
let mut adc = Self {
adc: value.adc,
phantom: PhantomData,
};
adc.enable_channel_tag();
adc
}
}
impl From<Adc<ChannelTagEnabled>> for Adc<ChannelTagDisabled> {
fn from(value: Adc<ChannelTagEnabled>) -> Self {
let mut adc = Self {
adc: value.adc,
phantom: PhantomData,
};
adc.disable_channel_tag();
adc
}
}

View File

@ -10,6 +10,8 @@
//! # 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::pac; use crate::pac;
use crate::time::Hertz; use crate::time::Hertz;
@ -52,7 +54,7 @@ pub enum PeripheralSelect {
PortG = 30, PortG = 30,
} }
pub type PeripheralClocks = PeripheralSelect; pub type PeripheralClock = PeripheralSelect;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum FilterClkSel { pub enum FilterClkSel {
@ -94,24 +96,34 @@ pub fn deassert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSele
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) }); .modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) });
} }
#[inline(always)]
fn assert_periph_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
assert_periph_reset(syscfg, periph);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_periph_reset(syscfg, periph);
}
pub trait SyscfgExt { pub trait SyscfgExt {
fn enable_peripheral_clock(&mut self, clock: PeripheralClocks); fn enable_peripheral_clock(&mut self, clock: PeripheralClock);
fn disable_peripheral_clock(&mut self, clock: PeripheralClocks); fn disable_peripheral_clock(&mut self, clock: PeripheralClock);
fn assert_periph_reset(&mut self, clock: PeripheralSelect); fn assert_periph_reset(&mut self, periph: PeripheralSelect);
fn deassert_periph_reset(&mut self, clock: PeripheralSelect); fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect);
} }
impl SyscfgExt for pac::Sysconfig { impl SyscfgExt for pac::Sysconfig {
#[inline(always)] #[inline(always)]
fn enable_peripheral_clock(&mut self, clock: PeripheralClocks) { fn enable_peripheral_clock(&mut self, clock: PeripheralClock) {
enable_peripheral_clock(self, clock) enable_peripheral_clock(self, clock)
} }
#[inline(always)] #[inline(always)]
fn disable_peripheral_clock(&mut self, clock: PeripheralClocks) { fn disable_peripheral_clock(&mut self, clock: PeripheralClock) {
disable_peripheral_clock(self, clock) disable_peripheral_clock(self, clock)
} }
@ -124,6 +136,11 @@ impl SyscfgExt for pac::Sysconfig {
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) { fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
deassert_periph_reset(self, clock) deassert_periph_reset(self, clock)
} }
#[inline(always)]
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) {
assert_periph_reset_for_two_cycles(self, periph)
}
} }
/// Refer to chapter 8 (p.57) of the programmers guide for detailed information. /// Refer to chapter 8 (p.57) of the programmers guide for detailed information.
@ -431,26 +448,33 @@ 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) });
// I will just do the ADC stuff like Vorago does it.
// ADC clock (must be 2-12.5 MHz)
// 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)
if final_sysclk.raw() <= 50_000_000 {
self.clkgen
.ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
} else {
self.clkgen
.ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
}
Ok(Clocks { Ok(Clocks {
sysclk: final_sysclk, sysclk: final_sysclk,
apb1: final_sysclk / 2, apb1: final_sysclk / 2,
apb2: final_sysclk / 4, 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.
// ADC clock (must be 2-12.5 MHz)
// 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)
if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
self.clkgen
.ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
final_sysclk / 4
} else {
self.clkgen
.ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
final_sysclk / 8
}
}
} }
/// Frozen clock frequencies /// Frozen clock frequencies
@ -464,6 +488,8 @@ pub struct Clocks {
sysclk: Hertz, sysclk: Hertz,
apb1: Hertz, apb1: Hertz,
apb2: Hertz, apb2: Hertz,
#[cfg(not(feature = "va41628"))]
adc_clk: Hertz,
} }
impl Clocks { impl Clocks {
@ -491,6 +517,12 @@ impl Clocks {
pub fn sysclk(&self) -> Hertz { pub fn sysclk(&self) -> Hertz {
self.sysclk self.sysclk
} }
/// Returns the ADC clock frequency which has a separate divider.
#[cfg(not(feature = "va41628"))]
pub fn adc_clk(&self) -> Hertz {
self.adc_clk
}
} }
pub fn rearm_sysclk_lost() { pub fn rearm_sysclk_lost() {

162
va416xx-hal/src/dac.rs Normal file
View File

@ -0,0 +1,162 @@
//! 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 crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt},
pac,
};
pub type DacRegisterBlock = pac::dac0::RegisterBlock;
/// Common trait implemented by all PAC peripheral access structures. The register block
/// format is the same for all DAC blocks.
pub trait Instance: Deref<Target = DacRegisterBlock> {
const IDX: u8;
fn ptr() -> *const DacRegisterBlock;
}
impl Instance for pac::Dac0 {
const IDX: u8 = 0;
#[inline(always)]
fn ptr() -> *const DacRegisterBlock {
Self::ptr()
}
}
impl Instance for pac::Dac1 {
const IDX: u8 = 1;
#[inline(always)]
fn ptr() -> *const DacRegisterBlock {
Self::ptr()
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DacSettling {
NoSettling = 0,
Apb2Times25 = 1,
Apb2Times50 = 2,
Apb2Times75 = 3,
Apb2Times100 = 4,
Apb2Times125 = 5,
Apb2Times150 = 6,
}
pub struct Dac<DacInstance> {
dac: DacInstance,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueTooLarge;
impl<DacInstance: Instance> Dac<DacInstance> {
/// Create a new [Dac] driver instance.
///
/// The [Clocks] structure is expected here as well to ensure the clock was set up properly.
pub fn new(
syscfg: &mut pac::Sysconfig,
dac: DacInstance,
dac_settling: DacSettling,
_clocks: &Clocks,
) -> Self {
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
dac.ctrl1().write(|w| {
w.dac_en().set_bit();
// SAFETY: Enum values are valid values only.
unsafe { w.dac_settling().bits(dac_settling as u8) }
});
let dac = Self { dac };
dac.clear_fifo();
dac.clear_irqs();
dac
}
#[inline(always)]
pub fn clear_irqs(&self) {
self.dac.irq_clr().write(|w| {
w.fifo_oflow().set_bit();
w.fifo_uflow().set_bit();
w.dac_done().set_bit();
w.trig_error().set_bit()
});
}
#[inline(always)]
pub fn clear_fifo(&self) {
self.dac.fifo_clr().write(|w| unsafe { w.bits(1) });
}
/// Load next value into the FIFO.
///
/// Uses the [nb] API to allow blocking and non-blocking usage.
#[inline(always)]
pub fn load_value(&self, val: u16) -> nb::Result<(), ValueTooLarge> {
if val > 2_u16.pow(12) - 1 {
return Err(nb::Error::Other(ValueTooLarge));
}
if self.dac.status().read().fifo_entry_cnt().bits() >= 32_u8 {
return Err(nb::Error::WouldBlock);
}
self.dac
.fifo_data()
.write(|w| unsafe { w.bits(val.into()) });
Ok(())
}
/// This loads and triggers the next value immediately. It also clears the FIFO before
/// loading the passed value.
#[inline(always)]
pub fn load_and_trigger_manually(&self, val: u16) -> Result<(), ValueTooLarge> {
if val > 2_u16.pow(12) - 1 {
return Err(ValueTooLarge);
}
self.clear_fifo();
// This should never block, the FIFO was cleared. We checked the value as well, so unwrap
// is okay here.
nb::block!(self.load_value(val)).unwrap();
self.trigger_manually();
Ok(())
}
/// Manually trigger the DAC. This will de-queue the next value inside the FIFO
/// to be processed by the DAC.
#[inline(always)]
pub fn trigger_manually(&self) {
self.dac.ctrl0().write(|w| w.man_trig_en().set_bit());
}
#[inline(always)]
pub fn enable_external_trigger(&self) {
self.dac.ctrl0().write(|w| w.ext_trig_en().set_bit());
}
pub fn is_settled(&self) -> nb::Result<(), ()> {
if self.dac.status().read().dac_busy().bit_is_set() {
return Err(nb::Error::WouldBlock);
}
Ok(())
}
#[inline(always)]
pub fn reset(&mut self, syscfg: &mut pac::Sysconfig) {
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dac);
}
/// Relases the DAC, which also disables its peripheral clock.
#[inline(always)]
pub fn release(self, syscfg: &mut pac::Sysconfig) -> DacInstance {
syscfg.disable_peripheral_clock(PeripheralSelect::Dac);
self.dac
}
}

571
va416xx-hal/src/dma.rs Normal file
View File

@ -0,0 +1,571 @@
//! API for the DMA peripheral
//!
//! ## Examples
//!
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
use crate::{
clock::{PeripheralClock, PeripheralSelect},
enable_interrupt, pac,
prelude::*,
};
const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
/// DMA cycle control values.
///
/// Refer to chapter 6.3.1 and 6.6.3 of the datasheet for more details.
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CycleControl {
/// Indicates that the data structure is invalid.
Stop = 0b000,
/// The controller must receive a new request prior to entering the arbitration
/// process, to enable the DMA cycle to complete. This means that the DMA will only
/// continue to do transfers as long as a trigger signal is still active. Therefore,
/// this should not be used for momentary triggers like a timer.
Basic = 0b001,
/// The controller automatically inserts a request for the appropriate channel during the
/// arbitration process. This means that the initial request is sufficient to enable the
/// DMA cycle to complete.
Auto = 0b010,
/// This is used to support continuous data flow. Both primary and alternate data structure
/// are used. The primary data structure is used first. When the first transfer is complete, an
/// interrupt can be generated, and the DMA switches to the alternate data structure. When the
/// second transfer is complete, the primary data structure is used. This pattern continues
/// until software disables the channel.
PingPong = 0b011,
MemScatterGatherPrimary = 0b100,
MemScatterGatherAlternate = 0b101,
PeriphScatterGatherPrimary = 0b110,
PeriphScatterGatherAlternate = 0b111,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddrIncrement {
Byte = 0b00,
Halfword = 0b01,
Word = 0b10,
None = 0b11,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DataSize {
Byte = 0b00,
Halfword = 0b01,
Word = 0b10,
}
/// This configuration controls how many DMA transfers can occur before the controller arbitrates.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RPower {
EachTransfer = 0b0000,
Every2 = 0b0001,
Every4 = 0b0010,
Every8 = 0b0011,
Every16 = 0b0100,
Every32 = 0b0101,
Every64 = 0b0110,
Every128 = 0b0111,
Every256 = 0b1000,
Every512 = 0b1001,
Every1024Min = 0b1010,
Every1024 = 0b1111,
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidCtrlBlockAddr;
bitfield::bitfield! {
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct ChannelConfig(u32);
impl Debug;
u32;
pub raw, set_raw: 31,0;
u8;
pub dst_inc, set_dst_inc: 31, 30;
u8;
pub dst_size, set_dst_size: 29, 28;
u8;
pub src_inc, set_src_inc: 27, 26;
u8;
pub src_size, set_src_size: 25, 24;
u8;
pub dest_prot_ctrl, set_dest_prot_ctrl: 23, 21;
u8;
pub src_prot_ctrl, set_src_prot_ctrl: 20, 18;
u8;
pub r_power, set_r_power: 17, 14;
u16;
pub n_minus_1, set_n_minus_1: 13, 4;
bool;
pub next_useburst, set_next_useburst: 3;
u8;
pub cycle_ctrl, set_cycle_ctr: 2, 0;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DmaChannelControl {
pub src_end_ptr: u32,
pub dest_end_ptr: u32,
pub cfg: ChannelConfig,
padding: u32,
}
impl DmaChannelControl {
const fn new() -> Self {
Self {
src_end_ptr: 0,
dest_end_ptr: 0,
cfg: ChannelConfig(0),
padding: 0,
}
}
}
impl Default for DmaChannelControl {
fn default() -> Self {
Self::new()
}
}
#[repr(C)]
#[repr(align(128))]
pub struct DmaCtrlBlock {
pub pri: [DmaChannelControl; 4],
pub alt: [DmaChannelControl; 4],
}
impl DmaCtrlBlock {
pub const fn new() -> Self {
Self {
pri: [DmaChannelControl::new(); 4],
alt: [DmaChannelControl::new(); 4],
}
}
}
impl Default for DmaCtrlBlock {
fn default() -> Self {
Self::new()
}
}
impl DmaCtrlBlock {
/// This function creates a DMA control block at the specified memory address.
///
/// The passed address must be 128-byte aligned. The user must also take care of specifying
/// a valid memory address for the DMA control block which is accessible by the system as well.
/// For example, the control block can be placed in the SRAM1.
pub fn new_at_addr(addr: u32) -> Result<*mut DmaCtrlBlock, InvalidCtrlBlockAddr> {
if addr & BASE_PTR_ADDR_MASK > 0 {
return Err(InvalidCtrlBlockAddr);
}
let ctrl_block_ptr = addr as *mut DmaCtrlBlock;
unsafe { core::ptr::write(ctrl_block_ptr, DmaCtrlBlock::default()) }
Ok(ctrl_block_ptr)
}
}
pub struct Dma {
dma: pac::Dma,
ctrl_block: *mut DmaCtrlBlock,
}
#[derive(Debug, Clone, Copy)]
pub enum DmaTransferInitError {
SourceDestLenMissmatch {
src_len: usize,
dest_len: usize,
},
/// Overflow when calculating the source or destination end address.
AddrOverflow,
/// Transfer size larger than 1024 units.
TransferSizeTooLarge(usize),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DmaCfg {
pub bufferable: bool,
pub cacheable: bool,
pub privileged: bool,
}
pub struct DmaChannel {
channel: u8,
done_interrupt: pac::Interrupt,
active_interrupt: pac::Interrupt,
pub dma: pac::Dma,
pub ch_ctrl_pri: &'static mut DmaChannelControl,
pub ch_ctrl_alt: &'static mut DmaChannelControl,
}
impl DmaChannel {
#[inline(always)]
pub fn channel(&self) -> u8 {
self.channel
}
#[inline(always)]
pub fn enable(&mut self) {
self.dma
.chnl_enable_set()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn is_enabled(&mut self) -> bool {
((self.dma.chnl_enable_set().read().bits() >> self.channel) & 0b1) != 0
}
#[inline(always)]
pub fn disable(&mut self) {
self.dma
.chnl_enable_clr()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn trigger_with_sw_request(&mut self) {
self.dma
.chnl_sw_request()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn state_raw(&self) -> u8 {
self.dma.status().read().state().bits()
}
#[inline(always)]
pub fn select_primary_structure(&self) {
self.dma
.chnl_pri_alt_clr()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn select_alternate_structure(&self) {
self.dma
.chnl_pri_alt_set()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
/// Enables the DMA_DONE interrupt for the DMA channel.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
pub unsafe fn enable_done_interrupt(&mut self) {
enable_interrupt(self.done_interrupt);
}
/// Enables the DMA_ACTIVE interrupt for the DMA channel.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
pub unsafe fn enable_active_interrupt(&mut self) {
enable_interrupt(self.active_interrupt);
}
/// Prepares a 8-bit DMA transfer from memory to memory.
///
/// This function does not enable the DMA channel and interrupts and only prepares
/// the DMA control block parameters for the transfer. It configures the primary channel control
/// structure to perform the transfer.
///
/// 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
/// start the DMA transfer.
///
/// # 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,
source: &[u8],
dest: &mut [u8],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
len,
(source.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
(dest.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Byte,
AddrIncrement::Byte,
);
Ok(())
}
/// Prepares a 16-bit DMA transfer from memory to memory.
///
/// This function does not enable the DMA channel and interrupts and only prepares
/// the DMA control block parameters for the transfer. It configures the primary channel control
/// structure to perform the transfer.
///
/// 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
/// start the DMA transfer.
///
/// # 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,
source: &[u16],
dest: &mut [u16],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
len,
(source.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u16>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
(dest.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u16>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Halfword,
AddrIncrement::Halfword,
);
Ok(())
}
/// Prepares a 32-bit DMA transfer from memory to memory.
///
/// This function does not enable the DMA channel and interrupts and only prepares
/// the DMA control block parameters for the transfer. It configures the primary channel control
/// structure to perform the transfer.
///
/// 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
/// start the DMA transfer.
///
/// # 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,
source: &[u32],
dest: &mut [u32],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
len,
(source.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u32>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
(dest.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u32>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Word,
AddrIncrement::Word,
);
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
// 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.
fn common_mem_transfer_checks(
src_len: usize,
dest_len: usize,
) -> Result<usize, DmaTransferInitError> {
if src_len != dest_len {
return Err(DmaTransferInitError::SourceDestLenMissmatch { src_len, dest_len });
}
if src_len > MAX_DMA_TRANSFERS_PER_CYCLE {
return Err(DmaTransferInitError::TransferSizeTooLarge(src_len));
}
Ok(src_len - 1)
}
fn generic_mem_to_mem_transfer_init(
&mut self,
n_minus_one: usize,
src_end_ptr: u32,
dest_end_ptr: u32,
data_size: DataSize,
addr_incr: AddrIncrement,
) {
self.ch_ctrl_pri.cfg.set_raw(0);
self.ch_ctrl_pri.src_end_ptr = src_end_ptr;
self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr;
self.ch_ctrl_pri.cfg.set_cycle_ctr(CycleControl::Auto as u8);
self.ch_ctrl_pri.cfg.set_src_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_dst_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_n_minus_1(n_minus_one as u16);
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4 as u8);
self.select_primary_structure();
}
}
impl Dma {
/// Create a new DMA instance.
///
/// You can also place the [DmaCtrlBlock] statically using a global static mutable
/// instance and the [DmaCtrlBlock::new] const constructor This also allows to place the control
/// block in a memory section using the [link_section](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute)
/// attribute and then creating a mutable pointer to it using [core::ptr::addr_of_mut].
///
/// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA
/// control block at a specific address.
pub fn new(
syscfg: &mut pac::Sysconfig,
dma: pac::Dma,
cfg: DmaCfg,
ctrl_block: *mut DmaCtrlBlock,
) -> Result<Self, InvalidCtrlBlockAddr> {
// The conversion to u32 is safe here because we are on a 32-bit system.
let raw_addr = ctrl_block as u32;
if raw_addr & BASE_PTR_ADDR_MASK > 0 {
return Err(InvalidCtrlBlockAddr);
}
syscfg.enable_peripheral_clock(PeripheralClock::Dma);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma);
let dma = Dma { dma, ctrl_block };
dma.dma
.ctrl_base_ptr()
.write(|w| unsafe { w.bits(raw_addr) });
dma.set_protection_bits(&cfg);
dma.enable();
Ok(dma)
}
#[inline(always)]
pub fn enable(&self) {
self.dma.cfg().write(|w| w.master_enable().set_bit());
}
#[inline(always)]
pub fn disable(&self) {
self.dma.cfg().write(|w| w.master_enable().clear_bit());
}
#[inline(always)]
pub fn set_protection_bits(&self, cfg: &DmaCfg) {
self.dma.cfg().write(|w| unsafe {
w.chnl_prot_ctrl().bits(
cfg.privileged as u8 | ((cfg.bufferable as u8) << 1) | ((cfg.cacheable as u8) << 2),
)
});
}
/// Split the DMA instance into four DMA channels which can be used individually. This allows
/// using the inidividual DMA channels in separate tasks.
pub fn split(self) -> (DmaChannel, DmaChannel, DmaChannel, DmaChannel) {
// Safety: The DMA channel API only operates on its respective channels.
(
DmaChannel {
channel: 0,
done_interrupt: pac::Interrupt::DMA_DONE0,
active_interrupt: pac::Interrupt::DMA_ACTIVE0,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[0] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[0] },
},
DmaChannel {
channel: 1,
done_interrupt: pac::Interrupt::DMA_DONE1,
active_interrupt: pac::Interrupt::DMA_ACTIVE1,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[1] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[1] },
},
DmaChannel {
channel: 2,
done_interrupt: pac::Interrupt::DMA_DONE2,
active_interrupt: pac::Interrupt::DMA_ACTIVE2,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[2] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[2] },
},
DmaChannel {
channel: 3,
done_interrupt: pac::Interrupt::DMA_DONE3,
active_interrupt: pac::Interrupt::DMA_ACTIVE3,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[3] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[3] },
},
)
}
}

66
va416xx-hal/src/edac.rs Normal file
View File

@ -0,0 +1,66 @@
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,12 +295,17 @@ pub trait PinId: Sealed {
} }
macro_rules! pin_id { macro_rules! pin_id {
($Group:ident, $Id:ident, $NUM:literal) => { ($Group:ident, $Id:ident, $NUM:literal $(, $meta: meta)?) => {
// 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,
@ -689,13 +694,14 @@ impl<I: PinId> Registers<I> {
macro_rules! pins { macro_rules! pins {
( (
$Port:ident, $PinsName:ident, $($Id:ident,)+, $Port:ident, $PinsName:ident, $($Id:ident $(, $meta:meta)?)+,
) => { ) => {
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>,
)+ )+
@ -718,6 +724,7 @@ 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() },
)+ )+
} }
@ -739,13 +746,15 @@ 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),)+] $Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
) => { ) => {
pins!($Port, $PinsName, $($Id,)+,); pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
$( $(
pin_id!($Group, $Id, $NUM); pin_id!($Group, $Id, $NUM $(, $meta)?);
)+ )+
} }
} }
@ -770,7 +779,7 @@ declare_pins!(
(PA12, 12), (PA12, 12),
(PA13, 13), (PA13, 13),
(PA14, 14), (PA14, 14),
(PA15, 15), (PA15, 15)
] ]
); );
@ -784,17 +793,17 @@ declare_pins!(
(PB2, 2), (PB2, 2),
(PB3, 3), (PB3, 3),
(PB4, 4), (PB4, 4),
(PB5, 5), (PB5, 5, cfg(not(feature = "va41628"))),
(PB6, 6), (PB6, 6, cfg(not(feature = "va41628"))),
(PB7, 7), (PB7, 7, cfg(not(feature = "va41628"))),
(PB8, 8), (PB8, 8, cfg(not(feature = "va41628"))),
(PB9, 9), (PB9, 9, cfg(not(feature = "va41628"))),
(PB10, 10), (PB10, 10, cfg(not(feature = "va41628"))),
(PB11, 11), (PB11, 11, cfg(not(feature = "va41628"))),
(PB12, 12), (PB12, 12),
(PB13, 13), (PB13, 13),
(PB14, 14), (PB14, 14),
(PB15, 15), (PB15, 15)
] ]
); );
@ -816,9 +825,9 @@ declare_pins!(
(PC10, 10), (PC10, 10),
(PC11, 11), (PC11, 11),
(PC12, 12), (PC12, 12),
(PC13, 13), (PC13, 13, cfg(not(feature = "va41628"))),
(PC14, 14), (PC14, 14),
(PC15, 15), (PC15, 15, cfg(not(feature = "va41628")))
] ]
); );
@ -827,22 +836,22 @@ declare_pins!(
PinsD, PinsD,
Portd, Portd,
[ [
(PD0, 0), (PD0, 0, cfg(not(feature = "va41628"))),
(PD1, 1), (PD1, 1, cfg(not(feature = "va41628"))),
(PD2, 2), (PD2, 2, cfg(not(feature = "va41628"))),
(PD3, 3), (PD3, 3, cfg(not(feature = "va41628"))),
(PD4, 4), (PD4, 4, cfg(not(feature = "va41628"))),
(PD5, 5), (PD5, 5, cfg(not(feature = "va41628"))),
(PD6, 6), (PD6, 6, cfg(not(feature = "va41628"))),
(PD7, 7), (PD7, 7, cfg(not(feature = "va41628"))),
(PD8, 8), (PD8, 8, cfg(not(feature = "va41628"))),
(PD9, 9), (PD9, 9, cfg(not(feature = "va41628"))),
(PD10, 10), (PD10, 10),
(PD11, 11), (PD11, 11),
(PD12, 12), (PD12, 12),
(PD13, 13), (PD13, 13),
(PD14, 14), (PD14, 14),
(PD15, 15), (PD15, 15)
] ]
); );
@ -861,12 +870,12 @@ declare_pins!(
(PE7, 7), (PE7, 7),
(PE8, 8), (PE8, 8),
(PE9, 9), (PE9, 9),
(PE10, 10), (PE10, 10, cfg(not(feature = "va41628"))),
(PE11, 11), (PE11, 11, cfg(not(feature = "va41628"))),
(PE12, 12), (PE12, 12),
(PE13, 13), (PE13, 13),
(PE14, 14), (PE14, 14),
(PE15, 15), (PE15, 15)
] ]
); );
@ -877,20 +886,20 @@ declare_pins!(
[ [
(PF0, 0), (PF0, 0),
(PF1, 1), (PF1, 1),
(PF2, 2), (PF2, 2, cfg(not(feature = "va41628"))),
(PF3, 3), (PF3, 3, cfg(not(feature = "va41628"))),
(PF4, 4), (PF4, 4, cfg(not(feature = "va41628"))),
(PF5, 5), (PF5, 5, cfg(not(feature = "va41628"))),
(PF6, 6), (PF6, 6, cfg(not(feature = "va41628"))),
(PF7, 7), (PF7, 7, cfg(not(feature = "va41628"))),
(PF8, 8), (PF8, 8, cfg(not(feature = "va41628"))),
(PF9, 9), (PF9, 9),
(PF10, 10), (PF10, 10, cfg(not(feature = "va41628"))),
(PF11, 11), (PF11, 11),
(PF12, 12), (PF12, 12),
(PF13, 13), (PF13, 13),
(PF14, 14), (PF14, 14),
(PF15, 15), (PF15, 15)
] ]
); );
@ -906,6 +915,6 @@ declare_pins!(
(PG4, 4), (PG4, 4),
(PG5, 5), (PG5, 5),
(PG6, 6), (PG6, 6),
(PG7, 7), (PG7, 7)
] ]
); );

View File

@ -4,11 +4,9 @@
//! //!
//! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs) //! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
use crate::{ use crate::{
clock::{ clock::{Clocks, PeripheralSelect},
assert_periph_reset, deassert_periph_reset, enable_peripheral_clock, Clocks,
PeripheralSelect,
},
pac, pac,
prelude::SyscfgExt,
time::Hertz, time::Hertz,
typelevel::Sealed, typelevel::Sealed,
}; };
@ -125,6 +123,7 @@ impl Instance for pac::I2c0 {
const IDX: u8 = 0; const IDX: u8 = 0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
#[inline(always)]
fn ptr() -> *const I2cRegBlock { fn ptr() -> *const I2cRegBlock {
Self::ptr() Self::ptr()
} }
@ -134,6 +133,7 @@ impl Instance for pac::I2c1 {
const IDX: u8 = 1; const IDX: u8 = 1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
#[inline(always)]
fn ptr() -> *const I2cRegBlock { fn ptr() -> *const I2cRegBlock {
Self::ptr() Self::ptr()
} }
@ -143,6 +143,7 @@ impl Instance for pac::I2c2 {
const IDX: u8 = 2; const IDX: u8 = 2;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c2; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c2;
#[inline(always)]
fn ptr() -> *const I2cRegBlock { fn ptr() -> *const I2cRegBlock {
Self::ptr() Self::ptr()
} }
@ -312,17 +313,13 @@ impl<I2C> I2cBase<I2C> {
impl<I2c: Instance> I2cBase<I2c> { impl<I2c: Instance> I2cBase<I2c> {
pub fn new( pub fn new(
i2c: I2c, i2c: I2c,
sys_cfg: &mut pac::Sysconfig, syscfg: &mut pac::Sysconfig,
clocks: &Clocks, clocks: &Clocks,
speed_mode: I2cSpeed, speed_mode: I2cSpeed,
ms_cfg: Option<&MasterConfig>, ms_cfg: Option<&MasterConfig>,
sl_cfg: Option<&SlaveConfig>, sl_cfg: Option<&SlaveConfig>,
) -> Result<Self, ClockTooSlowForFastI2c> { ) -> Result<Self, ClockTooSlowForFastI2c> {
enable_peripheral_clock(sys_cfg, I2c::PERIPH_SEL); syscfg.enable_peripheral_clock(I2c::PERIPH_SEL);
assert_periph_reset(sys_cfg, I2c::PERIPH_SEL);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_periph_reset(sys_cfg, I2c::PERIPH_SEL);
let mut i2c_base = I2cBase { let mut i2c_base = I2cBase {
i2c, i2c,

View File

@ -3,14 +3,26 @@
#[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 clock; pub mod clock;
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;
@ -19,6 +31,11 @@ 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,

267
va416xx-hal/src/nvm.rs Normal file
View File

@ -0,0 +1,267 @@
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,18 +8,20 @@ use core::{convert::Infallible, marker::PhantomData, ops::Deref};
use embedded_hal::spi::Mode; use embedded_hal::spi::Mode;
use crate::{ use crate::{
clock::PeripheralSelect, clock::{Clocks, 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, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1, PC10, PC11, PC7, PC8, PC9, PE12,
PC10, PC11, PC7, PC8, PC9, PE10, PE11, PE12, PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9, PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9, PF0, PF1, PG2, PG3, PG4,
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
//================================================================================================== //==================================================================================================
@ -27,6 +29,11 @@ use crate::{
// 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,
@ -75,15 +82,20 @@ 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), ($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident $(, $meta: meta)?),
)+ )+
) => { ) => {
$( $(
$(#[$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>;
)+ )+
}; };
@ -99,6 +111,14 @@ 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> {}
@ -106,9 +126,11 @@ 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> {}
@ -122,8 +144,11 @@ 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
@ -132,11 +157,18 @@ 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
@ -145,14 +177,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), (PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0, cfg(not(feature="va41628"))),
); );
hw_cs_pins!( hw_cs_pins!(
pac::Spi1, SpiId::Spi1: pac::Spi1, SpiId::Spi1:
(PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb), (PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb, cfg(not(feature="va41628"))),
(PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb), (PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb, cfg(not(feature="va41628"))),
(PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb), (PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb, cfg(not(feature="va41628"))),
(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),
@ -161,14 +193,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), (PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe, cfg(not(feature="va41628"))),
(PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe), (PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe, cfg(not(feature="va41628"))),
(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), (PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf, cfg(not(feature="va41628"))),
(PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg), (PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
); );
@ -183,9 +215,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), (PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf, cfg(not(feature="va41628"))),
(PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf), (PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf, cfg(not(feature="va41628"))),
(PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf), (PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf, cfg(not(feature="va41628"))),
); );
//================================================================================================== //==================================================================================================
@ -196,7 +228,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 frequency(&mut self, spi_clk: Hertz); fn clk_div(&mut self, clk_div: u16);
fn hw_cs_id(&self) -> u8; fn hw_cs_id(&self) -> u8;
} }
@ -204,8 +236,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 spi_clk: Hertz, pub clk_div: Option<u16>,
pub mode: Mode, pub mode: Option<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>,
@ -219,8 +251,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 spi_clk: Hertz, pub clk_div: Option<u16>,
pub mode: Mode, pub mode: Option<Mode>,
pub sod: bool, pub sod: bool,
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless /// If this is enabled, all data in the FIFO is transmitted in a single frame unless
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
@ -230,9 +262,14 @@ pub struct ErasedTransferConfig {
} }
impl TransferConfig<NoneT> { impl TransferConfig<NoneT> {
pub fn new_no_hw_cs(spi_clk: impl Into<Hertz>, mode: Mode, blockmode: bool, sod: bool) -> Self { pub fn new_no_hw_cs(
clk_div: Option<u16>,
mode: Option<Mode>,
blockmode: bool,
sod: bool,
) -> Self {
TransferConfig { TransferConfig {
spi_clk: spi_clk.into(), clk_div,
mode, mode,
hw_cs: None, hw_cs: None,
sod, sod,
@ -243,14 +280,14 @@ impl TransferConfig<NoneT> {
impl<HwCs: HwCsProvider> TransferConfig<HwCs> { impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
pub fn new( pub fn new(
spi_clk: impl Into<Hertz>, clk_div: Option<u16>,
mode: Mode, mode: Option<Mode>,
hw_cs: Option<HwCs>, hw_cs: Option<HwCs>,
blockmode: bool, blockmode: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfig { TransferConfig {
spi_clk: spi_clk.into(), clk_div,
mode, mode,
hw_cs, hw_cs,
sod, sod,
@ -260,7 +297,7 @@ impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
pub fn downgrade(self) -> ErasedTransferConfig { pub fn downgrade(self) -> ErasedTransferConfig {
ErasedTransferConfig { ErasedTransferConfig {
spi_clk: self.spi_clk, clk_div: self.clk_div,
mode: self.mode, mode: self.mode,
sod: self.sod, sod: self.sod,
blockmode: self.blockmode, blockmode: self.blockmode,
@ -280,11 +317,11 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
} }
fn mode(&mut self, mode: Mode) { fn mode(&mut self, mode: Mode) {
self.mode = mode; self.mode = Some(mode);
} }
fn frequency(&mut self, spi_clk: Hertz) { fn clk_div(&mut self, clk_div: u16) {
self.spi_clk = spi_clk; self.clk_div = Some(clk_div);
} }
fn hw_cs_id(&self) -> u8 { fn hw_cs_id(&self) -> u8 {
@ -292,13 +329,9 @@ 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 {
/// Serial clock rate divider. Together with the CLKPRESCALE register, it determines clk_div: u16,
/// 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
@ -309,12 +342,29 @@ 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
@ -365,6 +415,7 @@ impl Instance for pac::Spi0 {
const IDX: u8 = 0; const IDX: u8 = 0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
#[inline(always)]
fn ptr() -> *const SpiRegBlock { fn ptr() -> *const SpiRegBlock {
Self::ptr() Self::ptr()
} }
@ -374,6 +425,7 @@ impl Instance for pac::Spi1 {
const IDX: u8 = 1; const IDX: u8 = 1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
#[inline(always)]
fn ptr() -> *const SpiRegBlock { fn ptr() -> *const SpiRegBlock {
Self::ptr() Self::ptr()
} }
@ -383,6 +435,17 @@ impl Instance for pac::Spi2 {
const IDX: u8 = 2; const IDX: u8 = 2;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
#[inline(always)]
fn ptr() -> *const SpiRegBlock {
Self::ptr()
}
}
impl Instance for pac::Spi3 {
const IDX: u8 = 3;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi3;
#[inline(always)]
fn ptr() -> *const SpiRegBlock { fn ptr() -> *const SpiRegBlock {
Self::ptr() Self::ptr()
} }
@ -407,7 +470,7 @@ pub struct Spi<SpiInstance, Pins, Word = u8> {
pins: Pins, pins: Pins,
} }
fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { pub 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),
@ -416,10 +479,105 @@ 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 =
@ -428,6 +586,7 @@ 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) {
@ -438,6 +597,11 @@ 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());
@ -472,6 +636,7 @@ where
self.cfg_hw_cs(HwCs::CS_ID); self.cfg_hw_cs(HwCs::CS_ID);
} }
#[inline]
pub fn cfg_hw_cs_disable(&mut self) { pub fn cfg_hw_cs_disable(&mut self) {
self.spi.ctrl1().modify(|_, w| { self.spi.ctrl1().modify(|_, w| {
w.sod().set_bit(); w.sod().set_bit();
@ -482,9 +647,13 @@ 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> {
self.cfg_clock(transfer_cfg.spi_clk); if let Some(trans_clk_div) = transfer_cfg.clk_div {
self.cfg_mode(transfer_cfg.mode); self.cfg_clock_from_div(trans_clk_div)?;
}
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 {
@ -504,6 +673,7 @@ where
} }
w w
}); });
Ok(())
} }
/// Sends a word to the slave /// Sends a word to the slave
@ -571,99 +741,6 @@ where
} }
} }
/*
macro_rules! spi_ctor {
($spiI:ident, $PeriphSel: path) => {
/// Create a new SPI struct
///
/// You can delete the pin type information by calling the
/// [`downgrade`](Self::downgrade) function
///
/// ## Arguments
/// * `spi` - SPI bus to use
/// * `pins` - Pins to be used for SPI transactions. These pins are consumed
/// to ensure the pins can not be used for other purposes anymore
/// * `spi_cfg` - Configuration specific to the SPI bus
/// * `transfer_cfg` - Optional initial transfer configuration which includes
/// configuration which can change across individual SPI transfers like SPI mode
/// or SPI clock. If only one device is connected, this configuration only needs
/// to be done once.
/// * `syscfg` - Can be passed optionally to enable the peripheral clock
pub fn $spiI(
spi: SpiI,
pins: (Sck, Miso, Mosi),
clocks: &crate::clock::Clocks,
spi_cfg: SpiConfig,
syscfg: &mut pac::Sysconfig,
transfer_cfg: Option<&ErasedTransferConfig>,
) -> Self {
crate::clock::enable_peripheral_clock(syscfg, $PeriphSel);
let SpiConfig {
ser_clock_rate_div,
ms,
slave_output_disable,
loopback_mode,
master_delayer_capture,
} = spi_cfg;
let mut mode = embedded_hal::spi::MODE_0;
let mut clk_prescale = 0x02;
let mut ss = 0;
let mut init_blockmode = false;
let apb1_clk = clocks.apb1();
if let Some(transfer_cfg) = transfer_cfg {
mode = transfer_cfg.mode;
clk_prescale =
apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1));
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
ss = transfer_cfg.hw_cs as u8;
}
init_blockmode = transfer_cfg.blockmode;
}
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
spi.ctrl0().write(|w| {
unsafe {
w.size().bits(Word::word_reg());
w.scrdv().bits(ser_clock_rate_div);
// Clear clock phase and polarity. Will be set to correct value for each
// transfer
w.spo().bit(cpo_bit);
w.sph().bit(cph_bit)
}
});
spi.ctrl1().write(|w| {
w.lbm().bit(loopback_mode);
w.sod().bit(slave_output_disable);
w.ms().bit(ms);
w.mdlycap().bit(master_delayer_capture);
w.blockmode().bit(init_blockmode);
unsafe { w.ss().bits(ss) }
});
spi.fifo_clr().write(|w| {
w.rxfifo().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
// programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit());
Spi {
inner: SpiBase {
spi,
cfg: spi_cfg,
apb1_clk,
fill_word: Default::default(),
blockmode: init_blockmode,
word: PhantomData,
},
pins,
}
}
};
}
*/
impl< impl<
SpiI: Instance, SpiI: Instance,
Sck: PinSck<SpiI>, Sck: PinSck<SpiI>,
@ -690,41 +767,44 @@ 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>,
) -> Self { ) -> Result<Self, SpiClkConfigError> {
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
let SpiConfig { let SpiConfig {
ser_clock_rate_div, clk_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 mode = embedded_hal::spi::MODE_0; let mut init_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 {
mode = transfer_cfg.mode; if let Some(mode) = transfer_cfg.mode {
clk_prescale = init_mode = mode;
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 (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); let spi_clk_cfg = spi_clk_config_from_div(clk_div)?;
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
spi.ctrl0().write(|w| { spi.ctrl0().write(|w| {
unsafe { unsafe {
w.size().bits(Word::word_reg()); w.size().bits(Word::word_reg());
w.scrdv().bits(ser_clock_rate_div); w.scrdv().bits(spi_clk_cfg.scrdv);
// Clear clock phase and polarity. Will be set to correct value for each // Clear clock phase and polarity. Will be set to correct value for each
// transfer // transfer
w.spo().bit(cpo_bit); w.spo().bit(cpo_bit);
@ -739,16 +819,17 @@ 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());
Spi { Ok(Spi {
inner: SpiBase { inner: SpiBase {
spi, spi,
cfg: spi_cfg, cfg: spi_cfg,
@ -758,36 +839,39 @@ 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
//! //!
//! TODO. //! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs)
use core::cell::Cell; use core::cell::Cell;
use cortex_m::interrupt::Mutex; use cortex_m::interrupt::Mutex;
@ -10,12 +10,17 @@ 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, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1,
PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, PD0, PD1, PD10, PD11, PD12, PD13, PD14, PD15, PD2, PD3, PD10, PD11, PD12, PD13, PD14, PD15, PE0, PE1, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE6,
PD4, PD5, PD6, PD7, PD8, PD9, PE0, PE1, PE10, PE11, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE7, PE8, PE9, PF0, PF1, PF11, PF12, PF13, PF14, PF15, PF9, PG0, PG1, PG2, PG3, PG6,
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::*};
@ -196,10 +201,11 @@ 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), ($PinX:ident, $AltFunc:ident, $TimX:path $(, $meta: meta)?),
)+ )+
) => { ) => {
$( $(
$(#[$meta])?
impl TimPin for Pin<$PinX, $AltFunc> impl TimPin for Pin<$PinX, $AltFunc>
where where
$PinX: PinId, $PinX: PinId,
@ -207,6 +213,7 @@ 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
@ -217,6 +224,7 @@ macro_rules! valid_pin_and_tims {
{ {
} }
$(#[$meta])?
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {} impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
)+ )+
}; };
@ -242,29 +250,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), (PB5, AltFunc2, pac::Tim12, cfg(not(feature = "va41628"))),
(PB6, AltFunc2, pac::Tim11), (PB6, AltFunc2, pac::Tim11, cfg(not(feature = "va41628"))),
(PB7, AltFunc2, pac::Tim10), (PB7, AltFunc2, pac::Tim10, cfg(not(feature = "va41628"))),
(PB8, AltFunc2, pac::Tim9), (PB8, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
(PB9, AltFunc2, pac::Tim8), (PB9, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
(PB10, AltFunc2, pac::Tim7), (PB10, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
(PB11, AltFunc2, pac::Tim6), (PB11, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
(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), (PD0, AltFunc2, pac::Tim0, cfg(not(feature = "va41628"))),
(PD1, AltFunc2, pac::Tim1), (PD1, AltFunc2, pac::Tim1, cfg(not(feature = "va41628"))),
(PD2, AltFunc2, pac::Tim2), (PD2, AltFunc2, pac::Tim2, cfg(not(feature = "va41628"))),
(PD3, AltFunc2, pac::Tim3), (PD3, AltFunc2, pac::Tim3, cfg(not(feature = "va41628"))),
(PD4, AltFunc2, pac::Tim4), (PD4, AltFunc2, pac::Tim4, cfg(not(feature = "va41628"))),
(PD5, AltFunc2, pac::Tim5), (PD5, AltFunc2, pac::Tim5, cfg(not(feature = "va41628"))),
(PD6, AltFunc2, pac::Tim6), (PD6, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
(PD7, AltFunc2, pac::Tim7), (PD7, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
(PD8, AltFunc2, pac::Tim8), (PD8, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
(PD9, AltFunc2, pac::Tim9), (PD9, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
(PD10, AltFunc2, pac::Tim10), (PD10, AltFunc2, pac::Tim10),
(PD11, AltFunc2, pac::Tim11), (PD11, AltFunc2, pac::Tim11),
(PD12, AltFunc2, pac::Tim12), (PD12, AltFunc2, pac::Tim12),
@ -281,23 +289,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), (PE10, AltFunc3, pac::Tim18, cfg(not(feature = "va41628"))),
(PE11, AltFunc3, pac::Tim19), (PE11, AltFunc3, pac::Tim19, cfg(not(feature = "va41628"))),
(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), (PF2, AltFunc3, pac::Tim2, cfg(not(feature = "va41628"))),
(PF3, AltFunc3, pac::Tim3), (PF3, AltFunc3, pac::Tim3, cfg(not(feature = "va41628"))),
(PF4, AltFunc3, pac::Tim4), (PF4, AltFunc3, pac::Tim4, cfg(not(feature = "va41628"))),
(PF5, AltFunc3, pac::Tim5), (PF5, AltFunc3, pac::Tim5, cfg(not(feature = "va41628"))),
(PF6, AltFunc3, pac::Tim6), (PF6, AltFunc3, pac::Tim6, cfg(not(feature = "va41628"))),
(PF7, AltFunc3, pac::Tim7), (PF7, AltFunc3, pac::Tim7, cfg(not(feature = "va41628"))),
(PF8, AltFunc3, pac::Tim8), (PF8, AltFunc3, pac::Tim8, cfg(not(feature = "va41628"))),
(PF9, AltFunc3, pac::Tim9), (PF9, AltFunc3, pac::Tim9),
(PF10, AltFunc3, pac::Tim10), (PF10, AltFunc3, pac::Tim10, cfg(not(feature = "va41628"))),
(PF11, AltFunc3, pac::Tim11), (PF11, AltFunc3, pac::Tim11),
(PF12, AltFunc3, pac::Tim12), (PF12, AltFunc3, pac::Tim12),
(PF13, AltFunc2, pac::Tim19), (PF13, AltFunc2, pac::Tim19),
@ -459,7 +467,10 @@ 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,
@ -513,6 +524,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
/// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the /// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the
/// IRQSEL peripheral for the provided interrupt and unmasks the interrupt /// IRQSEL peripheral for the provided interrupt and unmasks the interrupt
#[inline]
pub fn listen(&mut self) { pub fn listen(&mut self) {
self.listening = true; self.listening = true;
self.enable_interrupt(); self.enable_interrupt();
@ -532,10 +544,12 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
} }
} }
#[inline]
pub fn stop(&mut self) { pub fn stop(&mut self) {
self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
} }
#[inline]
pub fn unlisten(&mut self) { pub fn unlisten(&mut self) {
self.listening = true; self.listening = true;
self.disable_interrupt(); self.disable_interrupt();
@ -552,6 +566,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().clear_bit()); self.tim.reg().ctrl().modify(|_, w| w.irq_enb().clear_bit());
} }
#[inline]
pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim { pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim {
self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
syscfg syscfg

View File

@ -3,38 +3,61 @@
//! ## 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}; use crate::clock::{Clocks, PeripheralSelect, SyscfgExt};
use crate::gpio::{AltFunc1, Pin, PD11, PD12, PE2, PE3, PF11, PF12, PF8, PF9, PG0, PG1}; use crate::gpio::PF13;
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::{AltFunc2, AltFunc3, PA2, PA3, PB14, PB15, PC14, PC15, PC4, PC5}, gpio::{
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 TxRxPins<Uart> {} pub trait RxPin<Uart> {}
pub trait TxPin<Uart> {}
impl TxRxPins<Uart0> for (Pin<PA2, AltFunc3>, Pin<PA3, AltFunc3>) {} impl TxPin<Uart0> for Pin<PA2, AltFunc3> {}
impl TxRxPins<Uart0> for (Pin<PC4, AltFunc2>, Pin<PC5, AltFunc2>) {} impl RxPin<Uart0> for Pin<PA3, AltFunc3> {}
impl TxRxPins<Uart0> for (Pin<PE2, AltFunc3>, Pin<PE3, AltFunc3>) {}
impl TxRxPins<Uart0> for (Pin<PG0, AltFunc1>, Pin<PG1, AltFunc1>) {}
impl TxRxPins<Uart1> for (Pin<PB14, AltFunc3>, Pin<PB15, AltFunc3>) {} impl TxPin<Uart0> for Pin<PC4, AltFunc2> {}
impl TxRxPins<Uart1> for (Pin<PD11, AltFunc3>, Pin<PD12, AltFunc3>) {} impl RxPin<Uart0> for Pin<PC5, AltFunc2> {}
impl TxRxPins<Uart1> for (Pin<PF11, AltFunc1>, Pin<PF12, AltFunc1>) {}
impl TxRxPins<Uart2> for (Pin<PC14, AltFunc2>, Pin<PC15, AltFunc2>) {} impl TxPin<Uart0> for Pin<PE2, AltFunc3> {}
impl TxRxPins<Uart2> for (Pin<PF8, AltFunc1>, Pin<PF9, AltFunc1>) {} impl RxPin<Uart0> for Pin<PE3, AltFunc3> {}
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
@ -312,27 +335,23 @@ pub struct UartWithIrqBase<UART> {
/// Serial receiver /// Serial receiver
pub struct Rx<Uart> { pub struct Rx<Uart> {
_usart: PhantomData<Uart>, uart: Uart,
} }
/// Serial transmitter /// Serial transmitter
pub struct Tx<Uart> { pub struct Tx<Uart> {
_usart: PhantomData<Uart>, uart: Uart,
} }
impl<Uart> Rx<Uart> { impl<Uart: Instance> Rx<Uart> {
fn new() -> Self { fn new(uart: Uart) -> Self {
Self { Self { uart }
_usart: PhantomData,
}
} }
} }
impl<Uart> Tx<Uart> { impl<Uart> Tx<Uart> {
fn new() -> Self { fn new(uart: Uart) -> Self {
Self { Self { uart }
_usart: PhantomData,
}
} }
} }
@ -342,6 +361,12 @@ 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;
} }
@ -351,6 +376,9 @@ 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 _
} }
@ -362,6 +390,9 @@ 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 _
} }
@ -373,6 +404,9 @@ 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 _
} }
@ -511,20 +545,24 @@ impl<Uart: Instance> UartBase<Uart> {
} }
} }
impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> { impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstance: Instance>
Uart<UartInstance, (TxPinInst, RxPinInst)>
{
pub fn new( pub fn new(
uart: UartInstance, uart: UartInstance,
pins: Pins, pins: (TxPinInst, RxPinInst),
config: impl Into<Config>, config: impl Into<Config>,
syscfg: &mut va416xx::Sysconfig, syscfg: &mut va416xx::Sysconfig,
clocks: &Clocks, clocks: &Clocks,
) -> Self { ) -> Self {
crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL); crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(UartInstance::PERIPH_SEL);
Uart { Uart {
inner: UartBase { inner: UartBase {
uart, uart,
tx: Tx::new(), tx: Tx::new(unsafe { UartInstance::steal() }),
rx: Rx::new(), rx: Rx::new(unsafe { UartInstance::steal() }),
}, },
pins, pins,
} }
@ -533,7 +571,7 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
pub fn new_with_clock_freq( pub fn new_with_clock_freq(
uart: UartInstance, uart: UartInstance,
pins: Pins, pins: (TxPinInst, RxPinInst),
config: impl Into<Config>, config: impl Into<Config>,
syscfg: &mut va416xx::Sysconfig, syscfg: &mut va416xx::Sysconfig,
clock: impl Into<Hertz>, clock: impl Into<Hertz>,
@ -542,8 +580,8 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
Uart { Uart {
inner: UartBase { inner: UartBase {
uart, uart,
tx: Tx::new(), tx: Tx::new(unsafe { UartInstance::steal() }),
rx: Rx::new(), rx: Rx::new(unsafe { UartInstance::steal() }),
}, },
pins, pins,
} }
@ -565,7 +603,7 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
/// 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, Pins> { pub fn into_uart_with_irq(self) -> UartWithIrq<UartInstance, (TxPinInst, RxPinInst)> {
let (inner, pins) = self.downgrade_internal(); let (inner, pins) = self.downgrade_internal();
UartWithIrq { UartWithIrq {
pins, pins,
@ -606,7 +644,7 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
} }
} }
fn downgrade_internal(self) -> (UartBase<UartInstance>, Pins) { fn downgrade_internal(self) -> (UartBase<UartInstance>, (TxPinInst, RxPinInst)) {
let base = UartBase { let base = UartBase {
uart: self.inner.uart, uart: self.inner.uart,
tx: self.inner.tx, tx: self.inner.tx,
@ -623,11 +661,41 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
} }
} }
pub fn release(self) -> (UartInstance, Pins) { pub fn release(self) -> (UartInstance, (TxPinInst, RxPinInst)) {
(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,11 +13,16 @@ use crate::{disable_interrupt, enable_interrupt};
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551; pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
pub struct WdtController { /// Watchdog peripheral driver.
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
@ -33,9 +38,8 @@ pub fn disable_wdt_interrupts() {
disable_interrupt(pac::Interrupt::WATCHDOG) disable_interrupt(pac::Interrupt::WATCHDOG)
} }
impl WdtController { impl Wdt {
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,
@ -51,12 +55,7 @@ impl WdtController {
wdt_freq_ms: u32, wdt_freq_ms: u32,
) -> Self { ) -> Self {
syscfg.enable_peripheral_clock(PeripheralSelect::Watchdog); syscfg.enable_peripheral_clock(PeripheralSelect::Watchdog);
// It's done like that in Vorago examples. Not exactly sure why the reset is necessary syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Watchdog);
// though..
syscfg.assert_periph_reset(PeripheralSelect::Watchdog);
cortex_m::asm::nop();
cortex_m::asm::nop();
syscfg.deassert_periph_reset(PeripheralSelect::Watchdog);
let wdt_clock = clocks.apb2(); let wdt_clock = clocks.apb2();
let mut wdt_ctrl = Self { let mut wdt_ctrl = Self {

View File

@ -4,8 +4,8 @@ version = "0.2.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021" edition = "2021"
description = "PAC for the Vorago VA416xx family of MCUs" description = "PAC for the Vorago VA416xx family of MCUs"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx" homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx" repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
license = "Apache-2.0" license = "Apache-2.0"
keywords = ["no-std", "arm", "cortex-m", "vorago", "va416xx"] keywords = ["no-std", "arm", "cortex-m", "vorago", "va416xx"]
categories = ["embedded", "no-std", "hardware-support"] categories = ["embedded", "no-std", "hardware-support"]

View File

@ -1,39 +0,0 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# uncomment ONE of these three option to make `cargo run` start a GDB session
# which option to pick depends on your system
# If the RevA board is used, replace jlink.gdb with jlink-reva.gdb
# runner = "arm-none-eabi-gdb -q -x jlink/jlink.gdb"
# runner = "gdb-multiarch -q -x jlink/jlink.gdb"
# runner = "arm-none-eabi-gdb -q -x jlink/jlink-reva.gdb"
# runner = "gdb-multiarch -q -x jlink/jlink-reva.gdb"
rustflags = [
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
"-C", "link-arg=--nmagic",
# LLD (shipped with the Rust toolchain) is used as the default linker
"-C", "link-arg=-Tlink.x",
# if you run into problems with LLD switch to the GNU linker by commenting out
# this line
# "-C", "linker=arm-none-eabi-ld",
# if you need to link to pre-compiled C libraries provided by a C toolchain
# use GCC as the linker by commenting out both lines above and then
# uncommenting the three lines below
# "-C", "linker=arm-none-eabi-gcc",
# "-C", "link-arg=-Wl,-Tlink.x",
# "-C", "link-arg=-nostartfiles",
]
[build]
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
# target = "thumbv8m.base-none-eabi" # Cortex-M23
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)

View File

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

View File

@ -4,6 +4,56 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"preLaunchTask": "blinky-example",
"type": "probe-rs-debug",
"request": "launch",
"name": "probe-rs Debug Blinky",
"flashingConfig": {
"flashingEnabled": true,
"haltAfterReset": true
},
"chip": "VA416xx",
"coreConfigs": [
{
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/blinky",
"rttEnabled": true,
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched"
}
]
},
{
"preLaunchTask": "rtt-log-example",
"type": "probe-rs-debug",
"request": "launch",
"name": "probe-rs Debug RTT",
"flashingConfig": {
"flashingEnabled": true,
"haltAfterReset": false
},
"chip": "VA416xx",
"coreConfigs": [
{
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/rtt-log",
"rttEnabled": true,
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched"
}
]
},
{
"preLaunchTask": "rtt-log-example",
"type": "probe-rs-debug",
"request": "attach",
"name": "probe-rs Attach RTT",
"chip": "VA416xx",
"coreConfigs": [
{
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/rtt-log",
"rttEnabled": true,
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd"
}
]
},
{ {
"type": "cortex-debug", "type": "cortex-debug",
"request": "launch", "request": "launch",
@ -19,7 +69,7 @@
"monitor reset", "monitor reset",
"load", "load",
], ],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/blinky-hal", "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/blinky",
"interface": "swd", "interface": "swd",
"runToEntryPoint": "main", "runToEntryPoint": "main",
"rttConfig": { "rttConfig": {
@ -185,5 +235,67 @@
] ]
} }
}, },
{
"type": "cortex-debug",
"request": "launch",
"name": "Debug DAC/ADC Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "dac-adc-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/dac-adc",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Debug ADC Example",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "adc-example",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/adc",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
// Have to use exact address unfortunately. "auto" does not work for some reason..
"address": "0x1fff8000",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
] ]
} }

View File

@ -36,7 +36,7 @@
"args": [ "args": [
"build", "build",
"--example", "--example",
"blinky-hal" "blinky"
], ],
"group": { "group": {
"kind": "build", "kind": "build",
@ -82,5 +82,31 @@
"kind": "build", "kind": "build",
} }
}, },
{
"label": "dac-adc-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--example",
"dac-adc"
],
"group": {
"kind": "build",
}
},
{
"label": "adc-example",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--example",
"adc"
],
"group": {
"kind": "build",
}
},
] ]
} }