Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7f7588e0b | |||
| f06919e87c | |||
| ff2485b979 | |||
| 3fc811a999 | |||
| a65b8d1dd3 | |||
| a8cbcda7cc | |||
| 946af6ff85 | |||
| 926fb15822 | |||
| 28c5628163 | |||
| ebd6fa060a | |||
| 03e9e6fa6a | |||
| 1baea3ccb2 | |||
| 027161d45d | |||
| 0d1bd3aa47 | |||
| 09ba2d3675 | |||
| 6225b8d183 | |||
| 1138b65203 | |||
| d36282d34b | |||
| 15fed28281 | |||
| adad36db89 | |||
| e1b3abcba9 |
@@ -36,7 +36,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
- run: cargo nextest run --all-features -p va108xx-hal --no-tests=pass
|
||||
- run: cargo nextest run -p va108xx-hal --no-tests=pass
|
||||
# I think we can skip those on an embedded crate..
|
||||
# - run: cargo test --doc -p va108xx-hal
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx --all-features
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal --all-features --no-deps
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal --features "defmt, embassy-oc30-oc31" --no-deps
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1 --no-deps
|
||||
|
||||
clippy:
|
||||
|
||||
@@ -22,13 +22,12 @@ check-va108xx:
|
||||
[working-directory: 'va416xx']
|
||||
check-va416xx:
|
||||
cargo check --target thumbv7em-none-eabihf
|
||||
cargo check --target thumbv7em-none-eabihf --examples
|
||||
cargo check -p va416xx --target thumbv7em-none-eabihf --all-features
|
||||
cargo check -p va416xx-hal --target thumbv7em-none-eabihf --features "defmt va41630"
|
||||
|
||||
[working-directory: 'va108xx']
|
||||
build-va108xx:
|
||||
cargo build --target thumbv6m-none-eabi
|
||||
cargo build --target thumbv6m-none-eabi --release
|
||||
|
||||
[working-directory: 'va416xx']
|
||||
build-va416xx:
|
||||
@@ -70,7 +69,7 @@ clippy-shared-hal:
|
||||
[working-directory: 'va108xx']
|
||||
docs-va108xx:
|
||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx --all-features
|
||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal --all-features --no-deps
|
||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal --features "defmt, embassy-oc30-oc31" --no-deps
|
||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1 --no-deps
|
||||
|
||||
[working-directory: 'va416xx']
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
output.log
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "va108xx-image-loader"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
fern = "0.7"
|
||||
object = "0.39"
|
||||
humantime = "2"
|
||||
crc = "3"
|
||||
log = "0.4"
|
||||
toml = "1.1.2+spec-1.1.0"
|
||||
anyhow = "1"
|
||||
serialport = "4"
|
||||
cobs = "0.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
postcard = { version = "1", features = ["alloc"] }
|
||||
spacepackets = { version = "0.17" }
|
||||
tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1" }
|
||||
|
||||
models = { path = "../../va108xx/flashloader/models" }
|
||||
@@ -0,0 +1,3 @@
|
||||
# VA108XX Flashloader Client
|
||||
|
||||
See the [flasherloader app](../../va108xx/flashloader/README.md) for more details.
|
||||
@@ -0,0 +1,2 @@
|
||||
[interface]
|
||||
serial_port = "/dev/ttyUSB0"
|
||||
@@ -0,0 +1,92 @@
|
||||
use std::path::Path;
|
||||
|
||||
use models::{
|
||||
APP_A_START_ADDR, APP_A_MAX_SIZE, APP_B_START_ADDR, APP_B_MAX_SIZE,
|
||||
BOOTLOADER_START_ADDR, BOOTLOADER_MAX_SIZE,
|
||||
};
|
||||
|
||||
use crate::Target;
|
||||
|
||||
pub struct LoadableSegment {
|
||||
pub name: String,
|
||||
pub offset: u32,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn parse_elf(target: &Target, path: &Path) -> anyhow::Result<Vec<LoadableSegment>> {
|
||||
use object::{Object, ObjectSection, ObjectSegment};
|
||||
|
||||
let raw = std::fs::read(path)?;
|
||||
let obj = object::File::parse(raw.as_slice())?;
|
||||
|
||||
let expected_base: u32 = match target {
|
||||
Target::Bl => BOOTLOADER_START_ADDR,
|
||||
Target::A => APP_A_START_ADDR,
|
||||
Target::B => APP_B_START_ADDR,
|
||||
};
|
||||
|
||||
let mut segments = Vec::new();
|
||||
|
||||
for (idx, segment) in obj.segments().enumerate() {
|
||||
let data = segment.data()?;
|
||||
if data.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let addr = segment.address() as u32;
|
||||
|
||||
if idx == 0 && addr != expected_base {
|
||||
anyhow::bail!(
|
||||
"unexpected base address {addr:#010x} for {target:?}, expected {expected_base:#010x}"
|
||||
);
|
||||
}
|
||||
|
||||
let name = obj
|
||||
.sections()
|
||||
.find(|s| {
|
||||
let s_addr = s.address() as u32;
|
||||
s_addr >= addr
|
||||
&& s_addr < addr + data.len() as u32
|
||||
&& !s.name().unwrap_or("").is_empty()
|
||||
})
|
||||
.and_then(|s| s.name().ok().map(str::to_owned))
|
||||
.unwrap_or_else(|| format!("<segment {idx}>"));
|
||||
|
||||
segments.push(LoadableSegment {
|
||||
name,
|
||||
offset: addr,
|
||||
data: data.to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(segments)
|
||||
}
|
||||
|
||||
pub fn check_total_size(target: &Target, total_size: usize) -> anyhow::Result<()> {
|
||||
let max = match target {
|
||||
Target::Bl => BOOTLOADER_MAX_SIZE,
|
||||
Target::A => APP_A_MAX_SIZE,
|
||||
Target::B => APP_B_MAX_SIZE,
|
||||
} as usize;
|
||||
|
||||
if total_size > max {
|
||||
anyhow::bail!(
|
||||
"{target:?} image is {total_size} bytes, exceeds maximum of {max} bytes"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log_segments_info(target: &Target, segments: &[LoadableSegment], total_size: usize, path: &Path) {
|
||||
log::info!(
|
||||
"Flashing {target:?} with image '{}' — {} segment(s), {total_size} bytes total",
|
||||
path.display(),
|
||||
segments.len()
|
||||
);
|
||||
for (i, seg) in segments.iter().enumerate() {
|
||||
log::info!(
|
||||
" segment {i}: '{}' @ {:#010x}, {} bytes",
|
||||
seg.name, seg.offset, seg.data.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use crc::{CRC_16_IBM_3740, Crc};
|
||||
use spacepackets::CcsdsPacketReader;
|
||||
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
|
||||
|
||||
use crate::{
|
||||
Target,
|
||||
elf::{check_total_size, log_segments_info, parse_elf},
|
||||
tc,
|
||||
};
|
||||
use models::{
|
||||
APP_A_CRC_ADDR, APP_A_SIZE_ADDR, APP_B_CRC_ADDR, APP_B_SIZE_ADDR, BOOTLOADER_CRC_ADDR,
|
||||
};
|
||||
use models::{Request, Response};
|
||||
|
||||
pub const CHUNK_SIZE: usize = 256;
|
||||
|
||||
fn send_and_await_ok(
|
||||
transport: &mut PacketTransportSerialCobs,
|
||||
request: &Request,
|
||||
payload: &[u8],
|
||||
timeout: std::time::Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
let tc = tc::create_tc_with_additional_payload(request, payload);
|
||||
log::debug!(
|
||||
"TX TC {:#010x}: {request:?} + {} payload bytes",
|
||||
tc.ccsds_packet_id_and_psc().raw(),
|
||||
payload.len()
|
||||
);
|
||||
transport.send(&tc.to_vec())?;
|
||||
|
||||
let deadline = std::time::Instant::now() + timeout;
|
||||
let mut got_ok = false;
|
||||
|
||||
while !got_ok {
|
||||
if std::time::Instant::now() > deadline {
|
||||
anyhow::bail!("timeout waiting for Ok response to {request:?}");
|
||||
}
|
||||
transport.receive(|packet| {
|
||||
let Ok(reader) = CcsdsPacketReader::new_with_checksum(packet) else {
|
||||
return;
|
||||
};
|
||||
match postcard::take_from_bytes::<Response>(reader.packet_data()) {
|
||||
Ok((Response::Ok, _)) => got_ok = true,
|
||||
#[allow(unreachable_patterns)]
|
||||
Ok((other, _)) => log::warn!("unexpected response: {other:?}"),
|
||||
Err(e) => log::error!("failed to deserialise response: {e}"),
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn flash_image(
|
||||
transport: &mut PacketTransportSerialCobs,
|
||||
target: &Target,
|
||||
path: &Path,
|
||||
) -> anyhow::Result<()> {
|
||||
let segments = parse_elf(target, path)
|
||||
.with_context(|| format!("failed to parse ELF: {}", path.display()))?;
|
||||
|
||||
let total_size: usize = segments.iter().map(|s| s.data.len()).sum();
|
||||
check_total_size(target, total_size)?;
|
||||
log_segments_info(target, &segments, total_size, path);
|
||||
|
||||
let ack_timeout = std::time::Duration::from_secs(2);
|
||||
|
||||
// 1. Write all segments in chunks, waiting for Ok after each.
|
||||
for seg in &segments {
|
||||
let mut pos = 0usize;
|
||||
while pos < seg.data.len() {
|
||||
let chunk_len = CHUNK_SIZE.min(seg.data.len() - pos);
|
||||
let offset = seg.offset + pos as u32;
|
||||
let chunk = &seg.data[pos..pos + chunk_len];
|
||||
|
||||
log::info!(
|
||||
"Writing {chunk_len} bytes @ {offset:#010x} ({}/{} of '{}')",
|
||||
pos + chunk_len,
|
||||
seg.data.len(),
|
||||
seg.name,
|
||||
);
|
||||
|
||||
send_and_await_ok(transport, &Request::WriteNvm { offset }, chunk, ack_timeout)?;
|
||||
pos += chunk_len;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. CRC + size postprocessing.
|
||||
match target {
|
||||
Target::Bl => {
|
||||
log::info!("Blanking bootloader CRC @ {BOOTLOADER_CRC_ADDR:#010x}");
|
||||
send_and_await_ok(
|
||||
transport,
|
||||
&Request::WriteNvm {
|
||||
offset: BOOTLOADER_CRC_ADDR,
|
||||
},
|
||||
&[0x00, 0x00],
|
||||
ack_timeout,
|
||||
)?;
|
||||
}
|
||||
Target::A | Target::B => {
|
||||
let (size_addr, crc_addr) = match target {
|
||||
Target::A => (APP_A_SIZE_ADDR, APP_A_CRC_ADDR),
|
||||
Target::B => (APP_B_SIZE_ADDR, APP_B_CRC_ADDR),
|
||||
Target::Bl => unreachable!(),
|
||||
};
|
||||
|
||||
log::info!("Writing app size {total_size} @ {size_addr:#010x}");
|
||||
send_and_await_ok(
|
||||
transport,
|
||||
&Request::WriteNvm { offset: size_addr },
|
||||
&(total_size as u32).to_be_bytes(),
|
||||
ack_timeout,
|
||||
)?;
|
||||
|
||||
let crc = Crc::<u16>::new(&CRC_16_IBM_3740);
|
||||
let mut digest = crc.digest();
|
||||
for seg in &segments {
|
||||
digest.update(&seg.data);
|
||||
}
|
||||
let checksum = digest.finalize().to_be_bytes();
|
||||
|
||||
log::info!(
|
||||
"Writing CRC [{:#04x}, {:#04x}] @ {crc_addr:#010x}",
|
||||
checksum[0],
|
||||
checksum[1]
|
||||
);
|
||||
send_and_await_ok(
|
||||
transport,
|
||||
&Request::WriteNvm { offset: crc_addr },
|
||||
&checksum,
|
||||
ack_timeout,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Flash complete.");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read as _,
|
||||
net::SocketAddr,
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use clap::Parser as _;
|
||||
use cobs::CobsDecoderOwned;
|
||||
use models::Response;
|
||||
use spacepackets::CcsdsPacketReader;
|
||||
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
|
||||
|
||||
pub mod elf;
|
||||
pub mod flash;
|
||||
pub mod tc;
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(name = "image-loader", about = "VA416XX Image Loader Application")]
|
||||
pub struct Cli {
|
||||
/// Serial port to use (overrides loader.toml)
|
||||
#[arg(short, long)]
|
||||
pub port: Option<String>,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
pub enum Command {
|
||||
/// Send a ping command
|
||||
Ping,
|
||||
|
||||
/// Set the active boot slot
|
||||
SetBootSlot {
|
||||
/// Boot slot to activate
|
||||
app: AppTarget,
|
||||
},
|
||||
|
||||
/// Corrupt an app slot (for testing)
|
||||
Corrupt {
|
||||
/// Target slot to corrupt
|
||||
app: AppTarget,
|
||||
},
|
||||
|
||||
/// Flash an ELF image to a target slot
|
||||
Flash {
|
||||
/// Target to flash
|
||||
target: Target,
|
||||
|
||||
/// Path to the ELF image
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
pub enum Target {
|
||||
/// Bootloader slot
|
||||
Bl,
|
||||
/// Application slot A
|
||||
A,
|
||||
/// Application slot B
|
||||
B,
|
||||
}
|
||||
|
||||
/// Only app slots can be corrupted (not the bootloader)
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
pub enum AppTarget {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
pub interface: Interface,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Interface {
|
||||
pub serial_port: Option<String>,
|
||||
pub udp_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new_from_file() -> Self {
|
||||
let mut config_file =
|
||||
File::open(Path::new("config.toml")).expect("opening config.toml file failed");
|
||||
let mut toml_str = String::new();
|
||||
config_file
|
||||
.read_to_string(&mut toml_str)
|
||||
.expect("reading config.toml file failed");
|
||||
let config: Config = toml::from_str(&toml_str).expect("parsing config.toml file failed");
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_logger() -> Result<(), fern::InitError> {
|
||||
fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"[{} {} {}] {}",
|
||||
humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||
record.level(),
|
||||
record.target(),
|
||||
message
|
||||
))
|
||||
})
|
||||
.level(log::LevelFilter::Info)
|
||||
.chain(std::io::stdout())
|
||||
.chain(fern::log_file("output.log")?)
|
||||
.apply()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
setup_logger().expect("failed to initialize logger");
|
||||
println!("-- VA108xx Flashloader Client --");
|
||||
let cli = Cli::parse();
|
||||
let config = Config::new_from_file();
|
||||
|
||||
if config.interface.serial_port.is_none() {
|
||||
bail!("Serial port not specified in configuration file.");
|
||||
}
|
||||
let serial_port = config.interface.serial_port.as_ref().unwrap();
|
||||
let serial = serialport::new(serial_port, 115200)
|
||||
.open()
|
||||
.expect("opening serial port failed");
|
||||
let mut transport = PacketTransportSerialCobs::new(serial, CobsDecoderOwned::new(4096));
|
||||
|
||||
match cli.command {
|
||||
Command::Ping => {
|
||||
let tc = tc::create_tc(&models::Request::Ping);
|
||||
log::info!(
|
||||
"Sending ping request with TC ID: {:#010x}",
|
||||
tc.ccsds_packet_id_and_psc().raw()
|
||||
);
|
||||
let tc_raw = tc.to_vec();
|
||||
transport.send(&tc_raw).unwrap();
|
||||
}
|
||||
Command::SetBootSlot { app } => {
|
||||
let app_sel = match app {
|
||||
AppTarget::A => models::AppSel::A,
|
||||
AppTarget::B => models::AppSel::B,
|
||||
};
|
||||
let tc = tc::create_tc(&models::Request::SetBootSlot(app_sel));
|
||||
log::info!(
|
||||
"Sending app select {:?} command with TC ID: {:#010x}",
|
||||
app_sel,
|
||||
tc.ccsds_packet_id_and_psc().raw()
|
||||
);
|
||||
transport.send(&tc.to_vec()).unwrap();
|
||||
}
|
||||
Command::Corrupt { app } => {
|
||||
let app_sel = match app {
|
||||
AppTarget::A => models::AppSel::A,
|
||||
AppTarget::B => models::AppSel::B,
|
||||
};
|
||||
let tc = tc::create_tc(&models::Request::SetBootSlot(app_sel));
|
||||
log::info!(
|
||||
"Sending corrupt slot {:?} command with TC ID: {:#010x}",
|
||||
app_sel,
|
||||
tc.ccsds_packet_id_and_psc().raw()
|
||||
);
|
||||
transport.send(&tc.to_vec()).unwrap();
|
||||
}
|
||||
Command::Flash { target, path } => {
|
||||
flash::flash_image(&mut transport, &target, &path)?;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Waiting for response...");
|
||||
loop {
|
||||
transport
|
||||
.receive(|packet: &[u8]| {
|
||||
let reader = CcsdsPacketReader::new_with_checksum(packet);
|
||||
log::debug!("Received packet: {:?}", reader);
|
||||
if let Ok(reader) = reader {
|
||||
let packet_data = reader.packet_data();
|
||||
let response_result = postcard::take_from_bytes::<Response>(packet_data);
|
||||
if let Ok((response, _remainder)) = response_result {
|
||||
log::info!("Received TM with response: {:?}", response);
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to parse response from packet data: {:?}",
|
||||
response_result
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use spacepackets::{CcsdsPacketCreatorOwned, SpHeader};
|
||||
|
||||
pub fn create_tc(request: &models::Request) -> CcsdsPacketCreatorOwned {
|
||||
let req_raw = postcard::to_allocvec(&request).unwrap();
|
||||
let sp_header = SpHeader::new_from_apid(models::APID);
|
||||
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
|
||||
}
|
||||
|
||||
pub fn create_tc_with_additional_payload(request: &models::Request, payload: &[u8]) -> CcsdsPacketCreatorOwned {
|
||||
let mut req_raw = postcard::to_allocvec(&request).unwrap();
|
||||
req_raw.extend_from_slice(payload);
|
||||
let sp_header = SpHeader::new_from_apid(models::APID);
|
||||
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
|
||||
}
|
||||
+5
-2
@@ -1,16 +1,16 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
resolver = "3"
|
||||
members = [
|
||||
"vorago-reb1",
|
||||
"va108xx",
|
||||
"va108xx-hal",
|
||||
"va108xx-embassy",
|
||||
"examples/simple",
|
||||
"examples/rtic",
|
||||
"examples/embassy",
|
||||
"board-tests",
|
||||
"bootloader",
|
||||
"flashloader",
|
||||
"flashloader/models",
|
||||
]
|
||||
exclude = [
|
||||
"flashloader/slot-a-blinky",
|
||||
@@ -44,3 +44,6 @@ lto = true
|
||||
opt-level = 'z' # <-
|
||||
overflow-checks = false # <-
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
|
||||
[patch.crates-io]
|
||||
spacepackets = { git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", rev = "ec952b771cc62a371786cb1a04fd1baa9c33523c" }
|
||||
|
||||
@@ -14,8 +14,6 @@ This workspace contains the following released crates:
|
||||
crate containing basic low-level register definition.
|
||||
- The [`va108xx-hal`](https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/branch/main/va108xx/va108xx-hal)
|
||||
HAL crate containing higher-level abstractions on top of the PAC register crate.
|
||||
- The [`va108xx-embassy`](https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/branch/main/va108xx/va108xx-embassy)
|
||||
crate containing support for running the embassy-rs asynchronous runtime.
|
||||
- The [`vorago-reb1`](https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/branch/main/va108xx/vorago-reb1)
|
||||
BSP crate containing support for the REB1 development board.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ panic-probe = { version = "1", features = ["print-defmt"] }
|
||||
embedded-hal = "1"
|
||||
|
||||
[dependencies.va108xx-hal]
|
||||
version = "0.12"
|
||||
version = "0.13"
|
||||
features = ["rt"]
|
||||
path = "../va108xx-hal"
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ fn main() -> ! {
|
||||
delay_timer.delay_ms(500);
|
||||
}
|
||||
let ahb_freq: Hertz = 50.MHz();
|
||||
let mut syst_delay = cortex_m::delay::Delay::new(cp.SYST, ahb_freq.raw());
|
||||
let mut syst_delay = cortex_m::delay::Delay::new(cp.SYST, ahb_freq.to_raw());
|
||||
// Release image should be used to verify timings for pin PA0
|
||||
for _ in 0..5 {
|
||||
pa0.toggle();
|
||||
|
||||
@@ -9,18 +9,18 @@ cortex-m-rt = "0.7"
|
||||
embedded-hal = "1"
|
||||
defmt-rtt = "1"
|
||||
defmt = "1"
|
||||
panic-probe = { version = "1", features = ["defmt"] }
|
||||
panic-probe = { version = "1" }
|
||||
crc = "3"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
static_assertions = "1"
|
||||
|
||||
[dependencies.va108xx-hal]
|
||||
version = "0.12"
|
||||
version = "0.13"
|
||||
path = "../va108xx-hal"
|
||||
features = ["defmt"]
|
||||
|
||||
[dependencies.vorago-reb1]
|
||||
version = "0.9"
|
||||
version = "0.10"
|
||||
path = "../vorago-reb1"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -159,6 +159,12 @@ fn main() -> ! {
|
||||
if check_app_crc(preferred_app) {
|
||||
boot_app(&dp.sysconfig, &cp, preferred_app, &mut timer)
|
||||
} else if check_app_crc(other_app) {
|
||||
if DEFMT_PRINTOUT {
|
||||
defmt::warn!(
|
||||
"CRC check for preferred image {} failed. Checking alternative slot",
|
||||
preferred_app
|
||||
);
|
||||
}
|
||||
boot_app(&dp.sysconfig, &cp, other_app, &mut timer)
|
||||
} else {
|
||||
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
|
||||
@@ -210,7 +216,6 @@ fn check_own_crc(
|
||||
crc_exp
|
||||
);
|
||||
}
|
||||
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
|
||||
boot_app(sysconfig, cp, AppSel::A, timer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,10 @@ embassy-executor = { version = "0.10", features = [
|
||||
"executor-interrupt"
|
||||
]}
|
||||
|
||||
va108xx-hal = { version = "0.12", path = "../../va108xx-hal", features = ["defmt"] }
|
||||
va108xx-embassy = { version = "0.3", path = "../../va108xx-embassy" }
|
||||
va108xx-hal = { version = "0.13", path = "../../va108xx-hal", features = ["defmt"] }
|
||||
|
||||
[features]
|
||||
default = ["ticks-hz-1_000", "va108xx-embassy/irq-oc30-oc31"]
|
||||
default = ["ticks-hz-1_000", "va108xx-hal/embassy-oc30-oc31"]
|
||||
custom-irqs = []
|
||||
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
|
||||
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]
|
||||
|
||||
@@ -61,7 +61,7 @@ async fn main(spawner: Spawner) {
|
||||
let dp = pac::Peripherals::take().unwrap();
|
||||
|
||||
// Safety: Only called once here.
|
||||
va108xx_embassy::init(dp.tim23, dp.tim22, SYSCLK_FREQ);
|
||||
va108xx_hal::embassy_time::init(dp.tim23, dp.tim22, SYSCLK_FREQ);
|
||||
unsafe {
|
||||
cortex_m::interrupt::enable();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ async fn main(spawner: Spawner) {
|
||||
let dp = pac::Peripherals::take().unwrap();
|
||||
|
||||
// Safety: Only called once here.
|
||||
va108xx_embassy::init(dp.tim23, dp.tim22, SYSCLK_FREQ);
|
||||
va108xx_hal::embassy_time::init(dp.tim23, dp.tim22, SYSCLK_FREQ);
|
||||
|
||||
let porta = PinsA::new(dp.porta);
|
||||
let mut led0 = Output::new(porta.pa10, PinState::Low);
|
||||
|
||||
@@ -41,7 +41,7 @@ async fn main(_spawner: Spawner) {
|
||||
let dp = pac::Peripherals::take().unwrap();
|
||||
|
||||
// Safety: Only called once here.
|
||||
va108xx_embassy::init(dp.tim23, dp.tim22, SYSCLK_FREQ);
|
||||
va108xx_hal::embassy_time::init(dp.tim23, dp.tim22, SYSCLK_FREQ);
|
||||
|
||||
let porta = PinsA::new(dp.porta);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use embassy_time::{Duration, Instant, Ticker};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "custom-irqs")] {
|
||||
use va108xx_embassy::embassy_time_driver_irqs;
|
||||
use va108xx_hal::embassy_time_driver_irqs;
|
||||
use va108xx_hal::pac::interrupt;
|
||||
embassy_time_driver_irqs!(timekeeper_irq = OC23, alarm_irq = OC24);
|
||||
}
|
||||
@@ -31,13 +31,13 @@ async fn main(_spawner: Spawner) {
|
||||
// Safety: Only called once here.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "custom-irqs"))] {
|
||||
va108xx_embassy::init(
|
||||
va108xx_hal::embassy_time::init(
|
||||
dp.tim23,
|
||||
dp.tim22,
|
||||
SYSCLK_FREQ,
|
||||
);
|
||||
} else {
|
||||
va108xx_embassy::init_with_custom_irqs(
|
||||
va108xx_hal::embassy_time::init_with_custom_irqs(
|
||||
dp.tim23,
|
||||
dp.tim22,
|
||||
SYSCLK_FREQ,
|
||||
|
||||
@@ -9,9 +9,10 @@ embedded-io = "0.7"
|
||||
defmt-rtt = "1"
|
||||
defmt = "1"
|
||||
panic-probe = { version = "1", features = ["defmt"] }
|
||||
fugit = "0.4"
|
||||
rtic = { version = "2", features = ["thumbv6-backend"] }
|
||||
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
|
||||
ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
|
||||
ringbuf = { version = "0.5", default-features = false, features = ["portable-atomic"] }
|
||||
|
||||
va108xx-hal = { version = "0.12", path = "../../va108xx-hal" }
|
||||
vorago-reb1 = { version = "0.9", path = "../../vorago-reb1" }
|
||||
va108xx-hal = { version = "0.13", path = "../../va108xx-hal" }
|
||||
vorago-reb1 = { version = "0.10", path = "../../vorago-reb1" }
|
||||
|
||||
@@ -43,7 +43,7 @@ mod app {
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
defmt::println!("-- Vorago Button IRQ Example --");
|
||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.to_raw());
|
||||
|
||||
let mode = DEFAULT_MODE;
|
||||
defmt::info!("Using {:?} mode", mode);
|
||||
|
||||
@@ -20,11 +20,11 @@ mod app {
|
||||
use panic_probe as _;
|
||||
// Import global logger.
|
||||
use defmt_rtt as _;
|
||||
use rtic_monotonics::fugit::ExtU32 as _;
|
||||
use rtic_monotonics::Monotonic;
|
||||
use va108xx_hal::{
|
||||
pac,
|
||||
pins::PinsA,
|
||||
prelude::*,
|
||||
uart::{self, RxWithInterrupt, Tx},
|
||||
InterruptConfig,
|
||||
};
|
||||
@@ -46,15 +46,18 @@ mod app {
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
defmt::println!("-- VA108xx UART Echo with IRQ example application--");
|
||||
|
||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.to_raw());
|
||||
|
||||
let dp = cx.device;
|
||||
let gpioa = PinsA::new(dp.porta);
|
||||
let tx = gpioa.pa9;
|
||||
let rx = gpioa.pa8;
|
||||
|
||||
let clock_config =
|
||||
uart::ClockConfig::calculate(SYSCLK_FREQ, 115200.Hz(), uart::BaudMode::_16);
|
||||
let clock_config = uart::ClockConfig::calculate(
|
||||
SYSCLK_FREQ,
|
||||
fugit::HertzU32::from_raw(115200),
|
||||
uart::BaudMode::_16,
|
||||
);
|
||||
let uart_config = uart::Config::new_with_clock_config(clock_config);
|
||||
let irq_uart = uart::Uart::new_with_interrupt_uart0(
|
||||
dp.uarta,
|
||||
@@ -64,7 +67,7 @@ mod app {
|
||||
InterruptConfig::new(pac::Interrupt::OC3, true, true),
|
||||
);
|
||||
let (tx, rx) = irq_uart.split();
|
||||
let mut rx = rx.into_rx_with_irq();
|
||||
let mut rx = rx.into_rx_with_interrupt();
|
||||
|
||||
rx.start();
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ mod app {
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
defmt::println!("-- Vorago VA108xx RTIC template --");
|
||||
|
||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.to_raw());
|
||||
|
||||
let porta = PinsA::new(cx.device.porta);
|
||||
let led0 = Output::new(porta.pa10, PinState::Low);
|
||||
|
||||
@@ -16,6 +16,6 @@ embedded-io = "0.7"
|
||||
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
|
||||
|
||||
[dependencies.va108xx-hal]
|
||||
version = "0.12"
|
||||
version = "0.13"
|
||||
path = "../../va108xx-hal"
|
||||
features = ["defmt"]
|
||||
|
||||
@@ -46,8 +46,8 @@ fn main() -> ! {
|
||||
}
|
||||
|
||||
let sys_clk: Hertz = 50.MHz();
|
||||
let cnt_ms = sys_clk.raw() / 1000 - 1;
|
||||
let cnt_sec = sys_clk.raw() - 1;
|
||||
let cnt_ms = sys_clk.to_raw() / 1000 - 1;
|
||||
let cnt_sec = sys_clk.to_raw() - 1;
|
||||
unsafe {
|
||||
dp.tim0.cnt_value().write(|w| w.bits(cnt_ms));
|
||||
dp.tim0.rst_value().write(|w| w.bits(cnt_ms));
|
||||
|
||||
@@ -11,25 +11,30 @@ defmt = "1"
|
||||
defmt-rtt = { version = "1" }
|
||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
cobs = { version = "0.5", default-features = false }
|
||||
satrs = { version = "0.3.0-alpha.3", default-features = false, features = ["defmt"] }
|
||||
cobs = { version = "0.5", default-features = false, features = ["defmt"] }
|
||||
fugit = "0.4"
|
||||
arbitrary-int = "2"
|
||||
ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
|
||||
# spacepackets = { version = "0.17", path = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false, features = ["defmt"] }
|
||||
embassy-sync = "0.8"
|
||||
embedded-io-async = "0.7"
|
||||
embassy-time = { version = "0.5", features = ["defmt-timestamp-uptime-ms"] }
|
||||
static_cell = "2"
|
||||
postcard = { version = "1", features = ["use-defmt"] }
|
||||
spacepackets = { version = "0.17", default-features = false, features = ["defmt"] }
|
||||
serde = { version = "1", 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.
|
||||
portable-atomic = {version = "1", features = ["unsafe-assume-single-core"]}
|
||||
models = { path = "./models", features = ["defmt"] }
|
||||
|
||||
rtic = { version = "2", features = ["thumbv6-backend"] }
|
||||
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
|
||||
|
||||
[dependencies.va108xx-hal]
|
||||
version = "0.12"
|
||||
version = "0.13"
|
||||
path = "../va108xx-hal"
|
||||
features = ["defmt"]
|
||||
features = ["defmt", "embassy-oc30-oc31"]
|
||||
|
||||
[dependencies.vorago-reb1]
|
||||
version = "0.9"
|
||||
version = "0.10"
|
||||
path = "../vorago-reb1"
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
|
||||
@@ -2,45 +2,40 @@ 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.
|
||||
a simple PUS (CCSDS) interface to update the software. It also provides a Rust application
|
||||
which can be used to upload compiled images to the flashloader application to write them to the NVM.
|
||||
You can find it inside the `tools/va108xx-image-loader` directory of the monorepo.
|
||||
|
||||
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/vorago-rs/src/branch/main/va108xx/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.
|
||||
The flashloader software could be be adapted to interface with a real primary on-board software
|
||||
instead of the loader application provided here to upload images because it already uses a
|
||||
low-level CCSDS based packet interface.
|
||||
|
||||
## Using the Python image loader
|
||||
## Using the image loader
|
||||
|
||||
The Python image loader communicates with the Rust flashload application using a dedicated serial
|
||||
port with a baudrate of 115200.
|
||||
Inside `tools/va108xx-image-loader` you can find a Rust application which can be used to
|
||||
update the image slots via a serial port.
|
||||
|
||||
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.
|
||||
You can install this tool using the following command inside the project folder:
|
||||
|
||||
After that, you can use
|
||||
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
to install all required dependencies.
|
||||
After that, you can run `va108xx-iamge-loader --help` to get some to get usage informations.
|
||||
|
||||
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.
|
||||
The flash loader uses the UART0 with the Pins PA8 (RX) and PA9 (TX) interface of the VA108xx to
|
||||
perform CCSDS based communication. The serial port can be set inside the `config.toml` file
|
||||
or with the `--port` argument.
|
||||
|
||||
### Examples
|
||||
|
||||
You can use
|
||||
|
||||
```sh
|
||||
./image-loader.py -p
|
||||
va108xx-image-loader ping
|
||||
```
|
||||
|
||||
to send a ping an verify the connection.
|
||||
@@ -50,8 +45,7 @@ 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
|
||||
va108xx-image-loader flash a ./target/thumbv6m-none-eabi/release/slot-a-blinky
|
||||
```
|
||||
|
||||
to build the slot A sample application and upload it to a running flash loader application
|
||||
@@ -60,7 +54,7 @@ to write it to slot A.
|
||||
You can use
|
||||
|
||||
```sh
|
||||
./image-loader.py -s a
|
||||
va108xx-image-loader set-boot-slot a
|
||||
```
|
||||
|
||||
to select the Slot A as a boot slot. The boot slot is stored in a reserved section in EEPROM
|
||||
@@ -69,7 +63,7 @@ and will be read and used by the bootloader to determine which slot to boot.
|
||||
You can use
|
||||
|
||||
```sh
|
||||
./image-loader.py -c -t a
|
||||
va108xx-image-loader corrupt a
|
||||
```
|
||||
|
||||
to corrupt the image A and test that it switches to image B after a failed CRC check instead.
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import List, Tuple
|
||||
from spacepackets.ecss.defs import PusService
|
||||
from spacepackets.ecss.tm import PusTm
|
||||
import toml
|
||||
import struct
|
||||
import logging
|
||||
import argparse
|
||||
import time
|
||||
import enum
|
||||
from com_interface import ComInterface
|
||||
from com_interface.serial_base import SerialCfg
|
||||
from com_interface.serial_cobs import SerialCobsComIF
|
||||
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_B_END_ADDR = 0x20000 - 8
|
||||
IMG_SLOT_SIZE = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
|
||||
|
||||
APP_A_END_ADDR = APP_A_START_ADDR + IMG_SLOT_SIZE
|
||||
# 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
|
||||
# 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
|
||||
|
||||
|
||||
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
|
||||
SET_BOOT_SLOT = 130
|
||||
|
||||
|
||||
_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 AppSel(enum.IntEnum):
|
||||
APP_A = 0
|
||||
APP_B = 1
|
||||
|
||||
|
||||
class ImageLoader:
|
||||
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
|
||||
self.com_if = com_if
|
||||
self.verificator = verificator
|
||||
|
||||
def handle_boot_sel_cmd(self, target: AppSel):
|
||||
_LOGGER.info("Sending ping command")
|
||||
action_tc = PusTc(
|
||||
apid=0x00,
|
||||
service=PusService.S8_FUNC_CMD,
|
||||
subservice=ActionId.SET_BOOT_SLOT,
|
||||
seq_count=SEQ_PROVIDER.get_and_increment(),
|
||||
app_data=bytes([target]),
|
||||
)
|
||||
self.verificator.add_tc(action_tc)
|
||||
self.com_if.send(bytes(action_tc.pack()))
|
||||
self.await_for_command_copletion("boot image selection command")
|
||||
|
||||
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()))
|
||||
self.await_for_command_copletion("ping command")
|
||||
|
||||
def await_for_command_copletion(self, context: str):
|
||||
done = False
|
||||
now = time.time()
|
||||
while time.time() - now < 2.0:
|
||||
if not self.com_if.data_available():
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
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(f"received {context} reply")
|
||||
done = True
|
||||
if done:
|
||||
break
|
||||
if not done:
|
||||
_LOGGER.warning(f"no {context} reply received")
|
||||
|
||||
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)
|
||||
check_segments(target, total_size)
|
||||
print_segments_info(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(
|
||||
"-s", "--sel", choices=["a", "b"], help="Set boot slot (Slot A or B)"
|
||||
)
|
||||
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 = input("Please specify the serial port manually: ")
|
||||
serial_cfg = SerialCfg(
|
||||
com_if_id="ser_cobs",
|
||||
serial_port=serial_port,
|
||||
baud_rate=BAUD_RATE,
|
||||
polling_frequency=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
|
||||
|
||||
boot_sel = None
|
||||
if args.sel:
|
||||
if args.sel == "a":
|
||||
boot_sel = AppSel.APP_A
|
||||
elif args.sel == "b":
|
||||
boot_sel = AppSel.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 args.sel and boot_sel is not None:
|
||||
image_loader.handle_boot_sel_cmd(boot_sel)
|
||||
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:
|
||||
if 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 check_segments(
|
||||
target: Target,
|
||||
total_size: int,
|
||||
):
|
||||
# Set context string and perform basic sanity checks.
|
||||
if target == Target.BOOTLOADER and total_size > BOOTLOADER_MAX_SIZE:
|
||||
raise ValueError(
|
||||
f"provided bootloader app larger than allowed {total_size} bytes"
|
||||
)
|
||||
elif target == Target.APP_A and total_size > APP_A_MAX_SIZE:
|
||||
raise ValueError(f"provided App A larger than allowed {total_size} bytes")
|
||||
elif target == Target.APP_B and total_size > APP_B_MAX_SIZE:
|
||||
raise ValueError(f"provided App B larger than allowed {total_size} bytes")
|
||||
|
||||
|
||||
def print_segments_info(
|
||||
target: Target,
|
||||
loadable_segments: List[LoadableSegment],
|
||||
total_size: int,
|
||||
file_path: Path,
|
||||
):
|
||||
# Set context string and perform basic sanity checks.
|
||||
if target == Target.BOOTLOADER:
|
||||
context_str = "Bootloader"
|
||||
elif target == Target.APP_A:
|
||||
context_str = "App Slot A"
|
||||
elif target == Target.APP_B:
|
||||
context_str = "App Slot B"
|
||||
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})")
|
||||
for idx, segment in enumerate(loadable_segments):
|
||||
_LOGGER.info(
|
||||
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and "
|
||||
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 +0,0 @@
|
||||
serial_port = "/dev/ttyUSB1"
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "models"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
postcard = { version = "1" }
|
||||
spacepackets = { version = "0.17", default-features = false, features = ["defmt"] }
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
defmt = { version = "1", optional = true }
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
cobs = { version = "0.5", default-features = false }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
arbitrary-int = "2"
|
||||
@@ -0,0 +1,98 @@
|
||||
#![no_std]
|
||||
|
||||
use arbitrary_int::u11;
|
||||
use cobs::DestBufTooSmallError;
|
||||
use spacepackets::{
|
||||
CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, SpacePacketHeader,
|
||||
};
|
||||
|
||||
pub const APID: u11 = u11::new(0x01);
|
||||
|
||||
pub const BOOTLOADER_START_ADDR: u32 = 0x0000_0000;
|
||||
pub const BOOTLOADER_END_ADDR: u32 = 0x0000_3000;
|
||||
pub const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
|
||||
pub const BOOTLOADER_MAX_SIZE: u32 = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 2;
|
||||
|
||||
pub const APP_A_START_ADDR: u32 = 0x0000_3000;
|
||||
pub const APP_B_END_ADDR: u32 = 0x0002_0000 - 8;
|
||||
pub const IMG_SLOT_SIZE: u32 = (APP_B_END_ADDR - APP_A_START_ADDR) / 2;
|
||||
|
||||
pub const APP_A_END_ADDR: u32 = APP_A_START_ADDR + IMG_SLOT_SIZE;
|
||||
pub const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
|
||||
pub const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
|
||||
pub const APP_A_MAX_SIZE: u32 = IMG_SLOT_SIZE - 8;
|
||||
|
||||
pub const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
|
||||
pub const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
|
||||
pub const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
|
||||
pub const APP_B_MAX_SIZE: u32 = IMG_SLOT_SIZE - 8;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
num_enum::TryFromPrimitive,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum AppSel {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Request {
|
||||
Ping,
|
||||
Corrupt(AppSel),
|
||||
WriteNvm { offset: u32 },
|
||||
SetBootSlot(AppSel),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Response {
|
||||
Ok,
|
||||
}
|
||||
|
||||
pub fn create_tm_packet(
|
||||
buf: &mut [u8],
|
||||
sp_header: SpacePacketHeader,
|
||||
response: Response,
|
||||
) -> Result<usize, CcsdsPacketCreationError> {
|
||||
let packet_data_size = postcard::experimental::serialized_size(&response).unwrap();
|
||||
let mut creator =
|
||||
CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;
|
||||
|
||||
let current_index = 0;
|
||||
postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
|
||||
Ok(creator.finish())
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PacketCreationAndEncodingError {
|
||||
#[error("packet creation failed: {0}")]
|
||||
Creation(#[from] CcsdsPacketCreationError),
|
||||
#[error("destination buffer too small: {0}")]
|
||||
Encoding(#[from] DestBufTooSmallError),
|
||||
}
|
||||
|
||||
pub fn create_encoded_tm_packet(
|
||||
buf: &mut [u8],
|
||||
encoded_buf: &mut [u8],
|
||||
sp_header: SpacePacketHeader,
|
||||
response: Response,
|
||||
) -> Result<usize, PacketCreationAndEncodingError> {
|
||||
let packet_len = create_tm_packet(buf, sp_header, response)?;
|
||||
let encoded_len =
|
||||
cobs::try_encode_including_sentinels(&buf[0..packet_len], &mut encoded_buf[..])?;
|
||||
Ok(encoded_len)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
||||
@@ -1,5 +0,0 @@
|
||||
spacepackets == 0.28
|
||||
com-interface == 0.1
|
||||
toml == 0.10
|
||||
pyelftools == 0.31
|
||||
crcmod == 1.7
|
||||
+171
-333
@@ -4,50 +4,22 @@
|
||||
#![no_std]
|
||||
|
||||
use defmt_rtt as _; // global logger
|
||||
use num_enum::TryFromPrimitive;
|
||||
use panic_probe as _;
|
||||
use ringbuf::{
|
||||
traits::{Consumer, Observer, Producer},
|
||||
StaticRb,
|
||||
};
|
||||
use va108xx_hal::prelude::*;
|
||||
use va108xx_hal::time::Hertz;
|
||||
|
||||
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
|
||||
const UART_BANK: va108xx_hal::uart::Bank = va108xx_hal::uart::Bank::Uart0;
|
||||
|
||||
const MAX_TC_SIZE: usize = 524;
|
||||
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
|
||||
pub const MAX_TC_SIZE: usize = 524;
|
||||
pub 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,
|
||||
SetBootSlot = 130,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, defmt::Format)]
|
||||
#[repr(u8)]
|
||||
enum AppSel {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
pub struct RingBufWrapper<const BUF_SIZE: usize, const SIZES_LEN: usize> {
|
||||
pub buf: StaticRb<u8, BUF_SIZE>,
|
||||
pub sizes: StaticRb<usize, SIZES_LEN>,
|
||||
}
|
||||
const TC_PIPE_SIZE: usize = 1024;
|
||||
const TM_PIPE_SIZE: usize = 128;
|
||||
|
||||
pub const APP_A_START_ADDR: u32 = 0x3000;
|
||||
pub const APP_A_END_ADDR: u32 = 0x117FC;
|
||||
@@ -59,21 +31,16 @@ pub const PREFERRED_SLOT_OFFSET: u32 = 0x20000 - 1;
|
||||
#[rtic::app(device = pac, dispatchers = [OC20, OC21, OC22])]
|
||||
mod app {
|
||||
use super::*;
|
||||
use arbitrary_int::traits::Integer as _;
|
||||
use arbitrary_int::{u11, u14};
|
||||
use cobs::CobsDecoderHeapless;
|
||||
use cortex_m::asm;
|
||||
use embedded_io::Write;
|
||||
use rtic::Mutex;
|
||||
use rtic_monotonics::systick::prelude::*;
|
||||
use satrs::pus::verification::{FailParams, VerificationReportCreator};
|
||||
use satrs::spacepackets::ecss::PusServiceId;
|
||||
use satrs::spacepackets::ecss::{
|
||||
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
|
||||
};
|
||||
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
|
||||
use embedded_io_async::Write as _;
|
||||
use models::{create_encoded_tm_packet, Response};
|
||||
use spacepackets::{CcsdsPacketReader, SpacePacketHeader};
|
||||
use va108xx_hal::pins::PinsA;
|
||||
use va108xx_hal::spi::SpiClockConfig;
|
||||
use va108xx_hal::uart::InterruptContextTimeoutOrMaxSize;
|
||||
use va108xx_hal::{pac, uart, InterruptConfig};
|
||||
use va108xx_hal::uart::{self, TxAsync};
|
||||
use va108xx_hal::{pac, InterruptConfig};
|
||||
use vorago_reb1::m95m01::M95M01;
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
@@ -87,40 +54,40 @@ mod app {
|
||||
#[local]
|
||||
struct Local {
|
||||
uart_rx: uart::RxWithInterrupt,
|
||||
uart_tx: uart::Tx,
|
||||
rx_context: InterruptContextTimeoutOrMaxSize,
|
||||
verif_reporter: VerificationReportCreator,
|
||||
uart_tx: uart::TxAsync,
|
||||
nvm: M95M01,
|
||||
tc_tx: embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, TC_PIPE_SIZE>,
|
||||
tc_rx: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, TC_PIPE_SIZE>,
|
||||
// We are only using this in threads, and RTIC ensures there are no conflicts.
|
||||
tm_tx: embassy_sync::pipe::Writer<'static, NoopRawMutex, TM_PIPE_SIZE>,
|
||||
tm_rx: embassy_sync::pipe::Reader<'static, NoopRawMutex, TM_PIPE_SIZE>,
|
||||
}
|
||||
|
||||
#[shared]
|
||||
struct Shared {
|
||||
// Having this shared allows multiple tasks to generate telemetry.
|
||||
tm_rb: RingBufWrapper<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
|
||||
tc_rb: RingBufWrapper<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
|
||||
}
|
||||
|
||||
rtic_monotonics::systick_monotonic!(Mono, 1000);
|
||||
struct Shared {}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
defmt::println!("-- Vorago flashloader --");
|
||||
|
||||
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
|
||||
let periphs = cx.device;
|
||||
va108xx_hal::embassy_time::init(periphs.tim14, periphs.tim15, SYSCLK_FREQ);
|
||||
|
||||
let dp = cx.device;
|
||||
let spi_clock_config = SpiClockConfig::new(2, 4);
|
||||
let nvm = M95M01::new(dp.spic, spi_clock_config);
|
||||
let nvm = M95M01::new(periphs.spic, spi_clock_config);
|
||||
|
||||
let gpioa = PinsA::new(dp.porta);
|
||||
let gpioa = PinsA::new(periphs.porta);
|
||||
let tx = gpioa.pa9;
|
||||
let rx = gpioa.pa8;
|
||||
|
||||
let clock_config =
|
||||
uart::ClockConfig::calculate(SYSCLK_FREQ, UART_BAUDRATE.Hz(), uart::BaudMode::_16);
|
||||
let clock_config = uart::ClockConfig::calculate(
|
||||
SYSCLK_FREQ,
|
||||
fugit::HertzU32::from_raw(UART_BAUDRATE),
|
||||
uart::BaudMode::_16,
|
||||
);
|
||||
let uart_config = uart::Config::new_with_clock_config(clock_config);
|
||||
let irq_uart = uart::Uart::new_with_interrupt_uart0(
|
||||
dp.uarta,
|
||||
periphs.uarta,
|
||||
tx,
|
||||
rx,
|
||||
uart_config,
|
||||
@@ -128,32 +95,32 @@ mod app {
|
||||
);
|
||||
let (tx, rx) = irq_uart.split();
|
||||
// Unwrap is okay, we explicitely set the interrupt ID.
|
||||
let mut rx = rx.into_rx_with_irq();
|
||||
let mut rx = rx.into_rx_with_interrupt();
|
||||
|
||||
let verif_reporter = VerificationReportCreator::new(u11::new(0));
|
||||
rx.start();
|
||||
tc_handler::spawn().unwrap();
|
||||
tm_tx_handler::spawn().unwrap();
|
||||
|
||||
let mut rx_context = InterruptContextTimeoutOrMaxSize::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();
|
||||
let tx_async = TxAsync::new(tx);
|
||||
|
||||
static TC_PIPE: static_cell::ConstStaticCell<
|
||||
embassy_sync::pipe::Pipe<CriticalSectionRawMutex, TC_PIPE_SIZE>,
|
||||
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
|
||||
static TM_PIPE: static_cell::ConstStaticCell<
|
||||
embassy_sync::pipe::Pipe<NoopRawMutex, TM_PIPE_SIZE>,
|
||||
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
|
||||
let (tc_rx, tc_tx) = TC_PIPE.take().split();
|
||||
let (tm_rx, tm_tx) = TM_PIPE.take().split();
|
||||
(
|
||||
Shared {
|
||||
tc_rb: RingBufWrapper {
|
||||
buf: StaticRb::default(),
|
||||
sizes: StaticRb::default(),
|
||||
},
|
||||
tm_rb: RingBufWrapper {
|
||||
buf: StaticRb::default(),
|
||||
sizes: StaticRb::default(),
|
||||
},
|
||||
},
|
||||
Shared {},
|
||||
Local {
|
||||
uart_rx: rx,
|
||||
uart_tx: tx,
|
||||
rx_context,
|
||||
verif_reporter,
|
||||
uart_tx: tx_async,
|
||||
nvm,
|
||||
tc_tx,
|
||||
tc_rx,
|
||||
tm_tx,
|
||||
tm_rx,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -170,285 +137,156 @@ mod app {
|
||||
#[task(
|
||||
binds = OC0,
|
||||
local = [
|
||||
cnt: u32 = 0,
|
||||
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
|
||||
rx_context,
|
||||
uart_rx,
|
||||
tc_tx
|
||||
],
|
||||
shared = [tc_rb]
|
||||
)]
|
||||
fn uart_rx_irq(mut cx: uart_rx_irq::Context) {
|
||||
match cx
|
||||
.local
|
||||
.uart_rx
|
||||
.on_interrupt_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
|
||||
{
|
||||
Ok(result) => {
|
||||
if RX_DEBUGGING {
|
||||
defmt::debug!("RX Info: {:?}", cx.local.rx_context);
|
||||
defmt::debug!("RX Result: {:?}", result);
|
||||
fn uart_irq(cx: uart_irq::Context) {
|
||||
let mut buf: [u8; 16] = [0; 16];
|
||||
let result = cx.local.uart_rx.on_interrupt(&mut buf);
|
||||
if result.bytes_read > 0 {
|
||||
let mut written_so_far = 0;
|
||||
while written_so_far < result.bytes_read {
|
||||
let write_result = cx
|
||||
.local
|
||||
.tc_tx
|
||||
.try_write(&buf[written_so_far..result.bytes_read]);
|
||||
if write_result.is_err() {
|
||||
defmt::warn!("TC pipe full, dropping bytes");
|
||||
break;
|
||||
}
|
||||
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() {
|
||||
defmt::warn!("COBS decoding failed");
|
||||
} else {
|
||||
let decoded_size = decoded_size.unwrap();
|
||||
let mut tc_rb_full = false;
|
||||
cx.shared.tc_rb.lock(|rb| {
|
||||
if rb.sizes.vacant_len() >= 1 && rb.buf.vacant_len() >= decoded_size
|
||||
{
|
||||
rb.sizes.try_push(decoded_size).unwrap();
|
||||
rb.buf.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
|
||||
} else {
|
||||
tc_rb_full = true;
|
||||
}
|
||||
});
|
||||
if tc_rb_full {
|
||||
defmt::warn!("COBS TC queue full");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
defmt::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() {
|
||||
defmt::warn!("UART error: {:?}", result.errors.unwrap());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
defmt::warn!("UART error: {:?}", e);
|
||||
written_so_far += write_result.unwrap();
|
||||
}
|
||||
}
|
||||
va108xx_hal::uart::tx_async::on_interrupt_tx(UART_BANK);
|
||||
}
|
||||
|
||||
#[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],
|
||||
nvm,
|
||||
verif_reporter
|
||||
tc_rx,
|
||||
tm_tx
|
||||
],
|
||||
shared=[tm_rb, tc_rb]
|
||||
)]
|
||||
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
|
||||
async fn tc_handler(cx: tc_handler::Context) {
|
||||
let mut read_buf: [u8; 64] = [0; 64];
|
||||
let mut tm_buf: [u8; MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE];
|
||||
let mut encoded_tm_buf: [u8; MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE];
|
||||
let mut cobs_decoder = CobsDecoderHeapless::<TC_PIPE_SIZE>::new();
|
||||
loop {
|
||||
// Try to read a TC from the ring buffer.
|
||||
let packet_len = cx.shared.tc_rb.lock(|rb| rb.sizes.try_pop());
|
||||
if packet_len.is_none() {
|
||||
// Small delay, TCs might arrive very quickly.
|
||||
Mono::delay(20.millis()).await;
|
||||
continue;
|
||||
let read_bytes = cx.local.tc_rx.read(&mut read_buf).await;
|
||||
for &byte in read_buf[0..read_bytes].iter() {
|
||||
match cobs_decoder.feed(byte) {
|
||||
Ok(result) => {
|
||||
if result.is_none() {
|
||||
continue;
|
||||
}
|
||||
let frame = result.unwrap();
|
||||
match CcsdsPacketReader::new_with_checksum(&cobs_decoder.dest()[0..frame]) {
|
||||
Ok(packet) => {
|
||||
let request = postcard::take_from_bytes::<models::Request>(
|
||||
packet.user_data(),
|
||||
);
|
||||
if request.is_err() {
|
||||
defmt::warn!(
|
||||
"Failed to parse command: {}",
|
||||
request.err().unwrap()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let (request, remainder) = request.unwrap();
|
||||
let response = match request {
|
||||
models::Request::Corrupt(slot) => {
|
||||
match slot {
|
||||
models::AppSel::A => {
|
||||
defmt::info!("corrupting App Image A");
|
||||
corrupt_image(APP_A_START_ADDR, cx.local.nvm);
|
||||
}
|
||||
models::AppSel::B => {
|
||||
defmt::info!("corrupting App Image B");
|
||||
corrupt_image(APP_B_START_ADDR, cx.local.nvm);
|
||||
}
|
||||
}
|
||||
Response::Ok
|
||||
}
|
||||
models::Request::WriteNvm { offset } => {
|
||||
defmt::info!(
|
||||
"writing {} bytes to NVM at offset 0x{:08x}",
|
||||
remainder.len(),
|
||||
offset
|
||||
);
|
||||
cx.local.nvm.write(offset as usize, remainder);
|
||||
defmt::info!("write complete");
|
||||
Response::Ok
|
||||
}
|
||||
models::Request::SetBootSlot(app_sel) => {
|
||||
defmt::info!(
|
||||
"received boot selection command with app select: {:?}",
|
||||
app_sel
|
||||
);
|
||||
cx.local.nvm.write(
|
||||
PREFERRED_SLOT_OFFSET as usize,
|
||||
&[app_sel as u8],
|
||||
);
|
||||
Response::Ok
|
||||
}
|
||||
models::Request::Ping => {
|
||||
defmt::info!("received ping TC");
|
||||
Response::Ok
|
||||
}
|
||||
};
|
||||
match create_encoded_tm_packet(
|
||||
&mut tm_buf,
|
||||
&mut encoded_tm_buf,
|
||||
SpacePacketHeader::new_from_apid(models::APID),
|
||||
response,
|
||||
) {
|
||||
Ok(encoded_len) => {
|
||||
cx.local
|
||||
.tm_tx
|
||||
.write_all(&encoded_tm_buf[0..encoded_len])
|
||||
.await;
|
||||
}
|
||||
Err(e) => {
|
||||
defmt::warn!("Failed to create or encode TM packet: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
defmt::warn!("CCSDS packet error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
defmt::warn!("COBS decoding error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
let packet_len = packet_len.unwrap();
|
||||
defmt::info!("received packet with length {}", packet_len);
|
||||
let popped_packet_len = cx
|
||||
.shared
|
||||
.tc_rb
|
||||
.lock(|rb| rb.buf.pop_slice(&mut cx.local.tc_buf[0..packet_len]));
|
||||
assert_eq!(popped_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 let Err(e) = pus_tc {
|
||||
defmt::warn!("PUS TC error: {}", e);
|
||||
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_rb.lock(|prod| {
|
||||
prod.sizes.try_push(tm.len_written()).unwrap();
|
||||
prod.buf.push_slice(&cx.local.verif_buf[0..written_size]);
|
||||
});
|
||||
};
|
||||
let request_id = cx.local.verif_reporter.read_request_id(&pus_tc);
|
||||
let tm = cx
|
||||
.local
|
||||
.verif_reporter
|
||||
.acceptance_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
|
||||
.expect("acceptance success failed");
|
||||
write_and_send(&tm);
|
||||
|
||||
let tm = cx
|
||||
.local
|
||||
.verif_reporter
|
||||
.start_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
|
||||
.expect("acceptance success failed");
|
||||
write_and_send(&tm);
|
||||
|
||||
if pus_tc.service_type_id() == 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);
|
||||
buf[0] += 1;
|
||||
cx.local.nvm.write(base_addr as usize + 32, &buf);
|
||||
let tm = cx
|
||||
.local
|
||||
.verif_reporter
|
||||
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
|
||||
.expect("completion success failed");
|
||||
write_and_send(&tm);
|
||||
};
|
||||
if pus_tc.message_subtype_id() == ActionId::CorruptImageA as u8 {
|
||||
defmt::info!("corrupting App Image A");
|
||||
corrupt_image(APP_A_START_ADDR);
|
||||
}
|
||||
if pus_tc.message_subtype_id() == ActionId::CorruptImageB as u8 {
|
||||
defmt::info!("corrupting App Image B");
|
||||
corrupt_image(APP_B_START_ADDR);
|
||||
}
|
||||
if pus_tc.message_subtype_id() == ActionId::SetBootSlot as u8 {
|
||||
if pus_tc.app_data().is_empty() {
|
||||
defmt::warn!("App data for preferred image command too short");
|
||||
}
|
||||
let app_sel_result = AppSel::try_from(pus_tc.app_data()[0]);
|
||||
if app_sel_result.is_err() {
|
||||
defmt::warn!("Invalid app selection value: {}", pus_tc.app_data()[0]);
|
||||
}
|
||||
defmt::info!(
|
||||
"received boot selection command with app select: {:?}",
|
||||
app_sel_result.unwrap()
|
||||
);
|
||||
cx.local
|
||||
.nvm
|
||||
.write(PREFERRED_SLOT_OFFSET as usize, &[pus_tc.app_data()[0]]);
|
||||
let tm = cx
|
||||
.local
|
||||
.verif_reporter
|
||||
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
|
||||
.expect("completion success failed");
|
||||
write_and_send(&tm);
|
||||
}
|
||||
}
|
||||
if pus_tc.service_type_id() == PusServiceId::Test as u8 && pus_tc.message_subtype_id() == 1
|
||||
{
|
||||
defmt::info!("received ping TC");
|
||||
let tm = cx
|
||||
.local
|
||||
.verif_reporter
|
||||
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
|
||||
.expect("completion success failed");
|
||||
write_and_send(&tm);
|
||||
} else if pus_tc.service_type_id() == PusServiceId::MemoryManagement as u8 {
|
||||
let tm = cx
|
||||
.local
|
||||
.verif_reporter
|
||||
.step_success(
|
||||
cx.local.src_data_buf,
|
||||
&request_id,
|
||||
u14::ZERO,
|
||||
0,
|
||||
&[],
|
||||
EcssEnumU8::new(0),
|
||||
)
|
||||
.expect("step success failed");
|
||||
write_and_send(&tm);
|
||||
// Raw memory write TC
|
||||
if pus_tc.message_subtype_id() == 2 {
|
||||
let app_data = pus_tc.app_data();
|
||||
if app_data.len() < 10 {
|
||||
defmt::warn!(
|
||||
"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 {
|
||||
defmt::warn!("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() {
|
||||
defmt::warn!(
|
||||
"invalid data length {} for raw mem write detected",
|
||||
data_len
|
||||
);
|
||||
// TODO: Error reporting
|
||||
return;
|
||||
}
|
||||
let data = &app_data[10..10 + data_len as usize];
|
||||
defmt::info!("writing {} bytes at offset {} to NVM", data_len, offset);
|
||||
cx.local.nvm.write(offset as usize, data);
|
||||
let tm = if !cx.local.nvm.verify(offset as usize, data) {
|
||||
defmt::warn!("verification of data written to NVM failed");
|
||||
cx.local
|
||||
.verif_reporter
|
||||
.completion_failure(
|
||||
cx.local.src_data_buf,
|
||||
&request_id,
|
||||
u14::ZERO,
|
||||
0,
|
||||
FailParams::new(&[], &EcssEnumU8::new(0), &[]),
|
||||
)
|
||||
.expect("completion success failed")
|
||||
} else {
|
||||
cx.local
|
||||
.verif_reporter
|
||||
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
|
||||
.expect("completion success failed")
|
||||
};
|
||||
write_and_send(&tm);
|
||||
defmt::info!("NVM operation done");
|
||||
}
|
||||
}
|
||||
pub fn corrupt_image(base_addr: u32, nvm: &mut M95M01) {
|
||||
let mut buf = [0u8; 4];
|
||||
nvm.read(base_addr as usize + 32, &mut buf);
|
||||
buf[0] += 1;
|
||||
nvm.write(base_addr as usize + 32, &buf);
|
||||
}
|
||||
|
||||
#[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_rx
|
||||
],
|
||||
shared=[tm_rb]
|
||||
)]
|
||||
async fn pus_tm_tx_handler(mut cx: pus_tm_tx_handler::Context) {
|
||||
async fn tm_tx_handler(cx: tm_tx_handler::Context) {
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
loop {
|
||||
let mut occupied_len = cx.shared.tm_rb.lock(|rb| rb.sizes.occupied_len());
|
||||
while occupied_len > 0 {
|
||||
let next_size = cx.shared.tm_rb.lock(|rb| {
|
||||
let next_size = rb.sizes.try_pop().unwrap();
|
||||
rb.buf.pop_slice(&mut cx.local.read_buf[0..next_size]);
|
||||
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_all(&cx.local.encoded_buf[0..send_size + 2])
|
||||
.unwrap();
|
||||
occupied_len -= 1;
|
||||
Mono::delay(2.millis()).await;
|
||||
let read_len = cx.local.tm_rx.read(&mut buf).await;
|
||||
if let Err(e) = cx.local.uart_tx.write_all(&buf[0..read_len]).await {
|
||||
defmt::warn!("UART TX overrun error: {}", e);
|
||||
}
|
||||
Mono::delay(50.millis()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = ["clippy", "llvm-tools"]
|
||||
target = "thumbv6m-none-eabi"
|
||||
@@ -1,36 +0,0 @@
|
||||
Change Log
|
||||
=======
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [unreleased]
|
||||
|
||||
## [v0.3.0] 2025-09-03
|
||||
|
||||
Bumped allowed va108xx-hal to v0.12
|
||||
|
||||
## [v0.2.1] 2025-03-07
|
||||
|
||||
- Bumped allowed va108xx-hal to v0.11
|
||||
|
||||
## [v0.2.0] 2025-02-17
|
||||
|
||||
- Bumped va108xx-hal to v0.10.0
|
||||
- Remove `embassy` module, expose public functions in library root directly
|
||||
|
||||
|
||||
## [v0.1.2] and [v0.1.1] 2025-02-13
|
||||
|
||||
Docs patch
|
||||
|
||||
## [v0.1.0] 2025-02-13
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.3.0...HEAD
|
||||
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.2.1...va10xx-embassy-v0.3.0
|
||||
[v0.2.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.2.0...va10xx-embassy-v0.2.1
|
||||
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.1.2...va10xx-embassy-v0.2.0
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "va108xx-embassy"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
description = "Embassy-rs support for the Vorago VA108xx family of microcontrollers"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/vorago-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/vorago-rs"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["no-std", "hal", "cortex-m", "vorago", "va108xx"]
|
||||
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
vorago-shared-hal = { version = "0.2", path = "../../vorago-shared-hal", features = ["vor1x"] }
|
||||
va108xx-hal = { version = "0.12", path = "../va108xx-hal" }
|
||||
|
||||
[features]
|
||||
default = ["irq-oc30-oc31"]
|
||||
irqs-in-lib = []
|
||||
# This determines the reserved interrupt functions for the embassy time drivers. Only one
|
||||
# is allowed to be selected!
|
||||
irq-oc28-oc29 = ["irqs-in-lib"]
|
||||
irq-oc29-oc30 = ["irqs-in-lib"]
|
||||
irq-oc30-oc31 = ["irqs-in-lib"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["thumbv6m-none-eabi"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
@@ -1,10 +1 @@
|
||||
[](https://crates.io/crates/va108xx-embassy)
|
||||
[](https://docs.rs/va108xx-embassy)
|
||||
|
||||
# Embassy-rs support for the Vorago VA108xx MCU family
|
||||
|
||||
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
|
||||
VA108xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM
|
||||
peripherals provided by the VA108xx family for this purpose.
|
||||
|
||||
The documentation contains more information on how to use this crate.
|
||||
This crate was moved into `va108xx-hal`.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
||||
cargo +nightly doc --open
|
||||
@@ -8,6 +8,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [unreleased]
|
||||
|
||||
## [v0.13.1] 2026-05-19
|
||||
|
||||
- Docs.rs feature fix
|
||||
|
||||
## [v0.13.0] 2026-05-19
|
||||
|
||||
- Bump `vorago-shared-hal` dependency to v0.4
|
||||
- Integrate `va108xx-embassy` as a `embassy-time` module.
|
||||
|
||||
## [v0.12.0] 2025-09-03
|
||||
|
||||
## Changed
|
||||
@@ -276,7 +285,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Added basic test binary in form of an example
|
||||
- README with basic instructions how to set up own binary crate
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.12.0...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/compare/va108xx-hal-v0.13.1...HEAD
|
||||
[v0.13.1]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/compare/va108xx-hal-v0.13.0...va108xx-hal-v0.13.1
|
||||
[v0.13.0]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/tag/va108xx-hal-v0.13.0
|
||||
[v0.12.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.11.1...va108xx-hal-v0.12.0
|
||||
[v0.11.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.11.0...va108xx-hal-v0.11.1
|
||||
[v0.11.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.10.0...va108xx-hal-v0.11.0
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "va108xx-hal"
|
||||
version = "0.12.0"
|
||||
version = "0.13.1"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
description = "HAL for the Vorago VA108xx family of microcontrollers"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/vorago-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/vorago-rs"
|
||||
@@ -12,8 +12,8 @@ categories = ["aerospace", "embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"]}
|
||||
vorago-shared-hal = { version = "0.2", path = "../../vorago-shared-hal", features = ["vor1x"] }
|
||||
fugit = "0.3"
|
||||
vorago-shared-hal = { version = "0.4", path = "../../vorago-shared-hal", features = ["vor1x"] }
|
||||
fugit = "0.4"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
va108xx = { version = "0.6", path = "../va108xx", default-features = false, features = ["critical-section"] }
|
||||
defmt = { version = "1", optional = true }
|
||||
@@ -28,9 +28,17 @@ default = ["rt"]
|
||||
rt = ["va108xx/rt"]
|
||||
defmt = ["dep:defmt", "vorago-shared-hal/defmt", "va108xx/defmt"]
|
||||
|
||||
# Embassy time features
|
||||
_irqs-in-lib = []
|
||||
# This determines the reserved interrupt functions for the embassy time drivers. Only one
|
||||
# is allowed to be selected!
|
||||
embassy-oc30-oc31 = ["_irqs-in-lib"]
|
||||
embassy-oc29-oc30 = ["_irqs-in-lib"]
|
||||
embassy-oc28-oc29 = ["_irqs-in-lib"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
targets = ["thumbv6m-none-eabi"]
|
||||
features = ["defmt", "embassy-oc30-oc31"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
|
||||
@@ -23,21 +23,20 @@
|
||||
//!
|
||||
//! You can disable the default features and then specify one of the features above to use the
|
||||
//! documented combination of IRQs. It is also possible to specify custom IRQs by importing and
|
||||
//! using the [embassy_time_driver_irqs] macro to declare the IRQ handlers in the
|
||||
//! using the [crate::embassy_time_driver_irqs] macro to declare the IRQ handlers in the
|
||||
//! application code. If this is done, [init_with_custom_irqs] must be used
|
||||
//! method to pass the IRQ numbers to the library.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/branch/main/va108xx/examples/embassy)
|
||||
#![no_std]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
#[cfg(feature = "irqs-in-lib")]
|
||||
use va108xx_hal::pac::{self, interrupt};
|
||||
use va108xx_hal::time::Hertz;
|
||||
use va108xx_hal::timer::TimInstance;
|
||||
use vorago_shared_hal::embassy::time_driver;
|
||||
#[cfg(feature = "_irqs-in-lib")]
|
||||
use crate::pac::{self, interrupt};
|
||||
use crate::time::Hertz;
|
||||
use crate::timer::TimInstance;
|
||||
|
||||
pub use vorago_shared_hal::embassy::time_driver;
|
||||
|
||||
/// Macro to define the IRQ handlers for the time driver.
|
||||
///
|
||||
@@ -45,7 +44,7 @@ use vorago_shared_hal::embassy::time_driver;
|
||||
/// the feature flags specified. However, the macro is exported to allow users to specify the
|
||||
/// interrupt handlers themselves.
|
||||
///
|
||||
/// Please note that you have to explicitely import the [macro@va108xx_hal::pac::interrupt]
|
||||
/// Please note that you have to explicitely import the [macro@crate::pac::interrupt]
|
||||
/// macro in the application code in case this macro is used there.
|
||||
#[macro_export]
|
||||
macro_rules! embassy_time_driver_irqs {
|
||||
@@ -59,7 +58,7 @@ macro_rules! embassy_time_driver_irqs {
|
||||
#[allow(non_snake_case)]
|
||||
fn $timekeeper_irq() {
|
||||
// Safety: We call it once here.
|
||||
unsafe { $crate::time_driver().on_interrupt_timekeeping() }
|
||||
unsafe { $crate::embassy_time::time_driver().on_interrupt_timekeeping() }
|
||||
}
|
||||
|
||||
const ALARM_IRQ: pac::Interrupt = pac::Interrupt::$alarm_irq;
|
||||
@@ -68,25 +67,25 @@ macro_rules! embassy_time_driver_irqs {
|
||||
#[allow(non_snake_case)]
|
||||
fn $alarm_irq() {
|
||||
// Safety: We call it once here.
|
||||
unsafe { $crate::time_driver().on_interrupt_alarm() }
|
||||
unsafe { $crate::embassy_time::time_driver().on_interrupt_alarm() }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Provide three combinations of IRQs for the time driver by default.
|
||||
|
||||
#[cfg(feature = "irq-oc30-oc31")]
|
||||
#[cfg(feature = "embassy-oc30-oc31")]
|
||||
embassy_time_driver_irqs!(timekeeper_irq = OC31, alarm_irq = OC30);
|
||||
#[cfg(feature = "irq-oc29-oc30")]
|
||||
#[cfg(feature = "embassy-oc29-oc30")]
|
||||
embassy_time_driver_irqs!(timekeeper_irq = OC30, alarm_irq = OC29);
|
||||
#[cfg(feature = "irq-oc28-oc29")]
|
||||
#[cfg(feature = "embassy-oc28-oc29")]
|
||||
embassy_time_driver_irqs!(timekeeper_irq = OC29, alarm_irq = OC28);
|
||||
|
||||
/// Initialization method for embassy.
|
||||
///
|
||||
/// This should be used if the interrupt handler is provided by the library, which is the
|
||||
/// default case.
|
||||
#[cfg(feature = "irqs-in-lib")]
|
||||
#[cfg(feature = "_irqs-in-lib")]
|
||||
pub fn init<TimekeeperTim: TimInstance, AlarmTim: TimInstance>(
|
||||
timekeeper_tim: TimekeeperTim,
|
||||
alarm_tim: AlarmTim,
|
||||
@@ -102,8 +101,8 @@ pub fn init_with_custom_irqs<TimekeeperTim: TimInstance, AlarmTim: TimInstance>(
|
||||
timekeeper_tim: TimekeeperTim,
|
||||
alarm_tim: AlarmTim,
|
||||
sysclk: Hertz,
|
||||
timekeeper_irq: pac::Interrupt,
|
||||
alarm_irq: pac::Interrupt,
|
||||
timekeeper_irq: crate::pac::Interrupt,
|
||||
alarm_irq: crate::pac::Interrupt,
|
||||
) {
|
||||
time_driver().__init(sysclk, timekeeper_tim, alarm_tim, timekeeper_irq, alarm_irq)
|
||||
}
|
||||
@@ -29,9 +29,11 @@ pub mod time;
|
||||
pub mod timer;
|
||||
pub mod uart;
|
||||
|
||||
pub mod embassy_time;
|
||||
|
||||
pub use vorago_shared_hal::{
|
||||
disable_nvic_interrupt, enable_nvic_interrupt, FunctionSelect, InterruptConfig,
|
||||
PeripheralSelect,
|
||||
FunctionSelect, InterruptConfig, PeripheralSelect, disable_nvic_interrupt,
|
||||
enable_nvic_interrupt,
|
||||
};
|
||||
|
||||
/// This is the NONE destination reigster value for the IRQSEL peripheral.
|
||||
|
||||
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [unreleased]
|
||||
|
||||
## [v0.6.1] 2026-05-19
|
||||
|
||||
- Rebuild docs for correct target
|
||||
|
||||
## [v0.6.0] 2025-09-03
|
||||
|
||||
- Re-generated PAC with `svd2rust` v0.37.0
|
||||
@@ -88,7 +92,8 @@ defmt version v1
|
||||
- First version of the PAC which builds. Uses a patched version
|
||||
of `svd2rust`: https://github.com/rust-embedded/svd2rust
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-v0.6.0...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/compare/va108xx-v0.6.1...HEAD
|
||||
[v0.6.1]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/tag/va108xx-v0.6.1
|
||||
[v0.6.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-v0.5.1...va108xx-v0.6.0
|
||||
[v0.5.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-v0.5.0...va108xx-v0.5.1
|
||||
[v0.5.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-v0.4.0...va108xx-v0.5.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "va108xx"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2021"
|
||||
description = "PAC for the Vorago VA108xx family of microcontrollers"
|
||||
|
||||
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [unreleased]
|
||||
|
||||
## [v0.10.0] 2026-05-19
|
||||
|
||||
- Bumped `va108xx-hal` dependency to 0.13
|
||||
|
||||
## [v0.9.0] 2025-09-03
|
||||
|
||||
- Bumped `va108xx-hal` dependency to 0.12
|
||||
@@ -66,7 +70,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
it provides a starting point
|
||||
- Added ADC base library and example building on the new max116xx-10bit device driver crate
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.9.0...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/compare/vorago-reb1-v0.10.0...HEAD
|
||||
[v0.10.0]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/tag/vorago-reb1-v0.10.0
|
||||
[v0.9.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.8.1...vorago-reb1-v0.9.0
|
||||
[v0.8.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.8.0...vorago-reb1-v0.8.1
|
||||
[v0.8.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.7.0...vorago-reb1-v0.8.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vorago-reb1"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2021"
|
||||
description = "Board Support Crate for the Vorago REB1 development board"
|
||||
@@ -11,20 +11,20 @@ keywords = ["no-std", "reb1", "cortex-m", "vorago", "va108xx"]
|
||||
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
cortex-m-rt = "0.7"
|
||||
cortex-m = { version = "0.7" }
|
||||
embedded-hal = "1"
|
||||
nb = "1"
|
||||
bitbybit = "1.3"
|
||||
bitbybit = "2"
|
||||
arbitrary-int = "2"
|
||||
max116xx-10bit = "0.3"
|
||||
|
||||
va108xx-hal = { version = "0.12", path = "../va108xx-hal", features = ["rt"] }
|
||||
va108xx-hal = { version = "0.13", path = "../va108xx-hal", features = ["rt"] }
|
||||
|
||||
[features]
|
||||
rt = ["va108xx-hal/rt"]
|
||||
|
||||
[dev-dependencies]
|
||||
cortex-m-rt = "0.7"
|
||||
panic-halt = "1"
|
||||
nb = "1"
|
||||
rtt-target = "0.6"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
resolver = "3"
|
||||
members = [
|
||||
"va416xx",
|
||||
"va416xx-hal",
|
||||
|
||||
@@ -76,7 +76,7 @@ async fn main(spawner: Spawner) {
|
||||
let uart_config = uart::Config::new_with_clock_config(clock_config);
|
||||
let uart0 = uart::Uart::new_for_uart0(dp.uart0, portg.pg0, portg.pg1, uart_config);
|
||||
let (mut tx, rx) = uart0.split();
|
||||
let mut rx = rx.into_rx_with_irq();
|
||||
let mut rx = rx.into_rx_with_interrupt();
|
||||
rx.start();
|
||||
RX.lock(|static_rx| {
|
||||
static_rx.borrow_mut().replace(rx);
|
||||
|
||||
@@ -41,7 +41,7 @@ mod app {
|
||||
.xtal_n_clk_with_src_freq(EXTCLK_FREQ)
|
||||
.freeze()
|
||||
.unwrap();
|
||||
Mono::start(cx.core.SYST, clocks.sysclk().raw());
|
||||
Mono::start(cx.core.SYST, clocks.sysclk().to_raw());
|
||||
let pinsg = PinsG::new(cx.device.portg);
|
||||
let led = Output::new(pinsg.pg5, PinState::Low);
|
||||
blinky::spawn().ok();
|
||||
|
||||
@@ -40,7 +40,7 @@ fn main() -> ! {
|
||||
.freeze()
|
||||
.unwrap();
|
||||
enable_and_init_irq_router();
|
||||
let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
|
||||
let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().to_raw());
|
||||
|
||||
let mut last_interrupt_counter = 0;
|
||||
let mut wdt_ctrl = Wdt::start(dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
|
||||
|
||||
@@ -10,6 +10,7 @@ defmt-rtt = "1"
|
||||
defmt = "1"
|
||||
panic-probe = { version = "1", features = ["defmt"] }
|
||||
static_cell = "2"
|
||||
fugit = "0.4"
|
||||
ringbuf = { version = "0.4", default-features = false }
|
||||
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
|
||||
satrs = { version = "0.3.0-alpha.3", default-features = false, features = ["defmt"] }
|
||||
|
||||
@@ -97,12 +97,12 @@ mod app {
|
||||
use arbitrary_int::{u11, u14};
|
||||
use cortex_m::asm;
|
||||
use embedded_io::Write;
|
||||
use rtic_monotonics::{fugit::ExtU32, Monotonic};
|
||||
// Import panic provider.
|
||||
use panic_probe as _;
|
||||
// Import logger.
|
||||
use defmt_rtt as _;
|
||||
use rtic::Mutex;
|
||||
use rtic_monotonics::systick::prelude::*;
|
||||
use satrs::pus::verification::VerificationReportCreator;
|
||||
use satrs::spacepackets::ecss::PusServiceId;
|
||||
use satrs::spacepackets::ecss::{
|
||||
@@ -116,7 +116,6 @@ mod app {
|
||||
nvm::Nvm,
|
||||
pac,
|
||||
pins::PinsG,
|
||||
prelude::*,
|
||||
uart::{self, Uart},
|
||||
};
|
||||
|
||||
@@ -171,7 +170,7 @@ mod app {
|
||||
let clock_config = uart::ClockConfig::calculate_with_clocks(
|
||||
uart::Bank::Uart0,
|
||||
&clocks,
|
||||
UART_BAUDRATE.Hz(),
|
||||
fugit::HertzU32::from_raw(UART_BAUDRATE),
|
||||
uart::BaudMode::_16,
|
||||
);
|
||||
let uart_config = uart::Config::new_with_clock_config(clock_config);
|
||||
@@ -195,10 +194,10 @@ mod app {
|
||||
.init(StaticRb::<usize, SIZES_RB_SIZE_TC>::default())
|
||||
.split_ref();
|
||||
|
||||
Mono::start(cx.core.SYST, clocks.sysclk().raw());
|
||||
Mono::start(cx.core.SYST, clocks.sysclk().to_raw());
|
||||
CLOCKS.set(clocks).unwrap();
|
||||
|
||||
let mut rx = rx.into_rx_with_irq();
|
||||
let mut rx = rx.into_rx_with_interrupt();
|
||||
let mut rx_context = InterruptContextTimeoutOrMaxSize::new(MAX_TC_FRAME_SIZE);
|
||||
rx.read_fixed_len_or_timeout_based_using_irq(&mut rx_context)
|
||||
.expect("initiating UART RX failed");
|
||||
|
||||
@@ -11,7 +11,7 @@ keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
|
||||
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
vorago-shared-hal = { version = "0.2", path = "../../vorago-shared-hal", features = ["vor4x"] }
|
||||
vorago-shared-hal = { version = "0.4", path = "../../vorago-shared-hal", features = ["vor4x"] }
|
||||
va416xx-hal = { version = "0.6", path = "../va416xx-hal" }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -15,7 +15,7 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
va416xx = { version = "0.5", path = "../va416xx", features = ["critical-section"], default-features = false }
|
||||
derive-mmio = "0.6.1"
|
||||
static_assertions = "1.1"
|
||||
vorago-shared-hal = { version = "0.2", path = "../../vorago-shared-hal", features = ["vor4x"] }
|
||||
vorago-shared-hal = { version = "0.4", path = "../../vorago-shared-hal", features = ["vor4x"] }
|
||||
|
||||
libm = "0.2"
|
||||
nb = "1"
|
||||
@@ -24,7 +24,7 @@ num_enum = { version = "0.7", default-features = false }
|
||||
bitflags = "2"
|
||||
bitbybit = "2"
|
||||
arbitrary-int = "2"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
embedded-can = "0.4"
|
||||
embassy-sync = "0.8"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
|
||||
@@ -150,18 +150,19 @@ impl ClockConfig {
|
||||
tseg2: u8,
|
||||
sjw: u8,
|
||||
) -> Result<ClockConfig, ClockConfigError> {
|
||||
if bitrate.raw() == 0 {
|
||||
if bitrate.to_raw() == 0 {
|
||||
return Err(ClockConfigError::BitrateIsZero);
|
||||
}
|
||||
let nominal_bit_time = 1 + tseg1 as u32 + tseg2 as u32;
|
||||
let prescaler =
|
||||
roundf(clocks.apb1().raw() as f32 / (bitrate.raw() as f32 * nominal_bit_time as f32))
|
||||
as u32;
|
||||
let prescaler = roundf(
|
||||
clocks.apb1().to_raw() as f32 / (bitrate.to_raw() as f32 * nominal_bit_time as f32),
|
||||
) as u32;
|
||||
if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) {
|
||||
return Err(ClockConfigError::CanNotFindPrescaler);
|
||||
}
|
||||
|
||||
let actual_bitrate = (clocks.apb1().raw() as f32) / (prescaler * nominal_bit_time) as f32;
|
||||
let actual_bitrate =
|
||||
(clocks.apb1().to_raw() as f32) / (prescaler * nominal_bit_time) as f32;
|
||||
let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
|
||||
if bitrate_deviation > MAX_BITRATE_DEVIATION {
|
||||
return Err(ClockConfigError::BitrateErrorTooLarge);
|
||||
@@ -269,17 +270,17 @@ pub const fn calculate_nominal_bit_time(
|
||||
target_bitrate: Hertz,
|
||||
prescaler: u8,
|
||||
) -> u32 {
|
||||
apb1_clock.raw() / (target_bitrate.raw() * prescaler as u32)
|
||||
apb1_clock.to_raw() / (target_bitrate.to_raw() * prescaler as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn calculate_actual_bitrate(apb1_clock: Hertz, prescaler: u8, nom_bit_time: u32) -> f32 {
|
||||
apb1_clock.raw() as f32 / (prescaler as u32 * nom_bit_time) as f32
|
||||
apb1_clock.to_raw() as f32 / (prescaler as u32 * nom_bit_time) as f32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn calculate_bitrate_deviation(actual_bitrate: f32, target_bitrate: Hertz) -> f32 {
|
||||
(actual_bitrate - target_bitrate.raw() as f32).abs() / target_bitrate.raw() as f32
|
||||
(actual_bitrate - target_bitrate.to_raw() as f32).abs() / target_bitrate.to_raw() as f32
|
||||
}
|
||||
|
||||
pub trait CanInstance {
|
||||
|
||||
@@ -94,9 +94,9 @@ pub struct PllConfig {
|
||||
pub const fn clock_after_division(clk: Hertz, div_sel: ClockDivisorSelect) -> Hertz {
|
||||
match div_sel {
|
||||
ClockDivisorSelect::Div1 => clk,
|
||||
ClockDivisorSelect::Div2 => Hertz::from_raw(clk.raw() / 2),
|
||||
ClockDivisorSelect::Div4 => Hertz::from_raw(clk.raw() / 4),
|
||||
ClockDivisorSelect::Div8 => Hertz::from_raw(clk.raw() / 8),
|
||||
ClockDivisorSelect::Div2 => Hertz::from_raw(clk.to_raw() / 2),
|
||||
ClockDivisorSelect::Div4 => Hertz::from_raw(clk.to_raw() / 4),
|
||||
ClockDivisorSelect::Div8 => Hertz::from_raw(clk.to_raw() / 8),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ impl ClockConfigurator {
|
||||
// ADC clock (must be 2-12.5 MHz)
|
||||
// NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue
|
||||
// For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range)
|
||||
if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
|
||||
if final_sysclk.to_raw() <= ADC_MAX_CLK.to_raw() * 4 {
|
||||
self.clkgen.ctrl1().modify(|_, w| unsafe {
|
||||
w.adc_clk_div_sel().bits(AdcClockDivisorSelect::Div4 as u8)
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ impl Wdt {
|
||||
|
||||
#[inline]
|
||||
pub fn set_freq(&mut self, freq_ms: u32) {
|
||||
let counter = (self.clock_freq.raw() / 1000) * freq_ms;
|
||||
let counter = (self.clock_freq.to_raw() / 1000) * freq_ms;
|
||||
self.wdt.wdogload().write(|w| unsafe { w.bits(counter) });
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [unreleased]
|
||||
|
||||
## [v0.4.0] 2026-05-19
|
||||
|
||||
## Changed
|
||||
|
||||
- Naming improvements for UART register module
|
||||
- Improved UART Async TX module. Only enable TX below threshold interrupts if the FIFO
|
||||
actually needs to be refilled.
|
||||
|
||||
## [v0.3.0] 2026-05-18
|
||||
|
||||
### Added
|
||||
|
||||
- Add `is_high` and `is_low` for `InputPinAsync`.
|
||||
@@ -16,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped `fugit` from v0.3 to v0.4
|
||||
- Added `RxWithInterrupt::steal`.
|
||||
- Renamed UART `Data` register `value` field to `data`
|
||||
- Improved type level support for resource management for SPI, PWM, UART.
|
||||
@@ -26,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- `Pull::Up` and `Pull::High` were inverted.
|
||||
- Removed HW CS pin provider implementation for PA23, PA22 and PA21, which are multi HW CS pins.
|
||||
- Added missing `AnyPin` trait impl for Multi HW CS pins.
|
||||
- Expose inner `Input` pin for `InputPinAsync`.
|
||||
@@ -53,6 +65,8 @@ Renamed to `vorago-shared-hal`
|
||||
|
||||
Init commit.
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/vorago-shared-hal/compare/v0.2.0...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/compare/vorago-shared-hal-v0.4.0...HEAD
|
||||
[v0.4.0]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/compare/vorago-shared-hal-v0.3.0...vorago-shared-hal-v0.4.0
|
||||
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/tag/vorago-shared-hal-v0.3.0
|
||||
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/vorago-shared-hal/compare/v0.1.0...v0.2.0
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/vorago-shared-hal/src/tag/v0.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vorago-shared-hal"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
description = "Peripheral HAL components shared between Vorago families"
|
||||
edition = "2024"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/vorago-rs"
|
||||
@@ -25,7 +25,7 @@ embedded-io-async = "0.7"
|
||||
raw-slicee = "0.1"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
paste = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
defmt = { version = "1", optional = true }
|
||||
va108xx = { version = "0.6", path = "../va108xx/va108xx", default-features = false, optional = true }
|
||||
va416xx = { version = "0.5", path = "../va416xx/va416xx", default-features = false, optional = true }
|
||||
|
||||
@@ -79,7 +79,9 @@ impl TimerDriver {
|
||||
let mut timekeeper_reg_block = unsafe { TimekeeperTim::ID.steal_regs() };
|
||||
let mut alarm_tim_reg_block = unsafe { AlarmTim::ID.steal_regs() };
|
||||
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
|
||||
SCALE.set((sysclk.raw() / TICK_HZ as u32) as u64).unwrap();
|
||||
SCALE
|
||||
.set((sysclk.to_raw() / TICK_HZ as u32) as u64)
|
||||
.unwrap();
|
||||
timekeeper_reg_block.write_reset_value(u32::MAX);
|
||||
// Decrementing counter.
|
||||
timekeeper_reg_block.write_count_value(u32::MAX);
|
||||
@@ -137,7 +139,7 @@ impl TimerDriver {
|
||||
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
|
||||
|
||||
SCALE
|
||||
.set((TimekeeperTim::clock(clocks).raw() / TICK_HZ as u32) as u64)
|
||||
.set((TimekeeperTim::clock(clocks).to_raw() / TICK_HZ as u32) as u64)
|
||||
.unwrap();
|
||||
timekeeper_regs.write_reset_value(u32::MAX);
|
||||
// Decrementing counter.
|
||||
|
||||
@@ -149,12 +149,12 @@ fn calc_clk_div_generic(
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<u8, ClockTooSlowForFastI2cError> {
|
||||
if speed_mode == I2cSpeed::Regular100khz {
|
||||
Ok(((ref_clk.raw() / CLK_100K.raw() / 20) - 1) as u8)
|
||||
Ok(((ref_clk.to_raw() / CLK_100K.to_raw() / 20) - 1) as u8)
|
||||
} else {
|
||||
if ref_clk.raw() < MIN_CLK_400K.raw() {
|
||||
if ref_clk.to_raw() < MIN_CLK_400K.to_raw() {
|
||||
return Err(ClockTooSlowForFastI2cError);
|
||||
}
|
||||
Ok(((ref_clk.raw() / CLK_400K.raw() / 25) - 1) as u8)
|
||||
Ok(((ref_clk.to_raw() / CLK_400K.to_raw() / 25) - 1) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ pub enum FilterClockSelect {
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Pull {
|
||||
Up = 0,
|
||||
Down = 1,
|
||||
Down = 0,
|
||||
Up = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
|
||||
@@ -164,10 +164,10 @@ impl<Mode> PwmPin<Mode> {
|
||||
pub fn set_period(&mut self, period: impl Into<Hertz>) {
|
||||
self.current_period = period.into();
|
||||
// Avoid division by 0
|
||||
if self.current_period.raw() == 0 {
|
||||
if self.current_period.to_raw() == 0 {
|
||||
return;
|
||||
}
|
||||
self.current_rst_val = self.ref_clk.raw() / self.current_period.raw();
|
||||
self.current_rst_val = self.ref_clk.to_raw() / self.current_period.to_raw();
|
||||
self.regs.write_reset_value(self.current_rst_val);
|
||||
}
|
||||
|
||||
|
||||
@@ -499,11 +499,11 @@ pub fn clk_div_for_target_clock(sys_clk: Hertz, spi_clk: Hertz) -> Option<u16> {
|
||||
}
|
||||
|
||||
// Step 1: Calculate raw divider.
|
||||
let raw_div = sys_clk.raw() / spi_clk.raw();
|
||||
let remainder = sys_clk.raw() % spi_clk.raw();
|
||||
let raw_div = sys_clk.to_raw() / spi_clk.to_raw();
|
||||
let remainder = sys_clk.to_raw() % spi_clk.to_raw();
|
||||
|
||||
// Step 2: Round up if necessary.
|
||||
let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
|
||||
let mut rounded_div = if remainder * 2 >= spi_clk.to_raw() {
|
||||
raw_div + 1
|
||||
} else {
|
||||
raw_div
|
||||
|
||||
@@ -406,7 +406,7 @@ impl CountdownTimer {
|
||||
pub fn load(&mut self, timeout: impl Into<Hertz>) {
|
||||
self.disable();
|
||||
self.curr_freq = timeout.into();
|
||||
self.rst_val = self.ref_clk.raw() / self.curr_freq.raw();
|
||||
self.rst_val = self.ref_clk.to_raw() / self.curr_freq.to_raw();
|
||||
self.set_reload(self.rst_val);
|
||||
self.set_count(self.rst_val);
|
||||
}
|
||||
@@ -497,7 +497,7 @@ impl CountdownTimer {
|
||||
//
|
||||
impl embedded_hal::delay::DelayNs for CountdownTimer {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
let ticks = (u64::from(ns)) * (u64::from(self.ref_clk.raw())) / 1_000_000_000;
|
||||
let ticks = (u64::from(ns)) * (u64::from(self.ref_clk.to_raw())) / 1_000_000_000;
|
||||
|
||||
let full_cycles = ticks >> 32;
|
||||
let mut last_count;
|
||||
|
||||
@@ -47,6 +47,9 @@ pub use tx_async::*;
|
||||
pub mod rx_async;
|
||||
pub use rx_async::*;
|
||||
|
||||
/// FIFO depth of the UART for both the RX and TX FIFO.
|
||||
pub const FIFO_DEPTH: usize = 16;
|
||||
|
||||
//==================================================================================================
|
||||
// Type-Level support
|
||||
//==================================================================================================
|
||||
@@ -161,11 +164,11 @@ impl ClockConfig {
|
||||
// This is the calculation: (64.0 * (x - integer_part as f32) + 0.5) as u32 without floating
|
||||
// point calculations.
|
||||
let multiplier = baud_mode.multiplier();
|
||||
let frac = ((ref_clk.raw() % (baudrate.raw() * multiplier)) * 64
|
||||
+ (baudrate.raw() * (multiplier / 2)))
|
||||
/ (baudrate.raw() * multiplier);
|
||||
let frac = ((ref_clk.to_raw() % (baudrate.to_raw() * multiplier)) * 64
|
||||
+ (baudrate.to_raw() * (multiplier / 2)))
|
||||
/ (baudrate.to_raw() * multiplier);
|
||||
// Calculations here are derived from chapter 4.8.5 (p.79) of the datasheet.
|
||||
let integer_div = ref_clk.raw() / (baudrate.raw() * multiplier);
|
||||
let integer_div = ref_clk.to_raw() / (baudrate.to_raw() * multiplier);
|
||||
Self {
|
||||
frac: u6::new(frac as u8),
|
||||
div: u18::new(integer_div),
|
||||
@@ -731,7 +734,7 @@ impl Uart {
|
||||
Parity::Odd => (true, false),
|
||||
Parity::Even => (true, true),
|
||||
};
|
||||
reg_block.write_ctrl(
|
||||
reg_block.write_control(
|
||||
Control::builder()
|
||||
.with_baud8(config.clock_config.baud_mode == BaudMode::_8)
|
||||
.with_auto_rts(false)
|
||||
@@ -813,14 +816,14 @@ impl Uart {
|
||||
}
|
||||
|
||||
pub fn listen(&mut self, event: Event) {
|
||||
self.tx.regs.modify_irq_enabled(|mut value| {
|
||||
self.tx.regs.modify_interrupt_enable(|mut value| {
|
||||
match event {
|
||||
Event::RxError => value.set_rx_status(true),
|
||||
Event::RxFifoHalfFull => value.set_rx(true),
|
||||
Event::RxTimeout => value.set_rx_timeout(true),
|
||||
Event::TxEmpty => value.set_tx_empty(true),
|
||||
Event::TxError => value.set_tx_status(true),
|
||||
Event::TxFifoHalfFull => value.set_tx(true),
|
||||
Event::TxFifoHalfFull => value.set_tx_below_trigger(true),
|
||||
Event::TxCts => value.set_tx_cts(true),
|
||||
}
|
||||
value
|
||||
@@ -828,14 +831,14 @@ impl Uart {
|
||||
}
|
||||
|
||||
pub fn unlisten(&mut self, event: Event) {
|
||||
self.tx.regs.modify_irq_enabled(|mut value| {
|
||||
self.tx.regs.modify_interrupt_enable(|mut value| {
|
||||
match event {
|
||||
Event::RxError => value.set_rx_status(false),
|
||||
Event::RxFifoHalfFull => value.set_rx(false),
|
||||
Event::RxTimeout => value.set_rx_timeout(false),
|
||||
Event::TxEmpty => value.set_tx_empty(false),
|
||||
Event::TxError => value.set_tx_status(false),
|
||||
Event::TxFifoHalfFull => value.set_tx(false),
|
||||
Event::TxFifoHalfFull => value.set_tx_below_trigger(false),
|
||||
Event::TxCts => value.set_tx_cts(false),
|
||||
}
|
||||
value
|
||||
@@ -904,7 +907,7 @@ pub fn disable_rx(uart: &mut MmioUart<'static>) {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enable_rx_interrupts(uart: &mut MmioUart<'static>, timeout: bool) {
|
||||
uart.modify_irq_enabled(|mut value| {
|
||||
uart.modify_interrupt_enable(|mut value| {
|
||||
value.set_rx_status(true);
|
||||
value.set_rx(true);
|
||||
if timeout {
|
||||
@@ -916,7 +919,7 @@ pub fn enable_rx_interrupts(uart: &mut MmioUart<'static>, timeout: bool) {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn disable_rx_interrupts(uart: &mut MmioUart<'static>) {
|
||||
uart.modify_irq_enabled(|mut value| {
|
||||
uart.modify_interrupt_enable(|mut value| {
|
||||
value.set_rx_status(false);
|
||||
value.set_rx(false);
|
||||
value.set_rx_timeout(false);
|
||||
@@ -1035,6 +1038,13 @@ impl Rx {
|
||||
self.regs.read_data().raw_value()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_rx_with_interrupt(self) -> RxWithInterrupt {
|
||||
RxWithInterrupt::new(self)
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.3.0", note = "Use into_rx_with_interrupt instead")]
|
||||
#[inline]
|
||||
pub fn into_rx_with_irq(self) -> RxWithInterrupt {
|
||||
RxWithInterrupt::new(self)
|
||||
}
|
||||
@@ -1101,9 +1111,9 @@ pub fn disable_tx(uart: &mut MmioUart<'static>) {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enable_tx_interrupts(uart: &mut MmioUart<'static>) {
|
||||
uart.modify_irq_enabled(|mut value| {
|
||||
value.set_tx(true);
|
||||
pub fn enable_tx_interrupts(tx_below_trigger: bool, uart: &mut MmioUart<'static>) {
|
||||
uart.modify_interrupt_enable(|mut value| {
|
||||
value.set_tx_below_trigger(tx_below_trigger);
|
||||
value.set_tx_empty(true);
|
||||
value.set_tx_status(true);
|
||||
value
|
||||
@@ -1112,8 +1122,8 @@ pub fn enable_tx_interrupts(uart: &mut MmioUart<'static>) {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn disable_tx_interrupts(uart: &mut MmioUart<'static>) {
|
||||
uart.modify_irq_enabled(|mut value| {
|
||||
value.set_tx(false);
|
||||
uart.modify_interrupt_enable(|mut value| {
|
||||
value.set_tx_below_trigger(false);
|
||||
value.set_tx_empty(false);
|
||||
value.set_tx_status(false);
|
||||
value
|
||||
@@ -1176,18 +1186,24 @@ impl Tx {
|
||||
|
||||
/// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
|
||||
///
|
||||
/// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty.
|
||||
/// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty and the
|
||||
/// `tx_below_trigger` parameter is set to `true`. This should be set to true if the total
|
||||
/// amount of data to be transmitted is larger than the FIFO size.
|
||||
/// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow
|
||||
/// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal
|
||||
/// is 0
|
||||
#[inline]
|
||||
pub fn enable_interrupts(&mut self, #[cfg(feature = "vor4x")] enable_in_nvic: bool) {
|
||||
pub fn enable_interrupts(
|
||||
&mut self,
|
||||
tx_below_trigger: bool,
|
||||
#[cfg(feature = "vor4x")] enable_in_nvic: bool,
|
||||
) {
|
||||
#[cfg(feature = "vor4x")]
|
||||
if enable_in_nvic {
|
||||
unsafe { enable_nvic_interrupt(self.id.interrupt_id_tx()) };
|
||||
}
|
||||
// Safety: We own the UART structure
|
||||
enable_tx_interrupts(&mut self.regs);
|
||||
enable_tx_interrupts(tx_below_trigger, &mut self.regs);
|
||||
}
|
||||
|
||||
/// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
|
||||
@@ -1297,6 +1313,7 @@ impl embedded_io::Write for Tx {
|
||||
pub struct RxWithInterrupt(Rx);
|
||||
|
||||
impl RxWithInterrupt {
|
||||
#[inline]
|
||||
pub fn new(rx: Rx) -> Self {
|
||||
Self(rx)
|
||||
}
|
||||
@@ -1378,8 +1395,8 @@ impl RxWithInterrupt {
|
||||
pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> InterruptResult {
|
||||
let mut result = InterruptResult::default();
|
||||
|
||||
let irq_status = self.0.regs.read_irq_status();
|
||||
let irq_enabled = self.0.regs.read_irq_enabled();
|
||||
let irq_status = self.0.regs.read_interrupt_status();
|
||||
let irq_enabled = self.0.regs.read_interrupt_enable();
|
||||
let rx_enabled = irq_enabled.rx();
|
||||
|
||||
// Half-Full interrupt. We have a guaranteed amount of data we can read.
|
||||
@@ -1443,7 +1460,7 @@ impl RxWithInterrupt {
|
||||
}
|
||||
let mut result = InterruptResultMaxSizeOrTimeout::default();
|
||||
|
||||
let irq_status = self.0.regs.read_irq_status();
|
||||
let irq_status = self.0.regs.read_interrupt_status();
|
||||
let rx_enabled = self.0.regs.read_enable().rx();
|
||||
|
||||
// Half-Full interrupt. We have a guaranteed amount of data we can read.
|
||||
|
||||
@@ -188,7 +188,7 @@ pub struct InterruptControl {
|
||||
|
||||
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
|
||||
#[bit(4, rw)]
|
||||
tx: bool,
|
||||
tx_below_trigger: bool,
|
||||
/// Generates an interrupt on TX FIFO overflow.
|
||||
#[bit(5, rw)]
|
||||
tx_status: bool,
|
||||
@@ -213,7 +213,7 @@ pub struct InterruptStatus {
|
||||
|
||||
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
|
||||
#[bit(4, r)]
|
||||
tx: bool,
|
||||
tx_below_trigger: bool,
|
||||
/// Generates an interrupt on TX FIFO overflow.
|
||||
#[bit(5, r)]
|
||||
tx_status: bool,
|
||||
@@ -262,7 +262,7 @@ pub struct State {
|
||||
pub struct Uart {
|
||||
data: Data,
|
||||
enable: Enable,
|
||||
ctrl: Control,
|
||||
control: Control,
|
||||
clkscale: ClockScale,
|
||||
#[mmio(PureRead)]
|
||||
rx_status: RxStatus,
|
||||
@@ -274,11 +274,11 @@ pub struct Uart {
|
||||
txbreak: u32,
|
||||
addr9: u32,
|
||||
addr9mask: u32,
|
||||
irq_enabled: InterruptControl,
|
||||
interrupt_enable: InterruptControl,
|
||||
#[mmio(PureRead)]
|
||||
irq_raw: InterruptStatus,
|
||||
interrupt_raw: InterruptStatus,
|
||||
#[mmio(PureRead)]
|
||||
irq_status: InterruptStatus,
|
||||
interrupt_status: InterruptStatus,
|
||||
#[mmio(Write)]
|
||||
irq_clr: InterruptClear,
|
||||
rx_fifo_trigger: FifoTrigger,
|
||||
|
||||
@@ -134,8 +134,8 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting(
|
||||
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8>>>>,
|
||||
) -> Result<(), AsyncUartErrors> {
|
||||
let uart_regs = unsafe { bank.steal_regs() };
|
||||
let irq_status = uart_regs.read_irq_status();
|
||||
let irq_enabled = uart_regs.read_irq_enabled();
|
||||
let irq_status = uart_regs.read_interrupt_status();
|
||||
let irq_enabled = uart_regs.read_interrupt_enable();
|
||||
let rx_enabled = irq_enabled.rx();
|
||||
let mut read_some_data = false;
|
||||
let mut queue_overflow = false;
|
||||
@@ -202,8 +202,8 @@ pub fn on_interrupt_rx_async_heapless_queue(
|
||||
prod: &mut heapless::spsc::Producer<'_, u8>,
|
||||
) -> Result<(), AsyncUartErrors> {
|
||||
let uart_regs = unsafe { bank.steal_regs() };
|
||||
let irq_status = uart_regs.read_irq_status();
|
||||
let irq_enabled = uart_regs.read_irq_enabled();
|
||||
let irq_status = uart_regs.read_interrupt_status();
|
||||
let irq_enabled = uart_regs.read_interrupt_enable();
|
||||
let rx_enabled = irq_enabled.rx();
|
||||
let mut read_some_data = false;
|
||||
let mut queue_overflow = false;
|
||||
|
||||
@@ -38,13 +38,14 @@ fn tx_is_drained(tx: &Tx) -> bool {
|
||||
pub fn on_interrupt_tx(bank: Bank) {
|
||||
let mut uart = unsafe { bank.steal_regs() };
|
||||
let idx = bank as usize;
|
||||
let irq_enabled = uart.read_irq_enabled();
|
||||
let irq_enabled = uart.read_interrupt_enable();
|
||||
// IRQ is not related to TX.
|
||||
if !irq_enabled.tx() && !irq_enabled.tx_empty() {
|
||||
if !irq_enabled.tx_below_trigger() && !irq_enabled.tx_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let tx_status = uart.read_tx_status();
|
||||
let interrupt_status = uart.read_interrupt_status();
|
||||
let unexpected_overrun = tx_status.wr_lost();
|
||||
let mut context = critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
@@ -54,17 +55,14 @@ pub fn on_interrupt_tx(bank: Bank) {
|
||||
// Safety: We documented that the user provided slice must outlive the future, so we convert
|
||||
// the raw pointer back to the slice here.
|
||||
let slice = unsafe { context.slice.get().unwrap() };
|
||||
if context.progress >= slice.len() && !tx_status.tx_busy() {
|
||||
uart.modify_irq_enabled(|mut value| {
|
||||
value.set_tx(false);
|
||||
value.set_tx_empty(false);
|
||||
value.set_tx_status(false);
|
||||
value
|
||||
});
|
||||
uart.modify_enable(|mut value| {
|
||||
value.set_tx(false);
|
||||
if context.progress >= slice.len() && interrupt_status.tx_empty() {
|
||||
uart.modify_interrupt_enable(|value| {
|
||||
value
|
||||
.with_tx_below_trigger(false)
|
||||
.with_tx_empty(false)
|
||||
.with_tx_status(false)
|
||||
});
|
||||
uart.modify_enable(|value| value.with_tx(false));
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
@@ -84,6 +82,10 @@ pub fn on_interrupt_tx(bank: Bank) {
|
||||
uart.write_data(Data::new_with_raw_value(slice[context.progress] as u32));
|
||||
context.progress += 1;
|
||||
}
|
||||
// Now we only require the TX empty interrupt.
|
||||
if context.progress == slice.len() {
|
||||
uart.modify_interrupt_enable(|value| value.with_tx_below_trigger(false));
|
||||
}
|
||||
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
@@ -125,7 +127,7 @@ impl TxFuture {
|
||||
tx.disable();
|
||||
tx.clear_fifo();
|
||||
|
||||
let init_fill_count = core::cmp::min(data.len(), 16);
|
||||
let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH);
|
||||
// We fill the FIFO.
|
||||
for data in data.iter().take(init_fill_count) {
|
||||
tx.regs.write_data(Data::new_with_raw_value(*data as u32));
|
||||
@@ -139,6 +141,7 @@ impl TxFuture {
|
||||
// Ensure those are enabled inside a critical section at the same time. Can lead to
|
||||
// weird glitches otherwise.
|
||||
tx.enable_interrupts(
|
||||
data.len() > FIFO_DEPTH,
|
||||
#[cfg(feature = "vor4x")]
|
||||
true,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user