continue update package
This commit is contained in:
parent
4bf50bfa88
commit
8434ec872d
13
Cargo.toml
13
Cargo.toml
@ -8,6 +8,7 @@ members = [
|
|||||||
"examples/rtic",
|
"examples/rtic",
|
||||||
"examples/embassy",
|
"examples/embassy",
|
||||||
"board-tests",
|
"board-tests",
|
||||||
|
"bootloader",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
@ -19,7 +20,8 @@ codegen-units = 1
|
|||||||
debug = 2
|
debug = 2
|
||||||
debug-assertions = true # <-
|
debug-assertions = true # <-
|
||||||
incremental = false
|
incremental = false
|
||||||
opt-level = 'z' # <-
|
# This is problematic for stepping..
|
||||||
|
# opt-level = 'z' # <-
|
||||||
overflow-checks = true # <-
|
overflow-checks = true # <-
|
||||||
|
|
||||||
# cargo build/run --release
|
# cargo build/run --release
|
||||||
@ -31,3 +33,12 @@ incremental = false
|
|||||||
lto = 'fat'
|
lto = 'fat'
|
||||||
opt-level = 3 # <-
|
opt-level = 3 # <-
|
||||||
overflow-checks = false # <-
|
overflow-checks = false # <-
|
||||||
|
|
||||||
|
[profile.small]
|
||||||
|
inherits = "release"
|
||||||
|
codegen-units = 1
|
||||||
|
debug-assertions = false # <-
|
||||||
|
lto = true
|
||||||
|
opt-level = 'z' # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
strip = true # Automatically strip symbols from the binary.
|
||||||
|
25
bootloader/Cargo.toml
Normal file
25
bootloader/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "bootloader"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cortex-m = "0.7"
|
||||||
|
cortex-m-rt = "0.7"
|
||||||
|
embedded-hal = "1"
|
||||||
|
embedded-hal-bus = "0.2"
|
||||||
|
dummy-pin = "1"
|
||||||
|
panic-rtt-target = { version = "0.1.3" }
|
||||||
|
panic-halt = { version = "0.2" }
|
||||||
|
rtt-target = { version = "0.5" }
|
||||||
|
crc = "3"
|
||||||
|
|
||||||
|
[dependencies.va108xx-hal]
|
||||||
|
path = "../va108xx-hal"
|
||||||
|
|
||||||
|
[dependencies.vorago-reb1]
|
||||||
|
path = "../vorago-reb1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
rtt-panic = []
|
312
bootloader/src/main.rs
Normal file
312
bootloader/src/main.rs
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
//! 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_16_IBM_3740};
|
||||||
|
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
|
||||||
|
#[cfg(not(feature = "rtt-panic"))]
|
||||||
|
use panic_halt as _;
|
||||||
|
#[cfg(feature = "rtt-panic")]
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use va108xx_hal::{
|
||||||
|
pac,
|
||||||
|
spi::{RomMiso, RomMosi, RomSck, Spi, SpiClkConfig, SpiConfig},
|
||||||
|
time::Hertz,
|
||||||
|
};
|
||||||
|
use vorago_reb1::m95m01::M95M01;
|
||||||
|
|
||||||
|
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
|
||||||
|
// the binary stays small enough.
|
||||||
|
const RTT_PRINTOUT: bool = true;
|
||||||
|
const DEBUG_PRINTOUTS: bool = false;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Register definitions for Cortex-M0 SCB register.
|
||||||
|
pub const SCB_AIRCR_VECTKEY_POS: u32 = 16;
|
||||||
|
pub const SCB_AIRCR_VECTKEY_MSK: u32 = 0xFFFF << SCB_AIRCR_VECTKEY_POS;
|
||||||
|
|
||||||
|
pub const SCB_AIRCR_SYSRESETREQ_POS: u32 = 2;
|
||||||
|
pub const SCB_AIRCR_SYSRESETREQ_MSK: u32 = 1 << SCB_AIRCR_SYSRESETREQ_POS;
|
||||||
|
|
||||||
|
const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
|
||||||
|
|
||||||
|
// Important bootloader addresses and offsets, vector table information.
|
||||||
|
|
||||||
|
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
||||||
|
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
|
||||||
|
// This is also the maximum size of the bootloader.
|
||||||
|
const BOOTLOADER_END_ADDR: u32 = 0x3000;
|
||||||
|
const APP_A_START_ADDR: u32 = 0x3000;
|
||||||
|
const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
|
||||||
|
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
|
||||||
|
const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
|
||||||
|
pub const APP_A_END_ADDR: u32 = 0x11000;
|
||||||
|
// The actual size of the image which is relevant for CRC calculation.
|
||||||
|
const APP_B_START_ADDR: u32 = 0x11000;
|
||||||
|
// The actual size of the image which is relevant for CRC calculation.
|
||||||
|
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
|
||||||
|
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
|
||||||
|
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
|
||||||
|
pub const APP_B_END_ADDR: u32 = 0x20000;
|
||||||
|
pub const APP_IMG_SZ: u32 = 0xE800;
|
||||||
|
|
||||||
|
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
|
||||||
|
pub const VECTOR_TABLE_LEN: u32 = 0xC0;
|
||||||
|
pub const RESET_VECTOR_OFFSET: u32 = 0x4;
|
||||||
|
|
||||||
|
const CRC_ALGO: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum AppSel {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complex type, but this is the price we pay for nice abstraction. It is also very explicit.
|
||||||
|
pub type Nvm = M95M01<
|
||||||
|
ExclusiveDevice<Spi<pac::Spic, (RomSck, RomMiso, RomMosi), u8>, dummy_pin::DummyPin, NoDelay>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
#[entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
if RTT_PRINTOUT {
|
||||||
|
rtt_init_print!();
|
||||||
|
rprintln!("-- VA108xx bootloader --");
|
||||||
|
}
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
let cp = cortex_m::Peripherals::take().unwrap();
|
||||||
|
|
||||||
|
let spi = Spi::new(
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
CLOCK_FREQ,
|
||||||
|
dp.spic,
|
||||||
|
(RomSck, RomMiso, RomMosi),
|
||||||
|
// These values are taken from the vorago bootloader app, don't want to experiment here..
|
||||||
|
SpiConfig::default().clk_cfg(SpiClkConfig::new(2, 4)),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let mut nvm =
|
||||||
|
M95M01::new(ExclusiveDevice::new_no_delay(spi, dummy_pin::DummyPin::new_low()).unwrap())
|
||||||
|
.expect("creating NVM structure failed");
|
||||||
|
|
||||||
|
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 - 6) as usize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut digest = CRC_ALGO.digest();
|
||||||
|
digest.update(&first_four_bytes);
|
||||||
|
digest.update(bootloader_data);
|
||||||
|
let bootloader_crc = digest.finalize();
|
||||||
|
|
||||||
|
nvm.write(0x0, &first_four_bytes)
|
||||||
|
.expect("writing to NVM failed");
|
||||||
|
nvm.write(0x4, bootloader_data)
|
||||||
|
.expect("writing to NVM failed");
|
||||||
|
if let Err(e) = nvm.verify(0x0, &first_four_bytes) {
|
||||||
|
if RTT_PRINTOUT {
|
||||||
|
rprintln!("verification of self-flash to NVM failed: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = nvm.verify(0x4, bootloader_data) {
|
||||||
|
if RTT_PRINTOUT {
|
||||||
|
rprintln!("verification of self-flash to NVM failed: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nvm.write(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes())
|
||||||
|
.expect("writing CRC failed");
|
||||||
|
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
|
||||||
|
if RTT_PRINTOUT {
|
||||||
|
rprintln!(
|
||||||
|
"error: CRC verification for bootloader self-flash failed: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bootloader's CRC (and write it if blank)
|
||||||
|
check_own_crc(&dp.sysconfig, &cp, &mut nvm);
|
||||||
|
|
||||||
|
if check_app_crc(AppSel::A) {
|
||||||
|
boot_app(&dp.sysconfig, &cp, AppSel::A)
|
||||||
|
} else if check_app_crc(AppSel::B) {
|
||||||
|
boot_app(&dp.sysconfig, &cp, AppSel::B)
|
||||||
|
} else {
|
||||||
|
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||||
|
rprintln!("both images corrupt! booting image A");
|
||||||
|
}
|
||||||
|
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
|
||||||
|
// Both images seem to be corrupt. Boot default image A.
|
||||||
|
boot_app(&dp.sysconfig, &cp, AppSel::A)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut Nvm) {
|
||||||
|
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() };
|
||||||
|
// 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 - 6) as usize,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let crc_calc = digest.finalize();
|
||||||
|
if crc_exp == 0x0000 || crc_exp == 0xffff {
|
||||||
|
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||||
|
rprintln!("BL CRC blank - prog new CRC");
|
||||||
|
}
|
||||||
|
// Blank CRC, write it to NVM.
|
||||||
|
nvm.write(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes())
|
||||||
|
.expect("writing CRC failed");
|
||||||
|
// 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 && RTT_PRINTOUT {
|
||||||
|
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(sysconfig, cp, AppSel::A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading from address 0x0 is problematic in Rust.
|
||||||
|
// See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/5.
|
||||||
|
// This solution falls back to assembler to deal with this.
|
||||||
|
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) -> bool {
|
||||||
|
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) -> bool {
|
||||||
|
let crc_exp = unsafe { (crc_addr as *const u16).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 {
|
||||||
|
if RTT_PRINTOUT {
|
||||||
|
rprintln!("detected invalid app size {}", image_size);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let crc_calc = CRC_ALGO.checksum(unsafe {
|
||||||
|
core::slice::from_raw_parts(start_addr as *const u8, image_size as usize)
|
||||||
|
});
|
||||||
|
if crc_calc == crc_exp {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The boot works by copying the interrupt vector table (IVT) of the respective app to the
|
||||||
|
// base address in code RAM (0x0) and then performing a soft reset.
|
||||||
|
fn boot_app(syscfg: &pac::Sysconfig, cp: &cortex_m::Peripherals, app_sel: AppSel) -> ! {
|
||||||
|
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||||
|
rprintln!("booting app {:?}", app_sel);
|
||||||
|
}
|
||||||
|
// Disable ROM protection.
|
||||||
|
syscfg.rom_prot().write(|w| unsafe { w.bits(1) });
|
||||||
|
let base_addr = if app_sel == AppSel::A {
|
||||||
|
APP_A_START_ADDR
|
||||||
|
} else {
|
||||||
|
APP_B_START_ADDR
|
||||||
|
};
|
||||||
|
// Clear all interrupts set.
|
||||||
|
unsafe {
|
||||||
|
cp.NVIC.icer[0].write(0xFFFFFFFF);
|
||||||
|
cp.NVIC.icpr[0].write(0xFFFFFFFF);
|
||||||
|
|
||||||
|
// First 4 bytes done with inline assembly, writing to the physical address 0x0 can not
|
||||||
|
// be done without it. See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/2.
|
||||||
|
core::ptr::read(base_addr as *const u32);
|
||||||
|
core::arch::asm!(
|
||||||
|
"str {0}, [{1}]", // Load 4 bytes from src into r0 register
|
||||||
|
in(reg) base_addr, // Input: App vector table.
|
||||||
|
in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer
|
||||||
|
);
|
||||||
|
core::slice::from_raw_parts_mut(
|
||||||
|
(BOOTLOADER_START_ADDR + 4) as *mut u32,
|
||||||
|
(VECTOR_TABLE_LEN - 4) as usize,
|
||||||
|
)
|
||||||
|
.copy_from_slice(core::slice::from_raw_parts(
|
||||||
|
(base_addr + 4) as *const u32,
|
||||||
|
(VECTOR_TABLE_LEN - 4) as usize,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/* Disable re-loading from FRAM/code ROM on soft reset */
|
||||||
|
syscfg
|
||||||
|
.rst_cntl_rom()
|
||||||
|
.modify(|_, w| w.sysrstreq().clear_bit());
|
||||||
|
soft_reset(cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soft reset based on https://github.com/ARM-software/CMSIS_6/blob/5782d6f8057906d360f4b95ec08a2354afe5c9b9/CMSIS/Core/Include/core_cm0.h#L874.
|
||||||
|
fn soft_reset(cp: &cortex_m::Peripherals) -> ! {
|
||||||
|
// Ensure all outstanding memory accesses included buffered write are completed before reset.
|
||||||
|
cortex_m::asm::dsb();
|
||||||
|
unsafe {
|
||||||
|
cp.SCB
|
||||||
|
.aircr
|
||||||
|
.write((0x5FA << SCB_AIRCR_VECTKEY_POS) | SCB_AIRCR_SYSRESETREQ_MSK);
|
||||||
|
}
|
||||||
|
// Ensure completion of memory access.
|
||||||
|
cortex_m::asm::dsb();
|
||||||
|
|
||||||
|
unreachable!();
|
||||||
|
}
|
@ -17,6 +17,9 @@ panic-rtt-target = { version = "0.1" }
|
|||||||
[dependencies.va108xx-hal]
|
[dependencies.va108xx-hal]
|
||||||
path = "../../va108xx-hal"
|
path = "../../va108xx-hal"
|
||||||
|
|
||||||
|
[dependencies.vorago-reb1]
|
||||||
|
path = "../../vorago-reb1"
|
||||||
|
|
||||||
[dependencies.rtic]
|
[dependencies.rtic]
|
||||||
version = "2"
|
version = "2"
|
||||||
features = ["thumbv6-backend"]
|
features = ["thumbv6-backend"]
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#[rtic::app(device = pac)]
|
#[rtic::app(device = pac)]
|
||||||
mod app {
|
mod app {
|
||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtic_monotonics::systick::Systick;
|
use rtic_example::SYSCLK_FREQ;
|
||||||
use rtt_target::{rprintln, rtt_init_default, set_print_channel};
|
use rtt_target::{rprintln, rtt_init_default, set_print_channel};
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
clock::{set_clk_div_register, FilterClkSel},
|
clock::{set_clk_div_register, FilterClkSel},
|
||||||
@ -17,6 +17,8 @@ mod app {
|
|||||||
use vorago_reb1::button::Button;
|
use vorago_reb1::button::Button;
|
||||||
use vorago_reb1::leds::Leds;
|
use vorago_reb1::leds::Leds;
|
||||||
|
|
||||||
|
rtic_monotonics::systick_monotonic!(Mono, 1_000);
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum PressMode {
|
pub enum PressMode {
|
||||||
Toggle,
|
Toggle,
|
||||||
@ -44,17 +46,11 @@ mod app {
|
|||||||
struct Shared {}
|
struct Shared {}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(ctx: init::Context) -> (Shared, Local) {
|
fn init(cx: init::Context) -> (Shared, Local) {
|
||||||
let channels = rtt_init_default!();
|
let channels = rtt_init_default!();
|
||||||
set_print_channel(channels.up.0);
|
set_print_channel(channels.up.0);
|
||||||
rprintln!("-- Vorago Button IRQ Example --");
|
rprintln!("-- Vorago Button IRQ Example --");
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
||||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
|
||||||
Systick::start(
|
|
||||||
ctx.core.SYST,
|
|
||||||
Hertz::from(50.MHz()).raw(),
|
|
||||||
systick_mono_token,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mode = match CFG_MODE {
|
let mode = match CFG_MODE {
|
||||||
// Ask mode from user via RTT
|
// Ask mode from user via RTT
|
||||||
@ -64,7 +60,7 @@ mod app {
|
|||||||
};
|
};
|
||||||
rprintln!("Using {:?} mode", mode);
|
rprintln!("Using {:?} mode", mode);
|
||||||
|
|
||||||
let mut dp = ctx.device;
|
let mut dp = cx.device;
|
||||||
let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
||||||
let edge_irq = match mode {
|
let edge_irq = match mode {
|
||||||
PressMode::Toggle => InterruptEdge::HighToLow,
|
PressMode::Toggle => InterruptEdge::HighToLow,
|
||||||
@ -117,14 +113,12 @@ mod app {
|
|||||||
let mode = cx.local.mode;
|
let mode = cx.local.mode;
|
||||||
if *mode == PressMode::Toggle {
|
if *mode == PressMode::Toggle {
|
||||||
leds[0].toggle();
|
leds[0].toggle();
|
||||||
} else {
|
} else if button.released() {
|
||||||
if button.released() {
|
|
||||||
leds[0].off();
|
leds[0].off();
|
||||||
} else {
|
} else {
|
||||||
leds[0].on();
|
leds[0].on();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = OC0)]
|
#[task(binds = OC0)]
|
||||||
fn ms_tick(_cx: ms_tick::Context) {
|
fn ms_tick(_cx: ms_tick::Context) {
|
||||||
@ -138,14 +132,11 @@ mod app {
|
|||||||
let mut read;
|
let mut read;
|
||||||
loop {
|
loop {
|
||||||
read = down_channel.read(&mut read_buf);
|
read = down_channel.read(&mut read_buf);
|
||||||
for i in 0..read {
|
for &byte in &read_buf[..read] {
|
||||||
let val = read_buf[i] as char;
|
match byte as char {
|
||||||
if val == '0' || val == '1' {
|
'0' => return PressMode::Toggle,
|
||||||
return if val == '0' {
|
'1' => return PressMode::Keep,
|
||||||
PressMode::Toggle
|
_ => continue, // Ignore other characters
|
||||||
} else {
|
|
||||||
PressMode::Keep
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,3 +18,6 @@ cortex-m-semihosting = "0.5.0"
|
|||||||
[dependencies.va108xx-hal]
|
[dependencies.va108xx-hal]
|
||||||
path = "../../va108xx-hal"
|
path = "../../va108xx-hal"
|
||||||
features = ["rt", "defmt"]
|
features = ["rt", "defmt"]
|
||||||
|
|
||||||
|
[dependencies.vorago-reb1]
|
||||||
|
path = "../../vorago-reb1"
|
||||||
|
@ -16,7 +16,7 @@ use va108xx_hal::{
|
|||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
pwm::{default_ms_irq_handler, set_up_ms_tick},
|
pwm::{default_ms_irq_handler, set_up_ms_tick},
|
||||||
spi::{self, Spi, SpiBase, TransferConfig},
|
spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs},
|
||||||
IrqCfg,
|
IrqCfg,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,6 +55,8 @@ fn main() -> ! {
|
|||||||
dp.tim0,
|
dp.tim0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz())
|
||||||
|
.expect("creating SPI clock config failed");
|
||||||
let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None);
|
let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None);
|
||||||
let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None);
|
let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None);
|
||||||
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
|
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
|
||||||
@ -123,17 +125,21 @@ fn main() -> ! {
|
|||||||
match SPI_BUS_SEL {
|
match SPI_BUS_SEL {
|
||||||
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
|
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
|
||||||
if let Some(ref mut spi) = *spia_ref.borrow_mut() {
|
if let Some(ref mut spi) = *spia_ref.borrow_mut() {
|
||||||
let transfer_cfg =
|
let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs(
|
||||||
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false);
|
Some(spi_clk_cfg),
|
||||||
|
Some(SPI_MODE),
|
||||||
|
BLOCKMODE,
|
||||||
|
false,
|
||||||
|
);
|
||||||
spi.cfg_transfer(&transfer_cfg);
|
spi.cfg_transfer(&transfer_cfg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SpiBusSelect::SpiBPortB => {
|
SpiBusSelect::SpiBPortB => {
|
||||||
if let Some(ref mut spi) = *spib_ref.borrow_mut() {
|
if let Some(ref mut spi) = *spib_ref.borrow_mut() {
|
||||||
let hw_cs_pin = pinsb.pb2.into_funsel_1();
|
let hw_cs_pin = pinsb.pb2.into_funsel_1();
|
||||||
let transfer_cfg = TransferConfig::new(
|
let transfer_cfg = TransferConfigWithHwcs::new(
|
||||||
SPI_SPEED_KHZ.kHz(),
|
Some(spi_clk_cfg),
|
||||||
SPI_MODE,
|
Some(SPI_MODE),
|
||||||
Some(hw_cs_pin),
|
Some(hw_cs_pin),
|
||||||
BLOCKMODE,
|
BLOCKMODE,
|
||||||
false,
|
false,
|
||||||
|
@ -26,6 +26,8 @@ use embedded_hal::spi::{Mode, MODE_0, MODE_1, MODE_2, MODE_3};
|
|||||||
// 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;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum HwChipSelectId {
|
pub enum HwChipSelectId {
|
||||||
Id0 = 0,
|
Id0 = 0,
|
||||||
@ -221,6 +223,17 @@ hw_cs_pins!(
|
|||||||
|
|
||||||
// SPIC
|
// SPIC
|
||||||
|
|
||||||
|
pub struct RomSck;
|
||||||
|
pub struct RomMosi;
|
||||||
|
pub struct RomMiso;
|
||||||
|
|
||||||
|
impl Sealed for RomSck {}
|
||||||
|
impl PinSck<pac::Spic> for RomSck {}
|
||||||
|
impl Sealed for RomMosi {}
|
||||||
|
impl PinMosi<pac::Spic> for RomMosi {}
|
||||||
|
impl Sealed for RomMiso {}
|
||||||
|
impl PinMiso<pac::Spic> for RomMiso {}
|
||||||
|
|
||||||
hw_cs_pins!(
|
hw_cs_pins!(
|
||||||
pac::Spic, SpiPort::Portc:
|
pac::Spic, SpiPort::Portc:
|
||||||
(PB9, AltFunc3, HwChipSelectId::Id1, HwCs1SpiCPortB0),
|
(PB9, AltFunc3, HwChipSelectId::Id1, HwCs1SpiCPortB0),
|
||||||
@ -241,35 +254,28 @@ hw_cs_pins!(
|
|||||||
// Config
|
// Config
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
pub trait GenericTransferConfig {
|
pub trait TransferConfigProvider {
|
||||||
fn sod(&mut self, sod: bool);
|
fn sod(&mut self, sod: bool);
|
||||||
fn blockmode(&mut self, blockmode: bool);
|
fn blockmode(&mut self, blockmode: bool);
|
||||||
fn mode(&mut self, mode: Mode);
|
fn mode(&mut self, mode: Mode);
|
||||||
fn frequency(&mut self, spi_clk: Hertz);
|
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig);
|
||||||
fn hw_cs_id(&self) -> u8;
|
fn hw_cs_id(&self) -> u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct contains all configuration parameter which are transfer specific
|
/// This struct contains all configuration parameter which are transfer specific
|
||||||
/// and might change for transfers to different SPI slaves
|
/// and might change for transfers to different SPI slaves
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct TransferConfig<HwCs> {
|
pub struct TransferConfigWithHwcs<HwCs> {
|
||||||
pub spi_clk: Hertz,
|
|
||||||
pub mode: Mode,
|
|
||||||
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
|
|
||||||
/// false
|
|
||||||
pub hw_cs: Option<HwCs>,
|
pub hw_cs: Option<HwCs>,
|
||||||
pub sod: bool,
|
pub cfg: CommonTransferConfig,
|
||||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
|
||||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
|
||||||
/// duration of multiple data words
|
|
||||||
pub blockmode: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
||||||
/// the SPI constructor.
|
/// the SPI constructor.
|
||||||
pub struct ReducedTransferConfig {
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub spi_clk: Hertz,
|
pub struct CommonTransferConfig {
|
||||||
pub mode: Mode,
|
pub clk_cfg: Option<SpiClkConfig>,
|
||||||
|
pub mode: Option<Mode>,
|
||||||
pub sod: bool,
|
pub sod: bool,
|
||||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||||
@ -278,62 +284,67 @@ pub struct ReducedTransferConfig {
|
|||||||
pub hw_cs: HwChipSelectId,
|
pub hw_cs: HwChipSelectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransferConfig<NoneT> {
|
impl TransferConfigWithHwcs<NoneT> {
|
||||||
pub fn new_no_hw_cs(spi_clk: impl Into<Hertz>, mode: Mode, blockmode: bool, sod: bool) -> Self {
|
pub fn new_no_hw_cs(
|
||||||
TransferConfig {
|
clk_cfg: Option<SpiClkConfig>,
|
||||||
spi_clk: spi_clk.into(),
|
mode: Option<Mode>,
|
||||||
mode,
|
blockmode: bool,
|
||||||
|
sod: bool,
|
||||||
|
) -> Self {
|
||||||
|
TransferConfigWithHwcs {
|
||||||
hw_cs: None,
|
hw_cs: None,
|
||||||
|
cfg: CommonTransferConfig {
|
||||||
|
clk_cfg,
|
||||||
|
mode,
|
||||||
sod,
|
sod,
|
||||||
blockmode,
|
blockmode,
|
||||||
|
hw_cs: HwChipSelectId::Invalid,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
spi_clk: impl Into<Hertz>,
|
clk_cfg: Option<SpiClkConfig>,
|
||||||
mode: Mode,
|
mode: Option<Mode>,
|
||||||
hw_cs: Option<HwCs>,
|
hw_cs: Option<HwCs>,
|
||||||
blockmode: bool,
|
blockmode: bool,
|
||||||
sod: bool,
|
sod: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
TransferConfig {
|
TransferConfigWithHwcs {
|
||||||
spi_clk: spi_clk.into(),
|
|
||||||
mode,
|
|
||||||
hw_cs,
|
hw_cs,
|
||||||
|
cfg: CommonTransferConfig {
|
||||||
|
clk_cfg,
|
||||||
|
mode,
|
||||||
sod,
|
sod,
|
||||||
blockmode,
|
blockmode,
|
||||||
|
hw_cs: HwCs::CS_ID,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn downgrade(self) -> ReducedTransferConfig {
|
pub fn downgrade(self) -> CommonTransferConfig {
|
||||||
ReducedTransferConfig {
|
self.cfg
|
||||||
spi_clk: self.spi_clk,
|
|
||||||
mode: self.mode,
|
|
||||||
sod: self.sod,
|
|
||||||
blockmode: self.blockmode,
|
|
||||||
hw_cs: HwCs::CS_ID,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<HwCs: HwCsProvider> GenericTransferConfig for TransferConfig<HwCs> {
|
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfigWithHwcs<HwCs> {
|
||||||
/// Slave Output Disable
|
/// Slave Output Disable
|
||||||
fn sod(&mut self, sod: bool) {
|
fn sod(&mut self, sod: bool) {
|
||||||
self.sod = sod;
|
self.cfg.sod = sod;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blockmode(&mut self, blockmode: bool) {
|
fn blockmode(&mut self, blockmode: bool) {
|
||||||
self.blockmode = blockmode;
|
self.cfg.blockmode = blockmode;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mode(&mut self, mode: Mode) {
|
fn mode(&mut self, mode: Mode) {
|
||||||
self.mode = mode;
|
self.cfg.mode = Some(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frequency(&mut self, spi_clk: Hertz) {
|
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) {
|
||||||
self.spi_clk = spi_clk;
|
self.cfg.clk_cfg = Some(clk_cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hw_cs_id(&self) -> u8 {
|
fn hw_cs_id(&self) -> u8 {
|
||||||
@ -341,26 +352,40 @@ impl<HwCs: HwCsProvider> GenericTransferConfig for TransferConfig<HwCs> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
||||||
pub struct SpiConfig {
|
pub struct SpiConfig {
|
||||||
/// Serial clock rate divider. Together with the CLKPRESCALE register, it determines
|
clk: SpiClkConfig,
|
||||||
/// the SPI clock rate in master mode. 0 by default. Specifying a higher value
|
|
||||||
/// limits the maximum attainable SPI speed
|
|
||||||
pub scrdv: 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
|
||||||
sod: bool,
|
pub slave_output_disable: bool,
|
||||||
/// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
|
/// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
|
||||||
lbm: bool,
|
pub loopback_mode: bool,
|
||||||
/// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
|
/// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
|
||||||
pub mdlycap: bool,
|
pub master_delayer_capture: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpiConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
// Default value is definitely valid.
|
||||||
|
clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
|
||||||
|
ms: Default::default(),
|
||||||
|
slave_output_disable: Default::default(),
|
||||||
|
loopback_mode: Default::default(),
|
||||||
|
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.lbm = enable;
|
self.loopback_mode = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
|
||||||
|
self.clk = clk_cfg;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +395,7 @@ impl SpiConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn slave_output_disable(mut self, sod: bool) -> Self {
|
pub fn slave_output_disable(mut self, sod: bool) -> Self {
|
||||||
self.sod = sod;
|
self.slave_output_disable = sod;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,6 +444,119 @@ 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) {
|
||||||
|
match mode {
|
||||||
|
embedded_hal::spi::MODE_0 => (false, false),
|
||||||
|
embedded_hal::spi::MODE_1 => (false, true),
|
||||||
|
embedded_hal::spi::MODE_2 => (true, false),
|
||||||
|
embedded_hal::spi::MODE_3 => (true, true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiClkConfig {
|
||||||
|
pub fn new(prescale_val: u16, scrdv: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
prescale_val,
|
||||||
|
scrdv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_div(div: u16) -> Result<Self, SpiClkConfigError> {
|
||||||
|
spi_clk_config_from_div(div)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_clk(sys_clk: impl Into<Hertz>, spi_clk: impl Into<Hertz>) -> Option<Self> {
|
||||||
|
clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(
|
||||||
|
sys_clk: impl Into<Hertz>,
|
||||||
|
spi_clk: impl Into<Hertz>,
|
||||||
|
) -> Option<u16> {
|
||||||
|
let spi_clk = spi_clk.into();
|
||||||
|
let sys_clk = sys_clk.into();
|
||||||
|
if spi_clk > sys_clk {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Calculate raw divider.
|
||||||
|
let raw_div = sys_clk.raw() / spi_clk.raw();
|
||||||
|
let remainder = sys_clk.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)
|
||||||
|
}
|
||||||
|
|
||||||
// Re-export this so it can be used for the constructor
|
// Re-export this so it can be used for the constructor
|
||||||
pub use crate::typelevel::NoneT;
|
pub use crate::typelevel::NoneT;
|
||||||
|
|
||||||
@ -453,59 +591,56 @@ where
|
|||||||
spi: SpiI,
|
spi: SpiI,
|
||||||
pins: (Sck, Miso, Mosi),
|
pins: (Sck, Miso, Mosi),
|
||||||
spi_cfg: SpiConfig,
|
spi_cfg: SpiConfig,
|
||||||
transfer_cfg: Option<&ReducedTransferConfig>,
|
transfer_cfg: Option<&CommonTransferConfig>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
|
enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
|
||||||
let SpiConfig {
|
let SpiConfig {
|
||||||
scrdv,
|
clk,
|
||||||
ms,
|
ms,
|
||||||
sod,
|
slave_output_disable,
|
||||||
lbm,
|
loopback_mode,
|
||||||
mdlycap,
|
master_delayer_capture,
|
||||||
} = spi_cfg;
|
} = spi_cfg;
|
||||||
let mut mode = MODE_0;
|
let mut init_mode = embedded_hal::spi::MODE_0;
|
||||||
let mut clk_prescale = 0x02;
|
|
||||||
let mut ss = 0;
|
let mut ss = 0;
|
||||||
let mut init_blockmode = false;
|
let mut init_blockmode = false;
|
||||||
if let Some(transfer_cfg) = transfer_cfg {
|
if let Some(transfer_cfg) = transfer_cfg {
|
||||||
mode = transfer_cfg.mode;
|
if let Some(mode) = transfer_cfg.mode {
|
||||||
clk_prescale = sys_clk.into().raw() / (transfer_cfg.spi_clk.raw() * (scrdv as u32 + 1));
|
init_mode = mode;
|
||||||
|
}
|
||||||
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
|
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
|
||||||
ss = transfer_cfg.hw_cs as u8;
|
ss = transfer_cfg.hw_cs as u8;
|
||||||
}
|
}
|
||||||
init_blockmode = transfer_cfg.blockmode;
|
init_blockmode = transfer_cfg.blockmode;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (cpo_bit, cph_bit) = match mode {
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
|
||||||
MODE_0 => (false, false),
|
|
||||||
MODE_1 => (false, true),
|
|
||||||
MODE_2 => (true, false),
|
|
||||||
MODE_3 => (true, true),
|
|
||||||
};
|
|
||||||
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(scrdv);
|
w.scrdv().bits(clk.scrdv);
|
||||||
// Clear clock phase and polarity. Will be set to correct value for each
|
// Clear clock phase and polarity. Will be set to correct value for each
|
||||||
// transfer
|
// transfer
|
||||||
w.spo().bit(cpo_bit);
|
w.spo().bit(cpo_bit);
|
||||||
w.sph().bit(cph_bit)
|
w.sph().bit(cph_bit)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
spi.ctrl1().write(|w| {
|
spi.ctrl1().write(|w| {
|
||||||
w.lbm().bit(lbm);
|
w.lbm().bit(loopback_mode);
|
||||||
w.sod().bit(sod);
|
w.sod().bit(slave_output_disable);
|
||||||
w.ms().bit(ms);
|
w.ms().bit(ms);
|
||||||
w.mdlycap().bit(mdlycap);
|
w.mdlycap().bit(master_delayer_capture);
|
||||||
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(clk.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());
|
||||||
@ -522,33 +657,33 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delegate::delegate! {
|
||||||
|
to self.inner {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
|
pub fn cfg_clock(&mut self, cfg: SpiClkConfig);
|
||||||
self.inner.cfg_clock(spi_clk);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cfg_mode(&mut self, mode: Mode) {
|
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
|
||||||
self.inner.cfg_mode(mode);
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_mode(&mut self, mode: Mode);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn perid(&self) -> u32;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fill_word(&self) -> Word;
|
||||||
|
|
||||||
|
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
|
||||||
|
&mut self, transfer_cfg: &TransferConfigWithHwcs<HwCs>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_word(&self) -> 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)
|
||||||
@ -564,12 +699,20 @@ where
|
|||||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
|
pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
|
||||||
let clk_prescale =
|
self.spi
|
||||||
self.sys_clk.raw() / (spi_clk.into().raw() * (self.cfg.scrdv as u32 + 1));
|
.ctrl0()
|
||||||
|
.modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) });
|
||||||
self.spi
|
self.spi
|
||||||
.clkprescale()
|
.clkprescale()
|
||||||
.write(|w| unsafe { w.bits(clk_prescale) });
|
.write(|w| unsafe { w.bits(cfg.prescale_val as u32) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
|
||||||
|
let val = spi_clk_config_from_div(div)?;
|
||||||
|
self.cfg_clock(val);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -586,6 +729,11 @@ where
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fill_word(&self) -> Word {
|
||||||
|
self.fill_word
|
||||||
|
}
|
||||||
|
|
||||||
#[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());
|
||||||
@ -629,13 +777,17 @@ 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: &TransferConfigWithHwcs<HwCs>,
|
||||||
) {
|
) {
|
||||||
self.cfg_clock(transfer_cfg.spi_clk);
|
if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg {
|
||||||
self.cfg_mode(transfer_cfg.mode);
|
self.cfg_clock(trans_clk_div);
|
||||||
self.blockmode = transfer_cfg.blockmode;
|
}
|
||||||
|
if let Some(mode) = transfer_cfg.cfg.mode {
|
||||||
|
self.cfg_mode(mode);
|
||||||
|
}
|
||||||
|
self.blockmode = transfer_cfg.cfg.blockmode;
|
||||||
self.spi.ctrl1().modify(|_, w| {
|
self.spi.ctrl1().modify(|_, w| {
|
||||||
if transfer_cfg.sod {
|
if transfer_cfg.cfg.sod {
|
||||||
w.sod().set_bit();
|
w.sod().set_bit();
|
||||||
} else if transfer_cfg.hw_cs.is_some() {
|
} else if transfer_cfg.hw_cs.is_some() {
|
||||||
w.sod().clear_bit();
|
w.sod().clear_bit();
|
||||||
@ -645,7 +797,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
w.sod().clear_bit();
|
w.sod().clear_bit();
|
||||||
}
|
}
|
||||||
if transfer_cfg.blockmode {
|
if transfer_cfg.cfg.blockmode {
|
||||||
w.blockmode().set_bit();
|
w.blockmode().set_bit();
|
||||||
} else {
|
} else {
|
||||||
w.blockmode().clear_bit();
|
w.blockmode().clear_bit();
|
||||||
@ -682,34 +834,52 @@ where
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flush_internal(&self) {
|
||||||
|
let mut status_reg = self.spi.status().read();
|
||||||
|
while status_reg.tfe().bit_is_clear()
|
||||||
|
|| status_reg.rne().bit_is_set()
|
||||||
|
|| status_reg.busy().bit_is_set()
|
||||||
|
{
|
||||||
|
if status_reg.rne().bit_is_set() {
|
||||||
|
self.read_single_word();
|
||||||
|
}
|
||||||
|
status_reg = self.spi.status().read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
|
fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
|
||||||
if words.is_empty() {
|
if words.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut status_reg = self.spi.status().read();
|
self.flush_internal();
|
||||||
// Wait until all bytes have been transferred.
|
|
||||||
while status_reg.tfe().bit_is_clear() {
|
|
||||||
// Ignore all received read words.
|
|
||||||
if status_reg.rne().bit_is_set() {
|
|
||||||
self.clear_rx_fifo();
|
|
||||||
}
|
|
||||||
status_reg = self.spi.status().read();
|
|
||||||
}
|
|
||||||
// Ignore all received read words.
|
|
||||||
if status_reg.rne().bit_is_set() {
|
|
||||||
self.clear_rx_fifo();
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize {
|
// Returns the actual bytes sent.
|
||||||
|
fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize {
|
||||||
if self.blockmode {
|
if self.blockmode {
|
||||||
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
|
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
|
||||||
}
|
}
|
||||||
// Fill the first half of the write FIFO
|
// Fill the first half of the write FIFO
|
||||||
let mut current_write_idx = 0;
|
let mut current_write_idx = 0;
|
||||||
for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) {
|
for _ in 0..core::cmp::min(FILL_DEPTH, words.len()) {
|
||||||
self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx]));
|
self.send_blocking(words[current_write_idx]);
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if self.blockmode {
|
||||||
|
self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit())
|
||||||
|
}
|
||||||
|
current_write_idx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize {
|
||||||
|
if self.blockmode {
|
||||||
|
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
|
||||||
|
}
|
||||||
|
// Fill the first half of the write FIFO
|
||||||
|
let mut current_write_idx = 0;
|
||||||
|
for _ in 0..core::cmp::min(FILL_DEPTH, send_len) {
|
||||||
|
self.send_blocking(self.fill_word);
|
||||||
current_write_idx += 1;
|
current_write_idx += 1;
|
||||||
}
|
}
|
||||||
if self.blockmode {
|
if self.blockmode {
|
||||||
@ -778,7 +948,7 @@ where
|
|||||||
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
self.transfer_preparation(words)?;
|
self.transfer_preparation(words)?;
|
||||||
let mut current_read_idx = 0;
|
let mut current_read_idx = 0;
|
||||||
let mut current_write_idx = self.initial_send_fifo_pumping(None);
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len());
|
||||||
loop {
|
loop {
|
||||||
if current_write_idx < words.len() {
|
if current_write_idx < words.len() {
|
||||||
self.send_blocking(self.fill_word);
|
self.send_blocking(self.fill_word);
|
||||||
@ -797,7 +967,7 @@ where
|
|||||||
|
|
||||||
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
||||||
self.transfer_preparation(words)?;
|
self.transfer_preparation(words)?;
|
||||||
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||||
while current_write_idx < words.len() {
|
while current_write_idx < words.len() {
|
||||||
self.send_blocking(words[current_write_idx]);
|
self.send_blocking(words[current_write_idx]);
|
||||||
current_write_idx += 1;
|
current_write_idx += 1;
|
||||||
@ -812,7 +982,7 @@ where
|
|||||||
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||||
self.transfer_preparation(write)?;
|
self.transfer_preparation(write)?;
|
||||||
let mut current_read_idx = 0;
|
let mut current_read_idx = 0;
|
||||||
let mut current_write_idx = self.initial_send_fifo_pumping(Some(write));
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write);
|
||||||
while current_read_idx < read.len() || current_write_idx < write.len() {
|
while current_read_idx < read.len() || current_write_idx < write.len() {
|
||||||
if current_write_idx < write.len() {
|
if current_write_idx < write.len() {
|
||||||
self.send_blocking(write[current_write_idx]);
|
self.send_blocking(write[current_write_idx]);
|
||||||
@ -830,7 +1000,7 @@ where
|
|||||||
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
self.transfer_preparation(words)?;
|
self.transfer_preparation(words)?;
|
||||||
let mut current_read_idx = 0;
|
let mut current_read_idx = 0;
|
||||||
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||||
|
|
||||||
while current_read_idx < words.len() || current_write_idx < words.len() {
|
while current_read_idx < words.len() || current_write_idx < words.len() {
|
||||||
if current_write_idx < words.len() {
|
if current_write_idx < words.len() {
|
||||||
@ -846,12 +1016,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
let status_reg = self.spi.status().read();
|
self.flush_internal();
|
||||||
while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() {
|
|
||||||
if status_reg.rne().bit_is_set() {
|
|
||||||
self.read_single_word();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -877,23 +1042,13 @@ impl<
|
|||||||
where
|
where
|
||||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
{
|
{
|
||||||
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
delegate::delegate! {
|
||||||
self.inner.read(words)
|
to self.inner {
|
||||||
|
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
|
||||||
|
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>;
|
||||||
|
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>;
|
||||||
|
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
|
||||||
|
fn flush(&mut self) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
|
||||||
self.inner.write(words)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
|
||||||
self.inner.transfer(read, write)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
|
||||||
self.inner.transfer_in_place(words)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
|
||||||
self.inner.flush()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,15 @@ categories = ["aerospace", "embedded", "no-std", "hardware-support"]
|
|||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
|
nb = "1"
|
||||||
|
bitfield = "0.17"
|
||||||
|
|
||||||
[dependencies.max116xx-10bit]
|
[dependencies.max116xx-10bit]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
|
||||||
[dependencies.va108xx-hal]
|
[dependencies.va108xx-hal]
|
||||||
version = "0.7"
|
version = ">=0.7, <0.8"
|
||||||
|
path = "../va108xx-hal"
|
||||||
features = ["rt"]
|
features = ["rt"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -28,20 +31,8 @@ rt = ["va108xx-hal/rt"]
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
panic-halt = "0.2"
|
panic-halt = "0.2"
|
||||||
nb = "1"
|
nb = "1"
|
||||||
|
rtt-target = "0.5"
|
||||||
[dev-dependencies.rtt-target]
|
panic-rtt-target = "0.1"
|
||||||
version = "0.5"
|
|
||||||
|
|
||||||
[dev-dependencies.panic-rtt-target]
|
|
||||||
version = "0.1"
|
|
||||||
|
|
||||||
[dev-dependencies.rtic]
|
|
||||||
version = "2"
|
|
||||||
features = ["thumbv6-backend"]
|
|
||||||
|
|
||||||
[dev-dependencies.rtic-monotonics]
|
|
||||||
version = "1"
|
|
||||||
features = ["cortex-m-systick"]
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
@ -9,11 +9,12 @@ use embedded_hal::spi::SpiBus;
|
|||||||
use embedded_hal::{delay::DelayNs, digital::OutputPin};
|
use embedded_hal::{delay::DelayNs, digital::OutputPin};
|
||||||
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 va108xx_hal::spi::SpiClkConfig;
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
gpio::PinsA,
|
gpio::PinsA,
|
||||||
pac,
|
pac,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
spi::{Spi, SpiConfig, TransferConfig},
|
spi::{Spi, SpiConfig, TransferConfigWithHwcs},
|
||||||
timer::set_up_ms_delay_provider,
|
timer::set_up_ms_delay_provider,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,9 +46,9 @@ fn main() -> ! {
|
|||||||
.set_high()
|
.set_high()
|
||||||
.expect("Setting ADC chip select high failed");
|
.expect("Setting ADC chip select high failed");
|
||||||
|
|
||||||
let transfer_cfg = TransferConfig::new(
|
let transfer_cfg = TransferConfigWithHwcs::new(
|
||||||
1.MHz(),
|
Some(SpiClkConfig::from_clk(50.MHz(), 1.MHz()).expect("creating SPI clock config failed")),
|
||||||
embedded_hal::spi::MODE_3,
|
Some(embedded_hal::spi::MODE_3),
|
||||||
Some(cs_pin),
|
Some(cs_pin),
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
@ -15,13 +15,13 @@ use max116xx_10bit::VoltageRefMode;
|
|||||||
use max116xx_10bit::{AveragingConversions, AveragingResults};
|
use max116xx_10bit::{AveragingConversions, AveragingResults};
|
||||||
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 va108xx_hal::spi::{NoneT, OptionalHwCs};
|
use va108xx_hal::spi::{OptionalHwCs, SpiClkConfig};
|
||||||
use va108xx_hal::timer::CountDownTimer;
|
use va108xx_hal::timer::CountDownTimer;
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
gpio::PinsA,
|
gpio::PinsA,
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
spi::{Spi, SpiBase, SpiConfig, TransferConfig},
|
spi::{Spi, SpiBase, SpiConfig, TransferConfigWithHwcs},
|
||||||
timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, IrqCfg},
|
timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, IrqCfg},
|
||||||
};
|
};
|
||||||
use va108xx_hal::{port_mux, FunSel, PortSel};
|
use va108xx_hal::{port_mux, FunSel, PortSel};
|
||||||
@ -103,6 +103,8 @@ impl<Delay: DelayNs, HwCs: OptionalHwCs<pac::Spib>> SpiDevice for SpiWithHwCs<De
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SYS_CLK: Hertz = Hertz::from_raw(50_000_000);
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
rtt_init_print!();
|
rtt_init_print!();
|
||||||
@ -113,7 +115,7 @@ fn main() -> ! {
|
|||||||
IrqCfg::new(pac::Interrupt::OC0, true, true),
|
IrqCfg::new(pac::Interrupt::OC0, true, true),
|
||||||
&mut dp.sysconfig,
|
&mut dp.sysconfig,
|
||||||
Some(&mut dp.irqsel),
|
Some(&mut dp.irqsel),
|
||||||
50.MHz(),
|
SYS_CLK,
|
||||||
dp.tim0,
|
dp.tim0,
|
||||||
);
|
);
|
||||||
let delay = DelayMs::new(tim0).unwrap();
|
let delay = DelayMs::new(tim0).unwrap();
|
||||||
@ -141,7 +143,12 @@ fn main() -> ! {
|
|||||||
.set_high()
|
.set_high()
|
||||||
.expect("Setting accelerometer chip select high failed");
|
.expect("Setting accelerometer chip select high failed");
|
||||||
|
|
||||||
let transfer_cfg = TransferConfig::<NoneT>::new(3.MHz(), spi::MODE_0, None, true, false);
|
let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs(
|
||||||
|
Some(SpiClkConfig::from_clk(SYS_CLK, 3.MHz()).unwrap()),
|
||||||
|
Some(spi::MODE_0),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
let spi = Spi::new(
|
let spi = Spi::new(
|
||||||
&mut dp.sysconfig,
|
&mut dp.sysconfig,
|
||||||
50.MHz(),
|
50.MHz(),
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
pub mod leds;
|
pub mod leds;
|
||||||
|
pub mod m95m01;
|
||||||
pub mod max11619;
|
pub mod max11619;
|
||||||
pub mod temp_sensor;
|
pub mod temp_sensor;
|
||||||
|
138
vorago-reb1/src/m95m01.rs
Normal file
138
vorago-reb1/src/m95m01.rs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
use embedded_hal::spi::SpiDevice;
|
||||||
|
|
||||||
|
bitfield::bitfield! {
|
||||||
|
pub struct StatusReg(u8);
|
||||||
|
impl Debug;
|
||||||
|
u8;
|
||||||
|
status_register_write_protect, _: 7, 0;
|
||||||
|
block_protection_bits, set_block_protection_bits: 3, 2;
|
||||||
|
write_enable_latch, _: 1;
|
||||||
|
write_in_progress, _: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registers.
|
||||||
|
pub mod regs {
|
||||||
|
/// Write status register command.
|
||||||
|
pub const WRSR: u8 = 0x01;
|
||||||
|
// Write command.
|
||||||
|
pub const WRITE: u8 = 0x02;
|
||||||
|
// Read command.
|
||||||
|
pub const READ: u8 = 0x03;
|
||||||
|
/// Write disable command.
|
||||||
|
pub const WRDI: u8 = 0x04;
|
||||||
|
/// Read status register command.
|
||||||
|
pub const RDSR: u8 = 0x05;
|
||||||
|
/// Write enable command.
|
||||||
|
pub const WREN: u8 = 0x06;
|
||||||
|
}
|
||||||
|
|
||||||
|
use regs::*;
|
||||||
|
|
||||||
|
/// Driver for the ST device M95M01 EEPROM memory.
|
||||||
|
pub struct M95M01<Spi: SpiDevice> {
|
||||||
|
spi: Spi,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Error<SpiError> {
|
||||||
|
Spi(SpiError),
|
||||||
|
BufTooShort,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SpiError> From<SpiError> for Error<SpiError> {
|
||||||
|
fn from(value: SpiError) -> Self {
|
||||||
|
Self::Spi(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Spi: SpiDevice> M95M01<Spi> {
|
||||||
|
pub fn new(spi: Spi) -> Result<Self, Spi::Error> {
|
||||||
|
let mut spi_dev = Self { spi };
|
||||||
|
spi_dev.clear_block_protection()?;
|
||||||
|
Ok(spi_dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(mut self) -> Result<Spi, Spi::Error> {
|
||||||
|
self.set_block_protection()?;
|
||||||
|
Ok(self.spi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the write-in-progress state is cleared. This exposes a [nb] API, so this function
|
||||||
|
// will return [nb::Error::WouldBlock] if the EEPROM is still busy.
|
||||||
|
pub fn writes_are_done(&mut self) -> nb::Result<(), Spi::Error> {
|
||||||
|
let mut read: [u8; 2] = [0; 2];
|
||||||
|
self.spi.transfer(&mut read, &[regs::RDSR, 0x00])?;
|
||||||
|
let rdsr = StatusReg(read[1]);
|
||||||
|
if rdsr.write_in_progress() {
|
||||||
|
return Err(nb::Error::WouldBlock);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_enable(&mut self) -> Result<(), Spi::Error> {
|
||||||
|
self.spi.write(&[regs::WREN])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_block_protection(&mut self) -> Result<(), Spi::Error> {
|
||||||
|
self.spi.write(&[WREN, WRSR, 0x00])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_block_protection(&mut self) -> Result<(), Spi::Error> {
|
||||||
|
let mut reg = StatusReg(0);
|
||||||
|
reg.set_block_protection_bits(0b11);
|
||||||
|
self.spi.write(&[WREN, WRSR, reg.0])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Spi::Error> {
|
||||||
|
self.write_enable()?;
|
||||||
|
self.spi.write(&[
|
||||||
|
WRITE,
|
||||||
|
((address >> 16) & 0xff) as u8,
|
||||||
|
((address >> 8) & 0xff) as u8,
|
||||||
|
(address & 0xff) as u8,
|
||||||
|
])?;
|
||||||
|
self.spi.write(data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(
|
||||||
|
&mut self,
|
||||||
|
address: u32,
|
||||||
|
size: usize,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Result<(), Error<Spi::Error>> {
|
||||||
|
if buf.len() < size {
|
||||||
|
return Err(Error::BufTooShort);
|
||||||
|
}
|
||||||
|
self.spi.write(&[
|
||||||
|
READ,
|
||||||
|
((address >> 16) & 0xff) as u8,
|
||||||
|
((address >> 8) & 0xff) as u8,
|
||||||
|
(address & 0xff) as u8,
|
||||||
|
])?;
|
||||||
|
self.spi.read(&mut buf[0..size])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, Spi::Error> {
|
||||||
|
// Write the read command and address
|
||||||
|
self.spi.write(&[
|
||||||
|
READ,
|
||||||
|
((address >> 16) & 0xff) as u8,
|
||||||
|
((address >> 8) & 0xff) as u8,
|
||||||
|
(address & 0xff) as u8,
|
||||||
|
])?;
|
||||||
|
|
||||||
|
// Read and compare each byte in place
|
||||||
|
for original_byte in data.iter() {
|
||||||
|
let mut read_byte = [0u8];
|
||||||
|
self.spi.read(&mut read_byte)?;
|
||||||
|
|
||||||
|
// Compare read byte with original
|
||||||
|
if read_byte[0] != *original_byte {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user