BL/FL progress
Some checks failed
Rust/va416xx-rs/pipeline/pr-main There was a failure building this commit
Some checks failed
Rust/va416xx-rs/pipeline/pr-main There was a failure building this commit
This commit is contained in:
parent
bc0b18ab7c
commit
c53702ee74
@ -32,10 +32,17 @@ use va416xx_hal::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const EXTCLK_FREQ: u32 = 40_000_000;
|
const EXTCLK_FREQ: u32 = 40_000_000;
|
||||||
const WITH_WDT: bool = true;
|
const WITH_WDT: bool = false;
|
||||||
const WDT_FREQ_MS: u32 = 50;
|
const WDT_FREQ_MS: u32 = 50;
|
||||||
const DEBUG_PRINTOUTS: bool = true;
|
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.
|
// Important bootloader addresses and offsets, vector table information.
|
||||||
|
|
||||||
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
||||||
@ -85,6 +92,8 @@ fn main() -> ! {
|
|||||||
rprintln!("-- VA416xx bootloader --");
|
rprintln!("-- VA416xx bootloader --");
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
let cp = cortex_m::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);
|
setup_edac(&mut dp.sysconfig);
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = dp
|
let clocks = dp
|
||||||
@ -103,6 +112,48 @@ fn main() -> ! {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
let nvm = Nvm::new(&mut dp.sysconfig, dp.spi3, &clocks);
|
let nvm = Nvm::new(&mut dp.sysconfig, dp.spi3, &clocks);
|
||||||
|
|
||||||
|
if FLASH_SELF {
|
||||||
|
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 first_four_bytes: [u8; 4] = [0; 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) &mut first_four_bytes as *mut [u8; 4], // Input: destination pointer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 bootloader's CRC (and write it if blank)
|
||||||
check_own_crc(&opt_wdt, &nvm, &cp);
|
check_own_crc(&opt_wdt, &nvm, &cp);
|
||||||
|
|
||||||
@ -114,7 +165,6 @@ fn main() -> ! {
|
|||||||
if DEBUG_PRINTOUTS {
|
if DEBUG_PRINTOUTS {
|
||||||
rprintln!("both images corrupt! booting image A");
|
rprintln!("both images corrupt! booting image A");
|
||||||
}
|
}
|
||||||
loop {}
|
|
||||||
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
|
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
|
||||||
// Both images seem to be corrupt. Boot default image A.
|
// Both images seem to be corrupt. Boot default image A.
|
||||||
boot_app(AppSel::A, &cp)
|
boot_app(AppSel::A, &cp)
|
||||||
@ -124,8 +174,11 @@ fn main() -> ! {
|
|||||||
fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
|
fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
|
||||||
let crc_exp = unsafe { *(BOOTLOADER_CRC_ADDR as *const u16) };
|
let crc_exp = unsafe { *(BOOTLOADER_CRC_ADDR as *const u16) };
|
||||||
wdt.feed();
|
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 crc_calc = CRC_ALGO.checksum(unsafe {
|
let crc_calc = CRC_ALGO.checksum(unsafe {
|
||||||
core::slice::from_raw_parts(
|
&*core::ptr::slice_from_raw_parts(
|
||||||
BOOTLOADER_START_ADDR as *const u8,
|
BOOTLOADER_START_ADDR as *const u8,
|
||||||
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4) as usize,
|
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4) as usize,
|
||||||
)
|
)
|
||||||
@ -150,6 +203,9 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
|
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
|
||||||
|
if DEBUG_PRINTOUTS {
|
||||||
|
rprintln!("Checking image {:?}", app_sel);
|
||||||
|
}
|
||||||
if app_sel == AppSel::A {
|
if app_sel == AppSel::A {
|
||||||
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR, wdt)
|
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR, wdt)
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,5 +23,5 @@ version = "2"
|
|||||||
features = ["thumbv6-backend"]
|
features = ["thumbv6-backend"]
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
[dependencies.rtic-monotonics]
|
||||||
version = "1"
|
version = "2"
|
||||||
features = ["cortex-m-systick"]
|
features = ["cortex-m-systick"]
|
||||||
|
@ -9,12 +9,13 @@ use embedded_hal::spi::{Mode, SpiBus, MODE_0};
|
|||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::spi::{Spi, TransferConfig};
|
use va416xx_hal::spi::{clk_div_for_target_clock, Spi, TransferConfig};
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
gpio::{PinsB, PinsC},
|
gpio::{PinsB, PinsC},
|
||||||
pac,
|
pac,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
spi::SpiConfig,
|
spi::SpiConfig,
|
||||||
|
time::Hertz,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
@ -56,21 +57,24 @@ fn main() -> ! {
|
|||||||
pins_c.pc1.into_funsel_1(),
|
pins_c.pc1.into_funsel_1(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut spi_cfg = SpiConfig::default();
|
let mut spi_cfg = SpiConfig::default().clk_div(
|
||||||
|
clk_div_for_target_clock(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
|
||||||
|
.expect("invalid target clock"),
|
||||||
|
);
|
||||||
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
||||||
spi_cfg = spi_cfg.loopback(true)
|
spi_cfg = spi_cfg.loopback(true)
|
||||||
}
|
}
|
||||||
let transfer_cfg =
|
let transfer_cfg = TransferConfig::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false);
|
||||||
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false);
|
|
||||||
// Create SPI peripheral.
|
// Create SPI peripheral.
|
||||||
let mut spi0 = Spi::new(
|
let mut spi0 = Spi::new(
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
&clocks,
|
||||||
dp.spi0,
|
dp.spi0,
|
||||||
(sck, miso, mosi),
|
(sck, miso, mosi),
|
||||||
&clocks,
|
|
||||||
spi_cfg,
|
spi_cfg,
|
||||||
&mut dp.sysconfig,
|
|
||||||
Some(&transfer_cfg.downgrade()),
|
Some(&transfer_cfg.downgrade()),
|
||||||
);
|
)
|
||||||
|
.expect("creating SPI peripheral failed");
|
||||||
spi0.set_fill_word(FILL_WORD);
|
spi0.set_fill_word(FILL_WORD);
|
||||||
loop {
|
loop {
|
||||||
let mut tx_buf: [u8; 3] = [1, 2, 3];
|
let mut tx_buf: [u8; 3] = [1, 2, 3];
|
||||||
|
1
flashloader/.gitignore
vendored
Normal file
1
flashloader/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/venv
|
@ -38,5 +38,5 @@ version = "2"
|
|||||||
features = ["thumbv7-backend"]
|
features = ["thumbv7-backend"]
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
[dependencies.rtic-monotonics]
|
||||||
version = "1"
|
version = "2"
|
||||||
features = ["cortex-m-systick"]
|
features = ["cortex-m-systick"]
|
||||||
|
86
flashloader/image-loader.py
Executable file
86
flashloader/image-loader.py
Executable file
@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from spacepackets.ecss.defs import PusService
|
||||||
|
import toml
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
from tmtccmd.com.serial_base import SerialCfg
|
||||||
|
from tmtccmd.com.serial_cobs import SerialCobsComIF
|
||||||
|
from tmtccmd.com.ser_utils import prompt_com_port
|
||||||
|
from spacepackets.ecss.tc import PusTc
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
BAUD_RATE = 115200
|
||||||
|
BOOTLOADER_START_ADDR = 0x0
|
||||||
|
BOOTLOADER_END_ADDR = 0x4000
|
||||||
|
BOOTLOADER_CRC_ADDR = 0x3FFE
|
||||||
|
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
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
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(
|
||||||
|
"-f",
|
||||||
|
"--flash",
|
||||||
|
choices=["bl", "a", "b"],
|
||||||
|
help="Flash target (Bootloader or slot A or B)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"path", nargs="?", default=None, help="Path to the App to flash"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
serial_port = None
|
||||||
|
if Path("loader.toml").exists():
|
||||||
|
with open("loader.toml", "r") as toml_file:
|
||||||
|
parsed_toml = toml.loads(toml_file.read())
|
||||||
|
if "serial_port" in parsed_toml:
|
||||||
|
serial_port = parsed_toml["serial_port"]
|
||||||
|
if serial_port is None:
|
||||||
|
serial_port = prompt_com_port()
|
||||||
|
serial_cfg = SerialCfg(
|
||||||
|
com_if_id="ser_cobs",
|
||||||
|
serial_port=serial_port,
|
||||||
|
baud_rate=BAUD_RATE,
|
||||||
|
serial_timeout=0.1,
|
||||||
|
)
|
||||||
|
com_if = SerialCobsComIF(serial_cfg)
|
||||||
|
com_if.open()
|
||||||
|
if args.flash and not args.path:
|
||||||
|
_LOGGER.error("App Path needs to be specified for the flash process")
|
||||||
|
ping_tc = PusTc(apid=0x00, service=PusService.S17_TEST, subservice=1)
|
||||||
|
if args.ping:
|
||||||
|
_LOGGER.info("Sending ping command")
|
||||||
|
com_if.send(ping_tc.pack())
|
||||||
|
if args.flash:
|
||||||
|
while True:
|
||||||
|
data_available = com_if.data_available(0.4)
|
||||||
|
if data_available:
|
||||||
|
reply = com_if.receive()
|
||||||
|
# TODO: Parse replies
|
||||||
|
print("Received replies: {}", reply)
|
||||||
|
break
|
||||||
|
com_if.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
1
flashloader/loader.toml
Normal file
1
flashloader/loader.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
serial_port = "/dev/ttyUSB0"
|
3
flashloader/requirements.txt
Normal file
3
flashloader/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
spacepackets == 0.24
|
||||||
|
tmtccmd == 8.0.1
|
||||||
|
toml == 0.10
|
@ -28,6 +28,8 @@ const COBS_FRAME_SEPARATOR: u8 = 0x0;
|
|||||||
const MAX_PACKET_SIZE: usize = 1024;
|
const MAX_PACKET_SIZE: usize = 1024;
|
||||||
const MAX_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_PACKET_SIZE);
|
const MAX_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_PACKET_SIZE);
|
||||||
const UART_BAUDRATE: u32 = 115200;
|
const UART_BAUDRATE: u32 = 115200;
|
||||||
|
const SERIAL_RX_WIRETAPPING: bool = false;
|
||||||
|
const COBS_RX_DEBUGGING: bool = false;
|
||||||
|
|
||||||
const BOOT_NVM_MEMORY_ID: u8 = 1;
|
const BOOT_NVM_MEMORY_ID: u8 = 1;
|
||||||
|
|
||||||
@ -52,8 +54,8 @@ mod app {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use embedded_hal_nb::nb;
|
use embedded_hal_nb::nb;
|
||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtic_monotonics::systick::ExtU32;
|
use rtic::Mutex;
|
||||||
use rtic_monotonics::systick::Systick;
|
use rtic_monotonics::systick::prelude::*;
|
||||||
use rtic_sync::{
|
use rtic_sync::{
|
||||||
channel::{Receiver, Sender},
|
channel::{Receiver, Sender},
|
||||||
make_channel,
|
make_channel,
|
||||||
@ -99,13 +101,14 @@ mod app {
|
|||||||
pub type TcTx = Sender<'static, usize, 2>;
|
pub type TcTx = Sender<'static, usize, 2>;
|
||||||
pub type TcRx = Receiver<'static, usize, 2>;
|
pub type TcRx = Receiver<'static, usize, 2>;
|
||||||
|
|
||||||
|
rtic_monotonics::systick_monotonic!(Mono, 10_000);
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(mut cx: init::Context) -> (Shared, Local) {
|
fn init(mut cx: init::Context) -> (Shared, Local) {
|
||||||
//rtt_init_default!();
|
//rtt_init_default!();
|
||||||
rtt_log::init();
|
rtt_log::init();
|
||||||
rprintln!("-- Vorago flashloader --");
|
rprintln!("-- Vorago flashloader --");
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = cx
|
let clocks = cx
|
||||||
.device
|
.device
|
||||||
@ -130,8 +133,11 @@ mod app {
|
|||||||
let (tx, rx) = uart0.split();
|
let (tx, rx) = uart0.split();
|
||||||
let (tc_tx, tc_rx) = make_channel!(usize, 2);
|
let (tc_tx, tc_rx) = make_channel!(usize, 2);
|
||||||
|
|
||||||
Systick::start(cx.core.SYST, clocks.sysclk().raw(), systick_mono_token);
|
Mono::start(cx.core.SYST, clocks.sysclk().raw());
|
||||||
CLOCKS.set(clocks).unwrap();
|
CLOCKS.set(clocks).unwrap();
|
||||||
|
pus_tc_handler::spawn().unwrap();
|
||||||
|
uart_reader_task::spawn().unwrap();
|
||||||
|
pus_tm_tx_handler::spawn().unwrap();
|
||||||
(
|
(
|
||||||
Shared {
|
Shared {
|
||||||
decode_buffer_busy: false,
|
decode_buffer_busy: false,
|
||||||
@ -168,15 +174,58 @@ mod app {
|
|||||||
async fn uart_reader_task(mut cx: uart_reader_task::Context) {
|
async fn uart_reader_task(mut cx: uart_reader_task::Context) {
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
loop {
|
loop {
|
||||||
match nb::block!(cx.local.uart_rx.read()) {
|
match cx.local.uart_rx.read() {
|
||||||
Ok(byte) => match cx.local.cobs_reader_state {
|
Ok(byte) => {
|
||||||
|
if SERIAL_RX_WIRETAPPING {
|
||||||
|
log::debug!("RX Byte: 0x{:x?}", byte);
|
||||||
|
}
|
||||||
|
handle_single_rx_byte(&mut cx, byte, &mut current_idx)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
nb::Error::Other(e) => {
|
||||||
|
log::warn!("UART error: {:?}", e);
|
||||||
|
match e {
|
||||||
|
uart::Error::Overrun => {
|
||||||
|
cx.local.uart_rx.clear_fifo();
|
||||||
|
}
|
||||||
|
uart::Error::FramingError => todo!(),
|
||||||
|
uart::Error::ParityError => todo!(),
|
||||||
|
uart::Error::BreakCondition => todo!(),
|
||||||
|
uart::Error::TransferPending => todo!(),
|
||||||
|
uart::Error::BufferTooShort => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nb::Error::WouldBlock => {
|
||||||
|
// Delay for a short period before polling again.
|
||||||
|
Mono::delay(400.micros()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_single_rx_byte(
|
||||||
|
cx: &mut uart_reader_task::Context,
|
||||||
|
byte: u8,
|
||||||
|
current_idx: &mut usize,
|
||||||
|
) {
|
||||||
|
match cx.local.cobs_reader_state {
|
||||||
CobsReaderStates::WaitingForStart => {
|
CobsReaderStates::WaitingForStart => {
|
||||||
if byte == COBS_FRAME_SEPARATOR {
|
if byte == COBS_FRAME_SEPARATOR {
|
||||||
|
if COBS_RX_DEBUGGING {
|
||||||
|
log::debug!("COBS start marker detected");
|
||||||
|
}
|
||||||
*cx.local.cobs_reader_state = CobsReaderStates::WatingForEnd;
|
*cx.local.cobs_reader_state = CobsReaderStates::WatingForEnd;
|
||||||
|
*current_idx = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CobsReaderStates::WatingForEnd => {
|
CobsReaderStates::WatingForEnd => {
|
||||||
if byte == COBS_FRAME_SEPARATOR {
|
if byte == COBS_FRAME_SEPARATOR {
|
||||||
|
if COBS_RX_DEBUGGING {
|
||||||
|
log::debug!("COBS end marker detected");
|
||||||
|
}
|
||||||
let mut sending_failed = false;
|
let mut sending_failed = false;
|
||||||
let mut decoding_error = false;
|
let mut decoding_error = false;
|
||||||
let mut decode_buffer_busy = false;
|
let mut decode_buffer_busy = false;
|
||||||
@ -185,18 +234,24 @@ mod app {
|
|||||||
decode_buffer_busy = true;
|
decode_buffer_busy = true;
|
||||||
} else {
|
} else {
|
||||||
cx.shared.decode_buf.lock(|buf| {
|
cx.shared.decode_buf.lock(|buf| {
|
||||||
match cobs::decode(&cx.local.read_buf[..current_idx], buf) {
|
match cobs::decode(&cx.local.read_buf[..*current_idx], buf) {
|
||||||
Ok(packet_len) => {
|
Ok(packet_len) => {
|
||||||
|
if COBS_RX_DEBUGGING {
|
||||||
|
log::debug!(
|
||||||
|
"COBS decoded packet with length {}",
|
||||||
|
packet_len
|
||||||
|
);
|
||||||
|
}
|
||||||
if cx.local.tc_tx.try_send(packet_len).is_err() {
|
if cx.local.tc_tx.try_send(packet_len).is_err() {
|
||||||
sending_failed = true;
|
sending_failed = true;
|
||||||
}
|
}
|
||||||
|
*busy = true;
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
decoding_error = true;
|
decoding_error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*busy = true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if sending_failed {
|
if sending_failed {
|
||||||
@ -208,29 +263,20 @@ mod app {
|
|||||||
if decode_buffer_busy {
|
if decode_buffer_busy {
|
||||||
log::warn!("decode buffer busy. data arriving too fast");
|
log::warn!("decode buffer busy. data arriving too fast");
|
||||||
}
|
}
|
||||||
} else if current_idx >= cx.local.read_buf.len() {
|
*cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart;
|
||||||
|
} else if *current_idx >= cx.local.read_buf.len() {
|
||||||
*cx.local.cobs_reader_state = CobsReaderStates::FrameOverflow;
|
*cx.local.cobs_reader_state = CobsReaderStates::FrameOverflow;
|
||||||
} else {
|
} else {
|
||||||
cx.local.read_buf[current_idx] = byte;
|
cx.local.read_buf[*current_idx] = byte;
|
||||||
current_idx += 1;
|
*current_idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CobsReaderStates::FrameOverflow => {
|
CobsReaderStates::FrameOverflow => {
|
||||||
if byte == COBS_FRAME_SEPARATOR {
|
if byte == COBS_FRAME_SEPARATOR {
|
||||||
*cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart;
|
*cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart;
|
||||||
current_idx = 0;
|
*current_idx = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Err(e) => match e {
|
|
||||||
uart::Error::Overrun => todo!(),
|
|
||||||
uart::Error::FramingError => todo!(),
|
|
||||||
uart::Error::ParityError => todo!(),
|
|
||||||
uart::Error::BreakCondition => todo!(),
|
|
||||||
uart::Error::TransferPending => todo!(),
|
|
||||||
uart::Error::BufferTooShort => todo!(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +291,8 @@ mod app {
|
|||||||
)]
|
)]
|
||||||
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
|
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
|
||||||
loop {
|
loop {
|
||||||
let _ = cx.local.tc_rx.recv().await;
|
let packet_len = cx.local.tc_rx.recv().await.expect("all senders down");
|
||||||
|
log::info!(target: "TC Handler", "received packet with length {}", packet_len);
|
||||||
// We still copy the data to a local buffer, so the exchange buffer can already be used
|
// We still copy the data to a local buffer, so the exchange buffer can already be used
|
||||||
// for the next packet / decode process.
|
// for the next packet / decode process.
|
||||||
cx.shared
|
cx.shared
|
||||||
@ -255,20 +302,21 @@ mod app {
|
|||||||
match PusTcReader::new(cx.local.read_buf) {
|
match PusTcReader::new(cx.local.read_buf) {
|
||||||
Ok((pus_tc, _)) => {
|
Ok((pus_tc, _)) => {
|
||||||
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
|
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
|
||||||
log::info!("Received ping TC");
|
log::info!(target: "TC Handler", "received ping TC");
|
||||||
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
|
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
|
||||||
// Raw memory write TC
|
// Raw memory write TC
|
||||||
if pus_tc.subservice() == 2 {
|
if pus_tc.subservice() == 2 {
|
||||||
let app_data = pus_tc.app_data();
|
let app_data = pus_tc.app_data();
|
||||||
if app_data.len() < 10 {
|
if app_data.len() < 10 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
target: "TC Handler",
|
||||||
"app data for raw memory write is too short: {}",
|
"app data for raw memory write is too short: {}",
|
||||||
app_data.len()
|
app_data.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let memory_id = app_data[0];
|
let memory_id = app_data[0];
|
||||||
if memory_id != BOOT_NVM_MEMORY_ID {
|
if memory_id != BOOT_NVM_MEMORY_ID {
|
||||||
log::warn!("memory ID {} not supported", memory_id);
|
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id);
|
||||||
// TODO: Error reporting
|
// TODO: Error reporting
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -276,6 +324,7 @@ mod app {
|
|||||||
let data_len = u32::from_be_bytes(app_data[6..10].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() {
|
if 10 + data_len as usize > app_data.len() {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
target: "TC Handler",
|
||||||
"invalid data length {} for raw mem write detected",
|
"invalid data length {} for raw mem write detected",
|
||||||
data_len
|
data_len
|
||||||
);
|
);
|
||||||
@ -300,7 +349,7 @@ mod app {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("PUS TC error: {}", e);
|
log::warn!("PUS TC error: {}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -314,7 +363,7 @@ mod app {
|
|||||||
)]
|
)]
|
||||||
async fn pus_tm_tx_handler(_cx: pus_tm_tx_handler::Context) {
|
async fn pus_tm_tx_handler(_cx: pus_tm_tx_handler::Context) {
|
||||||
loop {
|
loop {
|
||||||
Systick::delay(500.millis()).await;
|
Mono::delay(500.millis()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,11 @@ use embedded_hal::spi::MODE_0;
|
|||||||
|
|
||||||
use crate::clock::{Clocks, SyscfgExt};
|
use crate::clock::{Clocks, SyscfgExt};
|
||||||
use crate::pac;
|
use crate::pac;
|
||||||
use crate::spi::{mode_to_cpo_cph_bit, Instance, BMSTART_BMSTOP_MASK};
|
use crate::spi::{
|
||||||
|
mode_to_cpo_cph_bit, spi_clk_config_from_div, Instance, WordProvider, BMSTART_BMSTOP_MASK,
|
||||||
|
};
|
||||||
|
|
||||||
const NVM_SER_CLOCK_RATE_DIV: u8 = 4;
|
const NVM_CLOCK_DIV: u16 = 2;
|
||||||
|
|
||||||
// Commands. The internal FRAM is based on the Cypress FM25V20A device.
|
// Commands. The internal FRAM is based on the Cypress FM25V20A device.
|
||||||
|
|
||||||
@ -61,11 +63,12 @@ impl Nvm {
|
|||||||
// This is done in the C HAL.
|
// This is done in the C HAL.
|
||||||
syscfg.assert_periph_reset_for_two_cycles(pac::Spi3::PERIPH_SEL);
|
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);
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(MODE_0);
|
||||||
spi.ctrl0().write(|w| {
|
spi.ctrl0().write(|w| {
|
||||||
unsafe {
|
unsafe {
|
||||||
w.size().bits(8);
|
w.size().bits(u8::word_reg());
|
||||||
w.scrdv().bits(NVM_SER_CLOCK_RATE_DIV);
|
w.scrdv().bits(spi_clk_cfg.scrdv());
|
||||||
// Clear clock phase and polarity. Will be set to correct value for each
|
// Clear clock phase and polarity. Will be set to correct value for each
|
||||||
// transfer
|
// transfer
|
||||||
w.spo().bit(cpo_bit);
|
w.spo().bit(cpo_bit);
|
||||||
@ -73,14 +76,13 @@ impl Nvm {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
spi.ctrl1().write(|w| {
|
spi.ctrl1().write(|w| {
|
||||||
w.lbm().clear_bit();
|
|
||||||
w.sod().clear_bit();
|
|
||||||
w.ms().set_bit();
|
|
||||||
w.mdlycap().clear_bit();
|
|
||||||
w.blockmode().set_bit();
|
w.blockmode().set_bit();
|
||||||
unsafe { w.ss().bits(0) };
|
unsafe { w.ss().bits(0) };
|
||||||
|
w.bmstart().set_bit();
|
||||||
w.bmstall().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| {
|
spi.fifo_clr().write(|w| {
|
||||||
w.rxfifo().set_bit();
|
w.rxfifo().set_bit();
|
||||||
@ -101,6 +103,16 @@ impl Nvm {
|
|||||||
self.wait_for_tx_idle();
|
self.wait_for_tx_idle();
|
||||||
self.write_single(FRAM_WRSR);
|
self.write_single(FRAM_WRSR);
|
||||||
self.write_with_bmstop(0x00);
|
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) {
|
pub fn enable_write_prot(&mut self) {
|
||||||
@ -132,6 +144,18 @@ impl Nvm {
|
|||||||
while self.spi().status().read().tfe().bit_is_clear() {
|
while self.spi().status().read().tfe().bit_is_clear() {
|
||||||
cortex_m::asm::nop();
|
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)]
|
#[inline(always)]
|
||||||
@ -188,6 +212,8 @@ impl Nvm {
|
|||||||
self.wait_for_rx_available();
|
self.wait_for_rx_available();
|
||||||
let next_word = self.read_single_word() as u8;
|
let next_word = self.read_single_word() as u8;
|
||||||
if next_word != *byte {
|
if next_word != *byte {
|
||||||
|
self.write_with_bmstop(0);
|
||||||
|
self.wait_for_tx_idle();
|
||||||
return Err(VerifyError {
|
return Err(VerifyError {
|
||||||
addr: addr + idx as u32,
|
addr: addr + idx as u32,
|
||||||
found: next_word,
|
found: next_word,
|
||||||
@ -195,6 +221,8 @@ impl Nvm {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.write_with_bmstop(0);
|
||||||
|
self.wait_for_tx_idle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use core::{convert::Infallible, marker::PhantomData, ops::Deref};
|
|||||||
use embedded_hal::spi::Mode;
|
use embedded_hal::spi::Mode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clock::{PeripheralSelect, SyscfgExt},
|
clock::{Clocks, PeripheralSelect, SyscfgExt},
|
||||||
gpio::{
|
gpio::{
|
||||||
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
|
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
|
||||||
PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1,
|
PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1,
|
||||||
@ -27,6 +27,8 @@ use crate::{
|
|||||||
// FIFO has a depth of 16.
|
// FIFO has a depth of 16.
|
||||||
const FILL_DEPTH: usize = 12;
|
const FILL_DEPTH: usize = 12;
|
||||||
|
|
||||||
|
pub const DEFAULT_CLK_DIV: u16 = 2;
|
||||||
|
|
||||||
pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
|
pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
|
||||||
pub const BMSKIPDATA_MASK: u32 = 1 << 30;
|
pub const BMSKIPDATA_MASK: u32 = 1 << 30;
|
||||||
|
|
||||||
@ -211,7 +213,7 @@ pub trait TransferConfigProvider {
|
|||||||
fn sod(&mut self, sod: bool);
|
fn sod(&mut self, sod: bool);
|
||||||
fn blockmode(&mut self, blockmode: bool);
|
fn blockmode(&mut self, blockmode: bool);
|
||||||
fn mode(&mut self, mode: Mode);
|
fn mode(&mut self, mode: Mode);
|
||||||
fn frequency(&mut self, spi_clk: Hertz);
|
fn clk_div(&mut self, clk_div: u16);
|
||||||
fn hw_cs_id(&self) -> u8;
|
fn hw_cs_id(&self) -> u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,8 +221,8 @@ pub trait TransferConfigProvider {
|
|||||||
/// and might change for transfers to different SPI slaves
|
/// and might change for transfers to different SPI slaves
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TransferConfig<HwCs> {
|
pub struct TransferConfig<HwCs> {
|
||||||
pub spi_clk: Hertz,
|
pub clk_div: Option<u16>,
|
||||||
pub mode: Mode,
|
pub mode: Option<Mode>,
|
||||||
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
|
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
|
||||||
/// false
|
/// false
|
||||||
pub hw_cs: Option<HwCs>,
|
pub hw_cs: Option<HwCs>,
|
||||||
@ -234,8 +236,8 @@ pub struct TransferConfig<HwCs> {
|
|||||||
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
||||||
/// the SPI constructor.
|
/// the SPI constructor.
|
||||||
pub struct ErasedTransferConfig {
|
pub struct ErasedTransferConfig {
|
||||||
pub spi_clk: Hertz,
|
pub clk_div: Option<u16>,
|
||||||
pub mode: Mode,
|
pub mode: Option<Mode>,
|
||||||
pub sod: bool,
|
pub sod: bool,
|
||||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||||
@ -245,9 +247,14 @@ pub struct ErasedTransferConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TransferConfig<NoneT> {
|
impl TransferConfig<NoneT> {
|
||||||
pub fn new_no_hw_cs(spi_clk: impl Into<Hertz>, mode: Mode, blockmode: bool, sod: bool) -> Self {
|
pub fn new_no_hw_cs(
|
||||||
|
clk_div: Option<u16>,
|
||||||
|
mode: Option<Mode>,
|
||||||
|
blockmode: bool,
|
||||||
|
sod: bool,
|
||||||
|
) -> Self {
|
||||||
TransferConfig {
|
TransferConfig {
|
||||||
spi_clk: spi_clk.into(),
|
clk_div,
|
||||||
mode,
|
mode,
|
||||||
hw_cs: None,
|
hw_cs: None,
|
||||||
sod,
|
sod,
|
||||||
@ -258,14 +265,14 @@ impl TransferConfig<NoneT> {
|
|||||||
|
|
||||||
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
spi_clk: impl Into<Hertz>,
|
clk_div: Option<u16>,
|
||||||
mode: Mode,
|
mode: Option<Mode>,
|
||||||
hw_cs: Option<HwCs>,
|
hw_cs: Option<HwCs>,
|
||||||
blockmode: bool,
|
blockmode: bool,
|
||||||
sod: bool,
|
sod: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
TransferConfig {
|
TransferConfig {
|
||||||
spi_clk: spi_clk.into(),
|
clk_div,
|
||||||
mode,
|
mode,
|
||||||
hw_cs,
|
hw_cs,
|
||||||
sod,
|
sod,
|
||||||
@ -275,7 +282,7 @@ impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
|||||||
|
|
||||||
pub fn downgrade(self) -> ErasedTransferConfig {
|
pub fn downgrade(self) -> ErasedTransferConfig {
|
||||||
ErasedTransferConfig {
|
ErasedTransferConfig {
|
||||||
spi_clk: self.spi_clk,
|
clk_div: self.clk_div,
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
sod: self.sod,
|
sod: self.sod,
|
||||||
blockmode: self.blockmode,
|
blockmode: self.blockmode,
|
||||||
@ -295,11 +302,11 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mode(&mut self, mode: Mode) {
|
fn mode(&mut self, mode: Mode) {
|
||||||
self.mode = mode;
|
self.mode = Some(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frequency(&mut self, spi_clk: Hertz) {
|
fn clk_div(&mut self, clk_div: u16) {
|
||||||
self.spi_clk = spi_clk;
|
self.clk_div = Some(clk_div);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hw_cs_id(&self) -> u8 {
|
fn hw_cs_id(&self) -> u8 {
|
||||||
@ -307,13 +314,9 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
||||||
pub struct SpiConfig {
|
pub struct SpiConfig {
|
||||||
/// Serial clock rate divider. Together with the CLKPRESCALE register, it determines
|
clk_div: u16,
|
||||||
/// the SPI clock rate in master mode. 0 by default. Specifying a higher value
|
|
||||||
/// limits the maximum attainable SPI speed
|
|
||||||
pub ser_clock_rate_div: u8,
|
|
||||||
/// By default, configure SPI for master mode (ms == false)
|
/// By default, configure SPI for master mode (ms == false)
|
||||||
ms: bool,
|
ms: bool,
|
||||||
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
||||||
@ -324,14 +327,26 @@ 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 ser_clock_rate_div(mut self, div: u8) -> Self {
|
pub fn clk_div(mut self, clk_div: u16) -> Self {
|
||||||
self.ser_clock_rate_div = div;
|
self.clk_div = clk_div;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,10 +464,105 @@ 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 =
|
||||||
@ -461,6 +571,7 @@ where
|
|||||||
.clkprescale()
|
.clkprescale()
|
||||||
.write(|w| unsafe { w.bits(clk_prescale) });
|
.write(|w| unsafe { w.bits(clk_prescale) });
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cfg_mode(&mut self, mode: Mode) {
|
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||||
@ -521,9 +632,13 @@ where
|
|||||||
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
|
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
transfer_cfg: &TransferConfig<HwCs>,
|
transfer_cfg: &TransferConfig<HwCs>,
|
||||||
) {
|
) -> Result<(), SpiClkConfigError> {
|
||||||
self.cfg_clock(transfer_cfg.spi_clk);
|
if let Some(trans_clk_div) = transfer_cfg.clk_div {
|
||||||
self.cfg_mode(transfer_cfg.mode);
|
self.cfg_clock_from_div(trans_clk_div)?;
|
||||||
|
}
|
||||||
|
if let Some(mode) = transfer_cfg.mode {
|
||||||
|
self.cfg_mode(mode);
|
||||||
|
}
|
||||||
self.blockmode = transfer_cfg.blockmode;
|
self.blockmode = transfer_cfg.blockmode;
|
||||||
self.spi.ctrl1().modify(|_, w| {
|
self.spi.ctrl1().modify(|_, w| {
|
||||||
if transfer_cfg.sod {
|
if transfer_cfg.sod {
|
||||||
@ -543,6 +658,7 @@ where
|
|||||||
}
|
}
|
||||||
w
|
w
|
||||||
});
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a word to the slave
|
/// Sends a word to the slave
|
||||||
@ -637,42 +753,43 @@ where
|
|||||||
/// * `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,
|
syscfg: &mut pac::Sysconfig,
|
||||||
spi: SpiI,
|
|
||||||
clocks: &crate::clock::Clocks,
|
clocks: &crate::clock::Clocks,
|
||||||
|
spi: SpiI,
|
||||||
pins: (Sck, Miso, Mosi),
|
pins: (Sck, Miso, Mosi),
|
||||||
spi_cfg: SpiConfig,
|
spi_cfg: SpiConfig,
|
||||||
transfer_cfg: Option<&ErasedTransferConfig>,
|
transfer_cfg: Option<&ErasedTransferConfig>,
|
||||||
) -> Self {
|
) -> Result<Self, SpiClkConfigError> {
|
||||||
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
|
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
|
||||||
// This is done in the C HAL.
|
// 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 {
|
||||||
ser_clock_rate_div,
|
clk_div,
|
||||||
ms,
|
ms,
|
||||||
slave_output_disable,
|
slave_output_disable,
|
||||||
loopback_mode,
|
loopback_mode,
|
||||||
master_delayer_capture,
|
master_delayer_capture,
|
||||||
} = spi_cfg;
|
} = spi_cfg;
|
||||||
let mut mode = embedded_hal::spi::MODE_0;
|
let mut init_mode = embedded_hal::spi::MODE_0;
|
||||||
let mut clk_prescale = 0x02;
|
|
||||||
let mut ss = 0;
|
let mut ss = 0;
|
||||||
let mut init_blockmode = false;
|
let mut init_blockmode = false;
|
||||||
let apb1_clk = clocks.apb1();
|
let apb1_clk = clocks.apb1();
|
||||||
if let Some(transfer_cfg) = transfer_cfg {
|
if let Some(transfer_cfg) = transfer_cfg {
|
||||||
mode = transfer_cfg.mode;
|
if let Some(mode) = transfer_cfg.mode {
|
||||||
clk_prescale =
|
init_mode = mode;
|
||||||
apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1));
|
}
|
||||||
|
//self.cfg_clock_from_div(transfer_cfg.clk_div);
|
||||||
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
|
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
|
||||||
ss = transfer_cfg.hw_cs as u8;
|
ss = transfer_cfg.hw_cs as u8;
|
||||||
}
|
}
|
||||||
init_blockmode = transfer_cfg.blockmode;
|
init_blockmode = transfer_cfg.blockmode;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
|
let spi_clk_cfg = spi_clk_config_from_div(clk_div)?;
|
||||||
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
|
||||||
spi.ctrl0().write(|w| {
|
spi.ctrl0().write(|w| {
|
||||||
unsafe {
|
unsafe {
|
||||||
w.size().bits(Word::word_reg());
|
w.size().bits(Word::word_reg());
|
||||||
w.scrdv().bits(ser_clock_rate_div);
|
w.scrdv().bits(spi_clk_cfg.scrdv);
|
||||||
// Clear clock phase and polarity. Will be set to correct value for each
|
// Clear clock phase and polarity. Will be set to correct value for each
|
||||||
// transfer
|
// transfer
|
||||||
w.spo().bit(cpo_bit);
|
w.spo().bit(cpo_bit);
|
||||||
@ -687,16 +804,17 @@ where
|
|||||||
w.blockmode().bit(init_blockmode);
|
w.blockmode().bit(init_blockmode);
|
||||||
unsafe { w.ss().bits(ss) }
|
unsafe { w.ss().bits(ss) }
|
||||||
});
|
});
|
||||||
|
spi.clkprescale()
|
||||||
|
.write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val as u32) });
|
||||||
|
|
||||||
spi.fifo_clr().write(|w| {
|
spi.fifo_clr().write(|w| {
|
||||||
w.rxfifo().set_bit();
|
w.rxfifo().set_bit();
|
||||||
w.txfifo().set_bit()
|
w.txfifo().set_bit()
|
||||||
});
|
});
|
||||||
spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) });
|
|
||||||
// Enable the peripheral as the last step as recommended in the
|
// Enable the peripheral as the last step as recommended in the
|
||||||
// programmers guide
|
// programmers guide
|
||||||
spi.ctrl1().modify(|_, w| w.enable().set_bit());
|
spi.ctrl1().modify(|_, w| w.enable().set_bit());
|
||||||
Spi {
|
Ok(Spi {
|
||||||
inner: SpiBase {
|
inner: SpiBase {
|
||||||
spi,
|
spi,
|
||||||
cfg: spi_cfg,
|
cfg: spi_cfg,
|
||||||
@ -706,13 +824,13 @@ where
|
|||||||
word: PhantomData,
|
word: PhantomData,
|
||||||
},
|
},
|
||||||
pins,
|
pins,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate::delegate! {
|
delegate::delegate! {
|
||||||
to self.inner {
|
to self.inner {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>);
|
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn spi_instance(&self) -> &SpiI;
|
pub fn spi_instance(&self) -> &SpiI;
|
||||||
@ -723,8 +841,9 @@ where
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn perid(&self) -> u32;
|
pub fn perid(&self) -> u32;
|
||||||
|
|
||||||
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(&mut self, transfer_cfg: &TransferConfig<HwCs>);
|
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
|
||||||
|
&mut self, transfer_cfg: &TransferConfig<HwCs>
|
||||||
|
) -> Result<(), SpiClkConfigError>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
||||||
use core::marker::PhantomData;
|
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
|
|
||||||
use embedded_hal_nb::serial::Read;
|
use embedded_hal_nb::serial::Read;
|
||||||
@ -312,27 +311,23 @@ pub struct UartWithIrqBase<UART> {
|
|||||||
|
|
||||||
/// Serial receiver
|
/// Serial receiver
|
||||||
pub struct Rx<Uart> {
|
pub struct Rx<Uart> {
|
||||||
_usart: PhantomData<Uart>,
|
uart: Uart,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serial transmitter
|
/// Serial transmitter
|
||||||
pub struct Tx<Uart> {
|
pub struct Tx<Uart> {
|
||||||
_usart: PhantomData<Uart>,
|
uart: Uart,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Uart> Rx<Uart> {
|
impl<Uart: Instance> Rx<Uart> {
|
||||||
fn new() -> Self {
|
fn new(uart: Uart) -> Self {
|
||||||
Self {
|
Self { uart }
|
||||||
_usart: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Uart> Tx<Uart> {
|
impl<Uart> Tx<Uart> {
|
||||||
fn new() -> Self {
|
fn new(uart: Uart) -> Self {
|
||||||
Self {
|
Self { uart }
|
||||||
_usart: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,6 +337,12 @@ pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
|
|||||||
const IRQ_RX: pac::Interrupt;
|
const IRQ_RX: pac::Interrupt;
|
||||||
const IRQ_TX: pac::Interrupt;
|
const IRQ_TX: pac::Interrupt;
|
||||||
|
|
||||||
|
/// Retrieve the peripheral structure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This circumvents the safety guarantees of the HAL.
|
||||||
|
unsafe fn steal() -> Self;
|
||||||
fn ptr() -> *const uart_base::RegisterBlock;
|
fn ptr() -> *const uart_base::RegisterBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,6 +352,9 @@ impl Instance for Uart0 {
|
|||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
|
||||||
|
|
||||||
|
unsafe fn steal() -> Self {
|
||||||
|
pac::Peripherals::steal().uart0
|
||||||
|
}
|
||||||
fn ptr() -> *const uart_base::RegisterBlock {
|
fn ptr() -> *const uart_base::RegisterBlock {
|
||||||
Uart0::ptr() as *const _
|
Uart0::ptr() as *const _
|
||||||
}
|
}
|
||||||
@ -362,6 +366,9 @@ impl Instance for Uart1 {
|
|||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
|
||||||
|
|
||||||
|
unsafe fn steal() -> Self {
|
||||||
|
pac::Peripherals::steal().uart1
|
||||||
|
}
|
||||||
fn ptr() -> *const uart_base::RegisterBlock {
|
fn ptr() -> *const uart_base::RegisterBlock {
|
||||||
Uart1::ptr() as *const _
|
Uart1::ptr() as *const _
|
||||||
}
|
}
|
||||||
@ -373,6 +380,9 @@ impl Instance for Uart2 {
|
|||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
|
||||||
|
|
||||||
|
unsafe fn steal() -> Self {
|
||||||
|
pac::Peripherals::steal().uart2
|
||||||
|
}
|
||||||
fn ptr() -> *const uart_base::RegisterBlock {
|
fn ptr() -> *const uart_base::RegisterBlock {
|
||||||
Uart2::ptr() as *const _
|
Uart2::ptr() as *const _
|
||||||
}
|
}
|
||||||
@ -525,8 +535,8 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
|
|||||||
Uart {
|
Uart {
|
||||||
inner: UartBase {
|
inner: UartBase {
|
||||||
uart,
|
uart,
|
||||||
tx: Tx::new(),
|
tx: Tx::new(unsafe { UartInstance::steal() }),
|
||||||
rx: Rx::new(),
|
rx: Rx::new(unsafe { UartInstance::steal() }),
|
||||||
},
|
},
|
||||||
pins,
|
pins,
|
||||||
}
|
}
|
||||||
@ -544,8 +554,8 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
|
|||||||
Uart {
|
Uart {
|
||||||
inner: UartBase {
|
inner: UartBase {
|
||||||
uart,
|
uart,
|
||||||
tx: Tx::new(),
|
tx: Tx::new(unsafe { UartInstance::steal() }),
|
||||||
rx: Rx::new(),
|
rx: Rx::new(unsafe { UartInstance::steal() }),
|
||||||
},
|
},
|
||||||
pins,
|
pins,
|
||||||
}
|
}
|
||||||
@ -630,6 +640,36 @@ impl<UartInstance: Instance, Pins> Uart<UartInstance, Pins> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance> Rx<Uart> {
|
||||||
|
/// Direct access to the peripheral structure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must ensure that only registers related to the operation of the RX side are used.
|
||||||
|
pub unsafe fn uart(&self) -> &Uart {
|
||||||
|
&self.uart
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_fifo(&self) {
|
||||||
|
self.uart.fifo_clr().write(|w| w.rxfifo().set_bit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance> Tx<Uart> {
|
||||||
|
/// Direct access to the peripheral structure.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must ensure that only registers related to the operation of the TX side are used.
|
||||||
|
pub unsafe fn uart(&self) -> &Uart {
|
||||||
|
&self.uart
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_fifo(&self) {
|
||||||
|
self.uart.fifo_clr().write(|w| w.txfifo().set_bit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct IrqUartError {
|
pub struct IrqUartError {
|
||||||
overflow: bool,
|
overflow: bool,
|
||||||
|
Loading…
Reference in New Issue
Block a user