Flashloader and Bootloader #16
@ -9,10 +9,12 @@ members = [
|
|||||||
"examples/embassy",
|
"examples/embassy",
|
||||||
"board-tests",
|
"board-tests",
|
||||||
"bootloader",
|
"bootloader",
|
||||||
|
"flashloader",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
"defmt-testapp",
|
"defmt-testapp",
|
||||||
|
"flashloader/slot-a-blinky",
|
||||||
|
"flashloader/slot-b-blinky",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
@ -20,8 +22,8 @@ codegen-units = 1
|
|||||||
debug = 2
|
debug = 2
|
||||||
debug-assertions = true # <-
|
debug-assertions = true # <-
|
||||||
incremental = false
|
incremental = false
|
||||||
# This is problematic for stepping..
|
# 1 instead of 0, the flashloader is too larger otherwise..
|
||||||
# opt-level = 'z' # <-
|
# opt-level = 1 # <-
|
||||||
overflow-checks = true # <-
|
overflow-checks = true # <-
|
||||||
|
|
||||||
# cargo build/run --release
|
# cargo build/run --release
|
||||||
|
@ -19,6 +19,11 @@ This workspace contains the following released crates:
|
|||||||
|
|
||||||
It also contains the following helper crates:
|
It also contains the following helper crates:
|
||||||
|
|
||||||
|
- The [`bootloader`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/bootloader)
|
||||||
|
crate contains a sample bootloader strongly based on the one provided by Vorago.
|
||||||
|
- The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/flashloader)
|
||||||
|
crate contains a sample flashloader which is able to update the redundant images in the NVM which
|
||||||
|
is compatible to the provided bootloader as well.
|
||||||
- The [`board-tests`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/board-tests)
|
- The [`board-tests`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/board-tests)
|
||||||
contains an application which can be used to test the libraries on the board.
|
contains an application which can be used to test the libraries on the board.
|
||||||
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples)
|
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples)
|
||||||
|
@ -17,7 +17,7 @@ use va108xx_hal::{
|
|||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
time::Hertz,
|
time::Hertz,
|
||||||
timer::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer, IrqCfg},
|
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, IrqCfg},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -168,7 +168,7 @@ fn main() -> ! {
|
|||||||
ms_timer.delay_ms(500);
|
ms_timer.delay_ms(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut delay_timer = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
|
let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
|
||||||
let mut pa0 = pinsa.pa0.into_readable_push_pull_output();
|
let mut pa0 = pinsa.pa0.into_readable_push_pull_output();
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
led1.toggle().ok();
|
led1.toggle().ok();
|
||||||
|
@ -7,12 +7,11 @@ edition = "2021"
|
|||||||
cortex-m = "0.7"
|
cortex-m = "0.7"
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-hal-bus = "0.2"
|
|
||||||
dummy-pin = "1"
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
panic-rtt-target = { version = "0.1.3" }
|
||||||
panic-halt = { version = "0.2" }
|
panic-halt = { version = "0.2" }
|
||||||
rtt-target = { version = "0.5" }
|
rtt-target = { version = "0.5" }
|
||||||
crc = "3"
|
crc = "3"
|
||||||
|
static_assertions = "1"
|
||||||
|
|
||||||
[dependencies.va108xx-hal]
|
[dependencies.va108xx-hal]
|
||||||
path = "../va108xx-hal"
|
path = "../va108xx-hal"
|
||||||
|
48
bootloader/README.md
Normal file
48
bootloader/README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
VA108xx Bootloader Application
|
||||||
|
=======
|
||||||
|
|
||||||
|
This is the Rust version of the bootloader supplied by Vorago.
|
||||||
|
|
||||||
|
## Memory Map
|
||||||
|
|
||||||
|
The bootloader uses the following memory map:
|
||||||
|
|
||||||
|
| Address | Notes | Size |
|
||||||
|
| ------ | ---- | ---- |
|
||||||
|
| 0x0 | Bootloader start | code up to 0x3FFC bytes |
|
||||||
|
| 0x2FFE | Bootloader CRC | word |
|
||||||
|
| 0x3000 | App image A start | code up to 0xE7F8 (~58K) bytes |
|
||||||
|
| 0x117F8 | App image A CRC check length | word |
|
||||||
|
| 0x117FC | App image A CRC check value | word |
|
||||||
|
| 0x11800 | App image B start | code up to 0xE7F8 (~58K) bytes |
|
||||||
|
| 0x1FFF8 | App image B CRC check length | word |
|
||||||
|
| 0x1FFFC | App image B CRC check value | word |
|
||||||
|
| 0x20000 | End of NVM | end |
|
||||||
|
|
||||||
|
## Additional Information
|
||||||
|
|
||||||
|
This bootloader was specifically written for the REB1 board, so it assumes a M95M01 ST EEPROM
|
||||||
|
is used to load the application code. The bootloader will also delay for a configurable amount
|
||||||
|
of time before booting. This allows to catch the RTT printout, but should probably be disabled
|
||||||
|
for production firmware.
|
||||||
|
|
||||||
|
This bootloader does not provide tools to flash the NVM memory by itself. Instead, you can use
|
||||||
|
the [flashloader](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/flashloader)
|
||||||
|
application to perform this task using a CCSDS interface via a UART.
|
||||||
|
|
||||||
|
The bootloader performs the following steps:
|
||||||
|
|
||||||
|
1. The application will calculate the checksum of itself if the bootloader CRC is blank (all zeroes
|
||||||
|
or all ones). If the CRC is not blank and the checksum check fails, it will immediately boot
|
||||||
|
application image A. Otherwise, it proceeds to the next step.
|
||||||
|
2. Check the checksum of App A. If that checksum is valid, it will boot App A. If not, it will
|
||||||
|
proceed to the next step.
|
||||||
|
3. Check the checksum of App B. If that checksum is valid, it will boot App B. If not, it will
|
||||||
|
boot App A as the fallback image.
|
||||||
|
|
||||||
|
You could adapt and combine this bootloader with a non-volatile memory to select a prefered app
|
||||||
|
image, which would be a first step towards an updatable flight software.
|
||||||
|
|
||||||
|
Please note that you *MUST* compile the application at slot A and slot B with an appropriate
|
||||||
|
`memory.x` file where the base address of the `FLASH` was adapted according to the base address
|
||||||
|
shown in the memory map above. The memory files to do this were provided in the `scripts` folder.
|
@ -4,7 +4,7 @@ use core::convert::Infallible;
|
|||||||
|
|
||||||
/// Simple trait which makes swapping the NVM easier. NVMs only need to implement this interface.
|
/// Simple trait which makes swapping the NVM easier. NVMs only need to implement this interface.
|
||||||
pub trait NvmInterface {
|
pub trait NvmInterface {
|
||||||
fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Infallible>;
|
fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Infallible>;
|
||||||
fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), Infallible>;
|
fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), Infallible>;
|
||||||
fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, Infallible>;
|
fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, Infallible>;
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,21 @@
|
|||||||
use bootloader::NvmInterface;
|
use bootloader::NvmInterface;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use crc::{Crc, CRC_16_IBM_3740};
|
use crc::{Crc, CRC_16_IBM_3740};
|
||||||
|
use embedded_hal::delay::DelayNs;
|
||||||
#[cfg(not(feature = "rtt-panic"))]
|
#[cfg(not(feature = "rtt-panic"))]
|
||||||
use panic_halt as _;
|
use panic_halt as _;
|
||||||
#[cfg(feature = "rtt-panic")]
|
#[cfg(feature = "rtt-panic")]
|
||||||
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::{pac, time::Hertz};
|
use va108xx_hal::{pac, time::Hertz, timer::CountdownTimer};
|
||||||
use vorago_reb1::m95m01::M95M01;
|
use vorago_reb1::m95m01::M95M01;
|
||||||
|
|
||||||
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
|
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
|
||||||
// the binary stays small enough.
|
// the binary stays small enough.
|
||||||
const RTT_PRINTOUT: bool = true;
|
const RTT_PRINTOUT: bool = true;
|
||||||
const DEBUG_PRINTOUTS: bool = false;
|
const DEBUG_PRINTOUTS: bool = true;
|
||||||
|
// Small delay, allows RTT printout to catch up.
|
||||||
|
const BOOT_DELAY_MS: u32 = 2000;
|
||||||
|
|
||||||
// Dangerous option! An image with this option set to true will flash itself from RAM directly
|
// 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
|
// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM
|
||||||
@ -35,23 +38,32 @@ const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
|
|||||||
|
|
||||||
// Important bootloader addresses and offsets, vector table information.
|
// Important bootloader addresses and offsets, vector table information.
|
||||||
|
|
||||||
|
const NVM_SIZE: u32 = 0x20000;
|
||||||
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
||||||
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
|
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
|
||||||
// This is also the maximum size of the bootloader.
|
// This is also the maximum size of the bootloader.
|
||||||
const BOOTLOADER_END_ADDR: u32 = 0x3000;
|
const BOOTLOADER_END_ADDR: u32 = 0x3000;
|
||||||
const APP_A_START_ADDR: u32 = 0x3000;
|
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR;
|
||||||
|
// 0x117F8
|
||||||
const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
|
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.
|
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
|
||||||
|
// 0x117FC
|
||||||
const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
|
const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
|
||||||
pub const APP_A_END_ADDR: u32 = 0x11000;
|
// 0x11800
|
||||||
|
pub const APP_A_END_ADDR: u32 = APP_A_START_ADDR + APP_IMG_SZ;
|
||||||
// The actual size of the image which is relevant for CRC calculation.
|
// The actual size of the image which is relevant for CRC calculation.
|
||||||
const APP_B_START_ADDR: u32 = 0x11000;
|
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
|
||||||
// The actual size of the image which is relevant for CRC calculation.
|
// The actual size of the image which is relevant for CRC calculation.
|
||||||
|
// 0x1FFF8
|
||||||
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
|
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.
|
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
|
||||||
|
// 0x1FFFC
|
||||||
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
|
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
|
||||||
pub const APP_B_END_ADDR: u32 = 0x20000;
|
// 0x20000
|
||||||
pub const APP_IMG_SZ: u32 = 0xE800;
|
pub const APP_B_END_ADDR: u32 = NVM_SIZE;
|
||||||
|
pub const APP_IMG_SZ: u32 = (APP_B_END_ADDR - APP_A_START_ADDR) / 2;
|
||||||
|
|
||||||
|
static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
|
||||||
|
|
||||||
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
|
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
|
||||||
pub const VECTOR_TABLE_LEN: u32 = 0xC0;
|
pub const VECTOR_TABLE_LEN: u32 = 0xC0;
|
||||||
@ -69,15 +81,15 @@ pub struct NvmWrapper(pub M95M01);
|
|||||||
|
|
||||||
// Newtype pattern. We could now more easily swap the used NVM type.
|
// Newtype pattern. We could now more easily swap the used NVM type.
|
||||||
impl NvmInterface for NvmWrapper {
|
impl NvmInterface for NvmWrapper {
|
||||||
fn write(&mut self, address: u32, data: &[u8]) -> Result<(), core::convert::Infallible> {
|
fn write(&mut self, address: usize, data: &[u8]) -> Result<(), core::convert::Infallible> {
|
||||||
self.0.write(address, data)
|
self.0.write(address, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
|
fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
|
||||||
self.0.read(address, buf)
|
self.0.read(address, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, core::convert::Infallible> {
|
fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, core::convert::Infallible> {
|
||||||
self.0.verify(address, data)
|
self.0.verify(address, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,6 +102,7 @@ fn main() -> ! {
|
|||||||
}
|
}
|
||||||
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();
|
||||||
|
let mut timer = CountdownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
|
||||||
|
|
||||||
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
|
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
|
||||||
|
|
||||||
@ -124,9 +137,9 @@ fn main() -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nvm.write(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes())
|
nvm.write(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes())
|
||||||
.expect("writing CRC failed");
|
.expect("writing CRC failed");
|
||||||
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
|
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes()) {
|
||||||
if RTT_PRINTOUT {
|
if RTT_PRINTOUT {
|
||||||
rprintln!(
|
rprintln!(
|
||||||
"error: CRC verification for bootloader self-flash failed: {:?}",
|
"error: CRC verification for bootloader self-flash failed: {:?}",
|
||||||
@ -139,23 +152,28 @@ fn main() -> ! {
|
|||||||
let mut nvm = NvmWrapper(nvm);
|
let mut nvm = NvmWrapper(nvm);
|
||||||
|
|
||||||
// Check bootloader's CRC (and write it if blank)
|
// Check bootloader's CRC (and write it if blank)
|
||||||
check_own_crc(&dp.sysconfig, &cp, &mut nvm);
|
check_own_crc(&dp.sysconfig, &cp, &mut nvm, &mut timer);
|
||||||
|
|
||||||
if check_app_crc(AppSel::A) {
|
if check_app_crc(AppSel::A) {
|
||||||
boot_app(&dp.sysconfig, &cp, AppSel::A)
|
boot_app(&dp.sysconfig, &cp, AppSel::A, &mut timer)
|
||||||
} else if check_app_crc(AppSel::B) {
|
} else if check_app_crc(AppSel::B) {
|
||||||
boot_app(&dp.sysconfig, &cp, AppSel::B)
|
boot_app(&dp.sysconfig, &cp, AppSel::B, &mut timer)
|
||||||
} else {
|
} else {
|
||||||
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||||
rprintln!("both images corrupt! booting image A");
|
rprintln!("both images corrupt! booting image A");
|
||||||
}
|
}
|
||||||
// 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(&dp.sysconfig, &cp, AppSel::A)
|
boot_app(&dp.sysconfig, &cp, AppSel::A, &mut timer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut NvmWrapper) {
|
fn check_own_crc(
|
||||||
|
sysconfig: &pac::Sysconfig,
|
||||||
|
cp: &cortex_m::Peripherals,
|
||||||
|
nvm: &mut NvmWrapper,
|
||||||
|
timer: &mut CountdownTimer<pac::Tim0>,
|
||||||
|
) {
|
||||||
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() };
|
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
|
// 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
|
// because the address of the bootloader is 0x0, so the NULL check fails and the functions
|
||||||
@ -176,7 +194,7 @@ fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &m
|
|||||||
rprintln!("BL CRC blank - prog new CRC");
|
rprintln!("BL CRC blank - prog new CRC");
|
||||||
}
|
}
|
||||||
// Blank CRC, write it to NVM.
|
// Blank CRC, write it to NVM.
|
||||||
nvm.write(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes())
|
nvm.write(BOOTLOADER_CRC_ADDR as usize, &crc_calc.to_be_bytes())
|
||||||
.expect("writing CRC failed");
|
.expect("writing CRC failed");
|
||||||
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
|
// 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.
|
// necessary because somehow the boot will not work if we just continue as usual.
|
||||||
@ -191,7 +209,7 @@ fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &m
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
|
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
|
||||||
boot_app(sysconfig, cp, AppSel::A);
|
boot_app(sysconfig, cp, AppSel::A, timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,43 +258,52 @@ fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) ->
|
|||||||
|
|
||||||
// The boot works by copying the interrupt vector table (IVT) of the respective app to the
|
// 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.
|
// 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) -> ! {
|
fn boot_app(
|
||||||
|
syscfg: &pac::Sysconfig,
|
||||||
|
cp: &cortex_m::Peripherals,
|
||||||
|
app_sel: AppSel,
|
||||||
|
timer: &mut CountdownTimer<pac::Tim0>,
|
||||||
|
) -> ! {
|
||||||
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||||
rprintln!("booting app {:?}", app_sel);
|
rprintln!("booting app {:?}", app_sel);
|
||||||
}
|
}
|
||||||
|
timer.delay_ms(BOOT_DELAY_MS);
|
||||||
|
|
||||||
|
// Clear all interrupts set.
|
||||||
|
unsafe {
|
||||||
|
cp.NVIC.icer[0].write(0xFFFFFFFF);
|
||||||
|
cp.NVIC.icpr[0].write(0xFFFFFFFF);
|
||||||
|
}
|
||||||
// Disable ROM protection.
|
// Disable ROM protection.
|
||||||
syscfg.rom_prot().write(|w| unsafe { w.bits(1) });
|
syscfg.rom_prot().write(|w| w.wren().set_bit());
|
||||||
let base_addr = if app_sel == AppSel::A {
|
let base_addr = if app_sel == AppSel::A {
|
||||||
APP_A_START_ADDR
|
APP_A_START_ADDR
|
||||||
} else {
|
} else {
|
||||||
APP_B_START_ADDR
|
APP_B_START_ADDR
|
||||||
};
|
};
|
||||||
// Clear all interrupts set.
|
|
||||||
unsafe {
|
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
|
// 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.
|
// 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);
|
let first_four_bytes = core::ptr::read(base_addr as *const u32);
|
||||||
core::arch::asm!(
|
core::arch::asm!(
|
||||||
"str {0}, [{1}]", // Load 4 bytes from src into r0 register
|
"str {0}, [{1}]",
|
||||||
in(reg) base_addr, // Input: App vector table.
|
in(reg) first_four_bytes, // Input: App vector table.
|
||||||
in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer
|
in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer
|
||||||
);
|
);
|
||||||
core::slice::from_raw_parts_mut(
|
core::slice::from_raw_parts_mut(
|
||||||
(BOOTLOADER_START_ADDR + 4) as *mut u32,
|
(BOOTLOADER_START_ADDR + 4) as *mut u8,
|
||||||
(VECTOR_TABLE_LEN - 4) as usize,
|
(VECTOR_TABLE_LEN - 4) as usize,
|
||||||
)
|
)
|
||||||
.copy_from_slice(core::slice::from_raw_parts(
|
.copy_from_slice(core::slice::from_raw_parts(
|
||||||
(base_addr + 4) as *const u32,
|
(base_addr + 4) as *const u8,
|
||||||
(VECTOR_TABLE_LEN - 4) as usize,
|
(VECTOR_TABLE_LEN - 4) as usize,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/* Disable re-loading from FRAM/code ROM on soft reset */
|
// Disable re-loading from FRAM/code ROM on soft reset
|
||||||
syscfg
|
syscfg
|
||||||
.rst_cntl_rom()
|
.rst_cntl_rom()
|
||||||
.modify(|_, w| w.sysrstreq().clear_bit());
|
.modify(|_, w| w.sysrstreq().clear_bit());
|
||||||
|
|
||||||
soft_reset(cp);
|
soft_reset(cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,5 +319,8 @@ fn soft_reset(cp: &cortex_m::Peripherals) -> ! {
|
|||||||
// Ensure completion of memory access.
|
// Ensure completion of memory access.
|
||||||
cortex_m::asm::dsb();
|
cortex_m::asm::dsb();
|
||||||
|
|
||||||
unreachable!();
|
// Loop until the reset occurs.
|
||||||
|
loop {
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,11 @@ cortex-m-rt = "0.7"
|
|||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.6"
|
||||||
rtt-target = { version = "0.5" }
|
rtt-target = { version = "0.5" }
|
||||||
|
panic-rtt-target = { version = "0.1" }
|
||||||
|
|
||||||
# Even though we do not use this directly, we need to activate this feature explicitely
|
# Even though we do not use this directly, we need to activate this feature explicitely
|
||||||
# so that RTIC compiles because thumv6 does not have CAS operations natively.
|
# so that RTIC compiles because thumv6 does not have CAS operations natively.
|
||||||
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]}
|
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]}
|
||||||
panic-rtt-target = { version = "0.1" }
|
|
||||||
|
|
||||||
[dependencies.va108xx-hal]
|
|
||||||
path = "../../va108xx-hal"
|
|
||||||
|
|
||||||
[dependencies.vorago-reb1]
|
|
||||||
path = "../../vorago-reb1"
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
[dependencies.rtic]
|
||||||
version = "2"
|
version = "2"
|
||||||
@ -31,3 +26,19 @@ features = ["cortex-m-systick"]
|
|||||||
[dependencies.rtic-sync]
|
[dependencies.rtic-sync]
|
||||||
version = "1.3"
|
version = "1.3"
|
||||||
features = ["defmt-03"]
|
features = ["defmt-03"]
|
||||||
|
|
||||||
|
[dependencies.once_cell]
|
||||||
|
version = "1"
|
||||||
|
default-features = false
|
||||||
|
features = ["critical-section"]
|
||||||
|
|
||||||
|
[dependencies.ringbuf]
|
||||||
|
version = "0.4.7"
|
||||||
|
default-features = false
|
||||||
|
features = ["portable-atomic"]
|
||||||
|
|
||||||
|
[dependencies.va108xx-hal]
|
||||||
|
path = "../../va108xx-hal"
|
||||||
|
|
||||||
|
[dependencies.vorago-reb1]
|
||||||
|
path = "../../vorago-reb1"
|
||||||
|
143
examples/rtic/src/bin/uart-echo-rtic.rs
Normal file
143
examples/rtic/src/bin/uart-echo-rtic.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
//! More complex UART application
|
||||||
|
//!
|
||||||
|
//! Uses the IRQ capabilities of the VA10820 peripheral and the RTIC framework to poll the UART in
|
||||||
|
//! a non-blocking way. All received data will be sent back to the sender.
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use ringbuf::StaticRb;
|
||||||
|
|
||||||
|
// Larger buffer for TC to be able to hold the possibly large memory write packets.
|
||||||
|
const RX_RING_BUF_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
// Ring buffers to handling variable sized telemetry
|
||||||
|
static mut RINGBUF: Lazy<StaticRb<u8, RX_RING_BUF_SIZE>> =
|
||||||
|
Lazy::new(StaticRb::<u8, RX_RING_BUF_SIZE>::default);
|
||||||
|
|
||||||
|
#[rtic::app(device = pac, dispatchers = [OC4])]
|
||||||
|
mod app {
|
||||||
|
use super::*;
|
||||||
|
use embedded_io::Write;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use ringbuf::{
|
||||||
|
traits::{Consumer, Observer, Producer, SplitRef},
|
||||||
|
CachingCons, StaticProd,
|
||||||
|
};
|
||||||
|
use rtic_example::SYSCLK_FREQ;
|
||||||
|
use rtic_monotonics::Monotonic;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use va108xx_hal::{
|
||||||
|
gpio::PinsA,
|
||||||
|
pac,
|
||||||
|
prelude::*,
|
||||||
|
uart::{self, RxWithIrq, Tx},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {
|
||||||
|
data_producer: StaticProd<'static, u8, RX_RING_BUF_SIZE>,
|
||||||
|
data_consumer: CachingCons<&'static StaticRb<u8, RX_RING_BUF_SIZE>>,
|
||||||
|
rx: RxWithIrq<pac::Uarta>,
|
||||||
|
tx: Tx<pac::Uarta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
rtic_monotonics::systick_monotonic!(Mono, 1_000);
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(cx: init::Context) -> (Shared, Local) {
|
||||||
|
rtt_init_print!();
|
||||||
|
rprintln!("-- VA108xx UART Echo with IRQ example application--");
|
||||||
|
|
||||||
|
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
||||||
|
|
||||||
|
let mut dp = cx.device;
|
||||||
|
let gpioa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
||||||
|
let tx = gpioa.pa9.into_funsel_2();
|
||||||
|
let rx = gpioa.pa8.into_funsel_2();
|
||||||
|
|
||||||
|
let irq_uart = uart::Uart::new(
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
SYSCLK_FREQ,
|
||||||
|
dp.uarta,
|
||||||
|
(tx, rx),
|
||||||
|
115200.Hz(),
|
||||||
|
);
|
||||||
|
let (tx, rx) = irq_uart.split();
|
||||||
|
let mut rx = rx.into_rx_with_irq(&mut dp.sysconfig, &mut dp.irqsel, pac::interrupt::OC3);
|
||||||
|
|
||||||
|
rx.start();
|
||||||
|
|
||||||
|
let (data_producer, data_consumer) = unsafe { RINGBUF.split_ref() };
|
||||||
|
echo_handler::spawn().unwrap();
|
||||||
|
(
|
||||||
|
Shared {},
|
||||||
|
Local {
|
||||||
|
data_producer,
|
||||||
|
data_consumer,
|
||||||
|
rx,
|
||||||
|
tx,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `shared` cannot be accessed from this context
|
||||||
|
#[idle]
|
||||||
|
fn idle(_cx: idle::Context) -> ! {
|
||||||
|
loop {
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(
|
||||||
|
binds = OC3,
|
||||||
|
shared = [],
|
||||||
|
local = [
|
||||||
|
rx,
|
||||||
|
data_producer
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
fn reception_task(cx: reception_task::Context) {
|
||||||
|
let mut buf: [u8; 16] = [0; 16];
|
||||||
|
let mut ringbuf_full = false;
|
||||||
|
let result = cx.local.rx.irq_handler(&mut buf);
|
||||||
|
if result.bytes_read > 0 && result.errors.is_none() {
|
||||||
|
if cx.local.data_producer.vacant_len() < result.bytes_read {
|
||||||
|
ringbuf_full = true;
|
||||||
|
} else {
|
||||||
|
cx.local
|
||||||
|
.data_producer
|
||||||
|
.push_slice(&buf[0..result.bytes_read]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ringbuf_full {
|
||||||
|
// Could also drop oldest data, but that would require the consumer to be shared.
|
||||||
|
rprintln!("buffer full, data was dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(shared = [], local = [
|
||||||
|
buf: [u8; RX_RING_BUF_SIZE] = [0; RX_RING_BUF_SIZE],
|
||||||
|
data_consumer,
|
||||||
|
tx
|
||||||
|
], priority=1)]
|
||||||
|
async fn echo_handler(cx: echo_handler::Context) {
|
||||||
|
loop {
|
||||||
|
let bytes_to_read = cx.local.data_consumer.occupied_len();
|
||||||
|
if bytes_to_read > 0 {
|
||||||
|
let actual_read_bytes = cx
|
||||||
|
.local
|
||||||
|
.data_consumer
|
||||||
|
.pop_slice(&mut cx.local.buf[0..bytes_to_read]);
|
||||||
|
cx.local
|
||||||
|
.tx
|
||||||
|
.write_all(&cx.local.buf[0..actual_read_bytes])
|
||||||
|
.expect("Failed to write to TX");
|
||||||
|
}
|
||||||
|
Mono::delay(50.millis()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,165 +0,0 @@
|
|||||||
//! More complex UART application
|
|
||||||
//!
|
|
||||||
//! Uses the IRQ capabilities of the VA10820 peripheral and the RTIC framework to poll the UART in
|
|
||||||
//! a non-blocking way. You can send variably sized strings to the VA10820 which will be echoed
|
|
||||||
//! back to the sender.
|
|
||||||
//!
|
|
||||||
//! This script was tested with an Arduino Due. You can find the test script in the
|
|
||||||
//! [`/test/DueSerialTest`](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/test/DueSerialTest)
|
|
||||||
//! folder.
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
#[rtic::app(device = pac, dispatchers = [OC4])]
|
|
||||||
mod app {
|
|
||||||
use embedded_io::Write;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtic_example::SYSCLK_FREQ;
|
|
||||||
use rtic_sync::make_channel;
|
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
|
||||||
use va108xx_hal::{
|
|
||||||
gpio::PinsB,
|
|
||||||
pac,
|
|
||||||
prelude::*,
|
|
||||||
uart::{self, IrqCfg, IrqResult, UartWithIrqBase},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {
|
|
||||||
rx_info_tx: rtic_sync::channel::Sender<'static, RxInfo, 3>,
|
|
||||||
rx_info_rx: rtic_sync::channel::Receiver<'static, RxInfo, 3>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {
|
|
||||||
irq_uart: UartWithIrqBase<pac::Uartb>,
|
|
||||||
rx_buf: [u8; 64],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
struct RxInfo {
|
|
||||||
pub bytes_read: usize,
|
|
||||||
pub end_idx: usize,
|
|
||||||
pub timeout: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
rtic_monotonics::systick_monotonic!(Mono, 1_000);
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(cx: init::Context) -> (Shared, Local) {
|
|
||||||
rtt_init_print!();
|
|
||||||
rprintln!("-- VA108xx UART IRQ example application--");
|
|
||||||
|
|
||||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
|
||||||
|
|
||||||
let mut dp = cx.device;
|
|
||||||
let gpiob = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
|
|
||||||
let tx = gpiob.pb21.into_funsel_1();
|
|
||||||
let rx = gpiob.pb20.into_funsel_1();
|
|
||||||
|
|
||||||
let irq_cfg = IrqCfg::new(pac::interrupt::OC3, true, true);
|
|
||||||
let (mut irq_uart, _) =
|
|
||||||
uart::Uart::new(&mut dp.sysconfig, 50.MHz(), dp.uartb, (tx, rx), 115200.Hz())
|
|
||||||
.into_uart_with_irq(irq_cfg, Some(&mut dp.sysconfig), Some(&mut dp.irqsel))
|
|
||||||
.downgrade();
|
|
||||||
irq_uart
|
|
||||||
.read_fixed_len_using_irq(64, true)
|
|
||||||
.expect("Read initialization failed");
|
|
||||||
|
|
||||||
let (rx_info_tx, rx_info_rx) = make_channel!(RxInfo, 3);
|
|
||||||
let rx_buf: [u8; 64] = [0; 64];
|
|
||||||
(
|
|
||||||
Shared { irq_uart, rx_buf },
|
|
||||||
Local {
|
|
||||||
rx_info_tx,
|
|
||||||
rx_info_rx,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// `shared` cannot be accessed from this context
|
|
||||||
#[idle]
|
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
|
||||||
loop {
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
binds = OC3,
|
|
||||||
shared = [irq_uart, rx_buf],
|
|
||||||
local = [cnt: u32 = 0, result: IrqResult = IrqResult::new(), rx_info_tx],
|
|
||||||
)]
|
|
||||||
fn reception_task(cx: reception_task::Context) {
|
|
||||||
let result = cx.local.result;
|
|
||||||
let cnt: &mut u32 = cx.local.cnt;
|
|
||||||
let irq_uart = cx.shared.irq_uart;
|
|
||||||
let rx_buf = cx.shared.rx_buf;
|
|
||||||
let (completed, end_idx) = (irq_uart, rx_buf).lock(|irq_uart, rx_buf| {
|
|
||||||
match irq_uart.irq_handler(result, rx_buf) {
|
|
||||||
Ok(_) => {
|
|
||||||
if result.complete() {
|
|
||||||
// Initiate next transfer immediately
|
|
||||||
irq_uart
|
|
||||||
.read_fixed_len_using_irq(64, true)
|
|
||||||
.expect("Read operation init failed");
|
|
||||||
|
|
||||||
let mut end_idx = 0;
|
|
||||||
for (idx, val) in rx_buf.iter().enumerate() {
|
|
||||||
if (*val as char) == '\n' {
|
|
||||||
end_idx = idx;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(true, end_idx)
|
|
||||||
} else {
|
|
||||||
(false, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
rprintln!("reception error {:?}", e);
|
|
||||||
(false, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if completed {
|
|
||||||
rprintln!("counter: {}", cnt);
|
|
||||||
cx.local
|
|
||||||
.rx_info_tx
|
|
||||||
.try_send(RxInfo {
|
|
||||||
bytes_read: result.bytes_read,
|
|
||||||
end_idx,
|
|
||||||
timeout: result.timeout(),
|
|
||||||
})
|
|
||||||
.expect("RX queue full");
|
|
||||||
}
|
|
||||||
*cnt += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(shared = [irq_uart, rx_buf], local = [rx_info_rx], priority=1)]
|
|
||||||
async fn reply_handler(cx: reply_handler::Context) {
|
|
||||||
let mut irq_uart = cx.shared.irq_uart;
|
|
||||||
let mut rx_buf = cx.shared.rx_buf;
|
|
||||||
loop {
|
|
||||||
match cx.local.rx_info_rx.recv().await {
|
|
||||||
Ok(rx_info) => {
|
|
||||||
rprintln!("reception success, {} bytes read", rx_info.bytes_read);
|
|
||||||
if rx_info.timeout {
|
|
||||||
rprintln!("timeout occurred");
|
|
||||||
}
|
|
||||||
rx_buf.lock(|rx_buf| {
|
|
||||||
let string = core::str::from_utf8(&rx_buf[0..rx_info.end_idx])
|
|
||||||
.expect("Invalid string format");
|
|
||||||
rprintln!("read string: {}", string);
|
|
||||||
irq_uart.lock(|uart| {
|
|
||||||
writeln!(uart.uart, "{}", string).expect("Sending reply failed");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
rprintln!("error receiving RX info: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,8 +16,8 @@ use va108xx_hal::{
|
|||||||
gpio::PinsA,
|
gpio::PinsA,
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
pwm::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer},
|
|
||||||
timer::DelayMs,
|
timer::DelayMs,
|
||||||
|
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer},
|
||||||
IrqCfg,
|
IrqCfg,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ fn main() -> ! {
|
|||||||
dp.tim0,
|
dp.tim0,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut delay_tim1 = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
|
let mut delay_tim1 = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
|
||||||
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
||||||
let mut led1 = porta.pa10.into_readable_push_pull_output();
|
let mut led1 = porta.pa10.into_readable_push_pull_output();
|
||||||
let mut led2 = porta.pa7.into_readable_push_pull_output();
|
let mut led2 = porta.pa7.into_readable_push_pull_output();
|
||||||
|
@ -17,13 +17,13 @@ use va108xx_hal::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
timer::{
|
timer::{
|
||||||
default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource,
|
default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource,
|
||||||
CountDownTimer, Event, IrqCfg,
|
CountdownTimer, Event, IrqCfg,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static CSD_TGT_1: Mutex<RefCell<Option<CountDownTimer<pac::Tim4>>>> =
|
static CSD_TGT_1: Mutex<RefCell<Option<CountdownTimer<pac::Tim4>>>> =
|
||||||
Mutex::new(RefCell::new(None));
|
Mutex::new(RefCell::new(None));
|
||||||
static CSD_TGT_2: Mutex<RefCell<Option<CountDownTimer<pac::Tim5>>>> =
|
static CSD_TGT_2: Mutex<RefCell<Option<CountdownTimer<pac::Tim5>>>> =
|
||||||
Mutex::new(RefCell::new(None));
|
Mutex::new(RefCell::new(None));
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
@ -36,7 +36,7 @@ fn main() -> ! {
|
|||||||
|
|
||||||
// Will be started periodically to trigger a cascade
|
// Will be started periodically to trigger a cascade
|
||||||
let mut cascade_triggerer =
|
let mut cascade_triggerer =
|
||||||
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim3).auto_disable(true);
|
CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim3).auto_disable(true);
|
||||||
cascade_triggerer.listen(
|
cascade_triggerer.listen(
|
||||||
Event::TimeOut,
|
Event::TimeOut,
|
||||||
IrqCfg::new(pac::Interrupt::OC1, true, false),
|
IrqCfg::new(pac::Interrupt::OC1, true, false),
|
||||||
@ -46,7 +46,7 @@ fn main() -> ! {
|
|||||||
|
|
||||||
// First target for cascade
|
// First target for cascade
|
||||||
let mut cascade_target_1 =
|
let mut cascade_target_1 =
|
||||||
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true);
|
CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true);
|
||||||
cascade_target_1
|
cascade_target_1
|
||||||
.cascade_0_source(CascadeSource::Tim(3))
|
.cascade_0_source(CascadeSource::Tim(3))
|
||||||
.expect("Configuring cascade source for TIM4 failed");
|
.expect("Configuring cascade source for TIM4 failed");
|
||||||
@ -72,7 +72,7 @@ fn main() -> ! {
|
|||||||
|
|
||||||
// Activated by first cascade target
|
// Activated by first cascade target
|
||||||
let mut cascade_target_2 =
|
let mut cascade_target_2 =
|
||||||
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true);
|
CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true);
|
||||||
// Set TIM4 as cascade source
|
// Set TIM4 as cascade source
|
||||||
cascade_target_2
|
cascade_target_2
|
||||||
.cascade_1_source(CascadeSource::Tim(4))
|
.cascade_1_source(CascadeSource::Tim(4))
|
||||||
|
@ -15,8 +15,8 @@ use va108xx_hal::{
|
|||||||
gpio::{PinsA, PinsB},
|
gpio::{PinsA, PinsB},
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
pwm::{default_ms_irq_handler, set_up_ms_tick},
|
|
||||||
spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs},
|
spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs},
|
||||||
|
timer::{default_ms_irq_handler, set_up_ms_tick},
|
||||||
IrqCfg,
|
IrqCfg,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use va108xx_hal::{
|
|||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
time::Hertz,
|
time::Hertz,
|
||||||
timer::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer, Event, IrqCfg, MS_COUNTER},
|
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, Event, IrqCfg, MS_COUNTER},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -72,7 +72,7 @@ fn main() -> ! {
|
|||||||
dp.tim0,
|
dp.tim0,
|
||||||
);
|
);
|
||||||
let mut second_timer =
|
let mut second_timer =
|
||||||
CountDownTimer::new(&mut dp.sysconfig, get_sys_clock().unwrap(), dp.tim1);
|
CountdownTimer::new(&mut dp.sysconfig, get_sys_clock().unwrap(), dp.tim1);
|
||||||
second_timer.listen(
|
second_timer.listen(
|
||||||
Event::TimeOut,
|
Event::TimeOut,
|
||||||
IrqCfg::new(interrupt::OC1, true, true),
|
IrqCfg::new(interrupt::OC1, true, true),
|
||||||
|
1
flashloader/.gitignore
vendored
Normal file
1
flashloader/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/venv
|
65
flashloader/Cargo.toml
Normal file
65
flashloader/Cargo.toml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
[package]
|
||||||
|
name = "flashloader"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cortex-m = "0.7"
|
||||||
|
cortex-m-rt = "0.7"
|
||||||
|
embedded-hal = "1"
|
||||||
|
embedded-hal-nb = "1"
|
||||||
|
embedded-io = "0.6"
|
||||||
|
panic-rtt-target = { version = "0.1.3" }
|
||||||
|
rtt-target = { version = "0.5" }
|
||||||
|
log = "0.4"
|
||||||
|
crc = "3"
|
||||||
|
|
||||||
|
[dependencies.satrs]
|
||||||
|
version = "0.2"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.rtt-log]
|
||||||
|
version = "0.4"
|
||||||
|
|
||||||
|
[dependencies.ringbuf]
|
||||||
|
version = "0.4.7"
|
||||||
|
default-features = false
|
||||||
|
features = ["portable-atomic"]
|
||||||
|
|
||||||
|
[dependencies.once_cell]
|
||||||
|
version = "1"
|
||||||
|
default-features = false
|
||||||
|
features = ["critical-section"]
|
||||||
|
|
||||||
|
[dependencies.spacepackets]
|
||||||
|
version = "0.11"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.cobs]
|
||||||
|
git = "https://github.com/robamu/cobs.rs.git"
|
||||||
|
branch = "all_features"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
# Even though we do not use this directly, we need to activate this feature explicitely
|
||||||
|
# so that RTIC compiles because thumv6 does not have CAS operations natively.
|
||||||
|
[dependencies.portable-atomic]
|
||||||
|
version = "1"
|
||||||
|
features = ["unsafe-assume-single-core"]
|
||||||
|
|
||||||
|
[dependencies.rtic]
|
||||||
|
version = "2"
|
||||||
|
features = ["thumbv6-backend"]
|
||||||
|
|
||||||
|
[dependencies.rtic-monotonics]
|
||||||
|
version = "2"
|
||||||
|
features = ["cortex-m-systick"]
|
||||||
|
|
||||||
|
[dependencies.rtic-sync]
|
||||||
|
version = "1"
|
||||||
|
features = ["defmt-03"]
|
||||||
|
|
||||||
|
[dependencies.va108xx-hal]
|
||||||
|
path = "../va108xx-hal"
|
||||||
|
|
||||||
|
[dependencies.vorago-reb1]
|
||||||
|
path = "../vorago-reb1"
|
66
flashloader/README.md
Normal file
66
flashloader/README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
VA108xx Flashloader Application
|
||||||
|
========
|
||||||
|
|
||||||
|
This flashloader shows a minimal example for a self-updatable Rust software which exposes
|
||||||
|
a simple PUS (CCSDS) interface to update the software. It also provides a Python application
|
||||||
|
called the `image-loader.py` which can be used to upload compiled images to the flashloader
|
||||||
|
application to write them to the NVM.
|
||||||
|
|
||||||
|
Please note that the both the application and the image loader are tailored towards usage
|
||||||
|
with the [bootloader provided by this repository](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader).
|
||||||
|
|
||||||
|
The software can quickly be adapted to interface with a real primary on-board software instead of
|
||||||
|
the Python script provided here to upload images because it uses a low-level CCSDS based packet
|
||||||
|
interface.
|
||||||
|
|
||||||
|
## Using the Python image loader
|
||||||
|
|
||||||
|
The Python image loader communicates with the Rust flashload application using a dedicated serial
|
||||||
|
port with a baudrate of 115200.
|
||||||
|
|
||||||
|
It is recommended to run the script in a dedicated virtual environment. For example, on UNIX
|
||||||
|
systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
|
||||||
|
and activate a virtual environment.
|
||||||
|
|
||||||
|
After that, you can use
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
to install all required dependencies.
|
||||||
|
|
||||||
|
After that, it is recommended to use `./image-load.py -h` to get an overview of some options.
|
||||||
|
The flash loader uses the UART0 with the Pins PA8 (RX) and PA9 (TX) interface of the VA108xx to perform CCSDS based
|
||||||
|
communication. The Python image loader application will search for a file named `loader.toml` and
|
||||||
|
use the `serial_port` key to determine the serial port to use for serial communication.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
You can use
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./image-loader.py -p
|
||||||
|
```
|
||||||
|
|
||||||
|
to send a ping an verify the connection.
|
||||||
|
|
||||||
|
You can use
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd flashloader/slot-a-blinky
|
||||||
|
cargo build --release
|
||||||
|
cd ../..
|
||||||
|
./image-loader.py -t a ./slot-a-blinky/target/thumbv6m-none-eabi/release/slot-a-blinky
|
||||||
|
```
|
||||||
|
|
||||||
|
to build the slot A sample application and upload it to a running flash loader application
|
||||||
|
to write it to slot A.
|
||||||
|
|
||||||
|
You can use
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./image-loader.py -c -t a
|
||||||
|
```
|
||||||
|
|
||||||
|
to corrupt the image A and test that it switches to image B after a failed CRC check instead.
|
430
flashloader/image-loader.py
Executable file
430
flashloader/image-loader.py
Executable file
@ -0,0 +1,430 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from typing import List, Tuple
|
||||||
|
from spacepackets.ecss.defs import PusService
|
||||||
|
from spacepackets.ecss.tm import PusTm
|
||||||
|
from tmtccmd.com import ComInterface
|
||||||
|
import toml
|
||||||
|
import struct
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
import enum
|
||||||
|
from tmtccmd.com.serial_base import SerialCfg
|
||||||
|
from tmtccmd.com.serial_cobs import SerialCobsComIF
|
||||||
|
from tmtccmd.com.ser_utils import prompt_com_port
|
||||||
|
from crcmod.predefined import PredefinedCrc
|
||||||
|
from spacepackets.ecss.tc import PusTc
|
||||||
|
from spacepackets.ecss.pus_verificator import PusVerificator, StatusField
|
||||||
|
from spacepackets.ecss.pus_1_verification import Service1Tm, UnpackParams
|
||||||
|
from spacepackets.seqcount import SeqCountProvider
|
||||||
|
from pathlib import Path
|
||||||
|
import dataclasses
|
||||||
|
from elftools.elf.elffile import ELFFile
|
||||||
|
|
||||||
|
|
||||||
|
BAUD_RATE = 115200
|
||||||
|
|
||||||
|
BOOTLOADER_START_ADDR = 0x0
|
||||||
|
BOOTLOADER_END_ADDR = 0x3000
|
||||||
|
BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 2
|
||||||
|
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 2
|
||||||
|
|
||||||
|
APP_A_START_ADDR = 0x3000
|
||||||
|
APP_A_END_ADDR = 0x11800
|
||||||
|
# The actual size of the image which is relevant for CRC calculation.
|
||||||
|
APP_A_SIZE_ADDR = APP_A_END_ADDR - 8
|
||||||
|
APP_A_CRC_ADDR = APP_A_END_ADDR - 4
|
||||||
|
APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
|
||||||
|
|
||||||
|
APP_B_START_ADDR = APP_A_END_ADDR
|
||||||
|
APP_B_END_ADDR = 0x20000
|
||||||
|
# The actual size of the image which is relevant for CRC calculation.
|
||||||
|
APP_B_SIZE_ADDR = APP_B_END_ADDR - 8
|
||||||
|
APP_B_CRC_ADDR = APP_B_END_ADDR - 4
|
||||||
|
APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
|
||||||
|
|
||||||
|
APP_IMG_SZ = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
|
||||||
|
|
||||||
|
CHUNK_SIZE = 400
|
||||||
|
|
||||||
|
MEMORY_SERVICE = 6
|
||||||
|
ACTION_SERVICE = 8
|
||||||
|
|
||||||
|
RAW_MEMORY_WRITE_SUBSERVICE = 2
|
||||||
|
BOOT_NVM_MEMORY_ID = 1
|
||||||
|
PING_PAYLOAD_SIZE = 0
|
||||||
|
|
||||||
|
|
||||||
|
class ActionId(enum.IntEnum):
|
||||||
|
CORRUPT_APP_A = 128
|
||||||
|
CORRUPT_APP_B = 129
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class LoadableSegment:
|
||||||
|
name: str
|
||||||
|
offset: int
|
||||||
|
size: int
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
|
||||||
|
class Target(enum.Enum):
|
||||||
|
BOOTLOADER = 0
|
||||||
|
APP_A = 1
|
||||||
|
APP_B = 2
|
||||||
|
|
||||||
|
|
||||||
|
class ImageLoader:
|
||||||
|
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
|
||||||
|
self.com_if = com_if
|
||||||
|
self.verificator = verificator
|
||||||
|
|
||||||
|
def handle_ping_cmd(self):
|
||||||
|
_LOGGER.info("Sending ping command")
|
||||||
|
ping_tc = PusTc(
|
||||||
|
apid=0x00,
|
||||||
|
service=PusService.S17_TEST,
|
||||||
|
subservice=1,
|
||||||
|
seq_count=SEQ_PROVIDER.get_and_increment(),
|
||||||
|
app_data=bytes(PING_PAYLOAD_SIZE),
|
||||||
|
)
|
||||||
|
self.verificator.add_tc(ping_tc)
|
||||||
|
self.com_if.send(bytes(ping_tc.pack()))
|
||||||
|
|
||||||
|
data_available = self.com_if.data_available(0.4)
|
||||||
|
if not data_available:
|
||||||
|
_LOGGER.warning("no ping reply received")
|
||||||
|
for reply in self.com_if.receive():
|
||||||
|
result = self.verificator.add_tm(
|
||||||
|
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
|
||||||
|
)
|
||||||
|
if result is not None and result.completed:
|
||||||
|
_LOGGER.info("received ping completion reply")
|
||||||
|
|
||||||
|
def handle_corruption_cmd(self, target: Target):
|
||||||
|
|
||||||
|
if target == Target.BOOTLOADER:
|
||||||
|
_LOGGER.error("can not corrupt bootloader")
|
||||||
|
if target == Target.APP_A:
|
||||||
|
self.send_tc(
|
||||||
|
PusTc(
|
||||||
|
apid=0,
|
||||||
|
service=ACTION_SERVICE,
|
||||||
|
subservice=ActionId.CORRUPT_APP_A,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if target == Target.APP_B:
|
||||||
|
self.send_tc(
|
||||||
|
PusTc(
|
||||||
|
apid=0,
|
||||||
|
service=ACTION_SERVICE,
|
||||||
|
subservice=ActionId.CORRUPT_APP_B,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_flash_cmd(self, target: Target, file_path: Path) -> int:
|
||||||
|
loadable_segments = []
|
||||||
|
_LOGGER.info("Parsing ELF file for loadable sections")
|
||||||
|
total_size = 0
|
||||||
|
loadable_segments, total_size = create_loadable_segments(target, file_path)
|
||||||
|
segments_info_str(target, loadable_segments, total_size, file_path)
|
||||||
|
result = self._perform_flashing_algorithm(loadable_segments)
|
||||||
|
if result != 0:
|
||||||
|
return result
|
||||||
|
self._crc_and_app_size_postprocessing(target, total_size, loadable_segments)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _perform_flashing_algorithm(
|
||||||
|
self,
|
||||||
|
loadable_segments: List[LoadableSegment],
|
||||||
|
) -> int:
|
||||||
|
# Perform the flashing algorithm.
|
||||||
|
for segment in loadable_segments:
|
||||||
|
segment_end = segment.offset + segment.size
|
||||||
|
current_addr = segment.offset
|
||||||
|
pos_in_segment = 0
|
||||||
|
while pos_in_segment < segment.size:
|
||||||
|
next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
|
||||||
|
data = segment.data[pos_in_segment : pos_in_segment + next_chunk_size]
|
||||||
|
next_packet = pack_memory_write_command(current_addr, data)
|
||||||
|
_LOGGER.info(
|
||||||
|
f"Sending memory write command for address {current_addr:#08x} and data with "
|
||||||
|
f"length {len(data)}"
|
||||||
|
)
|
||||||
|
self.verificator.add_tc(next_packet)
|
||||||
|
self.com_if.send(bytes(next_packet.pack()))
|
||||||
|
current_addr += next_chunk_size
|
||||||
|
pos_in_segment += next_chunk_size
|
||||||
|
start_time = time.time()
|
||||||
|
while True:
|
||||||
|
if time.time() - start_time > 1.0:
|
||||||
|
_LOGGER.error("Timeout while waiting for reply")
|
||||||
|
return -1
|
||||||
|
data_available = self.com_if.data_available(0.1)
|
||||||
|
done = False
|
||||||
|
if not data_available:
|
||||||
|
continue
|
||||||
|
replies = self.com_if.receive()
|
||||||
|
for reply in replies:
|
||||||
|
tm = PusTm.unpack(reply, 0)
|
||||||
|
if tm.service != 1:
|
||||||
|
continue
|
||||||
|
service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
|
||||||
|
check_result = self.verificator.add_tm(service_1_tm)
|
||||||
|
# We could send after we have received the step reply, but that can
|
||||||
|
# somehow lead to overrun errors. I think it's okay to do it like
|
||||||
|
# this as long as the flash loader only uses polling..
|
||||||
|
if (
|
||||||
|
check_result is not None
|
||||||
|
and check_result.status.completed == StatusField.SUCCESS
|
||||||
|
):
|
||||||
|
done = True
|
||||||
|
|
||||||
|
# This is an optimized variant, but I think the small delay is not an issue.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
check_result is not None
|
||||||
|
and check_result.status.step == StatusField.SUCCESS
|
||||||
|
and len(check_result.status.step_list) == 1
|
||||||
|
):
|
||||||
|
done = True
|
||||||
|
"""
|
||||||
|
self.verificator.remove_completed_entries()
|
||||||
|
if done:
|
||||||
|
break
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _crc_and_app_size_postprocessing(
|
||||||
|
self,
|
||||||
|
target: Target,
|
||||||
|
total_size: int,
|
||||||
|
loadable_segments: List[LoadableSegment],
|
||||||
|
):
|
||||||
|
if target == Target.BOOTLOADER:
|
||||||
|
_LOGGER.info("Blanking the bootloader checksum")
|
||||||
|
# Blank the checksum. For the bootloader, the bootloader will calculate the
|
||||||
|
# checksum itself on the initial run.
|
||||||
|
checksum_write_packet = pack_memory_write_command(
|
||||||
|
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00])
|
||||||
|
)
|
||||||
|
self.send_tc(checksum_write_packet)
|
||||||
|
else:
|
||||||
|
crc_addr = None
|
||||||
|
size_addr = None
|
||||||
|
if target == Target.APP_A:
|
||||||
|
crc_addr = APP_A_CRC_ADDR
|
||||||
|
size_addr = APP_A_SIZE_ADDR
|
||||||
|
elif target == Target.APP_B:
|
||||||
|
crc_addr = APP_B_CRC_ADDR
|
||||||
|
size_addr = APP_B_SIZE_ADDR
|
||||||
|
assert crc_addr is not None
|
||||||
|
assert size_addr is not None
|
||||||
|
_LOGGER.info(f"Writing app size {total_size} at address {size_addr:#08x}")
|
||||||
|
size_write_packet = pack_memory_write_command(
|
||||||
|
size_addr, struct.pack("!I", total_size)
|
||||||
|
)
|
||||||
|
self.com_if.send(bytes(size_write_packet.pack()))
|
||||||
|
time.sleep(0.2)
|
||||||
|
crc_calc = PredefinedCrc("crc-ccitt-false")
|
||||||
|
for segment in loadable_segments:
|
||||||
|
crc_calc.update(segment.data)
|
||||||
|
checksum = crc_calc.digest()
|
||||||
|
_LOGGER.info(
|
||||||
|
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
|
||||||
|
)
|
||||||
|
self.send_tc(pack_memory_write_command(crc_addr, checksum))
|
||||||
|
|
||||||
|
def send_tc(self, tc: PusTc):
|
||||||
|
self.com_if.send(bytes(tc.pack()))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
print("Python VA108XX Image Loader Application")
|
||||||
|
logging.basicConfig(
|
||||||
|
format="[%(asctime)s] [%(levelname)s] %(message)s", level=logging.DEBUG
|
||||||
|
)
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="image-loader", description="Python VA416XX Image Loader Application"
|
||||||
|
)
|
||||||
|
parser.add_argument("-p", "--ping", action="store_true", help="Send ping command")
|
||||||
|
parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target")
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--target",
|
||||||
|
choices=["bl", "a", "b"],
|
||||||
|
help="Target (Bootloader or slot A or B)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"path", nargs="?", default=None, help="Path to the App to flash"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
serial_port = None
|
||||||
|
if Path("loader.toml").exists():
|
||||||
|
with open("loader.toml", "r") as toml_file:
|
||||||
|
parsed_toml = toml.loads(toml_file.read())
|
||||||
|
if "serial_port" in parsed_toml:
|
||||||
|
serial_port = parsed_toml["serial_port"]
|
||||||
|
if serial_port is None:
|
||||||
|
serial_port = prompt_com_port()
|
||||||
|
serial_cfg = SerialCfg(
|
||||||
|
com_if_id="ser_cobs",
|
||||||
|
serial_port=serial_port,
|
||||||
|
baud_rate=BAUD_RATE,
|
||||||
|
serial_timeout=0.1,
|
||||||
|
)
|
||||||
|
verificator = PusVerificator()
|
||||||
|
com_if = SerialCobsComIF(serial_cfg)
|
||||||
|
com_if.open()
|
||||||
|
target = None
|
||||||
|
if args.target == "bl":
|
||||||
|
target = Target.BOOTLOADER
|
||||||
|
elif args.target == "a":
|
||||||
|
target = Target.APP_A
|
||||||
|
elif args.target == "b":
|
||||||
|
target = Target.APP_B
|
||||||
|
image_loader = ImageLoader(com_if, verificator)
|
||||||
|
file_path = None
|
||||||
|
result = -1
|
||||||
|
if args.ping:
|
||||||
|
image_loader.handle_ping_cmd()
|
||||||
|
com_if.close()
|
||||||
|
return 0
|
||||||
|
if target:
|
||||||
|
if not args.corrupt:
|
||||||
|
if not args.path:
|
||||||
|
_LOGGER.error("App Path needs to be specified for the flash process")
|
||||||
|
file_path = Path(args.path)
|
||||||
|
if not file_path.exists():
|
||||||
|
_LOGGER.error("File does not exist")
|
||||||
|
if args.corrupt:
|
||||||
|
if not target:
|
||||||
|
_LOGGER.error("target for corruption command required")
|
||||||
|
com_if.close()
|
||||||
|
return -1
|
||||||
|
image_loader.handle_corruption_cmd(target)
|
||||||
|
else:
|
||||||
|
assert file_path is not None
|
||||||
|
assert target is not None
|
||||||
|
result = image_loader.handle_flash_cmd(target, file_path)
|
||||||
|
|
||||||
|
com_if.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def create_loadable_segments(
|
||||||
|
target: Target, file_path: Path
|
||||||
|
) -> Tuple[List[LoadableSegment], int]:
|
||||||
|
loadable_segments = []
|
||||||
|
total_size = 0
|
||||||
|
with open(file_path, "rb") as app_file:
|
||||||
|
elf_file = ELFFile(app_file)
|
||||||
|
|
||||||
|
for idx, segment in enumerate(elf_file.iter_segments("PT_LOAD")):
|
||||||
|
if segment.header.p_filesz == 0:
|
||||||
|
continue
|
||||||
|
# Basic validity checks of the base addresses.
|
||||||
|
if idx == 0:
|
||||||
|
if (
|
||||||
|
target == Target.BOOTLOADER
|
||||||
|
and segment.header.p_paddr != BOOTLOADER_START_ADDR
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
||||||
|
f"bootloader, expected {BOOTLOADER_START_ADDR}"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
target == Target.APP_A
|
||||||
|
and segment.header.p_paddr != APP_A_START_ADDR
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
||||||
|
f"App A, expected {APP_A_START_ADDR}"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
target == Target.APP_B
|
||||||
|
and segment.header.p_paddr != APP_B_START_ADDR
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
||||||
|
f"App B, expected {APP_B_START_ADDR}"
|
||||||
|
)
|
||||||
|
name = None
|
||||||
|
for section in elf_file.iter_sections():
|
||||||
|
if (
|
||||||
|
section.header.sh_offset == segment.header.p_offset
|
||||||
|
and section.header.sh_size > 0
|
||||||
|
):
|
||||||
|
name = section.name
|
||||||
|
if name is None:
|
||||||
|
_LOGGER.warning("no fitting section found for segment")
|
||||||
|
continue
|
||||||
|
# print(f"Segment Addr: {segment.header.p_paddr}")
|
||||||
|
# print(f"Segment Offset: {segment.header.p_offset}")
|
||||||
|
# print(f"Segment Filesize: {segment.header.p_filesz}")
|
||||||
|
loadable_segments.append(
|
||||||
|
LoadableSegment(
|
||||||
|
name=name,
|
||||||
|
offset=segment.header.p_paddr,
|
||||||
|
size=segment.header.p_filesz,
|
||||||
|
data=segment.data(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
total_size += segment.header.p_filesz
|
||||||
|
return loadable_segments, total_size
|
||||||
|
|
||||||
|
|
||||||
|
def segments_info_str(
|
||||||
|
target: Target,
|
||||||
|
loadable_segments: List[LoadableSegment],
|
||||||
|
total_size: int,
|
||||||
|
file_path: Path,
|
||||||
|
):
|
||||||
|
# Set context string and perform basic sanity checks.
|
||||||
|
if target == Target.BOOTLOADER:
|
||||||
|
if total_size > BOOTLOADER_MAX_SIZE:
|
||||||
|
_LOGGER.error(
|
||||||
|
f"provided bootloader app larger than allowed {total_size} bytes"
|
||||||
|
)
|
||||||
|
return -1
|
||||||
|
context_str = "Bootloader"
|
||||||
|
elif target == Target.APP_A:
|
||||||
|
if total_size > APP_A_MAX_SIZE:
|
||||||
|
_LOGGER.error(f"provided App A larger than allowed {total_size} bytes")
|
||||||
|
return -1
|
||||||
|
context_str = "App Slot A"
|
||||||
|
elif target == Target.APP_B:
|
||||||
|
if total_size > APP_B_MAX_SIZE:
|
||||||
|
_LOGGER.error(f"provided App B larger than allowed {total_size} bytes")
|
||||||
|
return -1
|
||||||
|
context_str = "App Slot B"
|
||||||
|
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})")
|
||||||
|
for idx, segment in enumerate(loadable_segments):
|
||||||
|
_LOGGER.info(
|
||||||
|
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and "
|
||||||
|
f"size {segment.size}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
|
||||||
|
app_data = bytearray()
|
||||||
|
app_data.append(BOOT_NVM_MEMORY_ID)
|
||||||
|
# N parameter is always 1 here.
|
||||||
|
app_data.append(1)
|
||||||
|
app_data.extend(struct.pack("!I", addr))
|
||||||
|
app_data.extend(struct.pack("!I", len(data)))
|
||||||
|
app_data.extend(data)
|
||||||
|
return PusTc(
|
||||||
|
apid=0,
|
||||||
|
service=MEMORY_SERVICE,
|
||||||
|
subservice=RAW_MEMORY_WRITE_SUBSERVICE,
|
||||||
|
seq_count=SEQ_PROVIDER.get_and_increment(),
|
||||||
|
app_data=bytes(app_data),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
1
flashloader/loader.toml
Normal file
1
flashloader/loader.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
serial_port = "/dev/ttyUSB0"
|
5
flashloader/requirements.txt
Normal file
5
flashloader/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
spacepackets == 0.24
|
||||||
|
tmtccmd == 8.0.2
|
||||||
|
toml == 0.10
|
||||||
|
pyelftools == 0.31
|
||||||
|
crcmod == 1.7
|
2
flashloader/slot-a-blinky/.gitignore
vendored
Normal file
2
flashloader/slot-a-blinky/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/app.map
|
42
flashloader/slot-a-blinky/Cargo.toml
Normal file
42
flashloader/slot-a-blinky/Cargo.toml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[package]
|
||||||
|
name = "slot-a-blinky"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cortex-m-rt = "0.7"
|
||||||
|
panic-rtt-target = { version = "0.1.3" }
|
||||||
|
rtt-target = { version = "0.5" }
|
||||||
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
|
embedded-hal = "1"
|
||||||
|
va108xx-hal = { path = "../../va108xx-hal" }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = true # <-
|
||||||
|
incremental = false
|
||||||
|
# This is problematic for stepping..
|
||||||
|
# opt-level = 'z' # <-
|
||||||
|
overflow-checks = true # <-
|
||||||
|
|
||||||
|
# cargo build/run --release
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = false # <-
|
||||||
|
incremental = false
|
||||||
|
lto = 'fat'
|
||||||
|
opt-level = 3 # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
|
||||||
|
[profile.small]
|
||||||
|
inherits = "release"
|
||||||
|
codegen-units = 1
|
||||||
|
debug-assertions = false # <-
|
||||||
|
lto = true
|
||||||
|
opt-level = 'z' # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
# strip = true # Automatically strip symbols from the binary.
|
11
flashloader/slot-a-blinky/memory.x
Normal file
11
flashloader/slot-a-blinky/memory.x
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* Special linker script for application slot A with an offset at address 0x3000 */
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH : ORIGIN = 0x00003000, LENGTH = 0xE800
|
||||||
|
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 32K */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is where the call stack will be allocated. */
|
||||||
|
/* The stack is of the full descending type. */
|
||||||
|
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
||||||
|
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
25
flashloader/slot-a-blinky/src/main.rs
Normal file
25
flashloader/slot-a-blinky/src/main.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//! Simple blinky example using the HAL
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use cortex_m_rt::entry;
|
||||||
|
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::CountdownTimer};
|
||||||
|
|
||||||
|
#[entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
rtt_init_print!();
|
||||||
|
rprintln!("VA108xx HAL blinky example for App Slot A");
|
||||||
|
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
let mut timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim0);
|
||||||
|
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
||||||
|
let mut led1 = porta.pa10.into_readable_push_pull_output();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
led1.toggle().ok();
|
||||||
|
timer.delay_ms(500);
|
||||||
|
}
|
||||||
|
}
|
2
flashloader/slot-b-blinky/.gitignore
vendored
Normal file
2
flashloader/slot-b-blinky/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/app.map
|
42
flashloader/slot-b-blinky/Cargo.toml
Normal file
42
flashloader/slot-b-blinky/Cargo.toml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[package]
|
||||||
|
name = "slot-b-blinky"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cortex-m-rt = "0.7"
|
||||||
|
panic-rtt-target = { version = "0.1.3" }
|
||||||
|
rtt-target = { version = "0.5" }
|
||||||
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
|
embedded-hal = "1"
|
||||||
|
va108xx-hal = { path = "../../va108xx-hal" }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = true # <-
|
||||||
|
incremental = false
|
||||||
|
# This is problematic for stepping..
|
||||||
|
# opt-level = 'z' # <-
|
||||||
|
overflow-checks = true # <-
|
||||||
|
|
||||||
|
# cargo build/run --release
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = false # <-
|
||||||
|
incremental = false
|
||||||
|
lto = 'fat'
|
||||||
|
opt-level = 3 # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
|
||||||
|
[profile.small]
|
||||||
|
inherits = "release"
|
||||||
|
codegen-units = 1
|
||||||
|
debug-assertions = false # <-
|
||||||
|
lto = true
|
||||||
|
opt-level = 'z' # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
# strip = true # Automatically strip symbols from the binary.
|
11
flashloader/slot-b-blinky/memory.x
Normal file
11
flashloader/slot-b-blinky/memory.x
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* Special linker script for application slot B with an offset at address 0x11800 */
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH : ORIGIN = 0x00011800, LENGTH = 0xE800
|
||||||
|
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 32K */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is where the call stack will be allocated. */
|
||||||
|
/* The stack is of the full descending type. */
|
||||||
|
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
||||||
|
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
25
flashloader/slot-b-blinky/src/main.rs
Normal file
25
flashloader/slot-b-blinky/src/main.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//! Simple blinky example using the HAL
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use cortex_m_rt::entry;
|
||||||
|
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::CountdownTimer};
|
||||||
|
|
||||||
|
#[entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
rtt_init_print!();
|
||||||
|
rprintln!("VA108xx HAL blinky example for App Slot B");
|
||||||
|
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
let mut timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim0);
|
||||||
|
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
||||||
|
let mut led2 = porta.pa7.into_readable_push_pull_output();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
led2.toggle().ok();
|
||||||
|
timer.delay_ms(1000);
|
||||||
|
}
|
||||||
|
}
|
9
flashloader/src/lib.rs
Normal file
9
flashloader/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#![no_std]
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn simple() {
|
||||||
|
assert_eq!(1 + 1, 2);
|
||||||
|
}
|
||||||
|
}
|
474
flashloader/src/main.rs
Normal file
474
flashloader/src/main.rs
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
//! Vorago flashloader which can be used to flash image A and image B via a simple
|
||||||
|
//! low-level CCSDS memory interface via a UART interface.
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use ringbuf::{
|
||||||
|
traits::{Consumer, Observer, Producer, SplitRef},
|
||||||
|
CachingCons, StaticProd, StaticRb,
|
||||||
|
};
|
||||||
|
use va108xx_hal::prelude::*;
|
||||||
|
|
||||||
|
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
|
||||||
|
|
||||||
|
const MAX_TC_SIZE: usize = 524;
|
||||||
|
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
|
||||||
|
|
||||||
|
const MAX_TM_SIZE: usize = 128;
|
||||||
|
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
|
||||||
|
|
||||||
|
const UART_BAUDRATE: u32 = 115200;
|
||||||
|
const BOOT_NVM_MEMORY_ID: u8 = 1;
|
||||||
|
const RX_DEBUGGING: bool = false;
|
||||||
|
|
||||||
|
pub enum ActionId {
|
||||||
|
CorruptImageA = 128,
|
||||||
|
CorruptImageB = 129,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Larger buffer for TC to be able to hold the possibly large memory write packets.
|
||||||
|
const BUF_RB_SIZE_TC: usize = 1024;
|
||||||
|
const SIZES_RB_SIZE_TC: usize = 16;
|
||||||
|
|
||||||
|
const BUF_RB_SIZE_TM: usize = 256;
|
||||||
|
const SIZES_RB_SIZE_TM: usize = 16;
|
||||||
|
|
||||||
|
// Ring buffers to handling variable sized telemetry
|
||||||
|
static mut BUF_RB_TM: Lazy<StaticRb<u8, BUF_RB_SIZE_TM>> =
|
||||||
|
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TM>::default);
|
||||||
|
static mut SIZES_RB_TM: Lazy<StaticRb<usize, SIZES_RB_SIZE_TM>> =
|
||||||
|
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TM>::default);
|
||||||
|
|
||||||
|
// Ring buffers to handling variable sized telecommands
|
||||||
|
static mut BUF_RB_TC: Lazy<StaticRb<u8, BUF_RB_SIZE_TC>> =
|
||||||
|
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TC>::default);
|
||||||
|
static mut SIZES_RB_TC: Lazy<StaticRb<usize, SIZES_RB_SIZE_TC>> =
|
||||||
|
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TC>::default);
|
||||||
|
|
||||||
|
pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
|
||||||
|
pub buf_prod: StaticProd<'static, u8, BUF_SIZE>,
|
||||||
|
pub sizes_prod: StaticProd<'static, usize, SIZES_LEN>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DataConsumer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
|
||||||
|
pub buf_cons: CachingCons<&'static StaticRb<u8, BUF_SIZE>>,
|
||||||
|
pub sizes_cons: CachingCons<&'static StaticRb<usize, SIZES_LEN>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const APP_A_START_ADDR: u32 = 0x3000;
|
||||||
|
pub const APP_A_END_ADDR: u32 = 0x11800;
|
||||||
|
pub const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
|
||||||
|
pub const APP_B_END_ADDR: u32 = 0x20000;
|
||||||
|
|
||||||
|
#[rtic::app(device = pac, dispatchers = [OC20, OC21, OC22])]
|
||||||
|
mod app {
|
||||||
|
use super::*;
|
||||||
|
use cortex_m::asm;
|
||||||
|
use embedded_io::Write;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtic::Mutex;
|
||||||
|
use rtic_monotonics::systick::prelude::*;
|
||||||
|
use rtt_target::rprintln;
|
||||||
|
use satrs::pus::verification::{FailParams, VerificationReportCreator};
|
||||||
|
use spacepackets::ecss::PusServiceId;
|
||||||
|
use spacepackets::ecss::{
|
||||||
|
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
|
||||||
|
};
|
||||||
|
use va108xx_hal::gpio::PinsA;
|
||||||
|
use va108xx_hal::uart::IrqContextTimeoutOrMaxSize;
|
||||||
|
use va108xx_hal::{pac, uart};
|
||||||
|
use vorago_reb1::m95m01::M95M01;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum CobsReaderStates {
|
||||||
|
#[default]
|
||||||
|
WaitingForStart,
|
||||||
|
WatingForEnd,
|
||||||
|
FrameOverflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {
|
||||||
|
uart_rx: uart::RxWithIrq<pac::Uarta>,
|
||||||
|
uart_tx: uart::Tx<pac::Uarta>,
|
||||||
|
rx_context: IrqContextTimeoutOrMaxSize,
|
||||||
|
// We handle all TM in one task.
|
||||||
|
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
|
||||||
|
// We consume all TC in one task.
|
||||||
|
tc_cons: DataConsumer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
|
||||||
|
// We produce all TC in one task.
|
||||||
|
tc_prod: DataProducer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
|
||||||
|
verif_reporter: VerificationReportCreator,
|
||||||
|
nvm: M95M01,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct Shared {
|
||||||
|
// Having this shared allows multiple tasks to generate telemetry.
|
||||||
|
tm_prod: DataProducer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
|
||||||
|
}
|
||||||
|
|
||||||
|
rtic_monotonics::systick_monotonic!(Mono, 1000);
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(cx: init::Context) -> (Shared, Local) {
|
||||||
|
rtt_log::init();
|
||||||
|
rprintln!("-- Vorago flashloader --");
|
||||||
|
|
||||||
|
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
||||||
|
|
||||||
|
let mut dp = cx.device;
|
||||||
|
let nvm = M95M01::new(&mut dp.sysconfig, SYSCLK_FREQ, dp.spic);
|
||||||
|
|
||||||
|
let gpioa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
|
||||||
|
let tx = gpioa.pa9.into_funsel_2();
|
||||||
|
let rx = gpioa.pa8.into_funsel_2();
|
||||||
|
|
||||||
|
let irq_uart = uart::Uart::new(
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
SYSCLK_FREQ,
|
||||||
|
dp.uarta,
|
||||||
|
(tx, rx),
|
||||||
|
UART_BAUDRATE.Hz(),
|
||||||
|
);
|
||||||
|
let (tx, rx) = irq_uart.split();
|
||||||
|
let mut rx = rx.into_rx_with_irq(&mut dp.sysconfig, &mut dp.irqsel, pac::interrupt::OC0);
|
||||||
|
|
||||||
|
let verif_reporter = VerificationReportCreator::new(0).unwrap();
|
||||||
|
|
||||||
|
let (buf_prod_tm, buf_cons_tm) = unsafe { BUF_RB_TM.split_ref() };
|
||||||
|
let (sizes_prod_tm, sizes_cons_tm) = unsafe { SIZES_RB_TM.split_ref() };
|
||||||
|
|
||||||
|
let (buf_prod_tc, buf_cons_tc) = unsafe { BUF_RB_TC.split_ref() };
|
||||||
|
let (sizes_prod_tc, sizes_cons_tc) = unsafe { SIZES_RB_TC.split_ref() };
|
||||||
|
|
||||||
|
let mut rx_context = IrqContextTimeoutOrMaxSize::new(MAX_TC_FRAME_SIZE);
|
||||||
|
rx.read_fixed_len_or_timeout_based_using_irq(&mut rx_context)
|
||||||
|
.expect("initiating UART RX failed");
|
||||||
|
pus_tc_handler::spawn().unwrap();
|
||||||
|
pus_tm_tx_handler::spawn().unwrap();
|
||||||
|
(
|
||||||
|
Shared {
|
||||||
|
tm_prod: DataProducer {
|
||||||
|
buf_prod: buf_prod_tm,
|
||||||
|
sizes_prod: sizes_prod_tm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Local {
|
||||||
|
uart_rx: rx,
|
||||||
|
uart_tx: tx,
|
||||||
|
rx_context,
|
||||||
|
tm_cons: DataConsumer {
|
||||||
|
buf_cons: buf_cons_tm,
|
||||||
|
sizes_cons: sizes_cons_tm,
|
||||||
|
},
|
||||||
|
tc_cons: DataConsumer {
|
||||||
|
buf_cons: buf_cons_tc,
|
||||||
|
sizes_cons: sizes_cons_tc,
|
||||||
|
},
|
||||||
|
tc_prod: DataProducer {
|
||||||
|
buf_prod: buf_prod_tc,
|
||||||
|
sizes_prod: sizes_prod_tc,
|
||||||
|
},
|
||||||
|
verif_reporter,
|
||||||
|
nvm,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `shared` cannot be accessed from this context
|
||||||
|
#[idle]
|
||||||
|
fn idle(_cx: idle::Context) -> ! {
|
||||||
|
loop {
|
||||||
|
asm::nop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the interrupt handler to read all bytes received on the UART0.
|
||||||
|
#[task(
|
||||||
|
binds = OC0,
|
||||||
|
local = [
|
||||||
|
cnt: u32 = 0,
|
||||||
|
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
|
||||||
|
rx_context,
|
||||||
|
uart_rx,
|
||||||
|
tc_prod
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
fn uart_rx_irq(cx: uart_rx_irq::Context) {
|
||||||
|
match cx
|
||||||
|
.local
|
||||||
|
.uart_rx
|
||||||
|
.irq_handler_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
|
||||||
|
{
|
||||||
|
Ok(result) => {
|
||||||
|
if RX_DEBUGGING {
|
||||||
|
log::debug!("RX Info: {:?}", cx.local.rx_context);
|
||||||
|
log::debug!("RX Result: {:?}", result);
|
||||||
|
}
|
||||||
|
if result.complete() {
|
||||||
|
// Check frame validity (must have COBS format) and decode the frame.
|
||||||
|
// Currently, we expect a full frame or a frame received through a timeout
|
||||||
|
// to be one COBS frame. We could parse for multiple COBS packets in one
|
||||||
|
// frame, but the additional complexity is not necessary here..
|
||||||
|
if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
|
||||||
|
let decoded_size =
|
||||||
|
cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
|
||||||
|
if decoded_size.is_err() {
|
||||||
|
log::warn!("COBS decoding failed");
|
||||||
|
} else {
|
||||||
|
let decoded_size = decoded_size.unwrap();
|
||||||
|
if cx.local.tc_prod.sizes_prod.vacant_len() >= 1
|
||||||
|
&& cx.local.tc_prod.buf_prod.vacant_len() >= decoded_size
|
||||||
|
{
|
||||||
|
// Should never fail, we checked there is enough space.
|
||||||
|
cx.local.tc_prod.sizes_prod.try_push(decoded_size).unwrap();
|
||||||
|
cx.local
|
||||||
|
.tc_prod
|
||||||
|
.buf_prod
|
||||||
|
.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
|
||||||
|
} else {
|
||||||
|
log::warn!("COBS TC queue full");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("COBS frame with invalid format, start and end bytes are not 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiate next transfer.
|
||||||
|
cx.local
|
||||||
|
.uart_rx
|
||||||
|
.read_fixed_len_or_timeout_based_using_irq(cx.local.rx_context)
|
||||||
|
.expect("read operation failed");
|
||||||
|
}
|
||||||
|
if result.has_errors() {
|
||||||
|
log::warn!("UART error: {:?}", result.errors.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("UART error: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(
|
||||||
|
priority = 2,
|
||||||
|
local=[
|
||||||
|
tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
|
||||||
|
readback_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
|
||||||
|
src_data_buf: [u8; 16] = [0; 16],
|
||||||
|
verif_buf: [u8; 32] = [0; 32],
|
||||||
|
tc_cons,
|
||||||
|
nvm,
|
||||||
|
verif_reporter
|
||||||
|
],
|
||||||
|
shared=[tm_prod]
|
||||||
|
)]
|
||||||
|
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
|
||||||
|
loop {
|
||||||
|
// Try to read a TC from the ring buffer.
|
||||||
|
let packet_len = cx.local.tc_cons.sizes_cons.try_pop();
|
||||||
|
if packet_len.is_none() {
|
||||||
|
// Small delay, TCs might arrive very quickly.
|
||||||
|
Mono::delay(20.millis()).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let packet_len = packet_len.unwrap();
|
||||||
|
log::info!(target: "TC Handler", "received packet with length {}", packet_len);
|
||||||
|
assert_eq!(
|
||||||
|
cx.local
|
||||||
|
.tc_cons
|
||||||
|
.buf_cons
|
||||||
|
.pop_slice(&mut cx.local.tc_buf[0..packet_len]),
|
||||||
|
packet_len
|
||||||
|
);
|
||||||
|
// Read a telecommand, now handle it.
|
||||||
|
handle_valid_pus_tc(&mut cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
|
||||||
|
let pus_tc = PusTcReader::new(cx.local.tc_buf);
|
||||||
|
if pus_tc.is_err() {
|
||||||
|
log::warn!(target: "TC Handler", "PUS TC error: {}", pus_tc.unwrap_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (pus_tc, _) = pus_tc.unwrap();
|
||||||
|
let mut write_and_send = |tm: &PusTmCreator| {
|
||||||
|
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
|
||||||
|
cx.shared.tm_prod.lock(|prod| {
|
||||||
|
prod.sizes_prod.try_push(tm.len_written()).unwrap();
|
||||||
|
prod.buf_prod
|
||||||
|
.push_slice(&cx.local.verif_buf[0..written_size]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let token = cx.local.verif_reporter.add_tc(&pus_tc);
|
||||||
|
let (tm, accepted_token) = cx
|
||||||
|
.local
|
||||||
|
.verif_reporter
|
||||||
|
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
|
||||||
|
.expect("acceptance success failed");
|
||||||
|
write_and_send(&tm);
|
||||||
|
|
||||||
|
let (tm, started_token) = cx
|
||||||
|
.local
|
||||||
|
.verif_reporter
|
||||||
|
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
|
||||||
|
.expect("acceptance success failed");
|
||||||
|
write_and_send(&tm);
|
||||||
|
|
||||||
|
if pus_tc.service() == PusServiceId::Action as u8 {
|
||||||
|
let mut corrupt_image = |base_addr: u32| {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
cx.local
|
||||||
|
.nvm
|
||||||
|
.read(base_addr as usize + 32, &mut buf)
|
||||||
|
.expect("reading from NVM failed");
|
||||||
|
buf[0] += 1;
|
||||||
|
cx.local
|
||||||
|
.nvm
|
||||||
|
.write(base_addr as usize + 32, &buf)
|
||||||
|
.expect("writing to NVM failed");
|
||||||
|
let tm = cx
|
||||||
|
.local
|
||||||
|
.verif_reporter
|
||||||
|
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
||||||
|
.expect("completion success failed");
|
||||||
|
write_and_send(&tm);
|
||||||
|
};
|
||||||
|
if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
|
||||||
|
rprintln!("corrupting App Image A");
|
||||||
|
corrupt_image(APP_A_START_ADDR);
|
||||||
|
}
|
||||||
|
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
|
||||||
|
rprintln!("corrupting App Image B");
|
||||||
|
corrupt_image(APP_B_START_ADDR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
|
||||||
|
log::info!(target: "TC Handler", "received ping TC");
|
||||||
|
let tm = cx
|
||||||
|
.local
|
||||||
|
.verif_reporter
|
||||||
|
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
||||||
|
.expect("completion success failed");
|
||||||
|
write_and_send(&tm);
|
||||||
|
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
|
||||||
|
let tm = cx
|
||||||
|
.local
|
||||||
|
.verif_reporter
|
||||||
|
.step_success(
|
||||||
|
cx.local.src_data_buf,
|
||||||
|
&started_token,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&[],
|
||||||
|
EcssEnumU8::new(0),
|
||||||
|
)
|
||||||
|
.expect("step success failed");
|
||||||
|
write_and_send(&tm);
|
||||||
|
// Raw memory write TC
|
||||||
|
if pus_tc.subservice() == 2 {
|
||||||
|
let app_data = pus_tc.app_data();
|
||||||
|
if app_data.len() < 10 {
|
||||||
|
log::warn!(
|
||||||
|
target: "TC Handler",
|
||||||
|
"app data for raw memory write is too short: {}",
|
||||||
|
app_data.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let memory_id = app_data[0];
|
||||||
|
if memory_id != BOOT_NVM_MEMORY_ID {
|
||||||
|
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id);
|
||||||
|
// TODO: Error reporting
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap());
|
||||||
|
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap());
|
||||||
|
if 10 + data_len as usize > app_data.len() {
|
||||||
|
log::warn!(
|
||||||
|
target: "TC Handler",
|
||||||
|
"invalid data length {} for raw mem write detected",
|
||||||
|
data_len
|
||||||
|
);
|
||||||
|
// TODO: Error reporting
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = &app_data[10..10 + data_len as usize];
|
||||||
|
log::info!(
|
||||||
|
target: "TC Handler",
|
||||||
|
"writing {} bytes at offset {} to NVM",
|
||||||
|
data_len,
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
cx.local
|
||||||
|
.nvm
|
||||||
|
.write(offset as usize, data)
|
||||||
|
.expect("writing to NVM failed");
|
||||||
|
let tm = if !cx
|
||||||
|
.local
|
||||||
|
.nvm
|
||||||
|
.verify(offset as usize, data)
|
||||||
|
.expect("NVM verification failed")
|
||||||
|
{
|
||||||
|
log::warn!("verification of data written to NVM failed");
|
||||||
|
cx.local
|
||||||
|
.verif_reporter
|
||||||
|
.completion_failure(
|
||||||
|
cx.local.src_data_buf,
|
||||||
|
started_token,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
FailParams::new(&[], &EcssEnumU8::new(0), &[]),
|
||||||
|
)
|
||||||
|
.expect("completion success failed")
|
||||||
|
} else {
|
||||||
|
cx.local
|
||||||
|
.verif_reporter
|
||||||
|
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
||||||
|
.expect("completion success failed")
|
||||||
|
};
|
||||||
|
write_and_send(&tm);
|
||||||
|
log::info!(
|
||||||
|
target: "TC Handler",
|
||||||
|
"NVM operation done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(
|
||||||
|
priority = 1,
|
||||||
|
local=[
|
||||||
|
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
|
||||||
|
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
|
||||||
|
uart_tx,
|
||||||
|
tm_cons
|
||||||
|
],
|
||||||
|
shared=[]
|
||||||
|
)]
|
||||||
|
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
|
||||||
|
loop {
|
||||||
|
while cx.local.tm_cons.sizes_cons.occupied_len() > 0 {
|
||||||
|
let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap();
|
||||||
|
cx.local
|
||||||
|
.tm_cons
|
||||||
|
.buf_cons
|
||||||
|
.pop_slice(&mut cx.local.read_buf[0..next_size]);
|
||||||
|
cx.local.encoded_buf[0] = 0;
|
||||||
|
let send_size = cobs::encode(
|
||||||
|
&cx.local.read_buf[0..next_size],
|
||||||
|
&mut cx.local.encoded_buf[1..],
|
||||||
|
);
|
||||||
|
cx.local.encoded_buf[send_size + 1] = 0;
|
||||||
|
cx.local
|
||||||
|
.uart_tx
|
||||||
|
.write(&cx.local.encoded_buf[0..send_size + 2])
|
||||||
|
.unwrap();
|
||||||
|
Mono::delay(2.millis()).await;
|
||||||
|
}
|
||||||
|
Mono::delay(50.millis()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
scripts/memory_app_a.x
Normal file
10
scripts/memory_app_a.x
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH : ORIGIN = 0x00003000, LENGTH = 0xE7F8 /* (128k - 12k) / 2 - 8 */
|
||||||
|
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 32K */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is where the call stack will be allocated. */
|
||||||
|
/* The stack is of the full descending type. */
|
||||||
|
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
||||||
|
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
10
scripts/memory_app_b.x
Normal file
10
scripts/memory_app_b.x
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH : ORIGIN = 0x00011800, LENGTH = 0xE7F8 /* (128k - 12k) / 2 - 8 */
|
||||||
|
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 32K */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is where the call stack will be allocated. */
|
||||||
|
/* The stack is of the full descending type. */
|
||||||
|
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
||||||
|
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now
|
- Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now
|
||||||
only expects a configuration structure and the transfer configuration needs to be applied in a
|
only expects a configuration structure and the transfer configuration needs to be applied in a
|
||||||
separate step.
|
separate step.
|
||||||
|
- Removed complete `timer` module re-export in `pwm` module
|
||||||
|
- `CountDownTimer` renamed to `CountdownTimer`
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
|
@ -9,8 +9,11 @@ use core::convert::Infallible;
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use crate::pac;
|
use crate::pac;
|
||||||
|
use crate::timer::{
|
||||||
|
TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTim, ValidTimAndPin,
|
||||||
|
};
|
||||||
use crate::{clock::enable_peripheral_clock, gpio::DynPinId};
|
use crate::{clock::enable_peripheral_clock, gpio::DynPinId};
|
||||||
pub use crate::{gpio::PinId, time::Hertz, timer::*};
|
pub use crate::{gpio::PinId, time::Hertz};
|
||||||
|
|
||||||
const DUTY_MAX: u16 = u16::MAX;
|
const DUTY_MAX: u16 = u16::MAX;
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ unsafe impl TimRegInterface for TimDynRegister {
|
|||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
/// Hardware timers
|
/// Hardware timers
|
||||||
pub struct CountDownTimer<TIM: ValidTim> {
|
pub struct CountdownTimer<TIM: ValidTim> {
|
||||||
tim: TimRegister<TIM>,
|
tim: TimRegister<TIM>,
|
||||||
curr_freq: Hertz,
|
curr_freq: Hertz,
|
||||||
irq_cfg: Option<IrqCfg>,
|
irq_cfg: Option<IrqCfg>,
|
||||||
@ -395,17 +395,17 @@ pub fn disable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
|
|||||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << idx)) });
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << idx)) });
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<TIM: ValidTim> TimRegInterface for CountDownTimer<TIM> {
|
unsafe impl<TIM: ValidTim> TimRegInterface for CountdownTimer<TIM> {
|
||||||
fn tim_id(&self) -> u8 {
|
fn tim_id(&self) -> u8 {
|
||||||
TIM::TIM_ID
|
TIM::TIM_ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TIM: ValidTim> CountDownTimer<TIM> {
|
impl<TIM: ValidTim> CountdownTimer<TIM> {
|
||||||
/// Configures a TIM peripheral as a periodic count down timer
|
/// Configures a TIM peripheral as a periodic count down timer
|
||||||
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, tim: TIM) -> Self {
|
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, tim: TIM) -> Self {
|
||||||
enable_tim_clk(syscfg, TIM::TIM_ID);
|
enable_tim_clk(syscfg, TIM::TIM_ID);
|
||||||
let cd_timer = CountDownTimer {
|
let cd_timer = CountdownTimer {
|
||||||
tim: unsafe { TimRegister::new(tim) },
|
tim: unsafe { TimRegister::new(tim) },
|
||||||
sys_clk: sys_clk.into(),
|
sys_clk: sys_clk.into(),
|
||||||
irq_cfg: None,
|
irq_cfg: None,
|
||||||
@ -614,7 +614,7 @@ impl<TIM: ValidTim> CountDownTimer<TIM> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// CountDown implementation for TIMx
|
/// CountDown implementation for TIMx
|
||||||
impl<TIM: ValidTim> CountDownTimer<TIM> {
|
impl<TIM: ValidTim> CountdownTimer<TIM> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn start<T>(&mut self, timeout: T)
|
pub fn start<T>(&mut self, timeout: T)
|
||||||
where
|
where
|
||||||
@ -647,7 +647,7 @@ impl<TIM: ValidTim> CountDownTimer<TIM> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TIM: ValidTim> embedded_hal::delay::DelayNs for CountDownTimer<TIM> {
|
impl<TIM: ValidTim> embedded_hal::delay::DelayNs for CountdownTimer<TIM> {
|
||||||
fn delay_ns(&mut self, ns: u32) {
|
fn delay_ns(&mut self, ns: u32) {
|
||||||
let ticks = (u64::from(ns)) * (u64::from(self.sys_clk.raw())) / 1_000_000_000;
|
let ticks = (u64::from(ns)) * (u64::from(self.sys_clk.raw())) / 1_000_000_000;
|
||||||
|
|
||||||
@ -709,8 +709,8 @@ pub fn set_up_ms_tick<TIM: ValidTim>(
|
|||||||
irq_sel: Option<&mut pac::Irqsel>,
|
irq_sel: Option<&mut pac::Irqsel>,
|
||||||
sys_clk: impl Into<Hertz>,
|
sys_clk: impl Into<Hertz>,
|
||||||
tim0: TIM,
|
tim0: TIM,
|
||||||
) -> CountDownTimer<TIM> {
|
) -> CountdownTimer<TIM> {
|
||||||
let mut ms_timer = CountDownTimer::new(sys_cfg, sys_clk, tim0);
|
let mut ms_timer = CountdownTimer::new(sys_cfg, sys_clk, tim0);
|
||||||
ms_timer.listen(timer::Event::TimeOut, irq_cfg, irq_sel, Some(sys_cfg));
|
ms_timer.listen(timer::Event::TimeOut, irq_cfg, irq_sel, Some(sys_cfg));
|
||||||
ms_timer.start(1000.Hz());
|
ms_timer.start(1000.Hz());
|
||||||
ms_timer
|
ms_timer
|
||||||
@ -720,8 +720,8 @@ pub fn set_up_ms_delay_provider<TIM: ValidTim>(
|
|||||||
sys_cfg: &mut pac::Sysconfig,
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
sys_clk: impl Into<Hertz>,
|
sys_clk: impl Into<Hertz>,
|
||||||
tim: TIM,
|
tim: TIM,
|
||||||
) -> CountDownTimer<TIM> {
|
) -> CountdownTimer<TIM> {
|
||||||
let mut provider = CountDownTimer::new(sys_cfg, sys_clk, tim);
|
let mut provider = CountdownTimer::new(sys_cfg, sys_clk, tim);
|
||||||
provider.start(1000.Hz());
|
provider.start(1000.Hz());
|
||||||
provider
|
provider
|
||||||
}
|
}
|
||||||
@ -745,10 +745,10 @@ pub fn get_ms_ticks() -> u32 {
|
|||||||
// Delay implementations
|
// Delay implementations
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
pub struct DelayMs(CountDownTimer<pac::Tim0>);
|
pub struct DelayMs(CountdownTimer<pac::Tim0>);
|
||||||
|
|
||||||
impl DelayMs {
|
impl DelayMs {
|
||||||
pub fn new(timer: CountDownTimer<pac::Tim0>) -> Option<Self> {
|
pub fn new(timer: CountdownTimer<pac::Tim0>) -> Option<Self> {
|
||||||
if timer.curr_freq() != Hertz::from_raw(1000) || !timer.listening() {
|
if timer.curr_freq() != Hertz::from_raw(1000) || !timer.listening() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ 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::{OptionalHwCs, SpiClkConfig};
|
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},
|
||||||
@ -154,7 +154,7 @@ fn main() -> ! {
|
|||||||
spi_cfg,
|
spi_cfg,
|
||||||
)
|
)
|
||||||
.downgrade();
|
.downgrade();
|
||||||
let delay_provider = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
|
let delay_provider = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
|
||||||
let spi_with_hwcs = SpiWithHwCs::new(spi, pinsa.pa17.into_funsel_2(), delay_provider);
|
let spi_with_hwcs = SpiWithHwCs::new(spi, pinsa.pa17.into_funsel_2(), delay_provider);
|
||||||
match EXAMPLE_MODE {
|
match EXAMPLE_MODE {
|
||||||
ExampleMode::NotUsingEoc => spi_example_externally_clocked(spi_with_hwcs, delay),
|
ExampleMode::NotUsingEoc => spi_example_externally_clocked(spi_with_hwcs, delay),
|
||||||
@ -162,7 +162,7 @@ fn main() -> ! {
|
|||||||
spi_example_internally_clocked(spi_with_hwcs, delay, pinsa.pa14.into_floating_input());
|
spi_example_internally_clocked(spi_with_hwcs, delay, pinsa.pa14.into_floating_input());
|
||||||
}
|
}
|
||||||
ExampleMode::NotUsingEocWithDelay => {
|
ExampleMode::NotUsingEocWithDelay => {
|
||||||
let delay_us = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim2);
|
let delay_us = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim2);
|
||||||
spi_example_externally_clocked_with_delay(spi_with_hwcs, delay, delay_us);
|
spi_example_externally_clocked_with_delay(spi_with_hwcs, delay, delay_us);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
//! Example application which interfaces with the boot EEPROM.
|
//! Example application which interfaces with the boot EEPROM.
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use va108xx_hal::{pac, pwm::CountDownTimer, time::Hertz};
|
use va108xx_hal::{pac, time::Hertz, timer::CountdownTimer};
|
||||||
use vorago_reb1::m95m01::M95M01;
|
use vorago_reb1::m95m01::{M95M01, PAGE_SIZE};
|
||||||
|
|
||||||
const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
|
const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
|
||||||
|
|
||||||
@ -18,46 +17,39 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
|
||||||
let mut timer = CountDownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
|
let mut timer = CountdownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
|
||||||
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
|
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
|
||||||
let status_reg = nvm.read_status_reg().expect("reading status reg failed");
|
let status_reg = nvm.read_status_reg().expect("reading status reg failed");
|
||||||
if status_reg.zero_segment() == 0b111 {
|
if status_reg.zero_segment() == 0b111 {
|
||||||
panic!("status register unexpected values");
|
panic!("status register unexpected values");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut orig_content: [u8; 16] = [0; 16];
|
let mut orig_content: [u8; 512] = [0; 512];
|
||||||
let mut read_buf: [u8; 16] = [0; 16];
|
let mut read_buf: [u8; 512] = [0; 512];
|
||||||
let write_buf: [u8; 16] = [0; 16];
|
let mut write_buf: [u8; 512] = [0; 512];
|
||||||
for (idx, val) in read_buf.iter_mut().enumerate() {
|
for (idx, val) in write_buf.iter_mut().enumerate() {
|
||||||
*val = idx as u8;
|
*val = ((idx as u16) % (u8::MAX as u16 + 1)) as u8;
|
||||||
}
|
}
|
||||||
nvm.read(0x4000, &mut orig_content).unwrap();
|
nvm.read(0, &mut orig_content).unwrap();
|
||||||
|
|
||||||
// One byte write and read.
|
nvm.write_page(0, 0, &[1, 2, 3, 4]).unwrap();
|
||||||
nvm.write(0x4000, &write_buf[0..1]).unwrap();
|
nvm.read(0, &mut read_buf[0..4]).unwrap();
|
||||||
nvm.read(0x4000, &mut read_buf[0..1]).unwrap();
|
|
||||||
assert_eq!(write_buf[0], read_buf[0]);
|
|
||||||
read_buf.fill(0);
|
|
||||||
|
|
||||||
// Four bytes write and read.
|
// Read the whole content. Write will internally be split across two page bounaries.
|
||||||
nvm.write(0x4000, &write_buf[0..4]).unwrap();
|
nvm.write(0, &write_buf).unwrap();
|
||||||
nvm.read(0x4000, &mut read_buf[0..4]).unwrap();
|
// Memory can be read in one go.
|
||||||
assert_eq!(&read_buf[0..4], &write_buf[0..4]);
|
nvm.read(0, &mut read_buf).unwrap();
|
||||||
read_buf.fill(0);
|
|
||||||
|
|
||||||
// Full sixteen bytes
|
|
||||||
nvm.write(0x4000, &write_buf).unwrap();
|
|
||||||
nvm.read(0x4000, &mut read_buf).unwrap();
|
|
||||||
assert_eq!(&read_buf, &write_buf);
|
assert_eq!(&read_buf, &write_buf);
|
||||||
|
assert!(nvm.verify(0, &write_buf).unwrap());
|
||||||
read_buf.fill(0);
|
read_buf.fill(0);
|
||||||
|
|
||||||
// 3 bytes
|
// Write along page boundary
|
||||||
nvm.write(0x4000, &write_buf[0..3]).unwrap();
|
nvm.write(PAGE_SIZE - 2, &write_buf[0..8]).unwrap();
|
||||||
nvm.read(0x4000, &mut read_buf[0..3]).unwrap();
|
nvm.read(PAGE_SIZE - 2, &mut read_buf[0..8]).unwrap();
|
||||||
assert_eq!(&read_buf[0..3], &write_buf[0..3]);
|
assert_eq!(&read_buf[0..8], &write_buf[0..8]);
|
||||||
|
assert!(nvm.verify(PAGE_SIZE - 2, &write_buf[0..8]).unwrap());
|
||||||
|
|
||||||
// Write back original content.
|
nvm.write(0, &orig_content).unwrap();
|
||||||
nvm.write(0x4000, &orig_content).unwrap();
|
|
||||||
loop {
|
loop {
|
||||||
timer.delay_ms(500);
|
timer.delay_ms(500);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use embedded_hal::spi::SpiBus;
|
use embedded_hal::spi::SpiBus;
|
||||||
|
|
||||||
|
pub const PAGE_SIZE: usize = 256;
|
||||||
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
pub struct StatusReg(u8);
|
pub struct StatusReg(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
@ -41,7 +43,7 @@ use regs::*;
|
|||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
pac,
|
pac,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
spi::{RomMiso, RomMosi, RomSck, Spi, SpiConfig, BMSTART_BMSTOP_MASK},
|
spi::{RomMiso, RomMosi, RomSck, Spi, SpiClkConfig, SpiConfig, BMSTART_BMSTOP_MASK},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type RomSpi = Spi<pac::Spic, (RomSck, RomMiso, RomMosi), u8>;
|
pub type RomSpi = Spi<pac::Spic, (RomSck, RomMiso, RomMosi), u8>;
|
||||||
@ -53,6 +55,9 @@ pub struct M95M01 {
|
|||||||
pub spi: RomSpi,
|
pub spi: RomSpi,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct PageBoundaryExceededError;
|
||||||
|
|
||||||
impl M95M01 {
|
impl M95M01 {
|
||||||
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, spi: pac::Spic) -> Self {
|
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, spi: pac::Spic) -> Self {
|
||||||
let spi = RomSpi::new(
|
let spi = RomSpi::new(
|
||||||
@ -60,7 +65,7 @@ impl M95M01 {
|
|||||||
sys_clk,
|
sys_clk,
|
||||||
spi,
|
spi,
|
||||||
(RomSck, RomMiso, RomMosi),
|
(RomSck, RomMiso, RomMosi),
|
||||||
SpiConfig::default(),
|
SpiConfig::default().clk_cfg(SpiClkConfig::new(2, 4)),
|
||||||
);
|
);
|
||||||
let mut spi_dev = Self { spi };
|
let mut spi_dev = Self { spi };
|
||||||
spi_dev.clear_block_protection().unwrap();
|
spi_dev.clear_block_protection().unwrap();
|
||||||
@ -105,7 +110,7 @@ impl M95M01 {
|
|||||||
self.spi.write(&[WRSR, reg.0])
|
self.spi.write(&[WRSR, reg.0])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn common_init_write_and_read(&mut self, address: u32, reg: u8) -> Result<(), Infallible> {
|
fn common_init_write_and_read(&mut self, address: usize, reg: u8) -> Result<(), Infallible> {
|
||||||
nb::block!(self.writes_are_done())?;
|
nb::block!(self.writes_are_done())?;
|
||||||
self.spi.flush()?;
|
self.spi.flush()?;
|
||||||
if reg == WRITE {
|
if reg == WRITE {
|
||||||
@ -114,41 +119,78 @@ impl M95M01 {
|
|||||||
} else {
|
} else {
|
||||||
self.spi.write_fifo_unchecked(READ as u32);
|
self.spi.write_fifo_unchecked(READ as u32);
|
||||||
}
|
}
|
||||||
self.spi.write_fifo_unchecked((address >> 16) & 0xff);
|
self.spi.write_fifo_unchecked((address as u32 >> 16) & 0xff);
|
||||||
self.spi.write_fifo_unchecked((address >> 8) & 0xff);
|
self.spi
|
||||||
self.spi.write_fifo_unchecked(address & 0xff);
|
.write_fifo_unchecked((address as u32 & 0x00ff00) >> 8);
|
||||||
|
self.spi.write_fifo_unchecked(address as u32 & 0xff);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn common_read(&mut self, address: u32) -> Result<(), Infallible> {
|
fn common_read(&mut self, address: usize) -> Result<(), Infallible> {
|
||||||
self.common_init_write_and_read(address, READ)?;
|
self.common_init_write_and_read(address, READ)?;
|
||||||
for _ in 0..4 {
|
for _ in 0..4 {
|
||||||
// Pump the FIFO.
|
// Pump the FIFO.
|
||||||
self.spi.write_fifo_unchecked(0);
|
self.spi.write_fifo_unchecked(0);
|
||||||
// Ignore the first 4 bytes.
|
// Ignore the first 4 bytes.
|
||||||
self.spi.read_fifo_unchecked();
|
nb::block!(self.spi.read_fifo())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Infallible> {
|
pub fn write(&mut self, mut address: usize, mut data: &[u8]) -> Result<(), Infallible> {
|
||||||
self.common_init_write_and_read(address, WRITE)?;
|
// Loop until all data is written
|
||||||
|
while !data.is_empty() {
|
||||||
|
// Calculate the page and the offset within the page from the address
|
||||||
|
let page = address / PAGE_SIZE;
|
||||||
|
let offset = address % PAGE_SIZE;
|
||||||
|
|
||||||
|
// Calculate how much space is left in the current page
|
||||||
|
let space_left = PAGE_SIZE - offset;
|
||||||
|
|
||||||
|
// Determine how much data to write in the current page
|
||||||
|
let to_write = data.len().min(space_left);
|
||||||
|
|
||||||
|
// Write the current portion of the data
|
||||||
|
self.write_page(page, offset, &data[..to_write]).unwrap();
|
||||||
|
|
||||||
|
// Update the address and data for the next iteration
|
||||||
|
address += to_write;
|
||||||
|
data = &data[to_write..];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_page(
|
||||||
|
&mut self,
|
||||||
|
page: usize,
|
||||||
|
offset: usize,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), PageBoundaryExceededError> {
|
||||||
|
// Check that the total data to be written does not exceed the page boundary
|
||||||
|
if offset + data.len() > PAGE_SIZE {
|
||||||
|
return Err(PageBoundaryExceededError);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.common_init_write_and_read(page * PAGE_SIZE + offset, WRITE)
|
||||||
|
.unwrap();
|
||||||
for val in data.iter().take(data.len() - 1) {
|
for val in data.iter().take(data.len() - 1) {
|
||||||
nb::block!(self.spi.write_fifo(*val as u32))?;
|
nb::block!(self.spi.write_fifo(*val as u32)).unwrap();
|
||||||
self.spi.read_fifo_unchecked();
|
nb::block!(self.spi.read_fifo()).unwrap();
|
||||||
}
|
}
|
||||||
nb::block!(self
|
nb::block!(self
|
||||||
.spi
|
.spi
|
||||||
.write_fifo(*data.last().unwrap() as u32 | BMSTART_BMSTOP_MASK))?;
|
.write_fifo(*data.last().unwrap() as u32 | BMSTART_BMSTOP_MASK))
|
||||||
self.spi.flush()?;
|
.unwrap();
|
||||||
nb::block!(self.writes_are_done())?;
|
self.spi.flush().unwrap();
|
||||||
|
nb::block!(self.writes_are_done()).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), Infallible> {
|
pub fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), Infallible> {
|
||||||
self.common_read(address)?;
|
self.common_read(address)?;
|
||||||
for val in buf.iter_mut() {
|
for val in buf.iter_mut() {
|
||||||
nb::block!(self.spi.write_fifo(0))?;
|
self.spi.write_fifo_unchecked(0);
|
||||||
*val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8;
|
*val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8;
|
||||||
}
|
}
|
||||||
nb::block!(self.spi.write_fifo(BMSTART_BMSTOP_MASK))?;
|
nb::block!(self.spi.write_fifo(BMSTART_BMSTOP_MASK))?;
|
||||||
@ -156,10 +198,10 @@ impl M95M01 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, Infallible> {
|
pub fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, Infallible> {
|
||||||
self.common_read(address)?;
|
self.common_read(address)?;
|
||||||
for val in data.iter() {
|
for val in data.iter() {
|
||||||
nb::block!(self.spi.write_fifo(0))?;
|
self.spi.write_fifo_unchecked(0);
|
||||||
let read_val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8;
|
let read_val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8;
|
||||||
if read_val != *val {
|
if read_val != *val {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
|
@ -222,6 +222,30 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "UART Echo with RTIC",
|
||||||
|
"servertype": "jlink",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"device": "Cortex-M0",
|
||||||
|
"svdFile": "./va108xx/svd/va108xx-base.svd.patched",
|
||||||
|
"preLaunchTask": "uart-echo-rtic-example",
|
||||||
|
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/uart-echo-rtic",
|
||||||
|
"interface": "jtag",
|
||||||
|
"runToEntryPoint": "main",
|
||||||
|
"rttConfig": {
|
||||||
|
"enabled": true,
|
||||||
|
"address": "auto",
|
||||||
|
"decoders": [
|
||||||
|
{
|
||||||
|
"port": 0,
|
||||||
|
"timestamp": true,
|
||||||
|
"type": "console"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "cortex-debug",
|
"type": "cortex-debug",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
@ -303,7 +327,7 @@
|
|||||||
"device": "Cortex-M0",
|
"device": "Cortex-M0",
|
||||||
"svdFile": "./va108xx/svd/va108xx.svd.patched",
|
"svdFile": "./va108xx/svd/va108xx.svd.patched",
|
||||||
"preLaunchTask": "blinky-hal",
|
"preLaunchTask": "blinky-hal",
|
||||||
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/adxl343-accelerometer",
|
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/blinky",
|
||||||
"interface": "jtag",
|
"interface": "jtag",
|
||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
},
|
},
|
||||||
@ -427,5 +451,53 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Bootloader",
|
||||||
|
"servertype": "jlink",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"device": "Cortex-M0",
|
||||||
|
"svdFile": "./va108xx/svd/va108xx.svd.patched",
|
||||||
|
"preLaunchTask": "bootloader",
|
||||||
|
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/release/bootloader",
|
||||||
|
"interface": "jtag",
|
||||||
|
"runToEntryPoint": "main",
|
||||||
|
"rttConfig": {
|
||||||
|
"enabled": true,
|
||||||
|
"address": "auto",
|
||||||
|
"decoders": [
|
||||||
|
{
|
||||||
|
"port": 0,
|
||||||
|
"timestamp": true,
|
||||||
|
"type": "console"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Flashloader",
|
||||||
|
"servertype": "jlink",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"device": "Cortex-M0",
|
||||||
|
"svdFile": "./va108xx/svd/va108xx.svd.patched",
|
||||||
|
"preLaunchTask": "flashloader",
|
||||||
|
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/release/flashloader",
|
||||||
|
"interface": "jtag",
|
||||||
|
"runToEntryPoint": "main",
|
||||||
|
"rttConfig": {
|
||||||
|
"enabled": true,
|
||||||
|
"address": "auto",
|
||||||
|
"decoders": [
|
||||||
|
{
|
||||||
|
"port": 0,
|
||||||
|
"timestamp": true,
|
||||||
|
"type": "console"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -38,7 +38,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"rtt-log",
|
"rtt-log"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -68,7 +68,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"uart",
|
"uart"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -82,7 +82,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"spi",
|
"spi"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -122,13 +122,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "rust: cargo build uart irq",
|
"label": "uart-echo-rtic-example",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--bin",
|
"--bin",
|
||||||
"uart-rtic",
|
"uart-echo-rtic"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -142,10 +142,10 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"blinky",
|
"blinky"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -155,7 +155,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"blinky-leds",
|
"blinky-leds"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -169,7 +169,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"blinky-button-irq",
|
"blinky-button-irq"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -183,7 +183,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"adt75-temp-sensor",
|
"adt75-temp-sensor"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -197,7 +197,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"blinky-button-rtic",
|
"blinky-button-rtic"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -225,7 +225,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"max11619-adc",
|
"max11619-adc"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -239,7 +239,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--example",
|
"--example",
|
||||||
"nvm",
|
"nvm"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@ -253,8 +253,8 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--bin",
|
"--bin",
|
||||||
"rtic-example",
|
"rtic-example"
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "embassy-example",
|
"label": "embassy-example",
|
||||||
@ -263,8 +263,30 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--bin",
|
"--bin",
|
||||||
"embassy-example",
|
"embassy-example"
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "bootloader",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin",
|
||||||
|
"bootloader",
|
||||||
|
"--release",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "flashloader",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin",
|
||||||
|
"flashloader",
|
||||||
|
"--release"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user