Compare commits
6 Commits
va416xx-ha
...
dma-experi
Author | SHA1 | Date | |
---|---|---|---|
6cd2e809d7
|
|||
16d2856fb2
|
|||
01341edc91
|
|||
d3deb8a467
|
|||
988d6adcdc
|
|||
c78c90b60d
|
@ -35,8 +35,6 @@ target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
|||||||
[alias]
|
[alias]
|
||||||
rb = "run --bin"
|
rb = "run --bin"
|
||||||
rrb = "run --release --bin"
|
rrb = "run --release --bin"
|
||||||
ut = "test --target=x86_64-unknown-linux-gnu"
|
|
||||||
genbin = "objcopy --release -- -O binary app.bin"
|
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
DEFMT_LOG = "info"
|
DEFMT_LOG = "info"
|
||||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install nextest
|
- name: Install nextest
|
||||||
uses: taiki-e/install-action@nextest
|
uses: taiki-e/install-action@nextest
|
||||||
- run: cargo nextest run --features va41630 -p va416xx-hal
|
- run: cargo nextest run --all-features -p va416xx-hal
|
||||||
# I think we can skip those on an embedded crate..
|
# I think we can skip those on an embedded crate..
|
||||||
# - run: cargo test --doc -p va108xx-hal
|
# - run: cargo test --doc -p va108xx-hal
|
||||||
|
|
||||||
@ -39,9 +39,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1
|
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630
|
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx
|
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
name: Clippy
|
name: Clippy
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,4 +14,3 @@ Cargo.lock
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
/app.map
|
/app.map
|
||||||
/app.bin
|
|
||||||
|
23
Cargo.toml
23
Cargo.toml
@ -1,18 +1,10 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"va416xx",
|
|
||||||
"va416xx-hal",
|
|
||||||
"vorago-peb1",
|
|
||||||
"bootloader",
|
|
||||||
"flashloader",
|
|
||||||
"examples/simple",
|
"examples/simple",
|
||||||
"examples/embassy",
|
"va416xx",
|
||||||
"examples/rtic",
|
"va416xx-hal",
|
||||||
]
|
"vorago-peb1"
|
||||||
exclude = [
|
|
||||||
"flashloader/slot-a-blinky",
|
|
||||||
"flashloader/slot-b-blinky",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
@ -33,12 +25,3 @@ incremental = false
|
|||||||
lto = 'fat'
|
lto = 'fat'
|
||||||
opt-level = 3 # <-
|
opt-level = 3 # <-
|
||||||
overflow-checks = false # <-
|
overflow-checks = false # <-
|
||||||
|
|
||||||
[profile.small]
|
|
||||||
inherits = "release"
|
|
||||||
codegen-units = 1
|
|
||||||
debug-assertions = false # <-
|
|
||||||
lto = true
|
|
||||||
opt-level = 'z' # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
# strip = true # Automatically strip symbols from the binary.
|
|
||||||
|
20
README.md
20
README.md
@ -3,7 +3,7 @@
|
|||||||
Vorago VA416xx Rust Support
|
Vorago VA416xx Rust Support
|
||||||
=========
|
=========
|
||||||
|
|
||||||
This crate collection provides support to write Rust applications for the VA416XX family
|
This crate collection provided support to write Rust applications for the VA416XX family
|
||||||
of devices.
|
of devices.
|
||||||
|
|
||||||
## List of crates
|
## List of crates
|
||||||
@ -19,16 +19,7 @@ This workspace contains the following crates:
|
|||||||
|
|
||||||
It also contains the following helper crates:
|
It also contains the following helper crates:
|
||||||
|
|
||||||
- The [`bootloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader)
|
- The `examples` crates contains various example applications for the HAL and the PAC.
|
||||||
crate contains a sample bootloader strongly based on the one provided by Vorago.
|
|
||||||
- The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
|
||||||
crate contains a sample flashloader which is able to update the redundant images in the NVM which
|
|
||||||
is compatible to the provided bootloader as well.
|
|
||||||
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples)
|
|
||||||
folder contains various example applications crates using the HAL and the PAC.
|
|
||||||
This folder also contains dedicated example applications using the
|
|
||||||
[`RTIC`](https://rtic.rs/2/book/en/) and [`embassy`](https://github.com/embassy-rs/embassy)
|
|
||||||
native Rust RTOSes.
|
|
||||||
|
|
||||||
## Using the `.cargo/config.toml` file
|
## Using the `.cargo/config.toml` file
|
||||||
|
|
||||||
@ -100,9 +91,7 @@ example.
|
|||||||
### Using VS Code
|
### Using VS Code
|
||||||
|
|
||||||
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
|
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
|
||||||
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). Please make sure that
|
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
|
||||||
[`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146)
|
|
||||||
are installed as well.
|
|
||||||
|
|
||||||
Some sample configuration files for VS code were provided and can be used by running
|
Some sample configuration files for VS code were provided and can be used by running
|
||||||
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
|
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
|
||||||
@ -117,5 +106,4 @@ configuration variables in your `settings.json`:
|
|||||||
- `"cortex-debug.gdbPath.osx"`
|
- `"cortex-debug.gdbPath.osx"`
|
||||||
|
|
||||||
The provided VS Code configurations also provide an integrated RTT logger, which you can access
|
The provided VS Code configurations also provide an integrated RTT logger, which you can access
|
||||||
via the terminal at `RTT Ch:0 console`. In order for the RTT block address detection to
|
via the terminal at `RTT Ch:0 console`.
|
||||||
work properly, `objdump-multiarch` and `nm-multiarch` need to be installed.
|
|
||||||
|
8
automation/Jenkinsfile
vendored
8
automation/Jenkinsfile
vendored
@ -25,9 +25,7 @@ pipeline {
|
|||||||
stage('Docs') {
|
stage('Docs') {
|
||||||
steps {
|
steps {
|
||||||
sh """
|
sh """
|
||||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1
|
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
|
||||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630
|
|
||||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,9 +36,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Check Examples') {
|
stage('Check Examples') {
|
||||||
steps {
|
steps {
|
||||||
sh """
|
sh 'cargo check --target thumbv7em-none-eabihf --examples'
|
||||||
cargo check --target thumbv7em-none-eabihf --examples
|
|
||||||
"""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "bootloader"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = "0.7"
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
embedded-hal = "1"
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
crc = "3"
|
|
||||||
|
|
||||||
[dependencies.va416xx-hal]
|
|
||||||
path = "../va416xx-hal"
|
|
@ -1,47 +0,0 @@
|
|||||||
VA416xx Bootloader Application
|
|
||||||
=======
|
|
||||||
|
|
||||||
This is the Rust version of the bootloader supplied by Vorago.
|
|
||||||
|
|
||||||
## Memory Map
|
|
||||||
|
|
||||||
The bootloader uses the following memory map:
|
|
||||||
|
|
||||||
| Address | Notes | Size |
|
|
||||||
| ------ | ---- | ---- |
|
|
||||||
| 0x0 | Bootloader start | code up to 0x3FFC bytes |
|
|
||||||
| 0x3FFC | Bootloader CRC | word |
|
|
||||||
| 0x4000 | App image A start | code up to 0x1DFFC (~120K) bytes |
|
|
||||||
| 0x21FFC | App image A CRC check length | word |
|
|
||||||
| 0x21FFE | App image A CRC check value | word |
|
|
||||||
| 0x22000 | App image B start | code up to 0x1DFFC (~120K) bytes |
|
|
||||||
| 0x3FFFC | App image B CRC check length | word |
|
|
||||||
| 0x3FFFE | App image B CRC check value | word |
|
|
||||||
| 0x40000 | End of NVM | end |
|
|
||||||
|
|
||||||
## Additional Information
|
|
||||||
|
|
||||||
As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
|
|
||||||
but does not scale that clock up. It also uses a word (4 bytes) instead of a half-word for the CRC
|
|
||||||
and uses the ISO 3309 CRC32 standard checksum.
|
|
||||||
|
|
||||||
This bootloader does not provide tools to flash the NVM memories by itself. Instead, you can use
|
|
||||||
the [flashloader](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
|
||||||
application to perform this task using a CCSDS interface via a UART.
|
|
||||||
|
|
||||||
The bootloader performs the following steps:
|
|
||||||
|
|
||||||
1. The application will calculate the checksum of itself if the bootloader CRC is blank (all zeroes
|
|
||||||
or all ones). If the CRC is not blank and the checksum check fails, it will immediately boot
|
|
||||||
application image A. Otherwise, it proceeds to the next step.
|
|
||||||
2. Check the checksum of App A. If that checksum is valid, it will boot App A. If not, it will
|
|
||||||
proceed to the next step.
|
|
||||||
3. Check the checksum of App B. If that checksum is valid, it will boot App B. If not, it will
|
|
||||||
boot App A as the fallback image.
|
|
||||||
|
|
||||||
You could adapt and combine this bootloader with a non-volatile memory to select a prefered app
|
|
||||||
image, which would be a first step towards an updatable flight software.
|
|
||||||
|
|
||||||
Please note that you *MUST* compile the application at slot A and slot B with an appropriate
|
|
||||||
`memory.x` file where the base address of the `FLASH` was adapted according to the base address
|
|
||||||
shown in the memory map above. The memory files to do this were provided in the `scripts` folder.
|
|
@ -1,339 +0,0 @@
|
|||||||
//! Vorago bootloader which can boot from two images.
|
|
||||||
//!
|
|
||||||
//! Bootloader memory map
|
|
||||||
//!
|
|
||||||
//! * <0x0> Bootloader start <code up to 0x3FFE bytes>
|
|
||||||
//! * <0x3FFE> Bootloader CRC <halfword>
|
|
||||||
//! * <0x4000> App image A start <code up to 0x1DFFC (~120K) bytes>
|
|
||||||
//! * <0x21FFC> App image A CRC check length <halfword>
|
|
||||||
//! * <0x21FFE> App image A CRC check value <halfword>
|
|
||||||
//! * <0x22000> App image B start <code up to 0x1DFFC (~120K) bytes>
|
|
||||||
//! * <0x3FFFC> App image B CRC check length <halfword>
|
|
||||||
//! * <0x3FFFE> App image B CRC check value <halfword>
|
|
||||||
//! * <0x40000> <end>
|
|
||||||
//!
|
|
||||||
//! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
|
|
||||||
//! but does not scale that clock up.
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
use crc::{Crc, CRC_32_ISO_HDLC};
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::{pll_setup_delay, ClkDivSel, ClkselSys},
|
|
||||||
edac,
|
|
||||||
nvm::Nvm,
|
|
||||||
pac::{self, interrupt},
|
|
||||||
prelude::*,
|
|
||||||
time::Hertz,
|
|
||||||
wdt::Wdt,
|
|
||||||
};
|
|
||||||
|
|
||||||
const EXTCLK_FREQ: u32 = 40_000_000;
|
|
||||||
const WITH_WDT: bool = false;
|
|
||||||
const WDT_FREQ_MS: u32 = 50;
|
|
||||||
const DEBUG_PRINTOUTS: bool = true;
|
|
||||||
|
|
||||||
// Dangerous option! An image with this option set to true will flash itself from RAM directly
|
|
||||||
// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM
|
|
||||||
// boot process. Please note that this will flash an image which will also always perform the
|
|
||||||
// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash
|
|
||||||
// loader to boot a bootloader without this feature.
|
|
||||||
const FLASH_SELF: bool = false;
|
|
||||||
|
|
||||||
// Important bootloader addresses and offsets, vector table information.
|
|
||||||
|
|
||||||
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
|
||||||
const BOOTLOADER_END_ADDR: u32 = 0x4000;
|
|
||||||
const BOOTLOADER_CRC_ADDR: u32 = 0x3FFC;
|
|
||||||
const APP_A_START_ADDR: u32 = 0x4000;
|
|
||||||
pub const APP_A_END_ADDR: u32 = 0x22000;
|
|
||||||
// The actual size of the image which is relevant for CRC calculation.
|
|
||||||
const APP_A_SIZE_ADDR: u32 = 0x21FF8;
|
|
||||||
const APP_A_CRC_ADDR: u32 = 0x21FFC;
|
|
||||||
const APP_B_START_ADDR: u32 = 0x22000;
|
|
||||||
pub const APP_B_END_ADDR: u32 = 0x40000;
|
|
||||||
// The actual size of the image which is relevant for CRC calculation.
|
|
||||||
const APP_B_SIZE_ADDR: u32 = 0x3FFF8;
|
|
||||||
const APP_B_CRC_ADDR: u32 = 0x3FFFC;
|
|
||||||
pub const APP_IMG_SZ: u32 = 0x1E000;
|
|
||||||
|
|
||||||
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
|
|
||||||
pub const VECTOR_TABLE_LEN: u32 = 0x350;
|
|
||||||
pub const RESET_VECTOR_OFFSET: u32 = 0x4;
|
|
||||||
|
|
||||||
const CRC_ALGO: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
enum AppSel {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait WdtInterface {
|
|
||||||
fn feed(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OptWdt(Option<Wdt>);
|
|
||||||
|
|
||||||
impl WdtInterface for OptWdt {
|
|
||||||
fn feed(&self) {
|
|
||||||
if self.0.is_some() {
|
|
||||||
self.0.as_ref().unwrap().feed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
rtt_init_print!();
|
|
||||||
rprintln!("-- VA416xx bootloader --");
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
let cp = cortex_m::Peripherals::take().unwrap();
|
|
||||||
// Disable ROM protection.
|
|
||||||
dp.sysconfig.rom_prot().write(|w| unsafe { w.bits(1) });
|
|
||||||
setup_edac(&mut dp.sysconfig);
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = dp
|
|
||||||
.clkgen
|
|
||||||
.constrain()
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze(&mut dp.sysconfig)
|
|
||||||
.unwrap();
|
|
||||||
let mut opt_wdt = OptWdt(None);
|
|
||||||
if WITH_WDT {
|
|
||||||
opt_wdt.0 = Some(Wdt::start(
|
|
||||||
&mut dp.sysconfig,
|
|
||||||
dp.watch_dog,
|
|
||||||
&clocks,
|
|
||||||
WDT_FREQ_MS,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let nvm = Nvm::new(&mut dp.sysconfig, dp.spi3, &clocks);
|
|
||||||
|
|
||||||
if FLASH_SELF {
|
|
||||||
let mut first_four_bytes: [u8; 4] = [0; 4];
|
|
||||||
read_four_bytes_at_addr_zero(&mut first_four_bytes);
|
|
||||||
let bootloader_data = {
|
|
||||||
unsafe {
|
|
||||||
&*core::ptr::slice_from_raw_parts(
|
|
||||||
(BOOTLOADER_START_ADDR + 4) as *const u8,
|
|
||||||
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut digest = CRC_ALGO.digest();
|
|
||||||
digest.update(&first_four_bytes);
|
|
||||||
digest.update(bootloader_data);
|
|
||||||
let bootloader_crc = digest.finalize();
|
|
||||||
|
|
||||||
nvm.write_data(0x0, &first_four_bytes);
|
|
||||||
nvm.write_data(0x4, bootloader_data);
|
|
||||||
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
|
|
||||||
rprintln!("verification of self-flash to NVM failed: {:?}", e);
|
|
||||||
}
|
|
||||||
if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
|
|
||||||
rprintln!("verification of self-flash to NVM failed: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes());
|
|
||||||
if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
|
|
||||||
rprintln!(
|
|
||||||
"error: CRC verification for bootloader self-flash failed: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check bootloader's CRC (and write it if blank)
|
|
||||||
check_own_crc(&opt_wdt, &nvm, &cp);
|
|
||||||
|
|
||||||
if check_app_crc(AppSel::A, &opt_wdt) {
|
|
||||||
boot_app(AppSel::A, &cp)
|
|
||||||
} else if check_app_crc(AppSel::B, &opt_wdt) {
|
|
||||||
boot_app(AppSel::B, &cp)
|
|
||||||
} else {
|
|
||||||
if DEBUG_PRINTOUTS {
|
|
||||||
rprintln!("both images corrupt! booting image A");
|
|
||||||
}
|
|
||||||
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
|
|
||||||
// Both images seem to be corrupt. Boot default image A.
|
|
||||||
boot_app(AppSel::A, &cp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
|
|
||||||
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u32).read_unaligned().to_be() };
|
|
||||||
wdt.feed();
|
|
||||||
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
|
|
||||||
// because the address of the bootloader is 0x0, so the NULL check fails and the functions
|
|
||||||
// panics.
|
|
||||||
let mut first_four_bytes: [u8; 4] = [0; 4];
|
|
||||||
read_four_bytes_at_addr_zero(&mut first_four_bytes);
|
|
||||||
let mut digest = CRC_ALGO.digest();
|
|
||||||
digest.update(&first_four_bytes);
|
|
||||||
digest.update(unsafe {
|
|
||||||
&*core::ptr::slice_from_raw_parts(
|
|
||||||
(BOOTLOADER_START_ADDR + 4) as *const u8,
|
|
||||||
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let crc_calc = digest.finalize();
|
|
||||||
wdt.feed();
|
|
||||||
if crc_exp == 0x0000 || crc_exp == 0xffff {
|
|
||||||
if DEBUG_PRINTOUTS {
|
|
||||||
rprintln!("BL CRC blank - prog new CRC");
|
|
||||||
}
|
|
||||||
// Blank CRC, write it to NVM.
|
|
||||||
nvm.write_data(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes());
|
|
||||||
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
|
|
||||||
// necessary because somehow the boot will not work if we just continue as usual.
|
|
||||||
// cortex_m::peripheral::SCB::sys_reset();
|
|
||||||
} else if crc_exp != crc_calc {
|
|
||||||
// Bootloader is corrupted. Try to run App A.
|
|
||||||
if DEBUG_PRINTOUTS {
|
|
||||||
rprintln!(
|
|
||||||
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
|
|
||||||
crc_calc,
|
|
||||||
crc_exp
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
|
|
||||||
boot_app(AppSel::A, cp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
|
|
||||||
unsafe {
|
|
||||||
core::arch::asm!(
|
|
||||||
"ldr r0, [{0}]", // Load 4 bytes from src into r0 register
|
|
||||||
"str r0, [{1}]", // Store r0 register into first_four_bytes
|
|
||||||
in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0)
|
|
||||||
in(reg) buf as *mut [u8; 4], // Input: destination pointer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
|
|
||||||
if DEBUG_PRINTOUTS {
|
|
||||||
rprintln!("Checking image {:?}", app_sel);
|
|
||||||
}
|
|
||||||
if app_sel == AppSel::A {
|
|
||||||
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR, wdt)
|
|
||||||
} else {
|
|
||||||
check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR, wdt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_app_given_addr(
|
|
||||||
crc_addr: u32,
|
|
||||||
start_addr: u32,
|
|
||||||
image_size_addr: u32,
|
|
||||||
wdt: &OptWdt,
|
|
||||||
) -> bool {
|
|
||||||
let crc_exp = unsafe { (crc_addr as *const u32).read_unaligned().to_be() };
|
|
||||||
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
|
|
||||||
// Sanity check.
|
|
||||||
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
|
|
||||||
rprintln!("detected invalid app size {}", image_size);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
wdt.feed();
|
|
||||||
let crc_calc = CRC_ALGO.checksum(unsafe {
|
|
||||||
core::slice::from_raw_parts(start_addr as *const u8, image_size as usize)
|
|
||||||
});
|
|
||||||
wdt.feed();
|
|
||||||
if crc_calc == crc_exp {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! {
|
|
||||||
if DEBUG_PRINTOUTS {
|
|
||||||
rprintln!("booting app {:?}", app_sel);
|
|
||||||
}
|
|
||||||
let clkgen = unsafe { pac::Clkgen::steal() };
|
|
||||||
clkgen
|
|
||||||
.ctrl0()
|
|
||||||
.modify(|_, w| unsafe { w.clksel_sys().bits(ClkselSys::Hbo as u8) });
|
|
||||||
pll_setup_delay();
|
|
||||||
clkgen
|
|
||||||
.ctrl0()
|
|
||||||
.modify(|_, w| unsafe { w.clk_div_sel().bits(ClkDivSel::Div1 as u8) });
|
|
||||||
// Clear all interrupts set.
|
|
||||||
unsafe {
|
|
||||||
cp.NVIC.icer[0].write(0xFFFFFFFF);
|
|
||||||
cp.NVIC.icpr[0].write(0xFFFFFFFF);
|
|
||||||
}
|
|
||||||
cortex_m::asm::dsb();
|
|
||||||
cortex_m::asm::isb();
|
|
||||||
unsafe {
|
|
||||||
if app_sel == AppSel::A {
|
|
||||||
cp.SCB.vtor.write(APP_A_START_ADDR);
|
|
||||||
} else {
|
|
||||||
cp.SCB.vtor.write(APP_B_START_ADDR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cortex_m::asm::dsb();
|
|
||||||
cortex_m::asm::isb();
|
|
||||||
vector_reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vector_reset() -> ! {
|
|
||||||
unsafe {
|
|
||||||
// Set R0 to VTOR address (0xE000ED08)
|
|
||||||
let vtor_address: u32 = 0xE000ED08;
|
|
||||||
|
|
||||||
// Load VTOR
|
|
||||||
let vtor: u32 = *(vtor_address as *const u32);
|
|
||||||
|
|
||||||
// Load initial MSP value
|
|
||||||
let initial_msp: u32 = *(vtor as *const u32);
|
|
||||||
|
|
||||||
// Set SP value (assume MSP is selected)
|
|
||||||
core::arch::asm!("mov sp, {0}", in(reg) initial_msp);
|
|
||||||
|
|
||||||
// Load reset vector
|
|
||||||
let reset_vector: u32 = *((vtor + 4) as *const u32);
|
|
||||||
|
|
||||||
// Branch to reset handler
|
|
||||||
core::arch::asm!("bx {0}", in(reg) reset_vector);
|
|
||||||
}
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_edac(syscfg: &mut pac::Sysconfig) {
|
|
||||||
// The scrub values are based on the Vorago provided bootloader.
|
|
||||||
edac::enable_rom_scrub(syscfg, 125);
|
|
||||||
edac::enable_ram0_scrub(syscfg, 1000);
|
|
||||||
edac::enable_ram1_scrub(syscfg, 1000);
|
|
||||||
edac::enable_sbe_irq();
|
|
||||||
edac::enable_mbe_irq();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn WATCHDOG() {
|
|
||||||
let wdt = unsafe { pac::WatchDog::steal() };
|
|
||||||
// Clear interrupt.
|
|
||||||
wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn EDAC_SBE() {
|
|
||||||
// TODO: Send some command via UART for notification purposes. Also identify the problematic
|
|
||||||
// memory.
|
|
||||||
edac::clear_sbe_irq();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn EDAC_MBE() {
|
|
||||||
// TODO: Send some command via UART for notification purposes.
|
|
||||||
edac::clear_mbe_irq();
|
|
||||||
// TODO: Reset like the vorago example?
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
VA416xx Example Applications
|
|
||||||
========
|
|
||||||
|
|
||||||
This folder contains various examples
|
|
||||||
Consult the main README first for setup of the repository.
|
|
||||||
|
|
||||||
## Simple examples
|
|
||||||
|
|
||||||
```rs
|
|
||||||
cargo run --example blinky
|
|
||||||
```
|
|
||||||
|
|
||||||
You can have a look at the `simple/examples` folder to see all available simple examples
|
|
||||||
|
|
||||||
## RTIC example
|
|
||||||
|
|
||||||
```rs
|
|
||||||
cargo run --bin rtic-example
|
|
||||||
```
|
|
||||||
|
|
||||||
## Embassy example
|
|
||||||
|
|
||||||
```rs
|
|
||||||
cargo run --bin embassy-example
|
|
||||||
```
|
|
@ -1,40 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "embassy-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
embedded-hal = "1"
|
|
||||||
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
panic-rtt-target = { version = "0.1" }
|
|
||||||
critical-section = "1"
|
|
||||||
|
|
||||||
embassy-sync = { version = "0.6.0" }
|
|
||||||
embassy-time = { version = "0.3.2" }
|
|
||||||
embassy-time-driver = { version = "0.1" }
|
|
||||||
|
|
||||||
[dependencies.once_cell]
|
|
||||||
version = "1"
|
|
||||||
default-features = false
|
|
||||||
features = ["critical-section"]
|
|
||||||
|
|
||||||
[dependencies.embassy-executor]
|
|
||||||
version = "0.6.0"
|
|
||||||
features = [
|
|
||||||
"arch-cortex-m",
|
|
||||||
"executor-thread",
|
|
||||||
"executor-interrupt",
|
|
||||||
"integrated-timers",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies.va416xx-hal]
|
|
||||||
path = "../../va416xx-hal"
|
|
||||||
features = ["va41630"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["ticks-hz-1_000"]
|
|
||||||
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
|
|
||||||
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]
|
|
@ -1,4 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
pub mod time_driver;
|
|
||||||
|
|
||||||
pub use time_driver::init;
|
|
@ -1,46 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_time::{Duration, Instant, Ticker};
|
|
||||||
use embedded_hal::digital::StatefulOutputPin;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
|
||||||
use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
|
|
||||||
|
|
||||||
const EXTCLK_FREQ: u32 = 40_000_000;
|
|
||||||
|
|
||||||
// main is itself an async function.
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(_spawner: Spawner) {
|
|
||||||
rtt_init_print!();
|
|
||||||
rprintln!("VA416xx Embassy Demo");
|
|
||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = dp
|
|
||||||
.clkgen
|
|
||||||
.constrain()
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze(&mut dp.sysconfig)
|
|
||||||
.unwrap();
|
|
||||||
// Safety: Only called once here.
|
|
||||||
unsafe {
|
|
||||||
embassy_example::init(
|
|
||||||
&mut dp.sysconfig,
|
|
||||||
&dp.irq_router,
|
|
||||||
dp.tim15,
|
|
||||||
dp.tim14,
|
|
||||||
&clocks,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
|
||||||
let mut led = portg.pg5.into_readable_push_pull_output();
|
|
||||||
let mut ticker = Ticker::every(Duration::from_secs(1));
|
|
||||||
loop {
|
|
||||||
ticker.next().await;
|
|
||||||
rprintln!("Current time: {}", Instant::now().as_secs());
|
|
||||||
led.toggle().ok();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,323 +0,0 @@
|
|||||||
//! This is a sample time driver implementation for the VA416xx family of devices, supporting
|
|
||||||
//! one alarm and requiring/reserving 2 TIM peripherals. You could adapt this implementation to
|
|
||||||
//! support more alarms.
|
|
||||||
use core::{
|
|
||||||
cell::Cell,
|
|
||||||
mem, ptr,
|
|
||||||
sync::atomic::{AtomicU32, AtomicU8, Ordering},
|
|
||||||
};
|
|
||||||
use critical_section::CriticalSection;
|
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
|
||||||
use embassy_sync::blocking_mutex::Mutex;
|
|
||||||
|
|
||||||
use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ};
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::Clocks,
|
|
||||||
enable_interrupt,
|
|
||||||
irq_router::enable_and_init_irq_router,
|
|
||||||
pac::{self, interrupt},
|
|
||||||
pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type TimekeeperClk = pac::Tim15;
|
|
||||||
pub type AlarmClk0 = pac::Tim14;
|
|
||||||
pub type AlarmClk1 = pac::Tim13;
|
|
||||||
pub type AlarmClk2 = pac::Tim12;
|
|
||||||
|
|
||||||
/// Initialization method for embassy
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// This has to be called once at initialization time to initiate the time driver for
|
|
||||||
/// embassy.
|
|
||||||
pub unsafe fn init(
|
|
||||||
syscfg: &mut pac::Sysconfig,
|
|
||||||
irq_router: &pac::IrqRouter,
|
|
||||||
timekeeper: TimekeeperClk,
|
|
||||||
alarm: AlarmClk0,
|
|
||||||
clocks: &Clocks,
|
|
||||||
) {
|
|
||||||
enable_and_init_irq_router(syscfg, irq_router);
|
|
||||||
DRIVER.init(syscfg, timekeeper, alarm, clocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn alarm_tim(idx: usize) -> &'static pac::tim0::RegisterBlock {
|
|
||||||
// Safety: This is a static memory-mapped peripheral.
|
|
||||||
match idx {
|
|
||||||
0 => unsafe { &*AlarmClk0::ptr() },
|
|
||||||
1 => unsafe { &*AlarmClk1::ptr() },
|
|
||||||
2 => unsafe { &*AlarmClk2::ptr() },
|
|
||||||
_ => {
|
|
||||||
panic!("invalid alarm timer index")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock {
|
|
||||||
// Safety: This is a memory-mapped peripheral.
|
|
||||||
unsafe { &*TimekeeperClk::ptr() }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AlarmState {
|
|
||||||
timestamp: Cell<u64>,
|
|
||||||
|
|
||||||
// This is really a Option<(fn(*mut ()), *mut ())>
|
|
||||||
// but fn pointers aren't allowed in const yet
|
|
||||||
callback: Cell<*const ()>,
|
|
||||||
ctx: Cell<*mut ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlarmState {
|
|
||||||
const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
timestamp: Cell::new(u64::MAX),
|
|
||||||
callback: Cell::new(ptr::null()),
|
|
||||||
ctx: Cell::new(ptr::null_mut()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for AlarmState {}
|
|
||||||
|
|
||||||
const ALARM_COUNT: usize = 1;
|
|
||||||
|
|
||||||
static SCALE: OnceCell<u64> = OnceCell::new();
|
|
||||||
|
|
||||||
pub struct TimerDriverEmbassy {
|
|
||||||
periods: AtomicU32,
|
|
||||||
alarm_count: AtomicU8,
|
|
||||||
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
|
|
||||||
alarms: Mutex<CriticalSectionRawMutex, [AlarmState; ALARM_COUNT]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimerDriverEmbassy {
|
|
||||||
fn init(
|
|
||||||
&self,
|
|
||||||
syscfg: &mut pac::Sysconfig,
|
|
||||||
timekeeper: TimekeeperClk,
|
|
||||||
alarm_tim: AlarmClk0,
|
|
||||||
clocks: &Clocks,
|
|
||||||
) {
|
|
||||||
enable_tim_clk(syscfg, TimekeeperClk::TIM_ID);
|
|
||||||
assert_tim_reset_for_two_cycles(syscfg, TimekeeperClk::TIM_ID);
|
|
||||||
|
|
||||||
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
|
|
||||||
SCALE
|
|
||||||
.set((TimekeeperClk::clock(clocks).raw() / TICK_HZ as u32) as u64)
|
|
||||||
.unwrap();
|
|
||||||
timekeeper
|
|
||||||
.rst_value()
|
|
||||||
.write(|w| unsafe { w.bits(u32::MAX) });
|
|
||||||
// Decrementing counter.
|
|
||||||
timekeeper
|
|
||||||
.cnt_value()
|
|
||||||
.write(|w| unsafe { w.bits(u32::MAX) });
|
|
||||||
// Switch on. Timekeeping should always be done.
|
|
||||||
unsafe {
|
|
||||||
enable_interrupt(TimekeeperClk::IRQ);
|
|
||||||
}
|
|
||||||
timekeeper.ctrl().modify(|_, w| w.irq_enb().set_bit());
|
|
||||||
timekeeper.enable().write(|w| unsafe { w.bits(1) });
|
|
||||||
|
|
||||||
enable_tim_clk(syscfg, AlarmClk0::TIM_ID);
|
|
||||||
assert_tim_reset_for_two_cycles(syscfg, AlarmClk0::TIM_ID);
|
|
||||||
|
|
||||||
// Explicitely disable alarm timer until needed.
|
|
||||||
alarm_tim.ctrl().modify(|_, w| {
|
|
||||||
w.irq_enb().clear_bit();
|
|
||||||
w.enable().clear_bit()
|
|
||||||
});
|
|
||||||
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
|
|
||||||
unsafe {
|
|
||||||
enable_interrupt(AlarmClk0::IRQ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be called inside the IRQ of the timekeeper timer.
|
|
||||||
fn on_interrupt_timekeeping(&self) {
|
|
||||||
self.next_period();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be called inside the IRQ of the alarm timer.
|
|
||||||
fn on_interrupt_alarm(&self, idx: usize) {
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
if self.alarms.borrow(cs)[idx].timestamp.get() <= self.now() {
|
|
||||||
self.trigger_alarm(idx, cs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_period(&self) {
|
|
||||||
let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
|
|
||||||
let t = (period as u64) << 32;
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
for i in 0..ALARM_COUNT {
|
|
||||||
let alarm = &self.alarms.borrow(cs)[i];
|
|
||||||
let at = alarm.timestamp.get();
|
|
||||||
let alarm_tim = alarm_tim(0);
|
|
||||||
if at < t {
|
|
||||||
self.trigger_alarm(i, cs);
|
|
||||||
} else {
|
|
||||||
let remaining_ticks = (at - t) * *SCALE.get().unwrap();
|
|
||||||
if remaining_ticks <= u32::MAX as u64 {
|
|
||||||
alarm_tim.enable().write(|w| unsafe { w.bits(0) });
|
|
||||||
alarm_tim
|
|
||||||
.cnt_value()
|
|
||||||
.write(|w| unsafe { w.bits(remaining_ticks as u32) });
|
|
||||||
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
|
|
||||||
alarm_tim.enable().write(|w| unsafe { w.bits(1) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
|
|
||||||
// safety: we're allowed to assume the AlarmState is created by us, and
|
|
||||||
// we never create one that's out of bounds.
|
|
||||||
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
|
|
||||||
alarm_tim(n).ctrl().modify(|_, w| {
|
|
||||||
w.irq_enb().clear_bit();
|
|
||||||
w.enable().clear_bit()
|
|
||||||
});
|
|
||||||
|
|
||||||
let alarm = &self.alarms.borrow(cs)[n];
|
|
||||||
// Setting the maximum value disables the alarm.
|
|
||||||
alarm.timestamp.set(u64::MAX);
|
|
||||||
|
|
||||||
// Call after clearing alarm, so the callback can set another alarm.
|
|
||||||
|
|
||||||
// safety:
|
|
||||||
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
|
|
||||||
// - other than that we only store valid function pointers into alarm.callback
|
|
||||||
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
|
|
||||||
f(alarm.ctx.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Driver for TimerDriverEmbassy {
|
|
||||||
fn now(&self) -> u64 {
|
|
||||||
if SCALE.get().is_none() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let mut period1: u32;
|
|
||||||
let mut period2: u32;
|
|
||||||
let mut counter_val: u32;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Acquire ensures that we get the latest value of `periods` and
|
|
||||||
// no instructions can be reordered before the load.
|
|
||||||
period1 = self.periods.load(Ordering::Acquire);
|
|
||||||
|
|
||||||
counter_val = u32::MAX - timekeeping_tim().cnt_value().read().bits();
|
|
||||||
|
|
||||||
// Double read to protect against race conditions when the counter is overflowing.
|
|
||||||
period2 = self.periods.load(Ordering::Relaxed);
|
|
||||||
if period1 == period2 {
|
|
||||||
let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap();
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
|
||||||
let id = self
|
|
||||||
.alarm_count
|
|
||||||
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
|
|
||||||
if x < ALARM_COUNT as u8 {
|
|
||||||
Some(x + 1)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
match id {
|
|
||||||
Ok(id) => Some(AlarmHandle::new(id)),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_alarm_callback(
|
|
||||||
&self,
|
|
||||||
alarm: embassy_time_driver::AlarmHandle,
|
|
||||||
callback: fn(*mut ()),
|
|
||||||
ctx: *mut (),
|
|
||||||
) {
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let alarm = self.get_alarm(cs, alarm);
|
|
||||||
|
|
||||||
alarm.callback.set(callback as *const ());
|
|
||||||
alarm.ctx.set(ctx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_alarm(&self, alarm: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool {
|
|
||||||
if SCALE.get().is_none() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let n = alarm.id();
|
|
||||||
let alarm_tim = alarm_tim(n.into());
|
|
||||||
alarm_tim.ctrl().modify(|_, w| {
|
|
||||||
w.irq_enb().clear_bit();
|
|
||||||
w.enable().clear_bit()
|
|
||||||
});
|
|
||||||
|
|
||||||
let alarm = self.get_alarm(cs, alarm);
|
|
||||||
alarm.timestamp.set(timestamp);
|
|
||||||
|
|
||||||
let t = self.now();
|
|
||||||
if timestamp <= t {
|
|
||||||
alarm.timestamp.set(u64::MAX);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it hasn't triggered yet, setup the relevant reset value, regardless of whether
|
|
||||||
// the interrupts are enabled or not. When they are enabled at a later point, the
|
|
||||||
// right value is already set.
|
|
||||||
|
|
||||||
// If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
|
|
||||||
// is not missed.
|
|
||||||
//
|
|
||||||
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
|
|
||||||
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
|
|
||||||
// and we don't do that here.
|
|
||||||
let safe_timestamp = timestamp.max(t + 3);
|
|
||||||
let timer_ticks = (safe_timestamp - t) * *SCALE.get().unwrap();
|
|
||||||
alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
|
|
||||||
if timer_ticks <= u32::MAX as u64 {
|
|
||||||
alarm_tim
|
|
||||||
.cnt_value()
|
|
||||||
.write(|w| unsafe { w.bits(timer_ticks as u32) });
|
|
||||||
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
|
|
||||||
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
|
|
||||||
}
|
|
||||||
// If it's too far in the future, don't enable timer yet.
|
|
||||||
// It will be enabled later by `next_period`.
|
|
||||||
|
|
||||||
true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time_driver_impl!(
|
|
||||||
static DRIVER: TimerDriverEmbassy = TimerDriverEmbassy {
|
|
||||||
periods: AtomicU32::new(0),
|
|
||||||
alarm_count: AtomicU8::new(0),
|
|
||||||
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [AlarmState::new(); ALARM_COUNT])
|
|
||||||
});
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn TIM15() {
|
|
||||||
DRIVER.on_interrupt_timekeeping()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn TIM14() {
|
|
||||||
DRIVER.on_interrupt_alarm(0)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "rtic-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
embedded-hal = "1"
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
rtic-sync = { version = "1.3", features = ["defmt-03"] }
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
|
||||||
|
|
||||||
[dependencies.va416xx-hal]
|
|
||||||
path = "../../va416xx-hal"
|
|
||||||
features = ["va41630"]
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
|
||||||
version = "2"
|
|
||||||
features = ["thumbv7-backend"]
|
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "2"
|
|
||||||
features = ["cortex-m-systick"]
|
|
@ -1,30 +0,0 @@
|
|||||||
//! Empty RTIC project template
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
#[rtic::app(device = pac)]
|
|
||||||
mod app {
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtt_target::{rprintln, rtt_init_default};
|
|
||||||
use va416xx_hal::pac;
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {}
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(_ctx: init::Context) -> (Shared, Local) {
|
|
||||||
rtt_init_default!();
|
|
||||||
rprintln!("-- Vorago RTIC template --");
|
|
||||||
(Shared {}, Local {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// `shared` cannot be accessed from this context
|
|
||||||
#[idle]
|
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
|
||||||
#[allow(clippy::empty_loop)]
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
//! RTIC minimal blinky
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
|
|
||||||
mod app {
|
|
||||||
use cortex_m::asm;
|
|
||||||
use embedded_hal::digital::StatefulOutputPin;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtic_monotonics::systick::prelude::*;
|
|
||||||
use rtic_monotonics::Monotonic;
|
|
||||||
use rtt_target::{rprintln, rtt_init_default};
|
|
||||||
use va416xx_hal::{
|
|
||||||
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
|
|
||||||
pac,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {
|
|
||||||
led: Pin<PG5, OutputReadablePushPull>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {}
|
|
||||||
|
|
||||||
rtic_monotonics::systick_monotonic!(Mono, 10_000);
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(_ctx: init::Context) -> (Shared, Local) {
|
|
||||||
rtt_init_default!();
|
|
||||||
rprintln!("-- Vorago RTIC template --");
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
|
||||||
let led = portg.pg5.into_readable_push_pull_output();
|
|
||||||
blinky::spawn().ok();
|
|
||||||
(Shared {}, Local { led })
|
|
||||||
}
|
|
||||||
|
|
||||||
// `shared` cannot be accessed from this context
|
|
||||||
#[idle]
|
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
|
||||||
loop {
|
|
||||||
asm::nop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
priority = 3,
|
|
||||||
local=[led],
|
|
||||||
)]
|
|
||||||
async fn blinky(cx: blinky::Context) {
|
|
||||||
loop {
|
|
||||||
cx.local.led.toggle().ok();
|
|
||||||
Mono::delay(200.millis()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,48 +4,15 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
critical-section = "1"
|
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"] }
|
||||||
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
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
|
||||||
version = "2"
|
|
||||||
features = ["thumbv7-backend"]
|
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "2"
|
|
||||||
features = ["cortex-m-systick"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["va41630"]
|
|
||||||
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"]
|
|
||||||
|
@ -36,12 +36,9 @@ fn main() -> ! {
|
|||||||
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
|
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
|
||||||
loop {
|
loop {
|
||||||
let single_value = adc
|
let single_value = adc
|
||||||
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::TempSensor)
|
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
|
||||||
.expect("reading single channel value failed");
|
.expect("reading single channel value failed");
|
||||||
rprintln!(
|
rprintln!("Read single ADC value on channel 0: {:?}", single_value);
|
||||||
"Read single ADC value on temperature sensor channel: {:?}",
|
|
||||||
single_value
|
|
||||||
);
|
|
||||||
let read_num = adc
|
let read_num = adc
|
||||||
.sweep_and_read_range(0, 7, &mut read_buf)
|
.sweep_and_read_range(0, 7, &mut read_buf)
|
||||||
.expect("ADC range read failed");
|
.expect("ADC range read failed");
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
|
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
|
|
||||||
|
use cortex_m::interrupt::Mutex;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use critical_section::Mutex;
|
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
|
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
|
||||||
use va416xx_hal::irq_router::enable_and_init_irq_router;
|
|
||||||
use va416xx_hal::pwm::CountdownTimer;
|
use va416xx_hal::pwm::CountdownTimer;
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
@ -26,13 +25,6 @@ static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
|||||||
#[link_section = ".sram1"]
|
#[link_section = ".sram1"]
|
||||||
static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new();
|
static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new();
|
||||||
|
|
||||||
// We can use statically allocated buffers for DMA transfers as well, and we can also place
|
|
||||||
// those into SRAM1.
|
|
||||||
#[link_section = ".sram1"]
|
|
||||||
static mut DMA_SRC_BUF: [u16; 36] = [0; 36];
|
|
||||||
#[link_section = ".sram1"]
|
|
||||||
static mut DMA_DEST_BUF: [u16; 36] = [0; 36];
|
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
rtt_init_print!();
|
rtt_init_print!();
|
||||||
@ -46,7 +38,6 @@ fn main() -> ! {
|
|||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze(&mut dp.sysconfig)
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
|
|
||||||
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
|
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
|
||||||
// statically.
|
// statically.
|
||||||
let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe {
|
let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe {
|
||||||
@ -57,10 +48,11 @@ fn main() -> ! {
|
|||||||
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
|
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
let mut src_buf_8_bit: [u8; 65] = [0; 65];
|
let mut src_buf_8_bit: [u8; 65] = [0; 65];
|
||||||
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
|
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
|
||||||
|
let mut src_buf_16_bit: [u16; 33] = [0; 33];
|
||||||
|
let mut dest_buf_16_bit: [u16; 33] = [0; 33];
|
||||||
let mut src_buf_32_bit: [u32; 17] = [0; 17];
|
let mut src_buf_32_bit: [u32; 17] = [0; 17];
|
||||||
let mut dest_buf_32_bit: [u32; 17] = [0; 17];
|
let mut dest_buf_32_bit: [u32; 17] = [0; 17];
|
||||||
loop {
|
loop {
|
||||||
// This example uses stack-allocated buffers.
|
|
||||||
transfer_example_8_bit(
|
transfer_example_8_bit(
|
||||||
&mut src_buf_8_bit,
|
&mut src_buf_8_bit,
|
||||||
&mut dest_buf_8_bit,
|
&mut dest_buf_8_bit,
|
||||||
@ -68,8 +60,12 @@ fn main() -> ! {
|
|||||||
&mut delay_ms,
|
&mut delay_ms,
|
||||||
);
|
);
|
||||||
delay_ms.delay_ms(500);
|
delay_ms.delay_ms(500);
|
||||||
// This example uses statically allocated buffers.
|
transfer_example_16_bit(
|
||||||
transfer_example_16_bit(&mut dma0, &mut delay_ms);
|
&mut src_buf_16_bit,
|
||||||
|
&mut dest_buf_16_bit,
|
||||||
|
&mut dma0,
|
||||||
|
&mut delay_ms,
|
||||||
|
);
|
||||||
delay_ms.delay_ms(500);
|
delay_ms.delay_ms(500);
|
||||||
transfer_example_32_bit(
|
transfer_example_32_bit(
|
||||||
&mut src_buf_32_bit,
|
&mut src_buf_32_bit,
|
||||||
@ -90,17 +86,15 @@ fn transfer_example_8_bit(
|
|||||||
(0..64).for_each(|i| {
|
(0..64).for_each(|i| {
|
||||||
src_buf[i] = i as u8;
|
src_buf[i] = i as u8;
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_DONE_FLAG.borrow(cs).set(false);
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
let dma_transfer = dma0
|
||||||
unsafe {
|
.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
|
||||||
dma0.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
|
.expect("error preparing transfer");
|
||||||
.expect("error preparing transfer");
|
|
||||||
}
|
|
||||||
// Enable all interrupts.
|
// Enable all interrupts.
|
||||||
// Safety: Not using mask based critical sections.
|
// Safety: Not using mask based critical sections.
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -111,10 +105,11 @@ fn transfer_example_8_bit(
|
|||||||
dma0.enable();
|
dma0.enable();
|
||||||
// We still need to manually trigger the DMA request.
|
// We still need to manually trigger the DMA request.
|
||||||
dma0.trigger_with_sw_request();
|
dma0.trigger_with_sw_request();
|
||||||
|
let dest_buf;
|
||||||
// Use polling for completion status.
|
// Use polling for completion status.
|
||||||
loop {
|
loop {
|
||||||
let mut dma_done = false;
|
let mut dma_done = false;
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
rprintln!("DMA0 is active with 8 bit transfer");
|
rprintln!("DMA0 is active with 8 bit transfer");
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
@ -125,6 +120,8 @@ fn transfer_example_8_bit(
|
|||||||
});
|
});
|
||||||
if dma_done {
|
if dma_done {
|
||||||
rprintln!("8-bit transfer done");
|
rprintln!("8-bit transfer done");
|
||||||
|
// Safety: We checked for transfer completion.
|
||||||
|
dest_buf = unsafe { dma_transfer.release() };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delay_ms.delay_ms(1);
|
delay_ms.delay_ms(1);
|
||||||
@ -137,28 +134,26 @@ fn transfer_example_8_bit(
|
|||||||
dest_buf.fill(0);
|
dest_buf.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<pac::Tim0>) {
|
fn transfer_example_16_bit(
|
||||||
let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..33]) };
|
src_buf: &mut [u16; 33],
|
||||||
unsafe {
|
dest_buf: &mut [u16; 33],
|
||||||
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
dma0: &mut DmaChannel,
|
||||||
(0..32).for_each(|i| {
|
delay_ms: &mut CountdownTimer<pac::Tim0>,
|
||||||
DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16;
|
) {
|
||||||
});
|
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
||||||
}
|
(0..32).for_each(|i| {
|
||||||
critical_section::with(|cs| {
|
src_buf[i] = (i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16;
|
||||||
|
});
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_DONE_FLAG.borrow(cs).set(false);
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
let dma_transfer = dma0
|
||||||
unsafe {
|
.prepare_mem_to_mem_transfer_16_bit(src_buf, dest_buf)
|
||||||
dma0.prepare_mem_to_mem_transfer_16_bit(
|
|
||||||
&*core::ptr::addr_of!(DMA_SRC_BUF[0..32]),
|
|
||||||
&mut dest_buf_ref[0..32],
|
|
||||||
)
|
|
||||||
.expect("error preparing transfer");
|
.expect("error preparing transfer");
|
||||||
}
|
dest_buf[5] = 2;
|
||||||
// Enable all interrupts.
|
// Enable all interrupts.
|
||||||
// Safety: Not using mask based critical sections.
|
// Safety: Not using mask based critical sections.
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -172,7 +167,7 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<
|
|||||||
// Use polling for completion status.
|
// Use polling for completion status.
|
||||||
loop {
|
loop {
|
||||||
let mut dma_done = false;
|
let mut dma_done = false;
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
rprintln!("DMA0 is active with 16-bit transfer");
|
rprintln!("DMA0 is active with 16-bit transfer");
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
@ -189,13 +184,13 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<
|
|||||||
}
|
}
|
||||||
(0..32).for_each(|i| {
|
(0..32).for_each(|i| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dest_buf_ref[i],
|
dest_buf[i],
|
||||||
(i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16
|
(i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// Sentinel value, should be 0.
|
// Sentinel value, should be 0.
|
||||||
assert_eq!(dest_buf_ref[32], 0);
|
assert_eq!(dest_buf[32], 0);
|
||||||
dest_buf_ref.fill(0);
|
dest_buf.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_example_32_bit(
|
fn transfer_example_32_bit(
|
||||||
@ -208,17 +203,14 @@ fn transfer_example_32_bit(
|
|||||||
(0..16).for_each(|i| {
|
(0..16).for_each(|i| {
|
||||||
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
|
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_DONE_FLAG.borrow(cs).set(false);
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
|
||||||
unsafe {
|
.expect("error preparing transfer");
|
||||||
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
|
|
||||||
.expect("error preparing transfer");
|
|
||||||
}
|
|
||||||
// Enable all interrupts.
|
// Enable all interrupts.
|
||||||
// Safety: Not using mask based critical sections.
|
// Safety: Not using mask based critical sections.
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -232,7 +224,7 @@ fn transfer_example_32_bit(
|
|||||||
// Use polling for completion status.
|
// Use polling for completion status.
|
||||||
loop {
|
loop {
|
||||||
let mut dma_done = false;
|
let mut dma_done = false;
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
rprintln!("DMA0 is active with 32-bit transfer");
|
rprintln!("DMA0 is active with 32-bit transfer");
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
@ -262,7 +254,7 @@ fn transfer_example_32_bit(
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn DMA_DONE0() {
|
fn DMA_DONE0() {
|
||||||
// Notify the main loop that the DMA transfer is finished.
|
// Notify the main loop that the DMA transfer is finished.
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_DONE_FLAG.borrow(cs).set(true);
|
DMA_DONE_FLAG.borrow(cs).set(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -271,7 +263,7 @@ fn DMA_DONE0() {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn DMA_ACTIVE0() {
|
fn DMA_ACTIVE0() {
|
||||||
// Notify the main loop that the DMA 0 is active now.
|
// Notify the main loop that the DMA 0 is active now.
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(true);
|
DMA_ACTIVE_FLAG.borrow(cs).set(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,12 @@ use embedded_hal::spi::{Mode, SpiBus, MODE_0};
|
|||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::spi::{clk_div_for_target_clock, Spi, TransferConfig};
|
use va416xx_hal::spi::{Spi, TransferConfig};
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
gpio::{PinsB, PinsC},
|
gpio::{PinsB, PinsC},
|
||||||
pac,
|
pac,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
spi::SpiConfig,
|
spi::SpiConfig,
|
||||||
time::Hertz,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
@ -57,24 +56,21 @@ fn main() -> ! {
|
|||||||
pins_c.pc1.into_funsel_1(),
|
pins_c.pc1.into_funsel_1(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut spi_cfg = SpiConfig::default().clk_div(
|
let mut spi_cfg = SpiConfig::default();
|
||||||
clk_div_for_target_clock(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
|
|
||||||
.expect("invalid target clock"),
|
|
||||||
);
|
|
||||||
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
||||||
spi_cfg = spi_cfg.loopback(true)
|
spi_cfg = spi_cfg.loopback(true)
|
||||||
}
|
}
|
||||||
let transfer_cfg = TransferConfig::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false);
|
let transfer_cfg =
|
||||||
|
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false);
|
||||||
// Create SPI peripheral.
|
// Create SPI peripheral.
|
||||||
let mut spi0 = Spi::new(
|
let mut spi0 = Spi::new(
|
||||||
&mut dp.sysconfig,
|
|
||||||
&clocks,
|
|
||||||
dp.spi0,
|
dp.spi0,
|
||||||
(sck, miso, mosi),
|
(sck, miso, mosi),
|
||||||
|
&clocks,
|
||||||
spi_cfg,
|
spi_cfg,
|
||||||
|
&mut dp.sysconfig,
|
||||||
Some(&transfer_cfg.downgrade()),
|
Some(&transfer_cfg.downgrade()),
|
||||||
)
|
);
|
||||||
.expect("creating SPI peripheral failed");
|
|
||||||
spi0.set_fill_word(FILL_WORD);
|
spi0.set_fill_word(FILL_WORD);
|
||||||
loop {
|
loop {
|
||||||
let mut tx_buf: [u8; 3] = [1, 2, 3];
|
let mut tx_buf: [u8; 3] = [1, 2, 3];
|
||||||
|
@ -3,14 +3,12 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
use cortex_m::asm;
|
use cortex_m::interrupt::Mutex;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use critical_section::Mutex;
|
|
||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
irq_router::enable_and_init_irq_router,
|
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
|
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
|
||||||
@ -37,21 +35,19 @@ fn main() -> ! {
|
|||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze(&mut dp.sysconfig)
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
|
|
||||||
let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
|
let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
|
let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
|
||||||
second_timer.listen();
|
|
||||||
second_timer.start(1.Hz());
|
second_timer.start(1.Hz());
|
||||||
|
second_timer.listen();
|
||||||
loop {
|
loop {
|
||||||
let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get());
|
let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get());
|
||||||
if current_ms >= last_ms + 1000 {
|
if current_ms - last_ms >= 1000 {
|
||||||
// To prevent drift.
|
last_ms = current_ms;
|
||||||
last_ms += 1000;
|
|
||||||
rprintln!("MS counter: {}", current_ms);
|
rprintln!("MS counter: {}", current_ms);
|
||||||
let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get());
|
let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get());
|
||||||
rprintln!("Second counter: {}", second);
|
rprintln!("Second counter: {}", second);
|
||||||
}
|
}
|
||||||
asm::delay(1000);
|
cortex_m::asm::delay(10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +60,7 @@ fn TIM0() {
|
|||||||
#[interrupt]
|
#[interrupt]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn TIM1() {
|
fn TIM1() {
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
let mut sec = SEC_COUNTER.borrow(cs).get();
|
let mut sec = SEC_COUNTER.borrow(cs).get();
|
||||||
sec += 1;
|
sec += 1;
|
||||||
SEC_COUNTER.borrow(cs).set(sec);
|
SEC_COUNTER.borrow(cs).set(sec);
|
||||||
|
@ -3,15 +3,14 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
|
use cortex_m::interrupt::Mutex;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use critical_section::Mutex;
|
|
||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::irq_router::enable_and_init_irq_router;
|
|
||||||
use va416xx_hal::pac::{self, interrupt};
|
use va416xx_hal::pac::{self, interrupt};
|
||||||
use va416xx_hal::prelude::*;
|
use va416xx_hal::prelude::*;
|
||||||
use va416xx_hal::wdt::Wdt;
|
use va416xx_hal::wdt::WdtController;
|
||||||
|
|
||||||
static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
|
static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
|
||||||
|
|
||||||
@ -41,17 +40,17 @@ fn main() -> ! {
|
|||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze(&mut dp.sysconfig)
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
|
|
||||||
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
|
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
|
||||||
|
|
||||||
let mut last_interrupt_counter = 0;
|
let mut last_interrupt_counter = 0;
|
||||||
let mut wdt_ctrl = Wdt::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
|
let mut wdt_ctrl =
|
||||||
|
WdtController::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
|
||||||
wdt_ctrl.enable_reset();
|
wdt_ctrl.enable_reset();
|
||||||
loop {
|
loop {
|
||||||
if TEST_MODE != TestMode::AllowReset {
|
if TEST_MODE != TestMode::AllowReset {
|
||||||
wdt_ctrl.feed();
|
wdt_ctrl.feed();
|
||||||
}
|
}
|
||||||
let interrupt_counter = critical_section::with(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
|
let interrupt_counter = cortex_m::interrupt::free(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
|
||||||
if interrupt_counter > last_interrupt_counter {
|
if interrupt_counter > last_interrupt_counter {
|
||||||
rprintln!("interrupt counter has increased to {}", interrupt_counter);
|
rprintln!("interrupt counter has increased to {}", interrupt_counter);
|
||||||
last_interrupt_counter = interrupt_counter;
|
last_interrupt_counter = interrupt_counter;
|
||||||
@ -67,7 +66,7 @@ fn main() -> ! {
|
|||||||
#[interrupt]
|
#[interrupt]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn WATCHDOG() {
|
fn WATCHDOG() {
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
WDT_INTRPT_COUNT
|
WDT_INTRPT_COUNT
|
||||||
.borrow(cs)
|
.borrow(cs)
|
||||||
.set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);
|
.set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);
|
||||||
|
1
flashloader/.gitignore
vendored
1
flashloader/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/venv
|
|
@ -1,51 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "flashloader"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = "0.7"
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
embedded-hal = "1"
|
|
||||||
embedded-hal-nb = "1"
|
|
||||||
embedded-io = "0.6"
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
rtt-log = "0.3"
|
|
||||||
log = "0.4"
|
|
||||||
crc = "3"
|
|
||||||
rtic-sync = "1"
|
|
||||||
|
|
||||||
[dependencies.satrs]
|
|
||||||
version = "0.2"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.ringbuf]
|
|
||||||
version = "0.4"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.once_cell]
|
|
||||||
version = "1"
|
|
||||||
default-features = false
|
|
||||||
features = ["critical-section"]
|
|
||||||
|
|
||||||
[dependencies.spacepackets]
|
|
||||||
version = "0.11"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.cobs]
|
|
||||||
git = "https://github.com/robamu/cobs.rs.git"
|
|
||||||
branch = "all_features"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.va416xx-hal]
|
|
||||||
path = "../va416xx-hal"
|
|
||||||
features = ["va41630"]
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
|
||||||
version = "2"
|
|
||||||
features = ["thumbv7-backend"]
|
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "2"
|
|
||||||
features = ["cortex-m-systick"]
|
|
@ -1,60 +0,0 @@
|
|||||||
VA416xx Flashloader Application
|
|
||||||
========
|
|
||||||
|
|
||||||
This flashloader shows a minimal example for a self-updatable Rust software which exposes
|
|
||||||
a simple PUS (CCSDS) interface to update the software. It also provides a Python application
|
|
||||||
called the `image-loader.py` which can be used to upload compiled images to the flashloader
|
|
||||||
application to write them to the NVM.
|
|
||||||
|
|
||||||
The software can quickly be adapted to interface with a real primary on-board software instead of
|
|
||||||
the Python script provided here to upload images because it uses a low-level CCSDS based packet
|
|
||||||
interface.
|
|
||||||
|
|
||||||
## Using the Python image loader
|
|
||||||
|
|
||||||
It is recommended to run the script in a dedicated virtual environment. For example, on UNIX
|
|
||||||
systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
|
|
||||||
and activate a virtual environment.
|
|
||||||
|
|
||||||
After that, you can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
to install all required dependencies.
|
|
||||||
|
|
||||||
After that, it is recommended to use `./image-load.py -h` to get an overview of some options.
|
|
||||||
The flash loader uses the UART0 interface of the VA416xx board to perform CCSDS based
|
|
||||||
communication. The Python image loader application will search for a file named `loader.toml` and
|
|
||||||
use the `serial_port` key to determine the serial port to use for serial communication.
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
You can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./image-loader.py -p
|
|
||||||
```
|
|
||||||
|
|
||||||
to send a ping an verify the connection.
|
|
||||||
|
|
||||||
You can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd flashloader/slot-a-blinky
|
|
||||||
cargo build --release
|
|
||||||
cd ../..
|
|
||||||
./image-loader.py -t a ./slot-a-blinky/target/thumbv7em-none-eabihf/release/slot-a-blinky
|
|
||||||
```
|
|
||||||
|
|
||||||
to build the slot A sample application and upload it to a running flash loader application
|
|
||||||
to write it to slot A.
|
|
||||||
|
|
||||||
You can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./image-loader.py -c -t a
|
|
||||||
```
|
|
||||||
|
|
||||||
to corrupt the image A and test that it switches to image B after a failed CRC check instead.
|
|
@ -1,332 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from spacepackets.ecss.defs import PusService
|
|
||||||
from spacepackets.ecss.tm import PusTm
|
|
||||||
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 spacepackets.ecss.pus_verificator import PusVerificator, StatusField
|
|
||||||
from spacepackets.ecss.pus_1_verification import Service1Tm, UnpackParams
|
|
||||||
from spacepackets.seqcount import SeqCountProvider
|
|
||||||
from pathlib import Path
|
|
||||||
import dataclasses
|
|
||||||
from elftools.elf.elffile import ELFFile
|
|
||||||
|
|
||||||
|
|
||||||
BAUD_RATE = 115200
|
|
||||||
BOOTLOADER_START_ADDR = 0x0
|
|
||||||
BOOTLOADER_END_ADDR = 0x4000
|
|
||||||
BOOTLOADER_CRC_ADDR = 0x3FFC
|
|
||||||
APP_A_START_ADDR = 0x4000
|
|
||||||
APP_A_END_ADDR = 0x22000
|
|
||||||
# The actual size of the image which is relevant for CRC calculation.
|
|
||||||
APP_A_SIZE_ADDR = 0x21FF8
|
|
||||||
APP_A_CRC_ADDR = 0x21FFC
|
|
||||||
APP_B_START_ADDR = 0x22000
|
|
||||||
APP_B_END_ADDR = 0x40000
|
|
||||||
# The actual size of the image which is relevant for CRC calculation.
|
|
||||||
APP_B_SIZE_ADDR = 0x3FFF8
|
|
||||||
APP_B_CRC_ADDR = 0x3FFFC
|
|
||||||
APP_IMG_SZ = 0x1E000
|
|
||||||
|
|
||||||
CHUNK_SIZE = 896
|
|
||||||
|
|
||||||
MEMORY_SERVICE = 6
|
|
||||||
ACTION_SERVICE = 8
|
|
||||||
|
|
||||||
RAW_MEMORY_WRITE_SUBSERVICE = 2
|
|
||||||
BOOT_NVM_MEMORY_ID = 1
|
|
||||||
PING_PAYLOAD_SIZE = 0
|
|
||||||
|
|
||||||
|
|
||||||
class ActionId(enum.IntEnum):
|
|
||||||
CORRUPT_APP_A = 128
|
|
||||||
CORRUPT_APP_B = 129
|
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class LoadableSegment:
|
|
||||||
name: str
|
|
||||||
offset: int
|
|
||||||
size: int
|
|
||||||
data: bytes
|
|
||||||
|
|
||||||
|
|
||||||
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
print("Python VA416XX Image Loader Application")
|
|
||||||
logging.basicConfig(
|
|
||||||
format="[%(asctime)s] [%(levelname)s] %(message)s", level=logging.DEBUG
|
|
||||||
)
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog="image-loader", description="Python VA416XX Image Loader Application"
|
|
||||||
)
|
|
||||||
parser.add_argument("-p", "--ping", action="store_true", help="Send ping command")
|
|
||||||
parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target")
|
|
||||||
parser.add_argument(
|
|
||||||
"-t",
|
|
||||||
"--target",
|
|
||||||
choices=["bl", "a", "b"],
|
|
||||||
help="Target (Bootloader or slot A or B)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"path", nargs="?", default=None, help="Path to the App to flash"
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
serial_port = None
|
|
||||||
if Path("loader.toml").exists():
|
|
||||||
with open("loader.toml", "r") as toml_file:
|
|
||||||
parsed_toml = toml.loads(toml_file.read())
|
|
||||||
if "serial_port" in parsed_toml:
|
|
||||||
serial_port = parsed_toml["serial_port"]
|
|
||||||
if serial_port is None:
|
|
||||||
serial_port = prompt_com_port()
|
|
||||||
serial_cfg = SerialCfg(
|
|
||||||
com_if_id="ser_cobs",
|
|
||||||
serial_port=serial_port,
|
|
||||||
baud_rate=BAUD_RATE,
|
|
||||||
serial_timeout=0.1,
|
|
||||||
)
|
|
||||||
verificator = PusVerificator()
|
|
||||||
com_if = SerialCobsComIF(serial_cfg)
|
|
||||||
com_if.open()
|
|
||||||
file_path = None
|
|
||||||
if args.ping:
|
|
||||||
_LOGGER.info("Sending ping command")
|
|
||||||
ping_tc = PusTc(
|
|
||||||
apid=0x00,
|
|
||||||
service=PusService.S17_TEST,
|
|
||||||
subservice=1,
|
|
||||||
seq_count=SEQ_PROVIDER.get_and_increment(),
|
|
||||||
app_data=bytes(PING_PAYLOAD_SIZE),
|
|
||||||
)
|
|
||||||
verificator.add_tc(ping_tc)
|
|
||||||
com_if.send(ping_tc.pack())
|
|
||||||
|
|
||||||
data_available = com_if.data_available(0.4)
|
|
||||||
if not data_available:
|
|
||||||
_LOGGER.warning("no ping reply received")
|
|
||||||
for reply in com_if.receive():
|
|
||||||
result = verificator.add_tm(
|
|
||||||
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
|
|
||||||
)
|
|
||||||
if result is not None and result.completed:
|
|
||||||
_LOGGER.info("received ping completion reply")
|
|
||||||
if not args.target:
|
|
||||||
return 0
|
|
||||||
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.corrupt:
|
|
||||||
if not args.target:
|
|
||||||
_LOGGER.error("target for corruption command required")
|
|
||||||
return -1
|
|
||||||
if args.target == "bl":
|
|
||||||
_LOGGER.error("can not corrupt bootloader")
|
|
||||||
if args.target == "a":
|
|
||||||
packet = PusTc(
|
|
||||||
apid=0,
|
|
||||||
service=ACTION_SERVICE,
|
|
||||||
subservice=ActionId.CORRUPT_APP_A,
|
|
||||||
)
|
|
||||||
com_if.send(packet.pack())
|
|
||||||
if args.target == "b":
|
|
||||||
packet = PusTc(
|
|
||||||
apid=0,
|
|
||||||
service=ACTION_SERVICE,
|
|
||||||
subservice=ActionId.CORRUPT_APP_B,
|
|
||||||
)
|
|
||||||
com_if.send(packet.pack())
|
|
||||||
else:
|
|
||||||
assert file_path is not None
|
|
||||||
loadable_segments = []
|
|
||||||
_LOGGER.info("Parsing ELF file for loadable sections")
|
|
||||||
total_size = 0
|
|
||||||
with open(file_path, "rb") as app_file:
|
|
||||||
elf_file = ELFFile(app_file)
|
|
||||||
|
|
||||||
for (idx, segment) in enumerate(elf_file.iter_segments("PT_LOAD")):
|
|
||||||
if segment.header.p_filesz == 0:
|
|
||||||
continue
|
|
||||||
# Basic validity checks of the base addresses.
|
|
||||||
if idx == 0:
|
|
||||||
if (
|
|
||||||
args.target == "bl"
|
|
||||||
and segment.header.p_paddr != BOOTLOADER_START_ADDR
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
|
||||||
f"bootloader, expected {BOOTLOADER_START_ADDR}"
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
args.target == "a"
|
|
||||||
and segment.header.p_paddr != APP_A_START_ADDR
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
|
||||||
f"App A, expected {APP_A_START_ADDR}"
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
args.target == "b"
|
|
||||||
and segment.header.p_paddr != APP_B_START_ADDR
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
|
||||||
f"App B, expected {APP_B_START_ADDR}"
|
|
||||||
)
|
|
||||||
name = None
|
|
||||||
for section in elf_file.iter_sections():
|
|
||||||
if (
|
|
||||||
section.header.sh_offset == segment.header.p_offset
|
|
||||||
and section.header.sh_size > 0
|
|
||||||
):
|
|
||||||
name = section.name
|
|
||||||
if name is None:
|
|
||||||
_LOGGER.warning("no fitting section found for segment")
|
|
||||||
continue
|
|
||||||
# print(f"Segment Addr: {segment.header.p_paddr}")
|
|
||||||
# print(f"Segment Offset: {segment.header.p_offset}")
|
|
||||||
# print(f"Segment Filesize: {segment.header.p_filesz}")
|
|
||||||
loadable_segments.append(
|
|
||||||
LoadableSegment(
|
|
||||||
name=name,
|
|
||||||
offset=segment.header.p_paddr,
|
|
||||||
size=segment.header.p_filesz,
|
|
||||||
data=segment.data(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
total_size += segment.header.p_filesz
|
|
||||||
context_str = None
|
|
||||||
if args.target == "bl":
|
|
||||||
context_str = "Bootloader"
|
|
||||||
elif args.target == "a":
|
|
||||||
context_str = "App Slot A"
|
|
||||||
elif args.target == "b":
|
|
||||||
context_str = "App Slot B"
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Flashing {context_str} with image {file_path} (size {total_size})"
|
|
||||||
)
|
|
||||||
for idx, segment in enumerate(loadable_segments):
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and size {segment.size}"
|
|
||||||
)
|
|
||||||
for segment in loadable_segments:
|
|
||||||
segment_end = segment.offset + segment.size
|
|
||||||
current_addr = segment.offset
|
|
||||||
pos_in_segment = 0
|
|
||||||
while pos_in_segment < segment.size:
|
|
||||||
next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
|
|
||||||
data = segment.data[
|
|
||||||
pos_in_segment : pos_in_segment + next_chunk_size
|
|
||||||
]
|
|
||||||
next_packet = pack_memory_write_command(current_addr, data)
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Sending memory write command for address {current_addr:#08x} and data with "
|
|
||||||
f"length {len(data)}"
|
|
||||||
)
|
|
||||||
verificator.add_tc(next_packet)
|
|
||||||
com_if.send(next_packet.pack())
|
|
||||||
current_addr += next_chunk_size
|
|
||||||
pos_in_segment += next_chunk_size
|
|
||||||
while True:
|
|
||||||
data_available = com_if.data_available(0.1)
|
|
||||||
done = False
|
|
||||||
if not data_available:
|
|
||||||
continue
|
|
||||||
replies = com_if.receive()
|
|
||||||
for reply in replies:
|
|
||||||
tm = PusTm.unpack(reply, 0)
|
|
||||||
if tm.service != 1:
|
|
||||||
continue
|
|
||||||
service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
|
|
||||||
check_result = verificator.add_tm(service_1_tm)
|
|
||||||
# We could send after we have received the step reply, but that can
|
|
||||||
# somehow lead to overrun errors. I think it's okay to do it like
|
|
||||||
# this as long as the flash loader only uses polling..
|
|
||||||
if (
|
|
||||||
check_result is not None
|
|
||||||
and check_result.status.completed == StatusField.SUCCESS
|
|
||||||
):
|
|
||||||
done = True
|
|
||||||
# Still keep a small delay
|
|
||||||
# time.sleep(0.05)
|
|
||||||
verificator.remove_completed_entries()
|
|
||||||
if done:
|
|
||||||
break
|
|
||||||
if args.target == "bl":
|
|
||||||
_LOGGER.info("Blanking the bootloader checksum")
|
|
||||||
# Blank the checksum. For the bootloader, the bootloader will calculate the
|
|
||||||
# checksum itself on the initial run.
|
|
||||||
checksum_write_packet = pack_memory_write_command(
|
|
||||||
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00, 0x00, 0x00])
|
|
||||||
)
|
|
||||||
com_if.send(checksum_write_packet.pack())
|
|
||||||
else:
|
|
||||||
crc_addr = None
|
|
||||||
size_addr = None
|
|
||||||
if args.target == "a":
|
|
||||||
crc_addr = APP_A_CRC_ADDR
|
|
||||||
size_addr = APP_A_SIZE_ADDR
|
|
||||||
elif args.target == "b":
|
|
||||||
crc_addr = APP_B_CRC_ADDR
|
|
||||||
size_addr = APP_B_SIZE_ADDR
|
|
||||||
assert crc_addr is not None
|
|
||||||
assert size_addr is not None
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Writing app size {total_size} at address {size_addr:#08x}"
|
|
||||||
)
|
|
||||||
size_write_packet = pack_memory_write_command(
|
|
||||||
size_addr, struct.pack("!I", total_size)
|
|
||||||
)
|
|
||||||
com_if.send(size_write_packet.pack())
|
|
||||||
time.sleep(0.2)
|
|
||||||
crc_calc = PredefinedCrc("crc-32")
|
|
||||||
for segment in loadable_segments:
|
|
||||||
crc_calc.update(segment.data)
|
|
||||||
checksum = crc_calc.digest()
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
|
|
||||||
)
|
|
||||||
checksum_write_packet = pack_memory_write_command(crc_addr, checksum)
|
|
||||||
com_if.send(checksum_write_packet.pack())
|
|
||||||
com_if.close()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
|
|
||||||
app_data = bytearray()
|
|
||||||
app_data.append(BOOT_NVM_MEMORY_ID)
|
|
||||||
# N parameter is always 1 here.
|
|
||||||
app_data.append(1)
|
|
||||||
app_data.extend(struct.pack("!I", addr))
|
|
||||||
app_data.extend(struct.pack("!I", len(data)))
|
|
||||||
app_data.extend(data)
|
|
||||||
return PusTc(
|
|
||||||
apid=0,
|
|
||||||
service=MEMORY_SERVICE,
|
|
||||||
subservice=RAW_MEMORY_WRITE_SUBSERVICE,
|
|
||||||
seq_count=SEQ_PROVIDER.get_and_increment(),
|
|
||||||
app_data=app_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1 +0,0 @@
|
|||||||
serial_port = "/dev/ttyUSB0"
|
|
@ -1,5 +0,0 @@
|
|||||||
spacepackets == 0.24
|
|
||||||
tmtccmd == 8.0.2
|
|
||||||
toml == 0.10
|
|
||||||
pyelftools == 0.31
|
|
||||||
crcmod == 1.7
|
|
2
flashloader/slot-a-blinky/.gitignore
vendored
2
flashloader/slot-a-blinky/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
/target
|
|
||||||
/app.map
|
|
@ -1,42 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "slot-a-blinky"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
va416xx-hal = { path = "../../va416xx-hal" }
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
embedded-hal = "1"
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = true # <-
|
|
||||||
incremental = false
|
|
||||||
# This is problematic for stepping..
|
|
||||||
# opt-level = 'z' # <-
|
|
||||||
overflow-checks = true # <-
|
|
||||||
|
|
||||||
# cargo build/run --release
|
|
||||||
[profile.release]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = 3 # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
|
|
||||||
[profile.small]
|
|
||||||
inherits = "release"
|
|
||||||
codegen-units = 1
|
|
||||||
debug-assertions = false # <-
|
|
||||||
lto = true
|
|
||||||
opt-level = 'z' # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
# strip = true # Automatically strip symbols from the binary.
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot A with an offset at address 0x4000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00004000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
//! Simple blinky example using the HAL
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
use embedded_hal::digital::StatefulOutputPin;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
|
||||||
use va416xx_hal::{gpio::PinsG, pac};
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
rtt_init_print!();
|
|
||||||
rprintln!("VA416xx HAL blinky example for App Slot A");
|
|
||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
|
||||||
let mut led = portg.pg5.into_readable_push_pull_output();
|
|
||||||
loop {
|
|
||||||
cortex_m::asm::delay(1_000_000);
|
|
||||||
led.toggle().ok();
|
|
||||||
}
|
|
||||||
}
|
|
2
flashloader/slot-b-blinky/.gitignore
vendored
2
flashloader/slot-b-blinky/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
/target
|
|
||||||
/app.map
|
|
@ -1,42 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "slot-b-blinky"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
va416xx-hal = { path = "../../va416xx-hal" }
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
embedded-hal = "1"
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = true # <-
|
|
||||||
incremental = false
|
|
||||||
# This is problematic for stepping..
|
|
||||||
# opt-level = 'z' # <-
|
|
||||||
overflow-checks = true # <-
|
|
||||||
|
|
||||||
# cargo build/run --release
|
|
||||||
[profile.release]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = 3 # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
|
|
||||||
[profile.small]
|
|
||||||
inherits = "release"
|
|
||||||
codegen-units = 1
|
|
||||||
debug-assertions = false # <-
|
|
||||||
lto = true
|
|
||||||
opt-level = 'z' # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
# strip = true # Automatically strip symbols from the binary.
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot B with an offset at address 0x22000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00022000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
//! Simple blinky example using the HAL
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
use embedded_hal::digital::StatefulOutputPin;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
|
||||||
use va416xx_hal::{gpio::PinsG, pac};
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
rtt_init_print!();
|
|
||||||
rprintln!("VA416xx HAL blinky example for App Slot B");
|
|
||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
|
||||||
let mut led = portg.pg5.into_readable_push_pull_output();
|
|
||||||
loop {
|
|
||||||
cortex_m::asm::delay(8_000_000);
|
|
||||||
led.toggle().ok();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn simple() {
|
|
||||||
assert_eq!(1 + 1, 2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,526 +0,0 @@
|
|||||||
//! Vorago flashloader which can be used to flash image A and image B via a simple
|
|
||||||
//! low-level CCSDS memory interface via a UART wire.
|
|
||||||
//!
|
|
||||||
//! This flash loader can be used after the bootloader was flashed to flash the images.
|
|
||||||
//! You can also use this as an starting application for a software update mechanism.
|
|
||||||
//!
|
|
||||||
//! Bootloader memory map
|
|
||||||
//!
|
|
||||||
//! * <0x0> Bootloader start <code up to 0x3FFE bytes>
|
|
||||||
//! * <0x3FFE> Bootloader CRC <halfword>
|
|
||||||
//! * <0x4000> App image A start <code up to 0x1DFFC (~120K) bytes>
|
|
||||||
//! * <0x21FFC> App image A CRC check length <halfword>
|
|
||||||
//! * <0x21FFE> App image A CRC check value <halfword>
|
|
||||||
//! * <0x22000> App image B start <code up to 0x1DFFC (~120K) bytes>
|
|
||||||
//! * <0x3FFFC> App image B CRC check length <halfword>
|
|
||||||
//! * <0x3FFFE> App image B CRC check value <halfword>
|
|
||||||
//! * <0x40000> <end>
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use 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 MAX_TC_SIZE: usize = 1024;
|
|
||||||
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
|
|
||||||
|
|
||||||
const MAX_TM_SIZE: usize = 128;
|
|
||||||
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
|
|
||||||
|
|
||||||
const UART_BAUDRATE: u32 = 115200;
|
|
||||||
const BOOT_NVM_MEMORY_ID: u8 = 1;
|
|
||||||
const RX_DEBUGGING: bool = false;
|
|
||||||
|
|
||||||
pub enum ActionId {
|
|
||||||
CorruptImageA = 128,
|
|
||||||
CorruptImageB = 129,
|
|
||||||
}
|
|
||||||
pub trait WdtInterface {
|
|
||||||
fn feed(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OptWdt(Option<Wdt>);
|
|
||||||
|
|
||||||
impl WdtInterface for OptWdt {
|
|
||||||
fn feed(&self) {
|
|
||||||
if self.0.is_some() {
|
|
||||||
self.0.as_ref().unwrap().feed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use ringbuf::{
|
|
||||||
traits::{Consumer, Observer, Producer, SplitRef},
|
|
||||||
CachingCons, StaticProd, StaticRb,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Larger buffer for TC to be able to hold the possibly large memory write packets.
|
|
||||||
const BUF_RB_SIZE_TC: usize = 2048;
|
|
||||||
const SIZES_RB_SIZE_TC: usize = 16;
|
|
||||||
|
|
||||||
const BUF_RB_SIZE_TM: usize = 512;
|
|
||||||
const SIZES_RB_SIZE_TM: usize = 16;
|
|
||||||
|
|
||||||
// Ring buffers to handling variable sized telemetry
|
|
||||||
static mut BUF_RB_TM: Lazy<StaticRb<u8, BUF_RB_SIZE_TM>> =
|
|
||||||
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TM>::default);
|
|
||||||
static mut SIZES_RB_TM: Lazy<StaticRb<usize, SIZES_RB_SIZE_TM>> =
|
|
||||||
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TM>::default);
|
|
||||||
|
|
||||||
// Ring buffers to handling variable sized telecommands
|
|
||||||
static mut BUF_RB_TC: Lazy<StaticRb<u8, BUF_RB_SIZE_TC>> =
|
|
||||||
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TC>::default);
|
|
||||||
static mut SIZES_RB_TC: Lazy<StaticRb<usize, SIZES_RB_SIZE_TC>> =
|
|
||||||
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TC>::default);
|
|
||||||
|
|
||||||
pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
|
|
||||||
pub buf_prod: StaticProd<'static, u8, BUF_SIZE>,
|
|
||||||
pub sizes_prod: StaticProd<'static, usize, SIZES_LEN>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DataConsumer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
|
|
||||||
pub buf_cons: CachingCons<&'static StaticRb<u8, BUF_SIZE>>,
|
|
||||||
pub sizes_cons: CachingCons<&'static StaticRb<usize, SIZES_LEN>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
static CLOCKS: OnceCell<Clocks> = OnceCell::new();
|
|
||||||
|
|
||||||
pub const APP_A_START_ADDR: u32 = 0x4000;
|
|
||||||
pub const APP_A_END_ADDR: u32 = 0x22000;
|
|
||||||
pub const APP_B_START_ADDR: u32 = 0x22000;
|
|
||||||
pub const APP_B_END_ADDR: u32 = 0x40000;
|
|
||||||
|
|
||||||
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
|
|
||||||
mod app {
|
|
||||||
use super::*;
|
|
||||||
use cortex_m::asm;
|
|
||||||
use embedded_io::Write;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtic::Mutex;
|
|
||||||
use rtic_monotonics::systick::prelude::*;
|
|
||||||
use rtt_target::rprintln;
|
|
||||||
use satrs::pus::verification::VerificationReportCreator;
|
|
||||||
use spacepackets::ecss::PusServiceId;
|
|
||||||
use spacepackets::ecss::{
|
|
||||||
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
|
|
||||||
};
|
|
||||||
use va416xx_hal::irq_router::enable_and_init_irq_router;
|
|
||||||
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::RxWithIrq<pac::Uart0>,
|
|
||||||
uart_tx: uart::Tx<pac::Uart0>,
|
|
||||||
rom_spi: Option<pac::Spi3>,
|
|
||||||
// We handle all TM in one task.
|
|
||||||
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
|
|
||||||
// We consume all TC in one task.
|
|
||||||
tc_cons: DataConsumer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
|
|
||||||
// We produce all TC in one task.
|
|
||||||
tc_prod: DataProducer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
|
|
||||||
verif_reporter: VerificationReportCreator,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {
|
|
||||||
// Having this shared allows multiple tasks to generate telemetry.
|
|
||||||
tm_prod: DataProducer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
|
|
||||||
setup_edac(&mut cx.device.sysconfig);
|
|
||||||
|
|
||||||
let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
|
|
||||||
let tx = gpiob.pg0.into_funsel_1();
|
|
||||||
let rx = gpiob.pg1.into_funsel_1();
|
|
||||||
|
|
||||||
let uart0 = Uart::new(
|
|
||||||
cx.device.uart0,
|
|
||||||
(tx, rx),
|
|
||||||
Hertz::from_raw(UART_BAUDRATE),
|
|
||||||
&mut cx.device.sysconfig,
|
|
||||||
&clocks,
|
|
||||||
);
|
|
||||||
let (tx, mut rx, _) = uart0.split_with_irq();
|
|
||||||
|
|
||||||
let verif_reporter = VerificationReportCreator::new(0).unwrap();
|
|
||||||
|
|
||||||
let (buf_prod_tm, buf_cons_tm) = unsafe { BUF_RB_TM.split_ref() };
|
|
||||||
let (sizes_prod_tm, sizes_cons_tm) = unsafe { SIZES_RB_TM.split_ref() };
|
|
||||||
|
|
||||||
let (buf_prod_tc, buf_cons_tc) = unsafe { BUF_RB_TC.split_ref() };
|
|
||||||
let (sizes_prod_tc, sizes_cons_tc) = unsafe { SIZES_RB_TC.split_ref() };
|
|
||||||
|
|
||||||
Mono::start(cx.core.SYST, clocks.sysclk().raw());
|
|
||||||
CLOCKS.set(clocks).unwrap();
|
|
||||||
|
|
||||||
rx.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
|
|
||||||
.expect("initiating UART RX failed");
|
|
||||||
pus_tc_handler::spawn().unwrap();
|
|
||||||
pus_tm_tx_handler::spawn().unwrap();
|
|
||||||
(
|
|
||||||
Shared {
|
|
||||||
tm_prod: DataProducer {
|
|
||||||
buf_prod: buf_prod_tm,
|
|
||||||
sizes_prod: sizes_prod_tm,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Local {
|
|
||||||
uart_rx: rx,
|
|
||||||
uart_tx: tx,
|
|
||||||
rom_spi: Some(cx.device.spi3),
|
|
||||||
tm_cons: DataConsumer {
|
|
||||||
buf_cons: buf_cons_tm,
|
|
||||||
sizes_cons: sizes_cons_tm,
|
|
||||||
},
|
|
||||||
tc_cons: DataConsumer {
|
|
||||||
buf_cons: buf_cons_tc,
|
|
||||||
sizes_cons: sizes_cons_tc,
|
|
||||||
},
|
|
||||||
tc_prod: DataProducer {
|
|
||||||
buf_prod: buf_prod_tc,
|
|
||||||
sizes_prod: sizes_prod_tc,
|
|
||||||
},
|
|
||||||
verif_reporter,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// `shared` cannot be accessed from this context
|
|
||||||
#[idle]
|
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
|
||||||
loop {
|
|
||||||
asm::nop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
binds = UART0_RX,
|
|
||||||
local = [
|
|
||||||
cnt: u32 = 0,
|
|
||||||
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
|
|
||||||
uart_rx,
|
|
||||||
tc_prod
|
|
||||||
],
|
|
||||||
)]
|
|
||||||
fn uart_rx_irq(cx: uart_rx_irq::Context) {
|
|
||||||
match cx.local.uart_rx.irq_handler(cx.local.rx_buf) {
|
|
||||||
Ok(result) => {
|
|
||||||
if RX_DEBUGGING {
|
|
||||||
log::debug!("RX Info: {:?}", cx.local.uart_rx.irq_info());
|
|
||||||
log::debug!("RX Result: {:?}", result);
|
|
||||||
}
|
|
||||||
if result.complete() {
|
|
||||||
// Check frame validity (must have COBS format) and decode the frame.
|
|
||||||
// Currently, we expect a full frame or a frame received through a timeout
|
|
||||||
// to be one COBS frame. We could parse for multiple COBS packets in one
|
|
||||||
// frame, but the additional complexity is not necessary here..
|
|
||||||
if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
|
|
||||||
let decoded_size =
|
|
||||||
cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
|
|
||||||
if decoded_size.is_err() {
|
|
||||||
log::warn!("COBS decoding failed");
|
|
||||||
} else {
|
|
||||||
let decoded_size = decoded_size.unwrap();
|
|
||||||
if cx.local.tc_prod.sizes_prod.vacant_len() >= 1
|
|
||||||
&& cx.local.tc_prod.buf_prod.vacant_len() >= decoded_size
|
|
||||||
{
|
|
||||||
// Should never fail, we checked there is enough space.
|
|
||||||
cx.local.tc_prod.sizes_prod.try_push(decoded_size).unwrap();
|
|
||||||
cx.local
|
|
||||||
.tc_prod
|
|
||||||
.buf_prod
|
|
||||||
.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
|
|
||||||
} else {
|
|
||||||
log::warn!("COBS TC queue full");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::warn!("COBS frame with invalid format, start and end bytes are not 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initiate next transfer.
|
|
||||||
cx.local
|
|
||||||
.uart_rx
|
|
||||||
.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
|
|
||||||
.expect("read operation failed");
|
|
||||||
}
|
|
||||||
if result.error() {
|
|
||||||
log::warn!("UART error: {:?}", result.error());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("UART error: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
priority = 2,
|
|
||||||
local=[
|
|
||||||
tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
|
|
||||||
src_data_buf: [u8; 16] = [0; 16],
|
|
||||||
verif_buf: [u8; 32] = [0; 32],
|
|
||||||
tc_cons,
|
|
||||||
rom_spi,
|
|
||||||
verif_reporter
|
|
||||||
],
|
|
||||||
shared=[tm_prod]
|
|
||||||
)]
|
|
||||||
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
|
|
||||||
loop {
|
|
||||||
// Try to read a TC from the ring buffer.
|
|
||||||
let packet_len = cx.local.tc_cons.sizes_cons.try_pop();
|
|
||||||
if packet_len.is_none() {
|
|
||||||
// Small delay, TCs might arrive very quickly.
|
|
||||||
Mono::delay(20.millis()).await;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let packet_len = packet_len.unwrap();
|
|
||||||
log::info!(target: "TC Handler", "received packet with length {}", packet_len);
|
|
||||||
assert_eq!(
|
|
||||||
cx.local
|
|
||||||
.tc_cons
|
|
||||||
.buf_cons
|
|
||||||
.pop_slice(&mut cx.local.tc_buf[0..packet_len]),
|
|
||||||
packet_len
|
|
||||||
);
|
|
||||||
// Read a telecommand, now handle it.
|
|
||||||
handle_valid_pus_tc(&mut cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
|
|
||||||
let pus_tc = PusTcReader::new(cx.local.tc_buf);
|
|
||||||
if pus_tc.is_err() {
|
|
||||||
log::warn!("PUS TC error: {}", pus_tc.unwrap_err());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let (pus_tc, _) = pus_tc.unwrap();
|
|
||||||
let mut write_and_send = |tm: &PusTmCreator| {
|
|
||||||
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
|
|
||||||
cx.shared.tm_prod.lock(|prod| {
|
|
||||||
prod.sizes_prod.try_push(tm.len_written()).unwrap();
|
|
||||||
prod.buf_prod
|
|
||||||
.push_slice(&cx.local.verif_buf[0..written_size]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let token = cx.local.verif_reporter.add_tc(&pus_tc);
|
|
||||||
let (tm, accepted_token) = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
|
|
||||||
.expect("acceptance success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
|
|
||||||
let (tm, started_token) = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
|
|
||||||
.expect("acceptance success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
|
|
||||||
if pus_tc.service() == PusServiceId::Action as u8 {
|
|
||||||
let mut corrupt_image = |base_addr: u32| {
|
|
||||||
// Safety: We only use this for NVM handling and we only do NVM
|
|
||||||
// handling here.
|
|
||||||
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
|
|
||||||
let nvm = Nvm::new(
|
|
||||||
&mut sys_cfg,
|
|
||||||
cx.local.rom_spi.take().unwrap(),
|
|
||||||
CLOCKS.get().as_ref().unwrap(),
|
|
||||||
);
|
|
||||||
let mut buf = [0u8; 4];
|
|
||||||
nvm.read_data(base_addr + 32, &mut buf);
|
|
||||||
buf[0] += 1;
|
|
||||||
nvm.write_data(base_addr + 32, &buf);
|
|
||||||
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
|
||||||
.expect("completion success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
};
|
|
||||||
if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
|
|
||||||
rprintln!("corrupting App Image A");
|
|
||||||
corrupt_image(APP_A_START_ADDR);
|
|
||||||
}
|
|
||||||
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
|
|
||||||
rprintln!("corrupting App Image B");
|
|
||||||
corrupt_image(APP_B_START_ADDR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
|
|
||||||
log::info!(target: "TC Handler", "received ping TC");
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
|
||||||
.expect("completion success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.step_success(
|
|
||||||
cx.local.src_data_buf,
|
|
||||||
&started_token,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
&[],
|
|
||||||
EcssEnumU8::new(0),
|
|
||||||
)
|
|
||||||
.expect("step success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
// Raw memory write TC
|
|
||||||
if pus_tc.subservice() == 2 {
|
|
||||||
let app_data = pus_tc.app_data();
|
|
||||||
if app_data.len() < 10 {
|
|
||||||
log::warn!(
|
|
||||||
target: "TC Handler",
|
|
||||||
"app data for raw memory write is too short: {}",
|
|
||||||
app_data.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let memory_id = app_data[0];
|
|
||||||
if memory_id != BOOT_NVM_MEMORY_ID {
|
|
||||||
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id);
|
|
||||||
// TODO: Error reporting
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap());
|
|
||||||
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap());
|
|
||||||
if 10 + data_len as usize > app_data.len() {
|
|
||||||
log::warn!(
|
|
||||||
target: "TC Handler",
|
|
||||||
"invalid data length {} for raw mem write detected",
|
|
||||||
data_len
|
|
||||||
);
|
|
||||||
// TODO: Error reporting
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let data = &app_data[10..10 + data_len as usize];
|
|
||||||
log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
|
|
||||||
// Safety: We only use this for NVM handling and we only do NVM
|
|
||||||
// handling here.
|
|
||||||
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
|
|
||||||
let nvm = Nvm::new(
|
|
||||||
&mut sys_cfg,
|
|
||||||
cx.local.rom_spi.take().unwrap(),
|
|
||||||
CLOCKS.get().as_ref().unwrap(),
|
|
||||||
);
|
|
||||||
nvm.write_data(offset, data);
|
|
||||||
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
|
||||||
.expect("completion success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
log::info!("NVM operation done");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
priority = 1,
|
|
||||||
local=[
|
|
||||||
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
|
|
||||||
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
|
|
||||||
uart_tx,
|
|
||||||
tm_cons
|
|
||||||
],
|
|
||||||
shared=[]
|
|
||||||
)]
|
|
||||||
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
|
|
||||||
loop {
|
|
||||||
while cx.local.tm_cons.sizes_cons.occupied_len() > 0 {
|
|
||||||
let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap();
|
|
||||||
cx.local
|
|
||||||
.tm_cons
|
|
||||||
.buf_cons
|
|
||||||
.pop_slice(&mut cx.local.read_buf[0..next_size]);
|
|
||||||
cx.local.encoded_buf[0] = 0;
|
|
||||||
let send_size = cobs::encode(
|
|
||||||
&cx.local.read_buf[0..next_size],
|
|
||||||
&mut cx.local.encoded_buf[1..],
|
|
||||||
);
|
|
||||||
cx.local.encoded_buf[send_size + 1] = 0;
|
|
||||||
cx.local
|
|
||||||
.uart_tx
|
|
||||||
.write(&cx.local.encoded_buf[0..send_size + 2])
|
|
||||||
.unwrap();
|
|
||||||
Mono::delay(2.millis()).await;
|
|
||||||
}
|
|
||||||
Mono::delay(50.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();
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot A with an offset at address 0x4000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00004000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot B with an offset at address 0x22000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00022000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,41 +0,0 @@
|
|||||||
Change Log
|
|
||||||
=======
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
||||||
|
|
||||||
# [unreleased]
|
|
||||||
|
|
||||||
# [v0.2.0] 2024-09-18
|
|
||||||
|
|
||||||
- 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
|
|
||||||
- Adaptions for the UART IRQ feature which are now only implemented for the RX part of the UART.
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
|
|
||||||
- Small fixes and improvements for ADC drivers
|
|
||||||
- Fixes for the SPI implementation where the clock divider values were not calculated
|
|
||||||
correctly
|
|
||||||
- Fixes for UART IRQ handler implementation
|
|
||||||
- Add new IRQ router initialization method `irq_router::enable_and_init_irq_router`. This method
|
|
||||||
also sets the initial values of some registers to 0 where the datasheet and the actual reset
|
|
||||||
value are inconsistent, which can lead to weird bugs like IRQs not being triggered properly.
|
|
||||||
|
|
||||||
## Added
|
|
||||||
|
|
||||||
- Added 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
|
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "va416xx-hal"
|
name = "va416xx-hal"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "HAL for the Vorago VA416xx family of MCUs"
|
description = "HAL for the Vorago VA416xx family of MCUs"
|
||||||
@ -12,16 +12,16 @@ categories = ["embedded", "no-std", "hardware-support"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
critical-section = "1"
|
|
||||||
nb = "1"
|
nb = "1"
|
||||||
paste = "1"
|
paste = "1"
|
||||||
embedded-hal-nb = "1"
|
embedded-hal-nb = "1"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.6"
|
||||||
|
embedded-dma = "0.2"
|
||||||
num_enum = { version = "0.7", default-features = false }
|
num_enum = { version = "0.7", default-features = false }
|
||||||
typenum = "1"
|
typenum = "1"
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
bitfield = "0.17"
|
bitfield = "0.15"
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "0.3", optional = true }
|
||||||
fugit = "0.3"
|
fugit = "0.3"
|
||||||
delegate = "0.12"
|
delegate = "0.12"
|
||||||
@ -39,16 +39,8 @@ features = ["critical-section"]
|
|||||||
default = ["rt", "revb"]
|
default = ["rt", "revb"]
|
||||||
rt = ["va416xx/rt"]
|
rt = ["va416xx/rt"]
|
||||||
defmt = ["dep:defmt", "fugit/defmt"]
|
defmt = ["dep:defmt", "fugit/defmt"]
|
||||||
|
|
||||||
va41630 = ["device-selected"]
|
|
||||||
va41620 = ["device-selected"]
|
|
||||||
|
|
||||||
va41629 = ["device-selected"]
|
|
||||||
va41628 = ["device-selected"]
|
|
||||||
|
|
||||||
device-selected = []
|
|
||||||
revb = []
|
revb = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["va41630", "defmt"]
|
all-features = true
|
||||||
rustdoc-args = ["--generate-link-to-definition"]
|
rustdoc-args = ["--generate-link-to-definition"]
|
||||||
|
@ -11,14 +11,6 @@ raw PAC. This crate also implements traits specified by the
|
|||||||
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
||||||
various drivers in the embedded rust ecosystem.
|
various drivers in the embedded rust ecosystem.
|
||||||
|
|
||||||
You have to enable one of the following device features to use this crate depending on
|
|
||||||
which chip you are using:
|
|
||||||
|
|
||||||
- `va41630`
|
|
||||||
- `va41629`
|
|
||||||
- `va41628`
|
|
||||||
- `va41620`
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
||||||
@ -64,7 +56,7 @@ is contained within the
|
|||||||
|
|
||||||
[dependencies.va416xx-hal]
|
[dependencies.va416xx-hal]
|
||||||
version = "<Most Recent Version>"
|
version = "<Most Recent Version>"
|
||||||
features = ["va41630"]
|
features = ["rt"]
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Build the application with `cargo build`
|
6. Build the application with `cargo build`
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
//! Analog to Digital Converter (ADC) driver.
|
|
||||||
//!
|
|
||||||
//! ## Examples
|
|
||||||
//!
|
|
||||||
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
|
|
||||||
//! - [ADC](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/adc.rs)
|
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use crate::clock::Clocks;
|
use crate::clock::Clocks;
|
||||||
@ -52,8 +46,6 @@ pub enum ChannelSelect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
/// This structure is used by the ADC multi-select API to
|
|
||||||
/// allow selecting multiple channels in a convenient manner.
|
|
||||||
pub struct MultiChannelSelect: u16 {
|
pub struct MultiChannelSelect: u16 {
|
||||||
const AnIn0 = 1;
|
const AnIn0 = 1;
|
||||||
const AnIn1 = 1 << 1;
|
const AnIn1 = 1 << 1;
|
||||||
@ -137,18 +129,6 @@ impl ChannelValue {
|
|||||||
pub enum ChannelTagEnabled {}
|
pub enum ChannelTagEnabled {}
|
||||||
pub enum ChannelTagDisabled {}
|
pub enum ChannelTagDisabled {}
|
||||||
|
|
||||||
/// ADC driver structure.
|
|
||||||
///
|
|
||||||
/// Currently, this structure supports three primary ways to measure channel value(s):
|
|
||||||
///
|
|
||||||
/// * Trigger and read a single value
|
|
||||||
/// * Trigger and read a range of ADC values using the sweep functionality
|
|
||||||
/// * Trigger and read multiple ADC values using the sweep functionality
|
|
||||||
///
|
|
||||||
/// The ADC channel tag feature is enabled or disabled at compile time using the
|
|
||||||
/// [ChannelTagEnabled] and [ChannelTagDisabled]. The [Adc::new] method returns a driver instance
|
|
||||||
/// with the channel tag enabled, while the [Adc::new_with_channel_tag] method can be used to
|
|
||||||
/// return an instance with the channel tag enabled.
|
|
||||||
pub struct Adc<TagEnabled = ChannelTagDisabled> {
|
pub struct Adc<TagEnabled = ChannelTagDisabled> {
|
||||||
adc: pac::Adc,
|
adc: pac::Adc,
|
||||||
phantom: PhantomData<TagEnabled>,
|
phantom: PhantomData<TagEnabled>,
|
||||||
@ -174,44 +154,34 @@ impl Adc<ChannelTagDisabled> {
|
|||||||
lower_bound_idx: u8,
|
lower_bound_idx: u8,
|
||||||
upper_bound_idx: u8,
|
upper_bound_idx: u8,
|
||||||
rx_buf: &mut [u16],
|
rx_buf: &mut [u16],
|
||||||
) -> Result<usize, AdcRangeReadError> {
|
) -> Result<(), AdcRangeReadError> {
|
||||||
self.generic_prepare_range_sweep_and_wait_until_ready(
|
self.generic_prepare_range_sweep_and_wait_until_ready(
|
||||||
lower_bound_idx,
|
lower_bound_idx,
|
||||||
upper_bound_idx,
|
upper_bound_idx,
|
||||||
rx_buf.len(),
|
rx_buf.len(),
|
||||||
)?;
|
)?;
|
||||||
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits();
|
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
|
||||||
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
|
|
||||||
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
||||||
}
|
}
|
||||||
Ok(fifo_entry_count as usize)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a sweep for selected ADC channels.
|
|
||||||
///
|
|
||||||
/// Returns the number of read values which were written to the passed RX buffer.
|
|
||||||
pub fn sweep_and_read_multiselect(
|
pub fn sweep_and_read_multiselect(
|
||||||
&self,
|
&self,
|
||||||
ch_select: MultiChannelSelect,
|
ch_select: MultiChannelSelect,
|
||||||
rx_buf: &mut [u16],
|
rx_buf: &mut [u16],
|
||||||
) -> Result<usize, BufferTooSmallError> {
|
) -> Result<(), BufferTooSmallError> {
|
||||||
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
||||||
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits();
|
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
|
||||||
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
|
|
||||||
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
||||||
}
|
}
|
||||||
Ok(fifo_entry_count as usize)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
|
pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
|
||||||
self.generic_try_read_single_value()
|
self.generic_try_read_single_value()
|
||||||
.map(|v| v.map(|v| v & 0xfff))
|
.map(|v| v.map(|v| v & 0xfff))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn channel_tag_enabled(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Adc<ChannelTagEnabled> {
|
impl Adc<ChannelTagEnabled> {
|
||||||
@ -260,21 +230,17 @@ impl Adc<ChannelTagEnabled> {
|
|||||||
Ok(fifo_entry_count as usize)
|
Ok(fifo_entry_count as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a sweep for selected ADC channels.
|
|
||||||
///
|
|
||||||
/// Returns the number of read values which were written to the passed RX buffer.
|
|
||||||
pub fn sweep_and_read_multiselect(
|
pub fn sweep_and_read_multiselect(
|
||||||
&self,
|
&self,
|
||||||
ch_select: MultiChannelSelect,
|
ch_select: MultiChannelSelect,
|
||||||
rx_buf: &mut [ChannelValue],
|
rx_buf: &mut [ChannelValue],
|
||||||
) -> Result<usize, BufferTooSmallError> {
|
) -> Result<(), BufferTooSmallError> {
|
||||||
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
||||||
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits();
|
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
|
||||||
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
|
|
||||||
rx_buf[i as usize] =
|
rx_buf[i as usize] =
|
||||||
self.create_channel_value(self.adc.fifo_data().read().bits() as u16);
|
self.create_channel_value(self.adc.fifo_data().read().bits() as u16);
|
||||||
}
|
}
|
||||||
Ok(fifo_entry_count as usize)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -284,11 +250,6 @@ impl Adc<ChannelTagEnabled> {
|
|||||||
channel: ChannelSelect::try_from(((raw_value >> 12) & 0xf) as u8).unwrap(),
|
channel: ChannelSelect::try_from(((raw_value >> 12) & 0xf) as u8).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn channel_tag_enabled(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TagEnabled> Adc<TagEnabled> {
|
impl<TagEnabled> Adc<TagEnabled> {
|
||||||
@ -313,6 +274,11 @@ impl<TagEnabled> Adc<TagEnabled> {
|
|||||||
self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit());
|
self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn channel_tag_enabled(&self) -> bool {
|
||||||
|
self.adc.ctrl().read().chan_tag_en().bit_is_set()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_fifo(&self) {
|
pub fn clear_fifo(&self) {
|
||||||
self.adc.fifo_clr().write(|w| unsafe { w.bits(1) });
|
self.adc.fifo_clr().write(|w| unsafe { w.bits(1) });
|
||||||
@ -360,6 +326,8 @@ impl<TagEnabled> Adc<TagEnabled> {
|
|||||||
ch_select |= 1 << i;
|
ch_select |= 1 << i;
|
||||||
}
|
}
|
||||||
self.generic_trigger_sweep(ch_select);
|
self.generic_trigger_sweep(ch_select);
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
cortex_m::asm::nop();
|
||||||
while self.adc.status().read().adc_busy().bit_is_set() {
|
while self.adc.status().read().adc_busy().bit_is_set() {
|
||||||
cortex_m::asm::nop();
|
cortex_m::asm::nop();
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
//! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
use crate::adc::ADC_MAX_CLK;
|
use crate::adc::ADC_MAX_CLK;
|
||||||
use crate::pac;
|
use crate::pac;
|
||||||
|
|
||||||
@ -311,12 +310,6 @@ impl ClkgenCfgr {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn pll_cfg(mut self, pll_cfg: PllCfg) -> Self {
|
|
||||||
self.pll_cfg = Some(pll_cfg);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self {
|
pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self {
|
||||||
self.ref_clk_sel = ref_clk_sel;
|
self.ref_clk_sel = ref_clk_sel;
|
||||||
@ -324,7 +317,7 @@ impl ClkgenCfgr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Configures all clocks and return a clock configuration structure containing the final
|
/// Configures all clocks and return a clock configuration structure containing the final
|
||||||
/// frozen clocks.
|
/// frozen clock.
|
||||||
///
|
///
|
||||||
/// Internal implementation details: This implementation is based on the HAL implementation
|
/// Internal implementation details: This implementation is based on the HAL implementation
|
||||||
/// which performs a lot of delays. I do not know if all of those are necessary, but
|
/// which performs a lot of delays. I do not know if all of those are necessary, but
|
||||||
@ -454,22 +447,11 @@ impl ClkgenCfgr {
|
|||||||
.ctrl0()
|
.ctrl0()
|
||||||
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
|
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
|
||||||
|
|
||||||
Ok(Clocks {
|
|
||||||
sysclk: final_sysclk,
|
|
||||||
apb1: final_sysclk / 2,
|
|
||||||
apb2: final_sysclk / 4,
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
adc_clk: self.cfg_adc_clk_div(final_sysclk),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
fn cfg_adc_clk_div(&self, final_sysclk: Hertz) -> Hertz {
|
|
||||||
// I will just do the ADC stuff like Vorago does it.
|
// I will just do the ADC stuff like Vorago does it.
|
||||||
// ADC clock (must be 2-12.5 MHz)
|
// ADC clock (must be 2-12.5 MHz)
|
||||||
// NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue
|
// NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue
|
||||||
// For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range)
|
// For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range)
|
||||||
if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
|
let adc_clk = if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
|
||||||
self.clkgen
|
self.clkgen
|
||||||
.ctrl1()
|
.ctrl1()
|
||||||
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
|
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
|
||||||
@ -479,7 +461,14 @@ impl ClkgenCfgr {
|
|||||||
.ctrl1()
|
.ctrl1()
|
||||||
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
|
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
|
||||||
final_sysclk / 8
|
final_sysclk / 8
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(Clocks {
|
||||||
|
sysclk: final_sysclk,
|
||||||
|
apb1: final_sysclk / 2,
|
||||||
|
apb2: final_sysclk / 4,
|
||||||
|
adc_clk,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,39 +483,37 @@ pub struct Clocks {
|
|||||||
sysclk: Hertz,
|
sysclk: Hertz,
|
||||||
apb1: Hertz,
|
apb1: Hertz,
|
||||||
apb2: Hertz,
|
apb2: Hertz,
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
adc_clk: Hertz,
|
adc_clk: Hertz,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clocks {
|
impl Clocks {
|
||||||
/// Returns the frequency of the HBO clock
|
/// Returns the frequency of the HBO clock
|
||||||
pub const fn hbo(&self) -> Hertz {
|
pub fn hbo(&self) -> Hertz {
|
||||||
HBO_FREQ
|
HBO_FREQ
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the frequency of the APB0 which is equal to the system clock.
|
/// Returns the frequency of the APB0 which is equal to the system clock.
|
||||||
pub const fn apb0(&self) -> Hertz {
|
pub fn apb0(&self) -> Hertz {
|
||||||
self.sysclk()
|
self.sysclk()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns system clock divied by 2.
|
/// Returns system clock divied by 2.
|
||||||
pub const fn apb1(&self) -> Hertz {
|
pub fn apb1(&self) -> Hertz {
|
||||||
self.apb1
|
self.apb1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns system clock divied by 4.
|
/// Returns system clock divied by 4.
|
||||||
pub const fn apb2(&self) -> Hertz {
|
pub fn apb2(&self) -> Hertz {
|
||||||
self.apb2
|
self.apb2
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the system (core) frequency
|
/// Returns the system (core) frequency
|
||||||
pub const fn sysclk(&self) -> Hertz {
|
pub fn sysclk(&self) -> Hertz {
|
||||||
self.sysclk
|
self.sysclk
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the ADC clock frequency which has a separate divider.
|
/// Returns the ADC clock frequency which has a separate divider.
|
||||||
#[cfg(not(feature = "va41628"))]
|
pub fn adc_clk(&self) -> Hertz {
|
||||||
pub const fn adc_clk(&self) -> Hertz {
|
|
||||||
self.adc_clk
|
self.adc_clk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
//! Digital to Analog Converter (DAC) driver.
|
|
||||||
//!
|
|
||||||
//! ## Examples
|
|
||||||
//!
|
|
||||||
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
|
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
|
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
|
||||||
|
use embedded_dma::WriteBuffer;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clock::{PeripheralClock, PeripheralSelect},
|
clock::{PeripheralClock, PeripheralSelect},
|
||||||
enable_interrupt, pac,
|
enable_interrupt, pac,
|
||||||
@ -203,6 +205,28 @@ pub struct DmaChannel {
|
|||||||
pub ch_ctrl_alt: &'static mut DmaChannelControl,
|
pub ch_ctrl_alt: &'static mut DmaChannelControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This transfer structure takes ownership of the mutable destination slice.
|
||||||
|
///
|
||||||
|
/// This avoids accidental violation of the ownership rules because the DMA now has mutable
|
||||||
|
/// access to that slice as well. The mutable slice can be retrieved after DMA transfer completion
|
||||||
|
/// by using the [Self::release] method.
|
||||||
|
pub struct DmaTransfer<W> {
|
||||||
|
buf: W,
|
||||||
|
//ch: DmaChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: WriteBuffer> DmaTransfer<W> {
|
||||||
|
/// Retrieve the mutable destination slice once the DMA transfer has completed.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// - The user MUST ensure that the DMA transfer has completed, for example by polling a
|
||||||
|
/// completion flag set by the DMA_DONE ISR.
|
||||||
|
pub unsafe fn release(self) -> W {
|
||||||
|
self.buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DmaChannel {
|
impl DmaChannel {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn channel(&self) -> u8 {
|
pub fn channel(&self) -> u8 {
|
||||||
@ -281,35 +305,25 @@ impl DmaChannel {
|
|||||||
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
/// start the DMA transfer.
|
/// start the DMA transfer.
|
||||||
///
|
pub fn prepare_mem_to_mem_transfer_8_bit<W: WriteBuffer<Word = u8>>(
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
|
||||||
/// is safe for DMA reads. The specific requirements can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
pub unsafe fn prepare_mem_to_mem_transfer_8_bit(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
source: &[u8],
|
source: &[u8],
|
||||||
dest: &mut [u8],
|
mut dest: W,
|
||||||
) -> Result<(), DmaTransferInitError> {
|
) -> Result<DmaTransfer<W>, DmaTransferInitError> {
|
||||||
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
let (write_ptr, len) = unsafe { dest.write_buffer() };
|
||||||
|
let len = Self::common_mem_transfer_checks(source.len(), len)?;
|
||||||
self.generic_mem_to_mem_transfer_init(
|
self.generic_mem_to_mem_transfer_init(
|
||||||
len,
|
len,
|
||||||
(source.as_ptr() as u32)
|
(source.as_ptr() as u32)
|
||||||
.checked_add(len as u32)
|
.checked_add(len as u32)
|
||||||
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
(dest.as_ptr() as u32)
|
(write_ptr as u32)
|
||||||
.checked_add(len as u32)
|
.checked_add(len as u32)
|
||||||
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
DataSize::Byte,
|
DataSize::Byte,
|
||||||
AddrIncrement::Byte,
|
AddrIncrement::Byte,
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(DmaTransfer { buf: dest })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares a 16-bit DMA transfer from memory to memory.
|
/// Prepares a 16-bit DMA transfer from memory to memory.
|
||||||
@ -321,21 +335,10 @@ impl DmaChannel {
|
|||||||
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
/// start the DMA transfer.
|
/// start the DMA transfer.
|
||||||
///
|
pub fn prepare_mem_to_mem_transfer_16_bit<'dest>(
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
|
||||||
/// is safe for DMA reads. The specific requirements can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
pub unsafe fn prepare_mem_to_mem_transfer_16_bit(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
source: &[u16],
|
source: &[u16],
|
||||||
dest: &mut [u16],
|
dest: &'dest mut [u16],
|
||||||
) -> Result<(), DmaTransferInitError> {
|
) -> Result<(), DmaTransferInitError> {
|
||||||
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
||||||
self.generic_mem_to_mem_transfer_init(
|
self.generic_mem_to_mem_transfer_init(
|
||||||
@ -361,21 +364,10 @@ impl DmaChannel {
|
|||||||
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
/// start the DMA transfer.
|
/// start the DMA transfer.
|
||||||
///
|
pub fn prepare_mem_to_mem_transfer_32_bit<'dest>(
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
|
||||||
/// is safe for DMA reads. The specific requirements can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
pub unsafe fn prepare_mem_to_mem_transfer_32_bit(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
source: &[u32],
|
source: &[u32],
|
||||||
dest: &mut [u32],
|
dest: &'dest mut [u32],
|
||||||
) -> Result<(), DmaTransferInitError> {
|
) -> Result<(), DmaTransferInitError> {
|
||||||
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
||||||
self.generic_mem_to_mem_transfer_init(
|
self.generic_mem_to_mem_transfer_init(
|
||||||
@ -392,54 +384,6 @@ impl DmaChannel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares a 8-bit DMA transfer from memory to a peripheral.
|
|
||||||
///
|
|
||||||
/// It is assumed that a peripheral with a 16-byte FIFO is used here and that the
|
|
||||||
/// transfer is activated by an IRQ trigger when the half-full interrupt of the peripheral
|
|
||||||
/// is fired. Therefore, this function configured the DMA in [CycleControl::Basic] mode with
|
|
||||||
/// rearbitration happening every 8 DMA cycles. It also configures the primary channel control
|
|
||||||
/// structure to perform the transfer.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the source buffer is safe for DMA reads. The specific requirements
|
|
||||||
/// can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
///
|
|
||||||
/// The destination address must be the pointer address of a peripheral FIFO register address.
|
|
||||||
/// You must also ensure that the regular synchronous transfer API of the peripheral is NOT
|
|
||||||
/// used to perform transfers.
|
|
||||||
pub unsafe fn prepare_mem_to_periph_transfer_8_bit(
|
|
||||||
&mut self,
|
|
||||||
source: &[u8],
|
|
||||||
dest: *mut u32,
|
|
||||||
) -> Result<(), DmaTransferInitError> {
|
|
||||||
if source.len() > MAX_DMA_TRANSFERS_PER_CYCLE {
|
|
||||||
return Err(DmaTransferInitError::TransferSizeTooLarge(source.len()));
|
|
||||||
}
|
|
||||||
let len = source.len() - 1;
|
|
||||||
self.ch_ctrl_pri.cfg.set_raw(0);
|
|
||||||
self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32)
|
|
||||||
.checked_add(len as u32)
|
|
||||||
.ok_or(DmaTransferInitError::AddrOverflow)?;
|
|
||||||
self.ch_ctrl_pri.dest_end_ptr = dest as u32;
|
|
||||||
self.ch_ctrl_pri
|
|
||||||
.cfg
|
|
||||||
.set_cycle_ctr(CycleControl::Basic as u8);
|
|
||||||
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte as u8);
|
|
||||||
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte as u8);
|
|
||||||
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte as u8);
|
|
||||||
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None as u8);
|
|
||||||
self.ch_ctrl_pri.cfg.set_n_minus_1(len as u16);
|
|
||||||
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8 as u8);
|
|
||||||
self.select_primary_structure();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function performs common checks and returns the source length minus one which is
|
// This function performs common checks and returns the source length minus one which is
|
||||||
// relevant for further configuration of the DMA. This is because the DMA API expects N minus
|
// relevant for further configuration of the DMA. This is because the DMA API expects N minus
|
||||||
// 1 and the source and end pointer need to point to the last transfer address.
|
// 1 and the source and end pointer need to point to the last transfer address.
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
use crate::{enable_interrupt, pac};
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_rom_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
|
|
||||||
syscfg
|
|
||||||
.rom_scrub()
|
|
||||||
.write(|w| unsafe { w.bits(counter_reset as u32) })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_ram0_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
|
|
||||||
syscfg
|
|
||||||
.ram0_scrub()
|
|
||||||
.write(|w| unsafe { w.bits(counter_reset as u32) })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_ram1_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
|
|
||||||
syscfg
|
|
||||||
.ram1_scrub()
|
|
||||||
.write(|w| unsafe { w.bits(counter_reset as u32) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function enables the SBE related interrupts. The user should also provide a
|
|
||||||
/// `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
|
|
||||||
/// `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 `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 `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()
|
|
||||||
});
|
|
||||||
}
|
|
@ -21,6 +21,7 @@
|
|||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
|
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub struct IsMaskedError;
|
pub struct IsMaskedError;
|
||||||
|
@ -295,17 +295,12 @@ pub trait PinId: Sealed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! pin_id {
|
macro_rules! pin_id {
|
||||||
($Group:ident, $Id:ident, $NUM:literal $(, $meta: meta)?) => {
|
($Group:ident, $Id:ident, $NUM:literal) => {
|
||||||
// Need paste macro to use ident in doc attribute
|
// Need paste macro to use ident in doc attribute
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
$(#[$meta])?
|
|
||||||
#[doc = "Pin ID representing pin " $Id]
|
#[doc = "Pin ID representing pin " $Id]
|
||||||
pub enum $Id {}
|
pub enum $Id {}
|
||||||
|
|
||||||
$(#[$meta])?
|
|
||||||
impl Sealed for $Id {}
|
impl Sealed for $Id {}
|
||||||
|
|
||||||
$(#[$meta])?
|
|
||||||
impl PinId for $Id {
|
impl PinId for $Id {
|
||||||
const DYN: DynPinId = DynPinId {
|
const DYN: DynPinId = DynPinId {
|
||||||
group: DynGroup::$Group,
|
group: DynGroup::$Group,
|
||||||
@ -694,14 +689,13 @@ impl<I: PinId> Registers<I> {
|
|||||||
|
|
||||||
macro_rules! pins {
|
macro_rules! pins {
|
||||||
(
|
(
|
||||||
$Port:ident, $PinsName:ident, $($Id:ident $(, $meta:meta)?)+,
|
$Port:ident, $PinsName:ident, $($Id:ident,)+,
|
||||||
) => {
|
) => {
|
||||||
paste::paste!(
|
paste::paste!(
|
||||||
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
|
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
|
||||||
pub struct $PinsName {
|
pub struct $PinsName {
|
||||||
port: $Port,
|
port: $Port,
|
||||||
$(
|
$(
|
||||||
$(#[$meta])?
|
|
||||||
#[doc = "Pin " $Id]
|
#[doc = "Pin " $Id]
|
||||||
pub [<$Id:lower>]: Pin<$Id, Reset>,
|
pub [<$Id:lower>]: Pin<$Id, Reset>,
|
||||||
)+
|
)+
|
||||||
@ -724,7 +718,6 @@ macro_rules! pins {
|
|||||||
port,
|
port,
|
||||||
// Safe because we only create one `Pin` per `PinId`
|
// Safe because we only create one `Pin` per `PinId`
|
||||||
$(
|
$(
|
||||||
$(#[$meta])?
|
|
||||||
[<$Id:lower>]: unsafe { Pin::new() },
|
[<$Id:lower>]: unsafe { Pin::new() },
|
||||||
)+
|
)+
|
||||||
}
|
}
|
||||||
@ -746,15 +739,13 @@ macro_rules! pins {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
|
|
||||||
//$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal, $meta: meta),)+]
|
|
||||||
macro_rules! declare_pins {
|
macro_rules! declare_pins {
|
||||||
(
|
(
|
||||||
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
|
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal),)+]
|
||||||
) => {
|
) => {
|
||||||
pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
|
pins!($Port, $PinsName, $($Id,)+,);
|
||||||
$(
|
$(
|
||||||
pin_id!($Group, $Id, $NUM $(, $meta)?);
|
pin_id!($Group, $Id, $NUM);
|
||||||
)+
|
)+
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -779,7 +770,7 @@ declare_pins!(
|
|||||||
(PA12, 12),
|
(PA12, 12),
|
||||||
(PA13, 13),
|
(PA13, 13),
|
||||||
(PA14, 14),
|
(PA14, 14),
|
||||||
(PA15, 15)
|
(PA15, 15),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -793,17 +784,17 @@ declare_pins!(
|
|||||||
(PB2, 2),
|
(PB2, 2),
|
||||||
(PB3, 3),
|
(PB3, 3),
|
||||||
(PB4, 4),
|
(PB4, 4),
|
||||||
(PB5, 5, cfg(not(feature = "va41628"))),
|
(PB5, 5),
|
||||||
(PB6, 6, cfg(not(feature = "va41628"))),
|
(PB6, 6),
|
||||||
(PB7, 7, cfg(not(feature = "va41628"))),
|
(PB7, 7),
|
||||||
(PB8, 8, cfg(not(feature = "va41628"))),
|
(PB8, 8),
|
||||||
(PB9, 9, cfg(not(feature = "va41628"))),
|
(PB9, 9),
|
||||||
(PB10, 10, cfg(not(feature = "va41628"))),
|
(PB10, 10),
|
||||||
(PB11, 11, cfg(not(feature = "va41628"))),
|
(PB11, 11),
|
||||||
(PB12, 12),
|
(PB12, 12),
|
||||||
(PB13, 13),
|
(PB13, 13),
|
||||||
(PB14, 14),
|
(PB14, 14),
|
||||||
(PB15, 15)
|
(PB15, 15),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -825,9 +816,9 @@ declare_pins!(
|
|||||||
(PC10, 10),
|
(PC10, 10),
|
||||||
(PC11, 11),
|
(PC11, 11),
|
||||||
(PC12, 12),
|
(PC12, 12),
|
||||||
(PC13, 13, cfg(not(feature = "va41628"))),
|
(PC13, 13),
|
||||||
(PC14, 14),
|
(PC14, 14),
|
||||||
(PC15, 15, cfg(not(feature = "va41628")))
|
(PC15, 15),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -836,22 +827,22 @@ declare_pins!(
|
|||||||
PinsD,
|
PinsD,
|
||||||
Portd,
|
Portd,
|
||||||
[
|
[
|
||||||
(PD0, 0, cfg(not(feature = "va41628"))),
|
(PD0, 0),
|
||||||
(PD1, 1, cfg(not(feature = "va41628"))),
|
(PD1, 1),
|
||||||
(PD2, 2, cfg(not(feature = "va41628"))),
|
(PD2, 2),
|
||||||
(PD3, 3, cfg(not(feature = "va41628"))),
|
(PD3, 3),
|
||||||
(PD4, 4, cfg(not(feature = "va41628"))),
|
(PD4, 4),
|
||||||
(PD5, 5, cfg(not(feature = "va41628"))),
|
(PD5, 5),
|
||||||
(PD6, 6, cfg(not(feature = "va41628"))),
|
(PD6, 6),
|
||||||
(PD7, 7, cfg(not(feature = "va41628"))),
|
(PD7, 7),
|
||||||
(PD8, 8, cfg(not(feature = "va41628"))),
|
(PD8, 8),
|
||||||
(PD9, 9, cfg(not(feature = "va41628"))),
|
(PD9, 9),
|
||||||
(PD10, 10),
|
(PD10, 10),
|
||||||
(PD11, 11),
|
(PD11, 11),
|
||||||
(PD12, 12),
|
(PD12, 12),
|
||||||
(PD13, 13),
|
(PD13, 13),
|
||||||
(PD14, 14),
|
(PD14, 14),
|
||||||
(PD15, 15)
|
(PD15, 15),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -870,12 +861,12 @@ declare_pins!(
|
|||||||
(PE7, 7),
|
(PE7, 7),
|
||||||
(PE8, 8),
|
(PE8, 8),
|
||||||
(PE9, 9),
|
(PE9, 9),
|
||||||
(PE10, 10, cfg(not(feature = "va41628"))),
|
(PE10, 10),
|
||||||
(PE11, 11, cfg(not(feature = "va41628"))),
|
(PE11, 11),
|
||||||
(PE12, 12),
|
(PE12, 12),
|
||||||
(PE13, 13),
|
(PE13, 13),
|
||||||
(PE14, 14),
|
(PE14, 14),
|
||||||
(PE15, 15)
|
(PE15, 15),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -886,20 +877,20 @@ declare_pins!(
|
|||||||
[
|
[
|
||||||
(PF0, 0),
|
(PF0, 0),
|
||||||
(PF1, 1),
|
(PF1, 1),
|
||||||
(PF2, 2, cfg(not(feature = "va41628"))),
|
(PF2, 2),
|
||||||
(PF3, 3, cfg(not(feature = "va41628"))),
|
(PF3, 3),
|
||||||
(PF4, 4, cfg(not(feature = "va41628"))),
|
(PF4, 4),
|
||||||
(PF5, 5, cfg(not(feature = "va41628"))),
|
(PF5, 5),
|
||||||
(PF6, 6, cfg(not(feature = "va41628"))),
|
(PF6, 6),
|
||||||
(PF7, 7, cfg(not(feature = "va41628"))),
|
(PF7, 7),
|
||||||
(PF8, 8, cfg(not(feature = "va41628"))),
|
(PF8, 8),
|
||||||
(PF9, 9),
|
(PF9, 9),
|
||||||
(PF10, 10, cfg(not(feature = "va41628"))),
|
(PF10, 10),
|
||||||
(PF11, 11),
|
(PF11, 11),
|
||||||
(PF12, 12),
|
(PF12, 12),
|
||||||
(PF13, 13),
|
(PF13, 13),
|
||||||
(PF14, 14),
|
(PF14, 14),
|
||||||
(PF15, 15)
|
(PF15, 15),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -915,6 +906,6 @@ declare_pins!(
|
|||||||
(PG4, 4),
|
(PG4, 4),
|
||||||
(PG5, 5),
|
(PG5, 5),
|
||||||
(PG6, 6),
|
(PG6, 6),
|
||||||
(PG7, 7)
|
(PG7, 7),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
//! IRQ Router peripheral support.
|
|
||||||
use crate::{
|
|
||||||
clock::{PeripheralSelect, SyscfgExt},
|
|
||||||
pac,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This enables and initiates the peripheral.
|
|
||||||
///
|
|
||||||
/// Please note that this method also writes 0 to the registers which do not have 0 as the default
|
|
||||||
/// reset value. The programmers guide v1.2 and the actual values inspected using a SVD viewer
|
|
||||||
/// are inconsistent here, and the registers being non-zero can actually lead to weird bugs
|
|
||||||
/// when working with interrupts. Registers DMASELx and ADCSEL/DMASELx will reset to 0x7f and 0x1f
|
|
||||||
/// respectively instead of 0x00.
|
|
||||||
pub fn enable_and_init_irq_router(sysconfig: &mut pac::Sysconfig, irq_router: &pac::IrqRouter) {
|
|
||||||
sysconfig.enable_peripheral_clock(PeripheralSelect::IrqRouter);
|
|
||||||
sysconfig.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter);
|
|
||||||
unsafe {
|
|
||||||
irq_router.dmasel0().write_with_zero(|w| w);
|
|
||||||
irq_router.dmasel1().write_with_zero(|w| w);
|
|
||||||
irq_router.dmasel2().write_with_zero(|w| w);
|
|
||||||
irq_router.dmasel3().write_with_zero(|w| w);
|
|
||||||
irq_router.adcsel().write_with_zero(|w| w);
|
|
||||||
irq_router.dacsel0().write_with_zero(|w| w);
|
|
||||||
irq_router.dacsel1().write_with_zero(|w| w);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +1,19 @@
|
|||||||
//! This is the **H**ardware **A**bstraction **L**ayer (HAL) for the VA416xx MCU family.
|
|
||||||
//!
|
|
||||||
//! It is an additional hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx).
|
|
||||||
|
|
||||||
//! It is the result of reading the datasheet for the device and encoding a type-safe layer over the
|
|
||||||
//! raw PAC. This crate also implements traits specified by the
|
|
||||||
//! [embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
|
||||||
//! various drivers in the embedded rust ecosystem.
|
|
||||||
|
|
||||||
//! You have to enable one of the following device features to use this crate depending on
|
|
||||||
//! which chip you are using:
|
|
||||||
|
|
||||||
//! - `va41630`
|
|
||||||
//! - `va41629`
|
|
||||||
//! - `va41628`
|
|
||||||
//! - `va41620`
|
|
||||||
//!
|
|
||||||
//! When using this HAL and writing applications for the VA416xx family in general, it is strongly
|
|
||||||
//! recommended that you set up the clock properly, because the default internal HBO clock
|
|
||||||
//! is not very accurate. You can use the [crate::clock] module for this. If you are working
|
|
||||||
//! with interrupts, it is strongly recommended to set up the IRQ router with the
|
|
||||||
//! [crate::irq_router] module at the very least because that peripheral has confusing and/or
|
|
||||||
//! faulty register reset values which might lead to weird bugs and glitches.
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
#[cfg(not(feature = "device-selected"))]
|
|
||||||
compile_error!(
|
|
||||||
"This crate requires one of the following device features enabled:
|
|
||||||
va41630
|
|
||||||
va41629
|
|
||||||
va41628
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
pub use va416xx as device;
|
pub use va416xx as device;
|
||||||
pub use va416xx as pac;
|
pub use va416xx as pac;
|
||||||
|
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod adc;
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
|
pub mod dac;
|
||||||
pub mod dma;
|
pub mod dma;
|
||||||
pub mod edac;
|
|
||||||
pub mod gpio;
|
pub mod gpio;
|
||||||
pub mod i2c;
|
pub mod i2c;
|
||||||
pub mod irq_router;
|
|
||||||
pub mod pwm;
|
pub mod pwm;
|
||||||
pub mod spi;
|
pub mod spi;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
@ -54,14 +22,6 @@ pub mod typelevel;
|
|||||||
pub mod uart;
|
pub mod uart;
|
||||||
pub mod wdt;
|
pub mod wdt;
|
||||||
|
|
||||||
#[cfg(feature = "va41630")]
|
|
||||||
pub mod nvm;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
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,
|
||||||
|
@ -1,274 +0,0 @@
|
|||||||
//! Non-volatile memory (NVM) driver.
|
|
||||||
//!
|
|
||||||
//! Provides a basic API to work with the internal NVM of the VA41630 MCU.
|
|
||||||
//!
|
|
||||||
//! # Examples
|
|
||||||
//!
|
|
||||||
//! - [Flashloader application](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
|
||||||
use embedded_hal::spi::MODE_0;
|
|
||||||
|
|
||||||
use 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", derive(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() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,20 +8,18 @@ use core::{convert::Infallible, marker::PhantomData, ops::Deref};
|
|||||||
use embedded_hal::spi::Mode;
|
use embedded_hal::spi::Mode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clock::{Clocks, PeripheralSelect, SyscfgExt},
|
clock::{PeripheralSelect, SyscfgExt},
|
||||||
gpio::{
|
gpio::{
|
||||||
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
|
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
|
||||||
PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1, PC10, PC11, PC7, PC8, PC9, PE12,
|
PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1,
|
||||||
PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9, PF0, PF1, PG2, PG3, PG4,
|
PC10, PC11, PC7, PC8, PC9, PE10, PE11, PE12, PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9,
|
||||||
|
PF0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PG2, PG3, PG4,
|
||||||
},
|
},
|
||||||
pac,
|
pac,
|
||||||
time::Hertz,
|
time::Hertz,
|
||||||
typelevel::{NoneT, Sealed},
|
typelevel::{NoneT, Sealed},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
use crate::gpio::{PB10, PB11, PB5, PB6, PB7, PB8, PB9, PE10, PE11, PF2, PF3, PF4, PF5, PF6, PF7};
|
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Defintions
|
// Defintions
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
@ -29,11 +27,6 @@ use crate::gpio::{PB10, PB11, PB5, PB6, PB7, PB8, PB9, PE10, PE11, PF2, PF3, PF4
|
|||||||
// FIFO has a depth of 16.
|
// FIFO has a depth of 16.
|
||||||
const FILL_DEPTH: usize = 12;
|
const FILL_DEPTH: usize = 12;
|
||||||
|
|
||||||
pub const DEFAULT_CLK_DIV: u16 = 2;
|
|
||||||
|
|
||||||
pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
|
|
||||||
pub const BMSKIPDATA_MASK: u32 = 1 << 30;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum HwChipSelectId {
|
pub enum HwChipSelectId {
|
||||||
Id0 = 0,
|
Id0 = 0,
|
||||||
@ -82,20 +75,15 @@ pub trait OptionalHwCs<Spi>: HwCsProvider + Sealed {}
|
|||||||
macro_rules! hw_cs_pins {
|
macro_rules! hw_cs_pins {
|
||||||
($SPIx:path, $portId: path:
|
($SPIx:path, $portId: path:
|
||||||
$(
|
$(
|
||||||
($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident $(, $meta: meta)?),
|
($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident),
|
||||||
)+
|
)+
|
||||||
) => {
|
) => {
|
||||||
$(
|
$(
|
||||||
$(#[$meta])?
|
|
||||||
impl HwCsProvider for Pin<$PXx, $AFx> {
|
impl HwCsProvider for Pin<$PXx, $AFx> {
|
||||||
const CS_ID: HwChipSelectId = $HwCsIdent;
|
const CS_ID: HwChipSelectId = $HwCsIdent;
|
||||||
const SPI_ID: SpiId = $portId;
|
const SPI_ID: SpiId = $portId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$(#[$meta])?
|
|
||||||
impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {}
|
impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {}
|
||||||
|
|
||||||
$(#[$meta])?
|
|
||||||
pub type $typedef = Pin<$PXx, $AFx>;
|
pub type $typedef = Pin<$PXx, $AFx>;
|
||||||
)+
|
)+
|
||||||
};
|
};
|
||||||
@ -111,14 +99,6 @@ impl OptionalHwCs<pac::Spi1> for NoneT {}
|
|||||||
impl OptionalHwCs<pac::Spi2> for NoneT {}
|
impl OptionalHwCs<pac::Spi2> for NoneT {}
|
||||||
impl OptionalHwCs<pac::Spi3> for NoneT {}
|
impl OptionalHwCs<pac::Spi3> for NoneT {}
|
||||||
|
|
||||||
pub struct RomSpiSck;
|
|
||||||
pub struct RomSpiMiso;
|
|
||||||
pub struct RomSpiMosi;
|
|
||||||
|
|
||||||
impl Sealed for RomSpiSck {}
|
|
||||||
impl Sealed for RomSpiMosi {}
|
|
||||||
impl Sealed for RomSpiMiso {}
|
|
||||||
|
|
||||||
// SPI 0
|
// SPI 0
|
||||||
|
|
||||||
impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {}
|
impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {}
|
||||||
@ -126,11 +106,9 @@ impl PinMosi<pac::Spi0> for Pin<PC1, AltFunc1> {}
|
|||||||
impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {}
|
impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {}
|
||||||
|
|
||||||
// SPI 1
|
// SPI 1
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {}
|
impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {}
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {}
|
impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {}
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {}
|
impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {}
|
||||||
|
|
||||||
impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {}
|
impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {}
|
||||||
@ -144,11 +122,8 @@ impl PinSck<pac::Spi1> for Pin<PE13, AltFunc2> {}
|
|||||||
impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {}
|
impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {}
|
||||||
impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {}
|
impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {}
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {}
|
impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {}
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {}
|
impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {}
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {}
|
impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {}
|
||||||
|
|
||||||
// SPI 2
|
// SPI 2
|
||||||
@ -157,18 +132,11 @@ impl PinSck<pac::Spi2> for Pin<PA5, AltFunc2> {}
|
|||||||
impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {}
|
impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {}
|
||||||
impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {}
|
impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {}
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {}
|
impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {}
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {}
|
impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {}
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {}
|
impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {}
|
||||||
|
|
||||||
// SPI3 is shared with the ROM SPI pins and has its own dedicated pins.
|
// SPI3 is shared with the ROM SPI pins and has its own dedicated pins.
|
||||||
//
|
|
||||||
impl PinSck<pac::Spi3> for RomSpiSck {}
|
|
||||||
impl PinMosi<pac::Spi3> for RomSpiMosi {}
|
|
||||||
impl PinMiso<pac::Spi3> for RomSpiMiso {}
|
|
||||||
|
|
||||||
// SPI 0 HW CS pins
|
// SPI 0 HW CS pins
|
||||||
|
|
||||||
@ -177,14 +145,14 @@ hw_cs_pins!(
|
|||||||
(PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0),
|
(PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0),
|
||||||
(PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0),
|
(PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0),
|
||||||
(PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0),
|
(PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0),
|
||||||
(PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0, cfg(not(feature="va41628"))),
|
(PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0),
|
||||||
);
|
);
|
||||||
|
|
||||||
hw_cs_pins!(
|
hw_cs_pins!(
|
||||||
pac::Spi1, SpiId::Spi1:
|
pac::Spi1, SpiId::Spi1:
|
||||||
(PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb, cfg(not(feature="va41628"))),
|
(PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb),
|
||||||
(PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb, cfg(not(feature="va41628"))),
|
(PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb),
|
||||||
(PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb, cfg(not(feature="va41628"))),
|
(PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb),
|
||||||
(PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb),
|
(PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb),
|
||||||
(PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb),
|
(PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb),
|
||||||
(PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb),
|
(PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb),
|
||||||
@ -193,14 +161,14 @@ hw_cs_pins!(
|
|||||||
(PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc),
|
(PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc),
|
||||||
(PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc),
|
(PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc),
|
||||||
(PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe),
|
(PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe),
|
||||||
(PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe, cfg(not(feature="va41628"))),
|
(PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe),
|
||||||
(PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe, cfg(not(feature="va41628"))),
|
(PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe),
|
||||||
(PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe),
|
(PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe),
|
||||||
(PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe),
|
(PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe),
|
||||||
(PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe),
|
(PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe),
|
||||||
(PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe),
|
(PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe),
|
||||||
(PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe),
|
(PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe),
|
||||||
(PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf, cfg(not(feature="va41628"))),
|
(PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf),
|
||||||
(PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
|
(PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -215,9 +183,9 @@ hw_cs_pins!(
|
|||||||
(PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa),
|
(PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa),
|
||||||
(PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf),
|
(PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf),
|
||||||
(PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf),
|
(PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf),
|
||||||
(PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf, cfg(not(feature="va41628"))),
|
(PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf),
|
||||||
(PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf, cfg(not(feature="va41628"))),
|
(PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf),
|
||||||
(PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf, cfg(not(feature="va41628"))),
|
(PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf),
|
||||||
);
|
);
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
@ -228,7 +196,7 @@ pub trait TransferConfigProvider {
|
|||||||
fn sod(&mut self, sod: bool);
|
fn sod(&mut self, sod: bool);
|
||||||
fn blockmode(&mut self, blockmode: bool);
|
fn blockmode(&mut self, blockmode: bool);
|
||||||
fn mode(&mut self, mode: Mode);
|
fn mode(&mut self, mode: Mode);
|
||||||
fn clk_div(&mut self, clk_div: u16);
|
fn frequency(&mut self, spi_clk: Hertz);
|
||||||
fn hw_cs_id(&self) -> u8;
|
fn hw_cs_id(&self) -> u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,8 +204,8 @@ pub trait TransferConfigProvider {
|
|||||||
/// and might change for transfers to different SPI slaves
|
/// and might change for transfers to different SPI slaves
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TransferConfig<HwCs> {
|
pub struct TransferConfig<HwCs> {
|
||||||
pub clk_div: Option<u16>,
|
pub spi_clk: Hertz,
|
||||||
pub mode: Option<Mode>,
|
pub mode: Mode,
|
||||||
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
|
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
|
||||||
/// false
|
/// false
|
||||||
pub hw_cs: Option<HwCs>,
|
pub hw_cs: Option<HwCs>,
|
||||||
@ -251,8 +219,8 @@ pub struct TransferConfig<HwCs> {
|
|||||||
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
||||||
/// the SPI constructor.
|
/// the SPI constructor.
|
||||||
pub struct ErasedTransferConfig {
|
pub struct ErasedTransferConfig {
|
||||||
pub clk_div: Option<u16>,
|
pub spi_clk: Hertz,
|
||||||
pub mode: Option<Mode>,
|
pub mode: Mode,
|
||||||
pub sod: bool,
|
pub sod: bool,
|
||||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||||
@ -262,14 +230,9 @@ pub struct ErasedTransferConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TransferConfig<NoneT> {
|
impl TransferConfig<NoneT> {
|
||||||
pub fn new_no_hw_cs(
|
pub fn new_no_hw_cs(spi_clk: impl Into<Hertz>, mode: Mode, blockmode: bool, sod: bool) -> Self {
|
||||||
clk_div: Option<u16>,
|
|
||||||
mode: Option<Mode>,
|
|
||||||
blockmode: bool,
|
|
||||||
sod: bool,
|
|
||||||
) -> Self {
|
|
||||||
TransferConfig {
|
TransferConfig {
|
||||||
clk_div,
|
spi_clk: spi_clk.into(),
|
||||||
mode,
|
mode,
|
||||||
hw_cs: None,
|
hw_cs: None,
|
||||||
sod,
|
sod,
|
||||||
@ -280,14 +243,14 @@ impl TransferConfig<NoneT> {
|
|||||||
|
|
||||||
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
clk_div: Option<u16>,
|
spi_clk: impl Into<Hertz>,
|
||||||
mode: Option<Mode>,
|
mode: Mode,
|
||||||
hw_cs: Option<HwCs>,
|
hw_cs: Option<HwCs>,
|
||||||
blockmode: bool,
|
blockmode: bool,
|
||||||
sod: bool,
|
sod: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
TransferConfig {
|
TransferConfig {
|
||||||
clk_div,
|
spi_clk: spi_clk.into(),
|
||||||
mode,
|
mode,
|
||||||
hw_cs,
|
hw_cs,
|
||||||
sod,
|
sod,
|
||||||
@ -297,7 +260,7 @@ impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
|||||||
|
|
||||||
pub fn downgrade(self) -> ErasedTransferConfig {
|
pub fn downgrade(self) -> ErasedTransferConfig {
|
||||||
ErasedTransferConfig {
|
ErasedTransferConfig {
|
||||||
clk_div: self.clk_div,
|
spi_clk: self.spi_clk,
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
sod: self.sod,
|
sod: self.sod,
|
||||||
blockmode: self.blockmode,
|
blockmode: self.blockmode,
|
||||||
@ -317,11 +280,11 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mode(&mut self, mode: Mode) {
|
fn mode(&mut self, mode: Mode) {
|
||||||
self.mode = Some(mode);
|
self.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clk_div(&mut self, clk_div: u16) {
|
fn frequency(&mut self, spi_clk: Hertz) {
|
||||||
self.clk_div = Some(clk_div);
|
self.spi_clk = spi_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hw_cs_id(&self) -> u8 {
|
fn hw_cs_id(&self) -> u8 {
|
||||||
@ -329,9 +292,13 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
||||||
pub struct SpiConfig {
|
pub struct SpiConfig {
|
||||||
clk_div: u16,
|
/// Serial clock rate divider. Together with the CLKPRESCALE register, it determines
|
||||||
|
/// the SPI clock rate in master mode. 0 by default. Specifying a higher value
|
||||||
|
/// limits the maximum attainable SPI speed
|
||||||
|
pub ser_clock_rate_div: u8,
|
||||||
/// By default, configure SPI for master mode (ms == false)
|
/// By default, configure SPI for master mode (ms == false)
|
||||||
ms: bool,
|
ms: bool,
|
||||||
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
||||||
@ -342,29 +309,12 @@ pub struct SpiConfig {
|
|||||||
pub master_delayer_capture: bool,
|
pub master_delayer_capture: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SpiConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
clk_div: DEFAULT_CLK_DIV,
|
|
||||||
ms: Default::default(),
|
|
||||||
slave_output_disable: Default::default(),
|
|
||||||
loopback_mode: Default::default(),
|
|
||||||
master_delayer_capture: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpiConfig {
|
impl SpiConfig {
|
||||||
pub fn loopback(mut self, enable: bool) -> Self {
|
pub fn loopback(mut self, enable: bool) -> Self {
|
||||||
self.loopback_mode = enable;
|
self.loopback_mode = enable;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clk_div(mut self, clk_div: u16) -> Self {
|
|
||||||
self.clk_div = clk_div;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn master_mode(mut self, master: bool) -> Self {
|
pub fn master_mode(mut self, master: bool) -> Self {
|
||||||
self.ms = !master;
|
self.ms = !master;
|
||||||
self
|
self
|
||||||
@ -441,16 +391,6 @@ impl Instance for pac::Spi2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance for pac::Spi3 {
|
|
||||||
const IDX: u8 = 3;
|
|
||||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi3;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn ptr() -> *const SpiRegBlock {
|
|
||||||
Self::ptr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Spi
|
// Spi
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
@ -470,7 +410,7 @@ pub struct Spi<SpiInstance, Pins, Word = u8> {
|
|||||||
pins: Pins,
|
pins: Pins,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
|
fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
|
||||||
match mode {
|
match mode {
|
||||||
embedded_hal::spi::MODE_0 => (false, false),
|
embedded_hal::spi::MODE_0 => (false, false),
|
||||||
embedded_hal::spi::MODE_1 => (false, true),
|
embedded_hal::spi::MODE_1 => (false, true),
|
||||||
@ -479,105 +419,10 @@ pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SpiClkConfig {
|
|
||||||
prescale_val: u16,
|
|
||||||
scrdv: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpiClkConfig {
|
|
||||||
pub fn prescale_val(&self) -> u16 {
|
|
||||||
self.prescale_val
|
|
||||||
}
|
|
||||||
pub fn scrdv(&self) -> u8 {
|
|
||||||
self.scrdv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SpiClkConfigError {
|
|
||||||
DivIsZero,
|
|
||||||
DivideValueNotEven,
|
|
||||||
ScrdvValueTooLarge,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn spi_clk_config_from_div(mut div: u16) -> Result<SpiClkConfig, SpiClkConfigError> {
|
|
||||||
if div == 0 {
|
|
||||||
return Err(SpiClkConfigError::DivIsZero);
|
|
||||||
}
|
|
||||||
if div % 2 != 0 {
|
|
||||||
return Err(SpiClkConfigError::DivideValueNotEven);
|
|
||||||
}
|
|
||||||
let mut prescale_val = 0;
|
|
||||||
|
|
||||||
// find largest (even) prescale value that divides into div
|
|
||||||
for i in (2..=0xfe).rev().step_by(2) {
|
|
||||||
if div % i == 0 {
|
|
||||||
prescale_val = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if prescale_val == 0 {
|
|
||||||
return Err(SpiClkConfigError::DivideValueNotEven);
|
|
||||||
}
|
|
||||||
|
|
||||||
div /= prescale_val;
|
|
||||||
if div > u8::MAX as u16 + 1 {
|
|
||||||
return Err(SpiClkConfigError::ScrdvValueTooLarge);
|
|
||||||
}
|
|
||||||
Ok(SpiClkConfig {
|
|
||||||
prescale_val,
|
|
||||||
scrdv: (div - 1) as u8,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn clk_div_for_target_clock(spi_clk: impl Into<Hertz>, clocks: &Clocks) -> Option<u16> {
|
|
||||||
let spi_clk = spi_clk.into();
|
|
||||||
if spi_clk > clocks.apb1() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: Calculate raw divider.
|
|
||||||
let raw_div = clocks.apb1().raw() / spi_clk.raw();
|
|
||||||
let remainder = clocks.apb1().raw() % spi_clk.raw();
|
|
||||||
|
|
||||||
// Step 2: Round up if necessary.
|
|
||||||
let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
|
|
||||||
raw_div + 1
|
|
||||||
} else {
|
|
||||||
raw_div
|
|
||||||
};
|
|
||||||
|
|
||||||
if rounded_div % 2 != 0 {
|
|
||||||
// Take slower clock conservatively.
|
|
||||||
rounded_div += 1;
|
|
||||||
}
|
|
||||||
if rounded_div > u16::MAX as u32 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(rounded_div as u16)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word>
|
impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word>
|
||||||
where
|
where
|
||||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
{
|
{
|
||||||
#[inline]
|
|
||||||
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
|
|
||||||
let val = spi_clk_config_from_div(div)?;
|
|
||||||
self.spi_instance()
|
|
||||||
.ctrl0()
|
|
||||||
.modify(|_, w| unsafe { w.scrdv().bits(val.scrdv as u8) });
|
|
||||||
self.spi_instance()
|
|
||||||
.clkprescale()
|
|
||||||
.write(|w| unsafe { w.bits(val.prescale_val as u32) });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
|
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
|
||||||
let clk_prescale =
|
let clk_prescale =
|
||||||
@ -586,7 +431,6 @@ where
|
|||||||
.clkprescale()
|
.clkprescale()
|
||||||
.write(|w| unsafe { w.bits(clk_prescale) });
|
.write(|w| unsafe { w.bits(clk_prescale) });
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cfg_mode(&mut self, mode: Mode) {
|
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||||
@ -597,11 +441,6 @@ where
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn spi_instance(&self) -> &SpiInstance {
|
|
||||||
&self.spi
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn clear_tx_fifo(&self) {
|
pub fn clear_tx_fifo(&self) {
|
||||||
self.spi.fifo_clr().write(|w| w.txfifo().set_bit());
|
self.spi.fifo_clr().write(|w| w.txfifo().set_bit());
|
||||||
@ -647,13 +486,9 @@ where
|
|||||||
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
|
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
transfer_cfg: &TransferConfig<HwCs>,
|
transfer_cfg: &TransferConfig<HwCs>,
|
||||||
) -> Result<(), SpiClkConfigError> {
|
) {
|
||||||
if let Some(trans_clk_div) = transfer_cfg.clk_div {
|
self.cfg_clock(transfer_cfg.spi_clk);
|
||||||
self.cfg_clock_from_div(trans_clk_div)?;
|
self.cfg_mode(transfer_cfg.mode);
|
||||||
}
|
|
||||||
if let Some(mode) = transfer_cfg.mode {
|
|
||||||
self.cfg_mode(mode);
|
|
||||||
}
|
|
||||||
self.blockmode = transfer_cfg.blockmode;
|
self.blockmode = transfer_cfg.blockmode;
|
||||||
self.spi.ctrl1().modify(|_, w| {
|
self.spi.ctrl1().modify(|_, w| {
|
||||||
if transfer_cfg.sod {
|
if transfer_cfg.sod {
|
||||||
@ -673,7 +508,6 @@ where
|
|||||||
}
|
}
|
||||||
w
|
w
|
||||||
});
|
});
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a word to the slave
|
/// Sends a word to the slave
|
||||||
@ -767,44 +601,43 @@ where
|
|||||||
/// to be done once.
|
/// to be done once.
|
||||||
/// * `syscfg` - Can be passed optionally to enable the peripheral clock
|
/// * `syscfg` - Can be passed optionally to enable the peripheral clock
|
||||||
pub fn new(
|
pub fn new(
|
||||||
syscfg: &mut pac::Sysconfig,
|
|
||||||
clocks: &crate::clock::Clocks,
|
|
||||||
spi: SpiI,
|
spi: SpiI,
|
||||||
pins: (Sck, Miso, Mosi),
|
pins: (Sck, Miso, Mosi),
|
||||||
|
clocks: &crate::clock::Clocks,
|
||||||
spi_cfg: SpiConfig,
|
spi_cfg: SpiConfig,
|
||||||
|
syscfg: &mut pac::Sysconfig,
|
||||||
transfer_cfg: Option<&ErasedTransferConfig>,
|
transfer_cfg: Option<&ErasedTransferConfig>,
|
||||||
) -> Result<Self, SpiClkConfigError> {
|
) -> Self {
|
||||||
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
|
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
|
||||||
// This is done in the C HAL.
|
// This is done in the C HAL.
|
||||||
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
|
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
|
||||||
let SpiConfig {
|
let SpiConfig {
|
||||||
clk_div,
|
ser_clock_rate_div,
|
||||||
ms,
|
ms,
|
||||||
slave_output_disable,
|
slave_output_disable,
|
||||||
loopback_mode,
|
loopback_mode,
|
||||||
master_delayer_capture,
|
master_delayer_capture,
|
||||||
} = spi_cfg;
|
} = spi_cfg;
|
||||||
let mut init_mode = embedded_hal::spi::MODE_0;
|
let mut mode = embedded_hal::spi::MODE_0;
|
||||||
|
let mut clk_prescale = 0x02;
|
||||||
let mut ss = 0;
|
let mut ss = 0;
|
||||||
let mut init_blockmode = false;
|
let mut init_blockmode = false;
|
||||||
let apb1_clk = clocks.apb1();
|
let apb1_clk = clocks.apb1();
|
||||||
if let Some(transfer_cfg) = transfer_cfg {
|
if let Some(transfer_cfg) = transfer_cfg {
|
||||||
if let Some(mode) = transfer_cfg.mode {
|
mode = transfer_cfg.mode;
|
||||||
init_mode = mode;
|
clk_prescale =
|
||||||
}
|
apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1));
|
||||||
//self.cfg_clock_from_div(transfer_cfg.clk_div);
|
|
||||||
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
|
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
|
||||||
ss = transfer_cfg.hw_cs as u8;
|
ss = transfer_cfg.hw_cs as u8;
|
||||||
}
|
}
|
||||||
init_blockmode = transfer_cfg.blockmode;
|
init_blockmode = transfer_cfg.blockmode;
|
||||||
}
|
}
|
||||||
|
|
||||||
let spi_clk_cfg = spi_clk_config_from_div(clk_div)?;
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
|
||||||
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
|
|
||||||
spi.ctrl0().write(|w| {
|
spi.ctrl0().write(|w| {
|
||||||
unsafe {
|
unsafe {
|
||||||
w.size().bits(Word::word_reg());
|
w.size().bits(Word::word_reg());
|
||||||
w.scrdv().bits(spi_clk_cfg.scrdv);
|
w.scrdv().bits(ser_clock_rate_div);
|
||||||
// Clear clock phase and polarity. Will be set to correct value for each
|
// Clear clock phase and polarity. Will be set to correct value for each
|
||||||
// transfer
|
// transfer
|
||||||
w.spo().bit(cpo_bit);
|
w.spo().bit(cpo_bit);
|
||||||
@ -819,17 +652,16 @@ where
|
|||||||
w.blockmode().bit(init_blockmode);
|
w.blockmode().bit(init_blockmode);
|
||||||
unsafe { w.ss().bits(ss) }
|
unsafe { w.ss().bits(ss) }
|
||||||
});
|
});
|
||||||
spi.clkprescale()
|
|
||||||
.write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val as u32) });
|
|
||||||
|
|
||||||
spi.fifo_clr().write(|w| {
|
spi.fifo_clr().write(|w| {
|
||||||
w.rxfifo().set_bit();
|
w.rxfifo().set_bit();
|
||||||
w.txfifo().set_bit()
|
w.txfifo().set_bit()
|
||||||
});
|
});
|
||||||
|
spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) });
|
||||||
// Enable the peripheral as the last step as recommended in the
|
// Enable the peripheral as the last step as recommended in the
|
||||||
// programmers guide
|
// programmers guide
|
||||||
spi.ctrl1().modify(|_, w| w.enable().set_bit());
|
spi.ctrl1().modify(|_, w| w.enable().set_bit());
|
||||||
Ok(Spi {
|
Spi {
|
||||||
inner: SpiBase {
|
inner: SpiBase {
|
||||||
spi,
|
spi,
|
||||||
cfg: spi_cfg,
|
cfg: spi_cfg,
|
||||||
@ -839,39 +671,36 @@ where
|
|||||||
word: PhantomData,
|
word: PhantomData,
|
||||||
},
|
},
|
||||||
pins,
|
pins,
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate::delegate! {
|
|
||||||
to self.inner {
|
|
||||||
#[inline]
|
|
||||||
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn spi_instance(&self) -> &SpiI;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cfg_mode(&mut self, mode: Mode);
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn perid(&self) -> u32;
|
|
||||||
|
|
||||||
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
|
|
||||||
&mut self, transfer_cfg: &TransferConfig<HwCs>
|
|
||||||
) -> Result<(), SpiClkConfigError>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
|
||||||
|
self.inner.cfg_clock(spi_clk);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||||
|
self.inner.cfg_mode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_fill_word(&mut self, fill_word: Word) {
|
pub fn set_fill_word(&mut self, fill_word: Word) {
|
||||||
self.inner.fill_word = fill_word;
|
self.inner.fill_word = fill_word;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn fill_word(&self) -> Word {
|
pub fn fill_word(&self) -> Word {
|
||||||
self.inner.fill_word
|
self.inner.fill_word
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn perid(&self) -> u32 {
|
||||||
|
self.inner.perid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(&mut self, transfer_cfg: &TransferConfig<HwCs>) {
|
||||||
|
self.inner.cfg_transfer(transfer_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
/// Releases the SPI peripheral and associated pins
|
/// Releases the SPI peripheral and associated pins
|
||||||
pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) {
|
pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) {
|
||||||
(self.inner.spi, self.pins, self.inner.cfg)
|
(self.inner.spi, self.pins, self.inner.cfg)
|
||||||
|
@ -2,26 +2,20 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs)
|
//! TODO.
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
|
|
||||||
use cortex_m::asm;
|
use cortex_m::interrupt::Mutex;
|
||||||
use critical_section::Mutex;
|
|
||||||
|
|
||||||
use crate::clock::Clocks;
|
use crate::clock::Clocks;
|
||||||
use crate::gpio::{
|
use crate::gpio::{
|
||||||
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
|
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
|
||||||
PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1,
|
PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3,
|
||||||
PD10, PD11, PD12, PD13, PD14, PD15, PE0, PE1, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE6,
|
PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, PD0, PD1, PD10, PD11, PD12, PD13, PD14, PD15, PD2, PD3,
|
||||||
PE7, PE8, PE9, PF0, PF1, PF11, PF12, PF13, PF14, PF15, PF9, PG0, PG1, PG2, PG3, PG6,
|
PD4, PD5, PD6, PD7, PD8, PD9, PE0, PE1, PE10, PE11, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5,
|
||||||
|
PE6, PE7, PE8, PE9, PF0, PF1, PF10, PF11, PF12, PF13, PF14, PF15, PF2, PF3, PF4, PF5, PF6, PF7,
|
||||||
|
PF8, PF9, PG0, PG1, PG2, PG3, PG6,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
use crate::gpio::{
|
|
||||||
PB10, PB11, PB5, PB6, PB7, PB8, PB9, PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PE10,
|
|
||||||
PE11, PF10, PF2, PF3, PF4, PF5, PF6, PF7, PF8,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::typelevel::Sealed;
|
use crate::typelevel::Sealed;
|
||||||
use crate::{disable_interrupt, prelude::*};
|
use crate::{disable_interrupt, prelude::*};
|
||||||
@ -170,14 +164,6 @@ macro_rules! tim_markers {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn const_clock<Tim: ValidTim + ?Sized>(_: &Tim, clocks: &Clocks) -> Hertz {
|
|
||||||
if Tim::TIM_ID <= 15 {
|
|
||||||
clocks.apb1()
|
|
||||||
} else {
|
|
||||||
clocks.apb2()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tim_markers!(
|
tim_markers!(
|
||||||
(pac::Tim0, 0, pac::Interrupt::TIM0),
|
(pac::Tim0, 0, pac::Interrupt::TIM0),
|
||||||
(pac::Tim1, 1, pac::Interrupt::TIM1),
|
(pac::Tim1, 1, pac::Interrupt::TIM1),
|
||||||
@ -210,11 +196,10 @@ pub trait ValidTimAndPin<Pin: TimPin, Tim: ValidTim>: Sealed {}
|
|||||||
macro_rules! valid_pin_and_tims {
|
macro_rules! valid_pin_and_tims {
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
($PinX:ident, $AltFunc:ident, $TimX:path $(, $meta: meta)?),
|
($PinX:ident, $AltFunc:ident, $TimX:path),
|
||||||
)+
|
)+
|
||||||
) => {
|
) => {
|
||||||
$(
|
$(
|
||||||
$(#[$meta])?
|
|
||||||
impl TimPin for Pin<$PinX, $AltFunc>
|
impl TimPin for Pin<$PinX, $AltFunc>
|
||||||
where
|
where
|
||||||
$PinX: PinId,
|
$PinX: PinId,
|
||||||
@ -222,7 +207,6 @@ macro_rules! valid_pin_and_tims {
|
|||||||
const DYN: DynPinId = $PinX::DYN;
|
const DYN: DynPinId = $PinX::DYN;
|
||||||
}
|
}
|
||||||
|
|
||||||
$(#[$meta])?
|
|
||||||
impl<
|
impl<
|
||||||
PinInstance: TimPin,
|
PinInstance: TimPin,
|
||||||
Tim: ValidTim
|
Tim: ValidTim
|
||||||
@ -233,7 +217,6 @@ macro_rules! valid_pin_and_tims {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
$(#[$meta])?
|
|
||||||
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
|
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
|
||||||
)+
|
)+
|
||||||
};
|
};
|
||||||
@ -259,29 +242,29 @@ valid_pin_and_tims!(
|
|||||||
(PB2, AltFunc2, pac::Tim15),
|
(PB2, AltFunc2, pac::Tim15),
|
||||||
(PB3, AltFunc2, pac::Tim14),
|
(PB3, AltFunc2, pac::Tim14),
|
||||||
(PB4, AltFunc2, pac::Tim13),
|
(PB4, AltFunc2, pac::Tim13),
|
||||||
(PB5, AltFunc2, pac::Tim12, cfg(not(feature = "va41628"))),
|
(PB5, AltFunc2, pac::Tim12),
|
||||||
(PB6, AltFunc2, pac::Tim11, cfg(not(feature = "va41628"))),
|
(PB6, AltFunc2, pac::Tim11),
|
||||||
(PB7, AltFunc2, pac::Tim10, cfg(not(feature = "va41628"))),
|
(PB7, AltFunc2, pac::Tim10),
|
||||||
(PB8, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
|
(PB8, AltFunc2, pac::Tim9),
|
||||||
(PB9, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
|
(PB9, AltFunc2, pac::Tim8),
|
||||||
(PB10, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
|
(PB10, AltFunc2, pac::Tim7),
|
||||||
(PB11, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
|
(PB11, AltFunc2, pac::Tim6),
|
||||||
(PB12, AltFunc2, pac::Tim5),
|
(PB12, AltFunc2, pac::Tim5),
|
||||||
(PB13, AltFunc2, pac::Tim4),
|
(PB13, AltFunc2, pac::Tim4),
|
||||||
(PB14, AltFunc2, pac::Tim3),
|
(PB14, AltFunc2, pac::Tim3),
|
||||||
(PB15, AltFunc2, pac::Tim2),
|
(PB15, AltFunc2, pac::Tim2),
|
||||||
(PC0, AltFunc2, pac::Tim1),
|
(PC0, AltFunc2, pac::Tim1),
|
||||||
(PC1, AltFunc2, pac::Tim0),
|
(PC1, AltFunc2, pac::Tim0),
|
||||||
(PD0, AltFunc2, pac::Tim0, cfg(not(feature = "va41628"))),
|
(PD0, AltFunc2, pac::Tim0),
|
||||||
(PD1, AltFunc2, pac::Tim1, cfg(not(feature = "va41628"))),
|
(PD1, AltFunc2, pac::Tim1),
|
||||||
(PD2, AltFunc2, pac::Tim2, cfg(not(feature = "va41628"))),
|
(PD2, AltFunc2, pac::Tim2),
|
||||||
(PD3, AltFunc2, pac::Tim3, cfg(not(feature = "va41628"))),
|
(PD3, AltFunc2, pac::Tim3),
|
||||||
(PD4, AltFunc2, pac::Tim4, cfg(not(feature = "va41628"))),
|
(PD4, AltFunc2, pac::Tim4),
|
||||||
(PD5, AltFunc2, pac::Tim5, cfg(not(feature = "va41628"))),
|
(PD5, AltFunc2, pac::Tim5),
|
||||||
(PD6, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
|
(PD6, AltFunc2, pac::Tim6),
|
||||||
(PD7, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
|
(PD7, AltFunc2, pac::Tim7),
|
||||||
(PD8, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
|
(PD8, AltFunc2, pac::Tim8),
|
||||||
(PD9, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
|
(PD9, AltFunc2, pac::Tim9),
|
||||||
(PD10, AltFunc2, pac::Tim10),
|
(PD10, AltFunc2, pac::Tim10),
|
||||||
(PD11, AltFunc2, pac::Tim11),
|
(PD11, AltFunc2, pac::Tim11),
|
||||||
(PD12, AltFunc2, pac::Tim12),
|
(PD12, AltFunc2, pac::Tim12),
|
||||||
@ -298,23 +281,23 @@ valid_pin_and_tims!(
|
|||||||
(PE7, AltFunc2, pac::Tim23),
|
(PE7, AltFunc2, pac::Tim23),
|
||||||
(PE8, AltFunc3, pac::Tim16),
|
(PE8, AltFunc3, pac::Tim16),
|
||||||
(PE9, AltFunc3, pac::Tim17),
|
(PE9, AltFunc3, pac::Tim17),
|
||||||
(PE10, AltFunc3, pac::Tim18, cfg(not(feature = "va41628"))),
|
(PE10, AltFunc3, pac::Tim18),
|
||||||
(PE11, AltFunc3, pac::Tim19, cfg(not(feature = "va41628"))),
|
(PE11, AltFunc3, pac::Tim19),
|
||||||
(PE12, AltFunc3, pac::Tim20),
|
(PE12, AltFunc3, pac::Tim20),
|
||||||
(PE13, AltFunc3, pac::Tim21),
|
(PE13, AltFunc3, pac::Tim21),
|
||||||
(PE14, AltFunc3, pac::Tim22),
|
(PE14, AltFunc3, pac::Tim22),
|
||||||
(PE15, AltFunc3, pac::Tim23),
|
(PE15, AltFunc3, pac::Tim23),
|
||||||
(PF0, AltFunc3, pac::Tim0),
|
(PF0, AltFunc3, pac::Tim0),
|
||||||
(PF1, AltFunc3, pac::Tim1),
|
(PF1, AltFunc3, pac::Tim1),
|
||||||
(PF2, AltFunc3, pac::Tim2, cfg(not(feature = "va41628"))),
|
(PF2, AltFunc3, pac::Tim2),
|
||||||
(PF3, AltFunc3, pac::Tim3, cfg(not(feature = "va41628"))),
|
(PF3, AltFunc3, pac::Tim3),
|
||||||
(PF4, AltFunc3, pac::Tim4, cfg(not(feature = "va41628"))),
|
(PF4, AltFunc3, pac::Tim4),
|
||||||
(PF5, AltFunc3, pac::Tim5, cfg(not(feature = "va41628"))),
|
(PF5, AltFunc3, pac::Tim5),
|
||||||
(PF6, AltFunc3, pac::Tim6, cfg(not(feature = "va41628"))),
|
(PF6, AltFunc3, pac::Tim6),
|
||||||
(PF7, AltFunc3, pac::Tim7, cfg(not(feature = "va41628"))),
|
(PF7, AltFunc3, pac::Tim7),
|
||||||
(PF8, AltFunc3, pac::Tim8, cfg(not(feature = "va41628"))),
|
(PF8, AltFunc3, pac::Tim8),
|
||||||
(PF9, AltFunc3, pac::Tim9),
|
(PF9, AltFunc3, pac::Tim9),
|
||||||
(PF10, AltFunc3, pac::Tim10, cfg(not(feature = "va41628"))),
|
(PF10, AltFunc3, pac::Tim10),
|
||||||
(PF11, AltFunc3, pac::Tim11),
|
(PF11, AltFunc3, pac::Tim11),
|
||||||
(PF12, AltFunc3, pac::Tim12),
|
(PF12, AltFunc3, pac::Tim12),
|
||||||
(PF13, AltFunc2, pac::Tim19),
|
(PF13, AltFunc2, pac::Tim19),
|
||||||
@ -337,27 +320,19 @@ valid_pin_and_tims!(
|
|||||||
///
|
///
|
||||||
/// Only the bit related to the corresponding TIM peripheral is modified
|
/// Only the bit related to the corresponding TIM peripheral is modified
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
|
fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
|
||||||
syscfg
|
syscfg
|
||||||
.tim_reset()
|
.tim_reset()
|
||||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) })
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
|
fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
|
||||||
syscfg
|
syscfg
|
||||||
.tim_reset()
|
.tim_reset()
|
||||||
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) })
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn assert_tim_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, tim_id: u8) {
|
|
||||||
assert_tim_reset(syscfg, tim_id);
|
|
||||||
asm::nop();
|
|
||||||
asm::nop();
|
|
||||||
deassert_tim_reset(syscfg, tim_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TimRegBlock = pac::tim0::RegisterBlock;
|
pub type TimRegBlock = pac::tim0::RegisterBlock;
|
||||||
|
|
||||||
/// Register interface.
|
/// Register interface.
|
||||||
@ -484,10 +459,7 @@ unsafe impl TimRegInterface for TimDynRegister {
|
|||||||
// Timers
|
// Timers
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
/// Hardware timers.
|
/// Hardware timers
|
||||||
///
|
|
||||||
/// These timers also implement the [embedded_hal::delay::DelayNs] trait and can be used to delay
|
|
||||||
/// with a higher resolution compared to the Cortex-M systick delays.
|
|
||||||
pub struct CountdownTimer<TIM: ValidTim> {
|
pub struct CountdownTimer<TIM: ValidTim> {
|
||||||
tim: TimRegister<TIM>,
|
tim: TimRegister<TIM>,
|
||||||
curr_freq: Hertz,
|
curr_freq: Hertz,
|
||||||
@ -498,7 +470,7 @@ pub struct CountdownTimer<TIM: ValidTim> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
|
fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
|
||||||
syscfg
|
syscfg
|
||||||
.tim_clk_enable()
|
.tim_clk_enable()
|
||||||
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
|
||||||
@ -596,10 +568,9 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
|
|||||||
pub fn load(&mut self, timeout: impl Into<Hertz>) {
|
pub fn load(&mut self, timeout: impl Into<Hertz>) {
|
||||||
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
|
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
|
||||||
self.curr_freq = timeout.into();
|
self.curr_freq = timeout.into();
|
||||||
self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1;
|
self.rst_val = self.clock.raw() / self.curr_freq.raw();
|
||||||
self.set_reload(self.rst_val);
|
self.set_reload(self.rst_val);
|
||||||
// Decrementing counter, to set the reset value.
|
self.set_count(0);
|
||||||
self.set_count(self.rst_val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -619,7 +590,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn enable(&mut self) {
|
pub fn enable(&mut self) {
|
||||||
self.tim.reg().enable().write(|w| unsafe { w.bits(1) });
|
self.tim.reg().ctrl().modify(|_, w| w.enable().set_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -796,7 +767,7 @@ pub fn set_up_ms_tick<Tim: ValidTim>(
|
|||||||
/// This function can be called in a specified interrupt handler to increment
|
/// This function can be called in a specified interrupt handler to increment
|
||||||
/// the MS counter
|
/// the MS counter
|
||||||
pub fn default_ms_irq_handler() {
|
pub fn default_ms_irq_handler() {
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
let mut ms = MS_COUNTER.borrow(cs).get();
|
let mut ms = MS_COUNTER.borrow(cs).get();
|
||||||
ms += 1;
|
ms += 1;
|
||||||
MS_COUNTER.borrow(cs).set(ms);
|
MS_COUNTER.borrow(cs).set(ms);
|
||||||
@ -805,7 +776,7 @@ pub fn default_ms_irq_handler() {
|
|||||||
|
|
||||||
/// Get the current MS tick count
|
/// Get the current MS tick count
|
||||||
pub fn get_ms_ticks() -> u32 {
|
pub fn get_ms_ticks() -> u32 {
|
||||||
critical_section::with(|cs| MS_COUNTER.borrow(cs).get())
|
cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);
|
pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);
|
||||||
|
@ -3,62 +3,38 @@
|
|||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
||||||
//! - [Flashloader app using UART with IRQs](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
use core::marker::PhantomData;
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
|
|
||||||
use embedded_hal_nb::serial::Read;
|
use embedded_hal_nb::serial::Read;
|
||||||
use fugit::RateExtU32;
|
use fugit::RateExtU32;
|
||||||
|
|
||||||
use crate::clock::{Clocks, PeripheralSelect, SyscfgExt};
|
use crate::clock::{Clocks, PeripheralSelect, SyscfgExt};
|
||||||
use crate::gpio::PF13;
|
use crate::gpio::{AltFunc1, Pin, PD11, PD12, PE2, PE3, PF11, PF12, PF8, PF9, PG0, PG1};
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::{disable_interrupt, enable_interrupt};
|
use crate::{disable_interrupt, enable_interrupt};
|
||||||
use crate::{
|
use crate::{
|
||||||
gpio::{
|
gpio::{AltFunc2, AltFunc3, PA2, PA3, PB14, PB15, PC14, PC15, PC4, PC5},
|
||||||
AltFunc1, AltFunc2, AltFunc3, Pin, PA2, PA3, PB14, PB15, PC14, PC4, PC5, PD11, PD12, PE2,
|
|
||||||
PE3, PF12, PF9, PG0, PG1,
|
|
||||||
},
|
|
||||||
pac::{self, uart0 as uart_base, Uart0, Uart1, Uart2},
|
pac::{self, uart0 as uart_base, Uart0, Uart1, Uart2},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
use crate::gpio::{PC15, PF8};
|
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Type-Level support
|
// Type-Level support
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
pub trait RxPin<Uart> {}
|
pub trait TxRxPins<Uart> {}
|
||||||
pub trait TxPin<Uart> {}
|
|
||||||
|
|
||||||
impl TxPin<Uart0> for Pin<PA2, AltFunc3> {}
|
impl TxRxPins<Uart0> for (Pin<PA2, AltFunc3>, Pin<PA3, AltFunc3>) {}
|
||||||
impl RxPin<Uart0> for Pin<PA3, AltFunc3> {}
|
impl TxRxPins<Uart0> for (Pin<PC4, AltFunc2>, Pin<PC5, AltFunc2>) {}
|
||||||
|
impl TxRxPins<Uart0> for (Pin<PE2, AltFunc3>, Pin<PE3, AltFunc3>) {}
|
||||||
|
impl TxRxPins<Uart0> for (Pin<PG0, AltFunc1>, Pin<PG1, AltFunc1>) {}
|
||||||
|
|
||||||
impl TxPin<Uart0> for Pin<PC4, AltFunc2> {}
|
impl TxRxPins<Uart1> for (Pin<PB14, AltFunc3>, Pin<PB15, AltFunc3>) {}
|
||||||
impl RxPin<Uart0> for Pin<PC5, AltFunc2> {}
|
impl TxRxPins<Uart1> for (Pin<PD11, AltFunc3>, Pin<PD12, AltFunc3>) {}
|
||||||
|
impl TxRxPins<Uart1> for (Pin<PF11, AltFunc1>, Pin<PF12, AltFunc1>) {}
|
||||||
|
|
||||||
impl TxPin<Uart0> for Pin<PE2, AltFunc3> {}
|
impl TxRxPins<Uart2> for (Pin<PC14, AltFunc2>, Pin<PC15, AltFunc2>) {}
|
||||||
impl RxPin<Uart0> for Pin<PE3, AltFunc3> {}
|
impl TxRxPins<Uart2> for (Pin<PF8, AltFunc1>, Pin<PF9, AltFunc1>) {}
|
||||||
|
|
||||||
impl TxPin<Uart0> for Pin<PG0, AltFunc1> {}
|
|
||||||
impl RxPin<Uart0> for Pin<PG1, AltFunc1> {}
|
|
||||||
|
|
||||||
impl TxPin<Uart1> for Pin<PB14, AltFunc3> {}
|
|
||||||
impl RxPin<Uart1> for Pin<PB15, AltFunc3> {}
|
|
||||||
|
|
||||||
impl TxPin<Uart1> for Pin<PD11, AltFunc3> {}
|
|
||||||
impl RxPin<Uart1> for Pin<PD12, AltFunc3> {}
|
|
||||||
|
|
||||||
impl TxPin<Uart1> for Pin<PF12, AltFunc1> {}
|
|
||||||
impl RxPin<Uart1> for Pin<PF13, AltFunc1> {}
|
|
||||||
|
|
||||||
impl TxPin<Uart2> for Pin<PC14, AltFunc2> {}
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl RxPin<Uart2> for Pin<PC15, AltFunc2> {}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
impl TxPin<Uart2> for Pin<PF8, AltFunc1> {}
|
|
||||||
impl RxPin<Uart2> for Pin<PF9, AltFunc1> {}
|
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Regular Definitions
|
// Regular Definitions
|
||||||
@ -198,36 +174,66 @@ impl From<Hertz> for Config {
|
|||||||
// IRQ Definitions
|
// IRQ Definitions
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
#[derive(Debug)]
|
struct IrqInfo {
|
||||||
pub struct IrqInfo {
|
|
||||||
rx_len: usize,
|
rx_len: usize,
|
||||||
rx_idx: usize,
|
rx_idx: usize,
|
||||||
mode: IrqReceptionMode,
|
mode: IrqReceptionMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum IrqResultMask {
|
||||||
|
Complete = 0,
|
||||||
|
Overflow = 1,
|
||||||
|
FramingError = 2,
|
||||||
|
ParityError = 3,
|
||||||
|
Break = 4,
|
||||||
|
Timeout = 5,
|
||||||
|
Addr9 = 6,
|
||||||
|
/// Should not happen
|
||||||
|
Unknown = 7,
|
||||||
|
}
|
||||||
|
|
||||||
/// This struct is used to return the default IRQ handler result to the user
|
/// This struct is used to return the default IRQ handler result to the user
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct IrqResult {
|
pub struct IrqResult {
|
||||||
complete: bool,
|
raw_res: u32,
|
||||||
timeout: bool,
|
|
||||||
pub errors: IrqUartError,
|
|
||||||
pub bytes_read: usize,
|
pub bytes_read: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IrqResult {
|
impl IrqResult {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
IrqResult {
|
IrqResult {
|
||||||
complete: false,
|
raw_res: 0,
|
||||||
timeout: false,
|
|
||||||
errors: IrqUartError::default(),
|
|
||||||
bytes_read: 0,
|
bytes_read: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IrqResult {
|
impl IrqResult {
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_result(&self) -> u32 {
|
||||||
|
self.raw_res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn clear_result(&mut self) {
|
||||||
|
self.raw_res = 0;
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_result(&mut self, flag: IrqResultMask) {
|
||||||
|
self.raw_res |= 1 << flag as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn complete(&self) -> bool {
|
||||||
|
if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x01 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn error(&self) -> bool {
|
pub fn error(&self) -> bool {
|
||||||
if self.errors.overflow || self.errors.parity || self.errors.framing {
|
if self.overflow_error() || self.framing_error() || self.parity_error() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@ -235,27 +241,34 @@ impl IrqResult {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn overflow_error(&self) -> bool {
|
pub fn overflow_error(&self) -> bool {
|
||||||
self.errors.overflow
|
if ((self.raw_res >> IrqResultMask::Overflow as u32) & 0x01) == 0x01 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn framing_error(&self) -> bool {
|
pub fn framing_error(&self) -> bool {
|
||||||
self.errors.framing
|
if ((self.raw_res >> IrqResultMask::FramingError as u32) & 0x01) == 0x01 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parity_error(&self) -> bool {
|
pub fn parity_error(&self) -> bool {
|
||||||
self.errors.parity
|
if ((self.raw_res >> IrqResultMask::ParityError as u32) & 0x01) == 0x01 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn timeout(&self) -> bool {
|
pub fn timeout(&self) -> bool {
|
||||||
self.timeout
|
if ((self.raw_res >> IrqResultMask::Timeout as u32) & 0x01) == 0x01 {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
#[inline]
|
false
|
||||||
pub fn complete(&self) -> bool {
|
|
||||||
self.complete
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,27 +294,45 @@ pub struct Uart<UartInstance, Pins> {
|
|||||||
pins: Pins,
|
pins: Pins,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serial receiver
|
/// UART using the IRQ capabilities of the peripheral. Can be created with the
|
||||||
pub struct Rx<Uart>(Uart);
|
/// [`Uart::into_uart_with_irq`] function. Currently, only the RX side for IRQ based reception
|
||||||
|
/// is implemented.
|
||||||
|
pub struct UartWithIrq<Uart, Pins> {
|
||||||
|
base: UartWithIrqBase<Uart>,
|
||||||
|
pins: Pins,
|
||||||
|
}
|
||||||
|
|
||||||
// Serial receiver, using interrupts to offload reading to the hardware.
|
/// Type-erased UART using the IRQ capabilities of the peripheral. Can be created with the
|
||||||
pub struct RxWithIrq<Uart> {
|
/// [`UartWithIrq::downgrade`] function. Currently, only the RX side for IRQ based reception
|
||||||
inner: Rx<Uart>,
|
/// is implemented.
|
||||||
|
pub struct UartWithIrqBase<UART> {
|
||||||
|
pub inner: UartBase<UART>,
|
||||||
irq_info: IrqInfo,
|
irq_info: IrqInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serial transmitter
|
/// Serial receiver
|
||||||
pub struct Tx<Uart>(Uart);
|
pub struct Rx<Uart> {
|
||||||
|
_usart: PhantomData<Uart>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<Uart: Instance> Rx<Uart> {
|
/// Serial transmitter
|
||||||
fn new(uart: Uart) -> Self {
|
pub struct Tx<Uart> {
|
||||||
Self(uart)
|
_usart: PhantomData<Uart>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart> Rx<Uart> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
_usart: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Uart> Tx<Uart> {
|
impl<Uart> Tx<Uart> {
|
||||||
fn new(uart: Uart) -> Self {
|
fn new() -> Self {
|
||||||
Self(uart)
|
Self {
|
||||||
|
_usart: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,12 +342,6 @@ pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
|
|||||||
const IRQ_RX: pac::Interrupt;
|
const IRQ_RX: pac::Interrupt;
|
||||||
const IRQ_TX: pac::Interrupt;
|
const IRQ_TX: pac::Interrupt;
|
||||||
|
|
||||||
/// Retrieve the peripheral structure.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This circumvents the safety guarantees of the HAL.
|
|
||||||
unsafe fn steal() -> Self;
|
|
||||||
fn ptr() -> *const uart_base::RegisterBlock;
|
fn ptr() -> *const uart_base::RegisterBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,9 +351,6 @@ impl Instance for Uart0 {
|
|||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
|
||||||
|
|
||||||
unsafe fn steal() -> Self {
|
|
||||||
pac::Peripherals::steal().uart0
|
|
||||||
}
|
|
||||||
fn ptr() -> *const uart_base::RegisterBlock {
|
fn ptr() -> *const uart_base::RegisterBlock {
|
||||||
Uart0::ptr() as *const _
|
Uart0::ptr() as *const _
|
||||||
}
|
}
|
||||||
@ -340,9 +362,6 @@ impl Instance for Uart1 {
|
|||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
|
||||||
|
|
||||||
unsafe fn steal() -> Self {
|
|
||||||
pac::Peripherals::steal().uart1
|
|
||||||
}
|
|
||||||
fn ptr() -> *const uart_base::RegisterBlock {
|
fn ptr() -> *const uart_base::RegisterBlock {
|
||||||
Uart1::ptr() as *const _
|
Uart1::ptr() as *const _
|
||||||
}
|
}
|
||||||
@ -354,9 +373,6 @@ impl Instance for Uart2 {
|
|||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
|
||||||
|
|
||||||
unsafe fn steal() -> Self {
|
|
||||||
pac::Peripherals::steal().uart2
|
|
||||||
}
|
|
||||||
fn ptr() -> *const uart_base::RegisterBlock {
|
fn ptr() -> *const uart_base::RegisterBlock {
|
||||||
Uart2::ptr() as *const _
|
Uart2::ptr() as *const _
|
||||||
}
|
}
|
||||||
@ -495,12 +511,10 @@ impl<Uart: Instance> UartBase<Uart> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstance: Instance>
|
impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
|
||||||
Uart<UartInstance, (TxPinInst, RxPinInst)>
|
|
||||||
{
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
uart: UartInstance,
|
uart: UartInstance,
|
||||||
pins: (TxPinInst, RxPinInst),
|
pins: Pins,
|
||||||
config: impl Into<Config>,
|
config: impl Into<Config>,
|
||||||
syscfg: &mut va416xx::Sysconfig,
|
syscfg: &mut va416xx::Sysconfig,
|
||||||
clocks: &Clocks,
|
clocks: &Clocks,
|
||||||
@ -511,8 +525,8 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
Uart {
|
Uart {
|
||||||
inner: UartBase {
|
inner: UartBase {
|
||||||
uart,
|
uart,
|
||||||
tx: Tx::new(unsafe { UartInstance::steal() }),
|
tx: Tx::new(),
|
||||||
rx: Rx::new(unsafe { UartInstance::steal() }),
|
rx: Rx::new(),
|
||||||
},
|
},
|
||||||
pins,
|
pins,
|
||||||
}
|
}
|
||||||
@ -521,7 +535,7 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
|
|
||||||
pub fn new_with_clock_freq(
|
pub fn new_with_clock_freq(
|
||||||
uart: UartInstance,
|
uart: UartInstance,
|
||||||
pins: (TxPinInst, RxPinInst),
|
pins: Pins,
|
||||||
config: impl Into<Config>,
|
config: impl Into<Config>,
|
||||||
syscfg: &mut va416xx::Sysconfig,
|
syscfg: &mut va416xx::Sysconfig,
|
||||||
clock: impl Into<Hertz>,
|
clock: impl Into<Hertz>,
|
||||||
@ -530,8 +544,8 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
Uart {
|
Uart {
|
||||||
inner: UartBase {
|
inner: UartBase {
|
||||||
uart,
|
uart,
|
||||||
tx: Tx::new(unsafe { UartInstance::steal() }),
|
tx: Tx::new(),
|
||||||
rx: Rx::new(unsafe { UartInstance::steal() }),
|
rx: Rx::new(),
|
||||||
},
|
},
|
||||||
pins,
|
pins,
|
||||||
}
|
}
|
||||||
@ -552,30 +566,20 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// If the IRQ capabilities of the peripheral are used, the UART needs to be converted
|
/// If the IRQ capabilities of the peripheral are used, the UART needs to be converted
|
||||||
/// with this function. Currently, IRQ abstractions are only implemented for the RX part
|
/// with this function
|
||||||
/// of the UART, so this function will release a TX and RX handle as well as the pin
|
pub fn into_uart_with_irq(self) -> UartWithIrq<UartInstance, Pins> {
|
||||||
/// instances.
|
|
||||||
pub fn split_with_irq(
|
|
||||||
self,
|
|
||||||
) -> (
|
|
||||||
Tx<UartInstance>,
|
|
||||||
RxWithIrq<UartInstance>,
|
|
||||||
(TxPinInst, RxPinInst),
|
|
||||||
) {
|
|
||||||
let (inner, pins) = self.downgrade_internal();
|
let (inner, pins) = self.downgrade_internal();
|
||||||
let (tx, rx) = inner.split();
|
UartWithIrq {
|
||||||
(
|
pins,
|
||||||
tx,
|
base: UartWithIrqBase {
|
||||||
RxWithIrq {
|
inner,
|
||||||
inner: rx,
|
|
||||||
irq_info: IrqInfo {
|
irq_info: IrqInfo {
|
||||||
rx_len: 0,
|
rx_len: 0,
|
||||||
rx_idx: 0,
|
rx_idx: 0,
|
||||||
mode: IrqReceptionMode::Idle,
|
mode: IrqReceptionMode::Idle,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pins,
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate::delegate! {
|
delegate::delegate! {
|
||||||
@ -604,7 +608,7 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade_internal(self) -> (UartBase<UartInstance>, (TxPinInst, RxPinInst)) {
|
fn downgrade_internal(self) -> (UartBase<UartInstance>, Pins) {
|
||||||
let base = UartBase {
|
let base = UartBase {
|
||||||
uart: self.inner.uart,
|
uart: self.inner.uart,
|
||||||
tx: self.inner.tx,
|
tx: self.inner.tx,
|
||||||
@ -621,73 +625,16 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn release(self) -> (UartInstance, (TxPinInst, RxPinInst)) {
|
pub fn release(self) -> (UartInstance, Pins) {
|
||||||
(self.inner.release(), self.pins)
|
(self.inner.release(), self.pins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Uart: Instance> Rx<Uart> {
|
|
||||||
/// Direct access to the peripheral structure.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that only registers related to the operation of the RX side are used.
|
|
||||||
pub unsafe fn uart(&self) -> &Uart {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn clear_fifo(&self) {
|
|
||||||
self.0.fifo_clr().write(|w| w.rxfifo().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn enable(&mut self) {
|
|
||||||
self.0.enable().modify(|_, w| w.rxenable().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn disable(&mut self) {
|
|
||||||
self.0.enable().modify(|_, w| w.rxenable().clear_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release(self) -> Uart {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn clear_fifo(&self) {
|
|
||||||
self.0.fifo_clr().write(|w| w.txfifo().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn enable(&mut self) {
|
|
||||||
self.0.enable().modify(|_, w| w.txenable().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn disable(&mut self) {
|
|
||||||
self.0.enable().modify(|_, w| w.txenable().clear_bit());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct IrqUartError {
|
pub struct IrqUartError {
|
||||||
overflow: bool,
|
overflow: bool,
|
||||||
framing: bool,
|
framing: bool,
|
||||||
parity: bool,
|
parity: bool,
|
||||||
other: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IrqUartError {
|
impl IrqUartError {
|
||||||
@ -702,7 +649,7 @@ pub enum IrqError {
|
|||||||
Uart(IrqUartError),
|
Uart(IrqUartError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Uart: Instance> RxWithIrq<Uart> {
|
impl<Uart: Instance> UartWithIrqBase<Uart> {
|
||||||
/// This initializes a non-blocking read transfer using the IRQ capabilities of the UART
|
/// This initializes a non-blocking read transfer using the IRQ capabilities of the UART
|
||||||
/// peripheral.
|
/// peripheral.
|
||||||
///
|
///
|
||||||
@ -722,7 +669,8 @@ impl<Uart: Instance> RxWithIrq<Uart> {
|
|||||||
self.irq_info.mode = IrqReceptionMode::Pending;
|
self.irq_info.mode = IrqReceptionMode::Pending;
|
||||||
self.irq_info.rx_idx = 0;
|
self.irq_info.rx_idx = 0;
|
||||||
self.irq_info.rx_len = max_len;
|
self.irq_info.rx_len = max_len;
|
||||||
self.inner.enable();
|
self.inner.enable_rx();
|
||||||
|
self.inner.enable_tx();
|
||||||
self.enable_rx_irq_sources(enb_timeout_irq);
|
self.enable_rx_irq_sources(enb_timeout_irq);
|
||||||
unsafe { enable_interrupt(Uart::IRQ_RX) };
|
unsafe { enable_interrupt(Uart::IRQ_RX) };
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -730,7 +678,7 @@ impl<Uart: Instance> RxWithIrq<Uart> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn enable_rx_irq_sources(&mut self, timeout: bool) {
|
fn enable_rx_irq_sources(&mut self, timeout: bool) {
|
||||||
self.inner.0.irq_enb().modify(|_, w| {
|
self.inner.uart.irq_enb().modify(|_, w| {
|
||||||
if timeout {
|
if timeout {
|
||||||
w.irq_rx_to().set_bit();
|
w.irq_rx_to().set_bit();
|
||||||
}
|
}
|
||||||
@ -741,22 +689,28 @@ impl<Uart: Instance> RxWithIrq<Uart> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn disable_rx_irq_sources(&mut self) {
|
fn disable_rx_irq_sources(&mut self) {
|
||||||
self.inner.0.irq_enb().modify(|_, w| {
|
self.inner.uart.irq_enb().modify(|_, w| {
|
||||||
w.irq_rx_to().clear_bit();
|
w.irq_rx_to().clear_bit();
|
||||||
w.irq_rx_status().clear_bit();
|
w.irq_rx_status().clear_bit();
|
||||||
w.irq_rx().clear_bit()
|
w.irq_rx().clear_bit()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cancel_transfer(&mut self) {
|
#[inline]
|
||||||
self.disable_rx_irq_sources();
|
pub fn enable_tx(&mut self) {
|
||||||
self.inner.clear_fifo();
|
self.inner.enable_tx()
|
||||||
self.irq_info.rx_idx = 0;
|
|
||||||
self.irq_info.rx_len = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uart(&self) -> &Uart {
|
#[inline]
|
||||||
&self.inner.0
|
pub fn disable_tx(&mut self) {
|
||||||
|
self.inner.disable_tx()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_transfer(&mut self) {
|
||||||
|
self.disable_rx_irq_sources();
|
||||||
|
self.inner.clear_tx_fifo();
|
||||||
|
self.irq_info.rx_idx = 0;
|
||||||
|
self.irq_info.rx_len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default IRQ handler which can be used to read the packets arriving on the UART peripheral.
|
/// Default IRQ handler which can be used to read the packets arriving on the UART peripheral.
|
||||||
@ -771,102 +725,104 @@ impl<Uart: Instance> RxWithIrq<Uart> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut res = IrqResult::default();
|
let mut res = IrqResult::default();
|
||||||
|
let mut possible_error = IrqUartError::default();
|
||||||
|
|
||||||
let irq_end = self.inner.0.irq_end().read();
|
let rx_status = self.inner.uart.rxstatus().read();
|
||||||
let enb_status = self.inner.0.enable().read();
|
res.raw_res = rx_status.bits();
|
||||||
|
let irq_end = self.inner.uart.irq_end().read();
|
||||||
|
let enb_status = self.inner.uart.enable().read();
|
||||||
let rx_enabled = enb_status.rxenable().bit_is_set();
|
let rx_enabled = enb_status.rxenable().bit_is_set();
|
||||||
|
let _tx_enabled = enb_status.txenable().bit_is_set();
|
||||||
// Half-Full interrupt. We have a guaranteed amount of data we can read.
|
let read_handler = |res: &mut IrqResult,
|
||||||
|
possible_error: &mut IrqUartError,
|
||||||
|
read_res: nb::Result<u8, Error>|
|
||||||
|
-> Option<u8> {
|
||||||
|
match read_res {
|
||||||
|
Ok(byte) => Some(byte),
|
||||||
|
Err(nb::Error::WouldBlock) => None,
|
||||||
|
Err(nb::Error::Other(e)) => {
|
||||||
|
match e {
|
||||||
|
Error::Overrun => {
|
||||||
|
possible_error.overflow = true;
|
||||||
|
}
|
||||||
|
Error::FramingError => {
|
||||||
|
possible_error.framing = true;
|
||||||
|
}
|
||||||
|
Error::ParityError => {
|
||||||
|
possible_error.parity = true;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
res.set_result(IrqResultMask::Unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
if irq_end.irq_rx().bit_is_set() {
|
if irq_end.irq_rx().bit_is_set() {
|
||||||
// Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO.
|
|
||||||
// We use this trick/hack because the timeout feature of the peripheral relies on data
|
|
||||||
// being in the RX FIFO. If data continues arriving, another half-full IRQ will fire.
|
|
||||||
// If not, the last byte(s) is/are emptied by the timeout interrupt.
|
|
||||||
let available_bytes = self.inner.0.rxfifoirqtrg().read().bits() as usize;
|
|
||||||
|
|
||||||
let bytes_to_read = core::cmp::min(
|
|
||||||
available_bytes.saturating_sub(1),
|
|
||||||
self.irq_info.rx_len - self.irq_info.rx_idx,
|
|
||||||
);
|
|
||||||
|
|
||||||
// If this interrupt bit is set, the trigger level is available at the very least.
|
// If this interrupt bit is set, the trigger level is available at the very least.
|
||||||
// Read everything as fast as possible
|
// Read everything as fast as possible
|
||||||
for _ in 0..bytes_to_read {
|
for _ in 0..core::cmp::min(
|
||||||
buf[self.irq_info.rx_idx] = (self.inner.0.data().read().bits() & 0xff) as u8;
|
self.inner.uart.rxfifoirqtrg().read().bits() as usize,
|
||||||
|
self.irq_info.rx_len,
|
||||||
|
) {
|
||||||
|
buf[self.irq_info.rx_idx] = (self.inner.uart.data().read().bits() & 0xff) as u8;
|
||||||
self.irq_info.rx_idx += 1;
|
self.irq_info.rx_idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On high-baudrates, data might be available immediately, and we possible have to
|
|
||||||
// read continuosly? Then again, the CPU should always be faster than that. I'd rather
|
|
||||||
// rely on the hardware firing another IRQ. I have not tried baudrates higher than
|
|
||||||
// 115200 so far.
|
|
||||||
}
|
|
||||||
let read_handler =
|
|
||||||
|possible_error: &mut IrqUartError, read_res: nb::Result<u8, Error>| -> Option<u8> {
|
|
||||||
match read_res {
|
|
||||||
Ok(byte) => Some(byte),
|
|
||||||
Err(nb::Error::WouldBlock) => None,
|
|
||||||
Err(nb::Error::Other(e)) => {
|
|
||||||
match e {
|
|
||||||
Error::Overrun => {
|
|
||||||
possible_error.overflow = true;
|
|
||||||
}
|
|
||||||
Error::FramingError => {
|
|
||||||
possible_error.framing = true;
|
|
||||||
}
|
|
||||||
Error::ParityError => {
|
|
||||||
possible_error.parity = true;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
possible_error.other = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Timeout, empty the FIFO completely.
|
|
||||||
if irq_end.irq_rx_to().bit_is_set() {
|
|
||||||
// While there is data in the FIFO, write it into the reception buffer
|
// While there is data in the FIFO, write it into the reception buffer
|
||||||
loop {
|
loop {
|
||||||
if self.irq_info.rx_idx == self.irq_info.rx_len {
|
if self.irq_info.rx_idx == self.irq_info.rx_len {
|
||||||
break;
|
self.irq_completion_handler(&mut res);
|
||||||
|
return Ok(res);
|
||||||
}
|
}
|
||||||
if let Some(byte) = read_handler(&mut res.errors, self.inner.read()) {
|
if let Some(byte) = read_handler(&mut res, &mut possible_error, self.inner.read()) {
|
||||||
buf[self.irq_info.rx_idx] = byte;
|
buf[self.irq_info.rx_idx] = byte;
|
||||||
self.irq_info.rx_idx += 1;
|
self.irq_info.rx_idx += 1;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.irq_completion_handler(&mut res);
|
|
||||||
return Ok(res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RX transfer not complete, check for RX errors
|
// RX transfer not complete, check for RX errors
|
||||||
if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled {
|
if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled {
|
||||||
// Read status register again, might have changed since reading received data
|
// Read status register again, might have changed since reading received data
|
||||||
let rx_status = self.inner.0.rxstatus().read();
|
let rx_status = self.inner.uart.rxstatus().read();
|
||||||
|
res.raw_res = rx_status.bits();
|
||||||
if rx_status.rxovr().bit_is_set() {
|
if rx_status.rxovr().bit_is_set() {
|
||||||
res.errors.overflow = true;
|
possible_error.overflow = true;
|
||||||
}
|
}
|
||||||
if rx_status.rxfrm().bit_is_set() {
|
if rx_status.rxfrm().bit_is_set() {
|
||||||
res.errors.framing = true;
|
possible_error.framing = true;
|
||||||
}
|
}
|
||||||
if rx_status.rxpar().bit_is_set() {
|
if rx_status.rxpar().bit_is_set() {
|
||||||
res.errors.parity = true;
|
possible_error.parity = true;
|
||||||
|
}
|
||||||
|
if rx_status.rxto().bit_is_set() {
|
||||||
|
// A timeout has occured but there might be some leftover data in the FIFO,
|
||||||
|
// so read that data as well
|
||||||
|
while let Some(byte) =
|
||||||
|
read_handler(&mut res, &mut possible_error, self.inner.read())
|
||||||
|
{
|
||||||
|
buf[self.irq_info.rx_idx] = byte;
|
||||||
|
self.irq_info.rx_idx += 1;
|
||||||
|
}
|
||||||
|
self.irq_completion_handler(&mut res);
|
||||||
|
res.set_result(IrqResultMask::Timeout);
|
||||||
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is not a timeout, it's an error
|
// If it is not a timeout, it's an error
|
||||||
if res.error() {
|
if possible_error.error() {
|
||||||
self.disable_rx_irq_sources();
|
self.disable_rx_irq_sources();
|
||||||
return Err(IrqError::Uart(res.errors));
|
return Err(IrqError::Uart(possible_error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the interrupt status bits
|
// Clear the interrupt status bits
|
||||||
self.inner
|
self.inner
|
||||||
.0
|
.uart
|
||||||
.irq_clr()
|
.irq_clr()
|
||||||
.write(|w| unsafe { w.bits(irq_end.bits()) });
|
.write(|w| unsafe { w.bits(irq_end.bits()) });
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@ -874,23 +830,48 @@ impl<Uart: Instance> RxWithIrq<Uart> {
|
|||||||
|
|
||||||
fn irq_completion_handler(&mut self, res: &mut IrqResult) {
|
fn irq_completion_handler(&mut self, res: &mut IrqResult) {
|
||||||
self.disable_rx_irq_sources();
|
self.disable_rx_irq_sources();
|
||||||
self.inner.disable();
|
self.inner.disable_rx();
|
||||||
res.bytes_read = self.irq_info.rx_idx;
|
res.bytes_read = self.irq_info.rx_idx;
|
||||||
res.complete = true;
|
res.clear_result();
|
||||||
|
res.set_result(IrqResultMask::Complete);
|
||||||
self.irq_info.mode = IrqReceptionMode::Idle;
|
self.irq_info.mode = IrqReceptionMode::Idle;
|
||||||
self.irq_info.rx_idx = 0;
|
self.irq_info.rx_idx = 0;
|
||||||
self.irq_info.rx_len = 0;
|
self.irq_info.rx_len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn irq_info(&self) -> &IrqInfo {
|
|
||||||
&self.irq_info
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release(self) -> Uart {
|
pub fn release(self) -> Uart {
|
||||||
self.inner.release()
|
self.inner.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance, Pins> UartWithIrq<Uart, Pins> {
|
||||||
|
/// See [`UartWithIrqBase::read_fixed_len_using_irq`] doc
|
||||||
|
pub fn read_fixed_len_using_irq(
|
||||||
|
&mut self,
|
||||||
|
max_len: usize,
|
||||||
|
enb_timeout_irq: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.base.read_fixed_len_using_irq(max_len, enb_timeout_irq)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_transfer(&mut self) {
|
||||||
|
self.base.cancel_transfer()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`UartWithIrqBase::irq_handler`] doc
|
||||||
|
pub fn irq_handler(&mut self, buf: &mut [u8]) -> Result<IrqResult, IrqError> {
|
||||||
|
self.base.irq_handler(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self) -> (Uart, Pins) {
|
||||||
|
(self.base.release(), self.pins)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn downgrade(self) -> (UartWithIrqBase<Uart>, Pins) {
|
||||||
|
(self.base, self.pins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl embedded_io::Error for Error {
|
impl embedded_io::Error for Error {
|
||||||
fn kind(&self) -> embedded_io::ErrorKind {
|
fn kind(&self) -> embedded_io::ErrorKind {
|
||||||
embedded_io::ErrorKind::Other
|
embedded_io::ErrorKind::Other
|
||||||
|
@ -13,16 +13,11 @@ use crate::{disable_interrupt, enable_interrupt};
|
|||||||
|
|
||||||
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
|
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
|
||||||
|
|
||||||
/// Watchdog peripheral driver.
|
pub struct WdtController {
|
||||||
pub struct Wdt {
|
|
||||||
clock_freq: Hertz,
|
clock_freq: Hertz,
|
||||||
wdt: pac::WatchDog,
|
wdt: pac::WatchDog,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type alias for backwards compatibility
|
|
||||||
#[deprecated(since = "0.2.0", note = "Please use `Wdt` instead")]
|
|
||||||
pub type WdtController = Wdt;
|
|
||||||
|
|
||||||
/// Enable the watchdog interrupt
|
/// Enable the watchdog interrupt
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -38,8 +33,9 @@ pub fn disable_wdt_interrupts() {
|
|||||||
disable_interrupt(pac::Interrupt::WATCHDOG)
|
disable_interrupt(pac::Interrupt::WATCHDOG)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wdt {
|
impl WdtController {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
&self,
|
||||||
syscfg: &mut pac::Sysconfig,
|
syscfg: &mut pac::Sysconfig,
|
||||||
wdt: pac::WatchDog,
|
wdt: pac::WatchDog,
|
||||||
clocks: &Clocks,
|
clocks: &Clocks,
|
||||||
|
@ -17,8 +17,7 @@ embedded-hal = "1"
|
|||||||
|
|
||||||
[dependencies.va416xx-hal]
|
[dependencies.va416xx-hal]
|
||||||
path = "../va416xx-hal"
|
path = "../va416xx-hal"
|
||||||
features = ["va41630"]
|
version = "0.1.0"
|
||||||
version = "0.2.0"
|
|
||||||
|
|
||||||
[dependencies.lis2dh12]
|
[dependencies.lis2dh12]
|
||||||
git = "https://github.com/us-irs/lis2dh12.git"
|
git = "https://github.com/us-irs/lis2dh12.git"
|
||||||
|
@ -74,7 +74,8 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
|
"address": "0x1fff8000",
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
@ -103,7 +104,8 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
|
"address": "0x1fff8000",
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
@ -132,7 +134,8 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
|
"address": "0x1fff8000",
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
@ -161,11 +164,11 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
|
"address": "0x1fff8000",
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
"timestamp": true,
|
|
||||||
"type": "console"
|
"type": "console"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -191,7 +194,8 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
|
"address": "0x1fff8000",
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
@ -220,7 +224,8 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
|
"address": "0x1fff8000",
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
@ -250,7 +255,8 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
|
"address": "0x1fff8000",
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
@ -280,187 +286,8 @@
|
|||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"address": "auto",
|
// Have to use exact address unfortunately. "auto" does not work for some reason..
|
||||||
"decoders": [
|
"address": "0x1fff8000",
|
||||||
{
|
|
||||||
"port": 0,
|
|
||||||
"timestamp": true,
|
|
||||||
"type": "console"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Debug PWM Example",
|
|
||||||
"servertype": "jlink",
|
|
||||||
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"device": "Cortex-M4",
|
|
||||||
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
|
|
||||||
"preLaunchTask": "pwm-example",
|
|
||||||
"overrideLaunchCommands": [
|
|
||||||
"monitor halt",
|
|
||||||
"monitor reset",
|
|
||||||
"load",
|
|
||||||
],
|
|
||||||
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/pwm",
|
|
||||||
"interface": "swd",
|
|
||||||
"runToEntryPoint": "main",
|
|
||||||
"rttConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"address": "auto",
|
|
||||||
"decoders": [
|
|
||||||
{
|
|
||||||
"port": 0,
|
|
||||||
"timestamp": true,
|
|
||||||
"type": "console"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Debug DMA Example",
|
|
||||||
"servertype": "jlink",
|
|
||||||
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"device": "Cortex-M4",
|
|
||||||
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
|
|
||||||
"preLaunchTask": "dma-example",
|
|
||||||
"overrideLaunchCommands": [
|
|
||||||
"monitor halt",
|
|
||||||
"monitor reset",
|
|
||||||
"load",
|
|
||||||
],
|
|
||||||
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/dma",
|
|
||||||
"interface": "swd",
|
|
||||||
"runToEntryPoint": "main",
|
|
||||||
"rttConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"address": "auto",
|
|
||||||
"decoders": [
|
|
||||||
{
|
|
||||||
"port": 0,
|
|
||||||
"timestamp": true,
|
|
||||||
"type": "console"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Bootloader",
|
|
||||||
"servertype": "jlink",
|
|
||||||
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"device": "Cortex-M4",
|
|
||||||
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
|
|
||||||
"preLaunchTask": "bootloader",
|
|
||||||
"overrideLaunchCommands": [
|
|
||||||
"monitor halt",
|
|
||||||
"monitor reset",
|
|
||||||
"load",
|
|
||||||
],
|
|
||||||
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/bootloader",
|
|
||||||
"interface": "swd",
|
|
||||||
"runToEntryPoint": "main",
|
|
||||||
"rttConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"address": "auto",
|
|
||||||
"decoders": [
|
|
||||||
{
|
|
||||||
"port": 0,
|
|
||||||
"timestamp": true,
|
|
||||||
"type": "console"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Flashloader",
|
|
||||||
"servertype": "jlink",
|
|
||||||
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"device": "Cortex-M4",
|
|
||||||
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
|
|
||||||
"preLaunchTask": "flashloader",
|
|
||||||
"overrideLaunchCommands": [
|
|
||||||
"monitor halt",
|
|
||||||
"monitor reset",
|
|
||||||
"load",
|
|
||||||
],
|
|
||||||
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/flashloader",
|
|
||||||
"interface": "swd",
|
|
||||||
"runToEntryPoint": "main",
|
|
||||||
"rttConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"address": "auto",
|
|
||||||
"decoders": [
|
|
||||||
{
|
|
||||||
"port": 0,
|
|
||||||
"timestamp": true,
|
|
||||||
"type": "console"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Embassy Example",
|
|
||||||
"servertype": "jlink",
|
|
||||||
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"device": "Cortex-M4",
|
|
||||||
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
|
|
||||||
"preLaunchTask": "embassy-example",
|
|
||||||
"overrideLaunchCommands": [
|
|
||||||
"monitor halt",
|
|
||||||
"monitor reset",
|
|
||||||
"load",
|
|
||||||
],
|
|
||||||
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/embassy-example",
|
|
||||||
"interface": "swd",
|
|
||||||
"runToEntryPoint": "main",
|
|
||||||
"rttConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"address": "auto",
|
|
||||||
"decoders": [
|
|
||||||
{
|
|
||||||
"port": 0,
|
|
||||||
"timestamp": true,
|
|
||||||
"type": "console"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "RTIC Example",
|
|
||||||
"servertype": "jlink",
|
|
||||||
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"device": "Cortex-M4",
|
|
||||||
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
|
|
||||||
"preLaunchTask": "embassy-example",
|
|
||||||
"overrideLaunchCommands": [
|
|
||||||
"monitor halt",
|
|
||||||
"monitor reset",
|
|
||||||
"load",
|
|
||||||
],
|
|
||||||
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/rtic-example",
|
|
||||||
"interface": "swd",
|
|
||||||
"runToEntryPoint": "main",
|
|
||||||
"rttConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"address": "auto",
|
|
||||||
"decoders": [
|
"decoders": [
|
||||||
{
|
{
|
||||||
"port": 0,
|
"port": 0,
|
||||||
|
@ -3,32 +3,6 @@
|
|||||||
// for the documentation about the tasks.json format
|
// for the documentation about the tasks.json format
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
|
||||||
"label": "bootloader",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--bin",
|
|
||||||
"bootloader"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "flashloader",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--bin",
|
|
||||||
"flashloader"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "blinky-pac-example",
|
"label": "blinky-pac-example",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
@ -55,19 +29,6 @@
|
|||||||
"kind": "build",
|
"kind": "build",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "timer-ticks-example",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--example",
|
|
||||||
"timer-ticks"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "blinky-example",
|
"label": "blinky-example",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
@ -95,19 +56,6 @@
|
|||||||
"kind": "build",
|
"kind": "build",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "pwm-example",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--example",
|
|
||||||
"pwm"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "wdt-example",
|
"label": "wdt-example",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
@ -160,44 +108,5 @@
|
|||||||
"kind": "build",
|
"kind": "build",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "dma-example",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--example",
|
|
||||||
"dma"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "embassy-example",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--bin",
|
|
||||||
"embassy-example"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "rtic-example",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--bin",
|
|
||||||
"rtic-example"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
Reference in New Issue
Block a user