Compare commits

..

5 Commits

Author SHA1 Message Date
Robin Mueller 1fafe149c1 need to add UDP to tmtc-utils 2026-05-13 10:33:00 +02:00
Robin Mueller bcc1cc850f first TC handling 2026-05-13 10:23:44 +02:00
Robin Mueller 2cab8d7fed new models library 2026-05-13 09:49:03 +02:00
Robin Mueller b6612ed861 introduce new embedded-models lib 2026-05-13 09:37:51 +02:00
Robin Mueller 9d15dbcf09 update STM32H7 example code 2026-05-12 15:05:24 +02:00
36 changed files with 1426 additions and 109563 deletions
+1
View File
@@ -9,6 +9,7 @@ members = [
"satrs-example/minisim", "satrs-example/minisim",
"satrs-shared", "satrs-shared",
"embedded-examples/embedded-client", "embedded-examples/embedded-client",
"embedded-examples/models",
] ]
exclude = [ exclude = [
@@ -10,6 +10,7 @@ toml = "0.9"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
satrs-stm32f3-disco-rtic = { path = "../stm32f3-disco-rtic" } satrs-stm32f3-disco-rtic = { path = "../stm32f3-disco-rtic" }
spacepackets = { version = "0.17" } spacepackets = { version = "0.17" }
embedded-models = { path = "../models" }
tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1" } tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1" }
postcard = { version = "1", features = ["alloc"] } postcard = { version = "1", features = ["alloc"] }
cobs = "0.5" cobs = "0.5"
@@ -1,13 +1,9 @@
use std::{ use std::time::Duration;
fs::File,
io::Read,
path::Path,
time::{Duration, SystemTime},
};
use clap::Parser; use clap::Parser;
use cobs::CobsDecoderOwned; use cobs::CobsDecoderOwned;
use satrs_stm32f3_disco_rtic::Request; use embedded_client::setup_logger;
use embedded_models::stm32f3;
use spacepackets::{CcsdsPacketCreatorOwned, CcsdsPacketReader, SpHeader}; use spacepackets::{CcsdsPacketCreatorOwned, CcsdsPacketReader, SpHeader};
use tmtc_utils::transport::serial::PacketTransportSerialCobs; use tmtc_utils::transport::serial::PacketTransportSerialCobs;
@@ -21,47 +17,11 @@ struct Cli {
set_led_frequency: Option<u32>, set_led_frequency: Option<u32>,
} }
#[derive(Debug, serde::Deserialize)]
struct Config {
interface: Interface,
}
#[derive(Debug, serde::Deserialize)]
struct Interface {
serial_port: String,
}
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::Debug)
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
.apply()?;
Ok(())
}
fn main() { fn main() {
setup_logger().expect("failed to initialize logger"); setup_logger().expect("failed to initialize logger");
println!("sat-rs embedded examples TMTC client"); println!("-- STM32F3 TMTC client --");
let cli = Cli::parse(); let cli = Cli::parse();
let mut config_file = let config = embedded_client::Config::new_from_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");
println!("Connecting to serial port {}", config.interface.serial_port);
let serial = serialport::new(config.interface.serial_port, 115200) let serial = serialport::new(config.interface.serial_port, 115200)
.open() .open()
@@ -69,8 +29,7 @@ fn main() {
let mut transport = PacketTransportSerialCobs::new(serial, CobsDecoderOwned::new(1024)); let mut transport = PacketTransportSerialCobs::new(serial, CobsDecoderOwned::new(1024));
if cli.ping { if cli.ping {
let request = Request::Ping; let tc = create_stm32f3_tc(&embedded_models::stm32f3::Request::Ping);
let tc = create_stm32f3_tc(&request);
log::info!( log::info!(
"Sending ping request with TC ID: {:#010x}", "Sending ping request with TC ID: {:#010x}",
tc.ccsds_packet_id_and_psc().raw() tc.ccsds_packet_id_and_psc().raw()
@@ -79,7 +38,7 @@ fn main() {
} }
if let Some(freq_ms) = cli.set_led_frequency { if let Some(freq_ms) = cli.set_led_frequency {
let request = Request::ChangeBlinkFrequency(Duration::from_millis(freq_ms as u64)); let request = stm32f3::Request::ChangeBlinkFrequency(Duration::from_millis(freq_ms as u64));
let tc = create_stm32f3_tc(&request); let tc = create_stm32f3_tc(&request);
log::info!( log::info!(
"Sending change blink frequency request {:?} with TC ID: {:#010x}", "Sending change blink frequency request {:?} with TC ID: {:#010x}",
@@ -100,7 +59,7 @@ fn main() {
} }
} }
fn create_stm32f3_tc(request: &Request) -> CcsdsPacketCreatorOwned { fn create_stm32f3_tc(request: &stm32f3::Request) -> CcsdsPacketCreatorOwned {
let req_raw = postcard::to_allocvec(&request).unwrap(); let req_raw = postcard::to_allocvec(&request).unwrap();
let sp_header = SpHeader::new_from_apid(satrs_stm32f3_disco_rtic::APID); let sp_header = SpHeader::new_from_apid(satrs_stm32f3_disco_rtic::APID);
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap() CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
@@ -0,0 +1,64 @@
use std::{fs::File, io::Read, path::Path, time::Duration};
use clap::Parser;
use cobs::CobsDecoderOwned;
use embedded_client::setup_logger;
use embedded_models::{stm32f3, stm32h7};
use spacepackets::{CcsdsPacketCreatorOwned, CcsdsPacketReader, SpHeader};
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
#[derive(Parser, Debug)]
struct Cli {
#[arg(short, long)]
ping: bool,
/// Set frequency in milliseconds.
#[arg(short, long)]
set_led_frequency: Option<u32>,
}
fn main() {
setup_logger().expect("failed to initialize logger");
println!("-- STM32H7 TMTC client --");
let cli = Cli::parse();
let config = embedded_client::Config::new_from_file();
/*
if cli.ping {
let tc = create_stm32f3_tc(&embedded_models::stm32f3::Request::Ping);
log::info!(
"Sending ping request with TC ID: {:#010x}",
tc.ccsds_packet_id_and_psc().raw()
);
transport.send(&tc.to_vec()).unwrap();
}
if let Some(freq_ms) = cli.set_led_frequency {
let request = stm32f3::Request::ChangeBlinkFrequency(Duration::from_millis(freq_ms as u64));
let tc = create_stm32f3_tc(&request);
log::info!(
"Sending change blink frequency request {:?} with TC ID: {:#010x}",
request,
tc.ccsds_packet_id_and_psc().raw()
);
transport.send(&tc.to_vec()).unwrap();
}
log::info!("Waiting for response...");
loop {
transport
.receive(|packet: &[u8]| {
let reader = CcsdsPacketReader::new_with_checksum(packet);
log::info!("Received packet: {:?}", reader);
})
.unwrap();
}
*/
}
fn create_stm32h7_tc(request: &stm32h7::Request) -> CcsdsPacketCreatorOwned {
let req_raw = postcard::to_allocvec(&request).unwrap();
let sp_header = SpHeader::new_from_apid(satrs_stm32f3_disco_rtic::APID);
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
}
@@ -0,0 +1,42 @@
use std::{fs::File, io::Read as _, path::Path, time::SystemTime};
#[derive(Debug, serde::Deserialize)]
pub struct Config {
pub interface: Interface,
}
#[derive(Debug, serde::Deserialize)]
pub struct Interface {
pub serial_port: String,
}
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");
println!("Connecting to serial port {}", config.interface.serial_port);
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::Debug)
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
.apply()?;
Ok(())
}
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "embedded-models"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1", default-features = false }
defmt = { version = "1", optional = true }
spacepackets = { version = "0.17", default-features = false, features = ["defmt", "serde"] }
postcard = { version = "1", features = ["defmt"] }
arbitrary-int = "2"
+84
View File
@@ -0,0 +1,84 @@
#![no_std]
use spacepackets::{
CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc,
SpacePacketHeader, ccsds_packet_len_for_user_data_len_with_checksum,
};
pub mod stm32f3 {
use arbitrary_int::u11;
use core::time::Duration;
pub const PUS_APID: u11 = u11::new(0x02);
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Request {
Ping,
ChangeBlinkFrequency(Duration),
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Response {
Ok,
}
}
/// This might look like a duplication, but we intentionally keep those separate so they can
/// change independently.
pub mod stm32h7 {
use arbitrary_int::u11;
use core::time::Duration;
pub const PUS_APID: u11 = u11::new(0x03);
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Request {
Ping,
ChangeBlinkFrequency(Duration),
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Response {
Ok,
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TmHeader {
pub tc_packet_id: Option<CcsdsPacketIdAndPsc>,
pub uptime_millis: u64,
}
pub fn tm_size<Response: serde::Serialize>(tm_header: &TmHeader, response: &Response) -> usize {
ccsds_packet_len_for_user_data_len_with_checksum(
postcard::experimental::serialized_size(tm_header).unwrap()
+ postcard::experimental::serialized_size(response).unwrap(),
)
.unwrap()
}
pub fn create_tm_packet<Response: serde::Serialize>(
buf: &mut [u8],
sp_header: SpacePacketHeader,
tm_header: TmHeader,
response: Response,
) -> Result<usize, CcsdsPacketCreationError> {
let packet_data_size = postcard::experimental::serialized_size(&tm_header).unwrap()
+ postcard::experimental::serialized_size(&response).unwrap();
let mut creator =
CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;
let current_index = postcard::to_slice(&tm_header, creator.packet_data_mut())
.unwrap()
.len();
postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
Ok(creator.finish())
}
#[cfg(test)]
mod tests {}
+104 -135
View File
@@ -13,9 +13,9 @@ dependencies = [
[[package]] [[package]]
name = "aligned" name = "aligned"
version = "0.4.3" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
dependencies = [ dependencies = [
"as-slice", "as-slice",
] ]
@@ -90,7 +90,7 @@ dependencies = [
"arbitrary-int 1.3.0", "arbitrary-int 1.3.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -202,7 +202,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -255,7 +255,7 @@ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -266,7 +266,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -298,7 +298,7 @@ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -322,24 +322,25 @@ dependencies = [
[[package]] [[package]]
name = "defmt-test" name = "defmt-test"
version = "0.5.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d326e211b94939affafdf96f5c1baf8745b960037dcf763f813597d32b03d51" checksum = "24076cc7203c365e7febfcec15d6667a9ef780bd2c5fd3b2a197400df78f299b"
dependencies = [ dependencies = [
"cortex-m-rt",
"cortex-m-semihosting",
"defmt 1.0.1", "defmt 1.0.1",
"defmt-test-macros", "defmt-test-macros",
"semihosting",
] ]
[[package]] [[package]]
name = "defmt-test-macros" name = "defmt-test-macros"
version = "0.3.3" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a7563a5468e1a1bd97f44cb75b658c2feec75af2b1389e70f4c0677b8402edd" checksum = "fe5520fd36862f281c026abeaab153ebbc001717c29a9b8e5ba9704d8f3a879d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -350,7 +351,7 @@ checksum = "6178a82cf56c836a3ba61a7935cdb1c49bfaa6fa4327cd5bf554a503087de26b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -364,15 +365,14 @@ dependencies = [
[[package]] [[package]]
name = "embassy-embedded-hal" name = "embassy-embedded-hal"
version = "0.6.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8"
dependencies = [ dependencies = [
"defmt 1.0.1", "defmt 1.0.1",
"embassy-futures", "embassy-futures",
"embassy-hal-internal 0.4.0", "embassy-hal-internal",
"embassy-sync", "embassy-sync",
"embassy-time",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-hal-async", "embedded-hal-async",
@@ -381,12 +381,6 @@ dependencies = [
"nb 1.1.0", "nb 1.1.0",
] ]
[[package]]
name = "embassy-executor-timer-queue"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c"
[[package]] [[package]]
name = "embassy-futures" name = "embassy-futures"
version = "0.1.2" version = "0.1.2"
@@ -395,18 +389,9 @@ checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01"
[[package]] [[package]]
name = "embassy-hal-internal" name = "embassy-hal-internal"
version = "0.4.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a"
dependencies = [
"num-traits",
]
[[package]]
name = "embassy-hal-internal"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "568659fc53866d3d85c60fa33723fb751aa69e71507634fc2c19e7649432fb75"
dependencies = [ dependencies = [
"cortex-m", "cortex-m",
"critical-section", "critical-section",
@@ -425,9 +410,9 @@ dependencies = [
[[package]] [[package]]
name = "embassy-stm32" name = "embassy-stm32"
version = "0.6.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486c0622deb5a519fc4d2cb8e3ef324f7568fcdfff201ff8fcab46557d663ceb" checksum = "0d972eab325cc96afee98f80a91ca6b00249b6356dc0fdbff68b70c200df9fae"
dependencies = [ dependencies = [
"aligned", "aligned",
"bit_field", "bit_field",
@@ -441,12 +426,10 @@ dependencies = [
"document-features", "document-features",
"embassy-embedded-hal", "embassy-embedded-hal",
"embassy-futures", "embassy-futures",
"embassy-hal-internal 0.5.0", "embassy-hal-internal",
"embassy-net-driver", "embassy-net-driver",
"embassy-sync", "embassy-sync",
"embassy-time", "embassy-time",
"embassy-time-driver",
"embassy-time-queue-utils",
"embassy-usb-driver", "embassy-usb-driver",
"embassy-usb-synopsys-otg", "embassy-usb-synopsys-otg",
"embedded-can", "embedded-can",
@@ -454,54 +437,50 @@ dependencies = [
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-hal-async", "embedded-hal-async",
"embedded-hal-nb", "embedded-hal-nb",
"embedded-io 0.7.1", "embedded-io",
"embedded-io-async 0.7.0", "embedded-io-async",
"embedded-storage", "embedded-storage",
"embedded-storage-async", "embedded-storage-async",
"futures-util", "futures-util",
"heapless 0.9.1",
"nb 1.1.0", "nb 1.1.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rand_core 0.6.4", "rand_core 0.6.4",
"rand_core 0.9.3", "rand_core 0.9.3",
"regex",
"sdio-host", "sdio-host",
"static_assertions", "static_assertions",
"stm32-fmc", "stm32-fmc",
"stm32-metapac", "stm32-metapac",
"trait-set",
"vcell", "vcell",
"volatile-register", "volatile-register",
] ]
[[package]] [[package]]
name = "embassy-sync" name = "embassy-sync"
version = "0.8.0" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"critical-section", "critical-section",
"defmt 1.0.1", "defmt 1.0.1",
"embedded-io-async 0.7.0", "embedded-io-async",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"heapless 0.9.1", "heapless 0.8.0",
] ]
[[package]] [[package]]
name = "embassy-time" name = "embassy-time"
version = "0.5.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"critical-section", "critical-section",
"defmt 1.0.1", "defmt 1.0.1",
"document-features", "document-features",
"embassy-time-driver", "embassy-time-driver",
"embassy-time-queue-utils",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-hal-async", "embedded-hal-async",
@@ -510,23 +489,13 @@ dependencies = [
[[package]] [[package]]
name = "embassy-time-driver" name = "embassy-time-driver"
version = "0.2.2" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6"
dependencies = [ dependencies = [
"document-features", "document-features",
] ]
[[package]]
name = "embassy-time-queue-utils"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168297bf80aaf114b3c9ad589bf38b01b3009b9af7f97cd18086c5bbf96f5693"
dependencies = [
"embassy-executor-timer-queue",
"heapless 0.9.1",
]
[[package]] [[package]]
name = "embassy-usb-driver" name = "embassy-usb-driver"
version = "0.2.0" version = "0.2.0"
@@ -534,14 +503,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17119855ccc2d1f7470a39756b12068454ae27a3eabb037d940b5c03d9c77b7a" checksum = "17119855ccc2d1f7470a39756b12068454ae27a3eabb037d940b5c03d9c77b7a"
dependencies = [ dependencies = [
"defmt 1.0.1", "defmt 1.0.1",
"embedded-io-async 0.6.1", "embedded-io-async",
] ]
[[package]] [[package]]
name = "embassy-usb-synopsys-otg" name = "embassy-usb-synopsys-otg"
version = "0.3.2" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbe46f4083109c7ea12a03ca61095d1e87c76fec52c7ca9ee06a42935606dacb" checksum = "288751f8eaa44a5cf2613f13cee0ca8e06e6638cb96e897e6834702c79084b23"
dependencies = [ dependencies = [
"critical-section", "critical-section",
"defmt 1.0.1", "defmt 1.0.1",
@@ -609,14 +578,8 @@ name = "embedded-io"
version = "0.6.1" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "embedded-io"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
dependencies = [ dependencies = [
"defmt 1.0.1", "defmt 0.3.100",
] ]
[[package]] [[package]]
@@ -625,17 +588,19 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f"
dependencies = [ dependencies = [
"embedded-io 0.6.1", "defmt 0.3.100",
"embedded-io",
] ]
[[package]] [[package]]
name = "embedded-io-async" name = "embedded-models"
version = "0.7.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0"
dependencies = [ dependencies = [
"arbitrary-int 2.0.0",
"defmt 1.0.1", "defmt 1.0.1",
"embedded-io 0.7.1", "postcard",
"serde",
"spacepackets",
] ]
[[package]] [[package]]
@@ -671,7 +636,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -692,6 +657,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fugit"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7"
dependencies = [
"gcd",
]
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.31" version = "0.3.31"
@@ -722,6 +696,12 @@ dependencies = [
"pin-utils", "pin-utils",
] ]
[[package]]
name = "gcd"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
[[package]] [[package]]
name = "generator" name = "generator"
version = "0.8.7" version = "0.8.7"
@@ -923,7 +903,7 @@ checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -973,6 +953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [ dependencies = [
"cobs 0.3.0", "cobs 0.3.0",
"defmt 1.0.1",
"heapless 0.7.17", "heapless 0.7.17",
"serde", "serde",
] ]
@@ -996,7 +977,7 @@ dependencies = [
"proc-macro-error-attr2", "proc-macro-error-attr2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -1029,18 +1010,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.13" version = "0.4.13"
@@ -1098,7 +1067,20 @@ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
]
[[package]]
name = "rtic-monotonics"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99f9923ce32637ee40c0cb28fbbc2fad880d8aebea2d545918c6971ae9be3d17"
dependencies = [
"cfg-if",
"cortex-m",
"fugit",
"portable-atomic",
"rtic-time",
] ]
[[package]] [[package]]
@@ -1117,6 +1099,20 @@ dependencies = [
"rtic-common", "rtic-common",
] ]
[[package]]
name = "rtic-time"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61485474f5a23247ae1d4875f2bfe860be9b3030dbf87c232e50799e021429a1"
dependencies = [
"critical-section",
"embedded-hal 1.0.0",
"embedded-hal-async",
"fugit",
"futures-util",
"rtic-common",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"
@@ -1154,13 +1150,14 @@ dependencies = [
"defmt-rtt", "defmt-rtt",
"defmt-test", "defmt-test",
"embassy-stm32", "embassy-stm32",
"embassy-time",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-models",
"enumset", "enumset",
"heapless 0.9.1", "heapless 0.9.1",
"panic-probe", "panic-probe",
"postcard", "postcard",
"rtic", "rtic",
"rtic-monotonics",
"rtic-sync", "rtic-sync",
"serde", "serde",
"spacepackets", "spacepackets",
@@ -1186,12 +1183,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b328e2cb950eeccd55b7f55c3a963691455dcd044cfb5354f0c5e68d2c2d6ee2" checksum = "b328e2cb950eeccd55b7f55c3a963691455dcd044cfb5354f0c5e68d2c2d6ee2"
[[package]]
name = "semihosting"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8e4abf97879f4e80db69a9fba7bd64998e9bdad25f58ef045a778e191172fd4"
[[package]] [[package]]
name = "semver" name = "semver"
version = "0.9.0" version = "0.9.0"
@@ -1240,7 +1231,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -1316,35 +1307,24 @@ dependencies = [
[[package]] [[package]]
name = "stm32-fmc" name = "stm32-fmc"
version = "0.4.0" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72692594faa67f052e5e06dd34460951c21e83bc55de4feb8d2666e2f15480a2" checksum = "c7f0639399e2307c2446c54d91d4f1596343a1e1d5cab605b9cce11d0ab3858c"
dependencies = [ dependencies = [
"embedded-hal 1.0.0", "embedded-hal 0.2.7",
] ]
[[package]] [[package]]
name = "stm32-metapac" name = "stm32-metapac"
version = "21.0.0" version = "18.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74b78632cea498cfb28386a29f8bfae7476d6570a78733eb5fecbee66c2f4ce" checksum = "6fd8ec3a292a0d9fc4798416a61b21da5ae50341b2e7b8d12e662bf305366097"
dependencies = [ dependencies = [
"cortex-m", "cortex-m",
"cortex-m-rt", "cortex-m-rt",
"defmt 0.3.100", "defmt 0.3.100",
] ]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.108" version = "2.0.108"
@@ -1373,7 +1353,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -1434,17 +1414,6 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "trait-set"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.22" version = "1.0.22"
@@ -1532,7 +1501,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -1543,7 +1512,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
[[package]] [[package]]
@@ -1621,5 +1590,5 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn",
] ]
@@ -7,6 +7,7 @@ default-run = "satrs-stm32f3-disco-rtic"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
embedded-models = { path = "../models", features = ["defmt"] }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
defmt = "1" defmt = "1"
@@ -14,8 +15,7 @@ defmt-rtt = { version = "1" }
panic-probe = { version = "1", features = ["print-defmt"] } panic-probe = { version = "1", features = ["print-defmt"] }
embedded-hal = "1" embedded-hal = "1"
cortex-m-semihosting = "0.5.0" cortex-m-semihosting = "0.5.0"
embassy-stm32 = { version = "0.6", features = ["defmt", "stm32f303vc", "memory-x", "unstable-pac", "time-driver-any"] } embassy-stm32 = { version = "0.4", features = ["defmt", "stm32f303vc", "unstable-pac"] }
embassy-time = { version = "0.5", features = ["defmt", "generic-queue-16", "defmt-timestamp-uptime-ms"]}
enumset = "1" enumset = "1"
heapless = "0.9" heapless = "0.9"
spacepackets = { version = "0.17", default-features = false, features = ["defmt", "serde"] } spacepackets = { version = "0.17", default-features = false, features = ["defmt", "serde"] }
@@ -28,9 +28,10 @@ serde = { version = "1", default-features = false, features = ["derive"] }
rtic = { version = "2", features = ["thumbv7-backend"] } rtic = { version = "2", features = ["thumbv7-backend"] }
rtic-sync = { version = "1" } rtic-sync = { version = "1" }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
[dev-dependencies] [dev-dependencies]
defmt-test = "0.5" defmt-test = "0.4"
# cargo test # cargo test
[profile.test] [profile.test]
@@ -0,0 +1,33 @@
/* Linker script for the STM32F303VCT6 */
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
FLASH : ORIGIN = 0x08000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 40K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* You may want to use this variable to locate the call stack and static
variables in different memory regions. Below is shown the default value */
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */
/* You can use this symbol to customize the location of the .text section */
/* If omitted the .text section will be placed right after the .vector_table
section */
/* This is required only on microcontrollers that store some configuration right
after the vector table */
/* _stext = ORIGIN(FLASH) + 0x400; */
/* Example of putting non-initialized variables into custom RAM locations. */
/* This assumes you have defined a region RAM2 above, and in the Rust
sources added the attribute `#[link_section = ".ram2bss"]` to the data
you want to place there. */
/* Note that the section will not be zero-initialized by the runtime! */
/* SECTIONS {
.ram2bss (NOLOAD) : ALIGN(4) {
*(.ram2bss);
. = ALIGN(4);
} > RAM2
} INSERT AFTER .bss;
*/
@@ -6,8 +6,11 @@ use rtic::app;
#[app(device = embassy_stm32)] #[app(device = embassy_stm32)]
mod app { mod app {
use embassy_time::Timer; use rtic_monotonics::fugit::ExtU32;
use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds}; use rtic_monotonics::Monotonic as _;
use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds};
rtic_monotonics::systick_monotonic!(Mono, 1000);
#[shared] #[shared]
struct Shared {} struct Shared {}
@@ -19,7 +22,7 @@ use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds};
} }
#[init] #[init]
fn init(_cx: init::Context) -> (Shared, Local) { fn init(cx: init::Context) -> (Shared, Local) {
let p = embassy_stm32::init(Default::default()); let p = embassy_stm32::init(Default::default());
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery using RTICv2"); defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery using RTICv2");
@@ -36,6 +39,8 @@ use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds};
}; };
let leds = Leds::new(led_pin_set); let leds = Leds::new(led_pin_set);
// Initialize the systick interrupt & obtain the token to prove that we did
Mono::start(cx.core.SYST, 8_000_000);
blinky::spawn().expect("failed to spawn blinky task"); blinky::spawn().expect("failed to spawn blinky task");
( (
Shared {}, Shared {},
@@ -50,7 +55,7 @@ use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds};
async fn blinky(cx: blinky::Context) { async fn blinky(cx: blinky::Context) {
loop { loop {
cx.local.leds.blink_next(cx.local.current_dir); cx.local.leds.blink_next(cx.local.current_dir);
Timer::after_millis(200).await; Mono::delay(200.millis()).await;
} }
} }
} }
@@ -2,15 +2,9 @@
#![no_std] #![no_std]
use defmt_rtt as _; use defmt_rtt as _;
use panic_probe as _;
use arbitrary_int::u11; use arbitrary_int::u11;
use core::time::Duration;
use embassy_stm32::gpio::Output; use embassy_stm32::gpio::Output;
use spacepackets::{
ccsds_packet_len_for_user_data_len_with_checksum, CcsdsPacketCreationError,
CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
};
pub const APID: u11 = u11::new(0x02); pub const APID: u11 = u11::new(0x02);
@@ -43,49 +37,6 @@ impl Direction {
} }
} }
#[derive(Copy, Clone, Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
pub enum Request {
Ping,
ChangeBlinkFrequency(Duration),
}
#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
pub struct TmHeader {
pub tc_packet_id: Option<CcsdsPacketIdAndPsc>,
pub uptime_millis: u32,
}
#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
pub enum Response {
CommandDone,
}
pub fn tm_size(tm_header: &TmHeader, response: &Response) -> usize {
ccsds_packet_len_for_user_data_len_with_checksum(
postcard::experimental::serialized_size(tm_header).unwrap()
+ postcard::experimental::serialized_size(response).unwrap(),
)
.unwrap()
}
pub fn create_tm_packet(
buf: &mut [u8],
sp_header: SpacePacketHeader,
tm_header: TmHeader,
response: Response,
) -> Result<usize, CcsdsPacketCreationError> {
let packet_data_size = postcard::experimental::serialized_size(&tm_header).unwrap()
+ postcard::experimental::serialized_size(&response).unwrap();
let mut creator =
CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;
let current_index = postcard::to_slice(&tm_header, creator.packet_data_mut())
.unwrap()
.len();
postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
Ok(creator.finish())
}
pub struct Leds { pub struct Leds {
pub north: Output<'static>, pub north: Output<'static>,
pub north_east: Output<'static>, pub north_east: Output<'static>,
@@ -1,20 +1,28 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use arbitrary_int::{u11, u14}; use arbitrary_int::u14;
use cortex_m_semihosting::debug::{self, EXIT_FAILURE, EXIT_SUCCESS}; use cortex_m_semihosting::debug::{self, EXIT_FAILURE, EXIT_SUCCESS};
use satrs_stm32f3_disco_rtic::{create_tm_packet, tm_size, Request, Response}; use embedded_models::{create_tm_packet, stm32f3, tm_size, TmHeader};
use spacepackets::{CcsdsPacketCreationError, CcsdsPacketIdAndPsc, SpHeader}; use spacepackets::{CcsdsPacketCreationError, CcsdsPacketIdAndPsc, SpHeader};
use defmt_rtt as _; // global logger
use panic_probe as _;
use rtic::app; use rtic::app;
#[allow(unused_imports)]
use rtic_monotonics::fugit::{MillisDurationU32, TimerInstantU32};
use rtic_monotonics::systick::prelude::*;
use crate::app::Mono;
const UART_BAUD: u32 = 115200; const UART_BAUD: u32 = 115200;
const DEFAULT_BLINK_FREQ_MS: u32 = 1000; const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
const TX_HANDLER_FREQ_MS: u32 = 20; const TX_HANDLER_FREQ_MS: u32 = 20;
const MAX_TC_LEN: usize = 128; const MAX_TC_LEN: usize = 128;
const MAX_TM_LEN: usize = 128; const MAX_TM_LEN: usize = 128;
pub const PUS_APID: u11 = u11::new(0x02);
// This is the predictable maximum overhead of the COBS encoding scheme. // This is the predictable maximum overhead of the COBS encoding scheme.
// It is simply the maximum packet lenght dividied by 254 rounded up. // It is simply the maximum packet lenght dividied by 254 rounded up.
const COBS_TM_OVERHEAD: usize = cobs::max_encoding_overhead(MAX_TM_LEN); const COBS_TM_OVERHEAD: usize = cobs::max_encoding_overhead(MAX_TM_LEN);
@@ -37,7 +45,7 @@ pub enum TmSendError {
#[derive(Debug, defmt::Format)] #[derive(Debug, defmt::Format)]
pub struct RequestWithTcId { pub struct RequestWithTcId {
pub request: Request, pub request: stm32f3::Request,
pub tc_id: CcsdsPacketIdAndPsc, pub tc_id: CcsdsPacketIdAndPsc,
} }
@@ -47,19 +55,19 @@ mod app {
use super::*; use super::*;
use arbitrary_int::u14; use arbitrary_int::u14;
use embassy_time::Timer; use embedded_models::stm32f3::{Request, Response};
use rtic::Mutex; use rtic::Mutex;
use rtic_sync::{ use rtic_sync::{
channel::{Receiver, Sender}, channel::{Receiver, Sender},
make_channel, make_channel,
}; };
use satrs_stm32f3_disco_rtic::{LedPinSet, Request, Response}; use satrs_stm32f3_disco_rtic::LedPinSet;
use spacepackets::CcsdsPacketReader; use spacepackets::CcsdsPacketReader;
systick_monotonic!(Mono, 1000);
embassy_stm32::bind_interrupts!(struct Irqs { embassy_stm32::bind_interrupts!(struct Irqs {
USART2 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART2>; USART2 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART2>;
DMA1_CHANNEL6 => embassy_stm32::dma::InterruptHandler<embassy_stm32::peripherals::DMA1_CH6>;
DMA1_CHANNEL7 => embassy_stm32::dma::InterruptHandler<embassy_stm32::peripherals::DMA1_CH7>;
}); });
#[shared] #[shared]
@@ -77,13 +85,15 @@ mod app {
} }
#[init] #[init]
fn init(_cx: init::Context) -> (Shared, Local) { fn init(cx: init::Context) -> (Shared, Local) {
static DMA_BUF: static_cell::ConstStaticCell<[u8; TC_DMA_BUF_LEN]> = static DMA_BUF: static_cell::ConstStaticCell<[u8; TC_DMA_BUF_LEN]> =
static_cell::ConstStaticCell::new([0; TC_DMA_BUF_LEN]); static_cell::ConstStaticCell::new([0; TC_DMA_BUF_LEN]);
let p = embassy_stm32::init(Default::default()); let p = embassy_stm32::init(Default::default());
let (req_sender, req_receiver) = make_channel!(RequestWithTcId, 16); let (req_sender, req_receiver) = make_channel!(RequestWithTcId, 16);
// Initialize the systick interrupt & obtain the token to prove that we did
Mono::start(cx.core.SYST, 8_000_000);
defmt::info!("sat-rs demo application for the STM32F3-Discovery with RTICv2"); defmt::info!("sat-rs demo application for the STM32F3-Discovery with RTICv2");
let led_pin_set = LedPinSet { let led_pin_set = LedPinSet {
@@ -101,7 +111,7 @@ mod app {
let mut config = embassy_stm32::usart::Config::default(); let mut config = embassy_stm32::usart::Config::default();
config.baudrate = UART_BAUD; config.baudrate = UART_BAUD;
let uart = embassy_stm32::usart::Uart::new( let uart = embassy_stm32::usart::Uart::new(
p.USART2, p.PA3, p.PA2, p.DMA1_CH7, p.DMA1_CH6, Irqs, config, p.USART2, p.PA3, p.PA2, Irqs, p.DMA1_CH7, p.DMA1_CH6, config,
) )
.unwrap(); .unwrap();
@@ -131,7 +141,10 @@ mod app {
loop { loop {
cx.local.leds.blink_next(cx.local.current_dir); cx.local.leds.blink_next(cx.local.current_dir);
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current); let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
Timer::after_millis(current_blink_freq.as_millis() as u64).await; Mono::delay(MillisDurationU32::from_ticks(
current_blink_freq.as_millis() as u32,
))
.await;
} }
} }
@@ -155,7 +168,7 @@ mod app {
.unwrap(); .unwrap();
continue; continue;
} }
Timer::after_millis(TX_HANDLER_FREQ_MS as u64).await; Mono::delay(TX_HANDLER_FREQ_MS.millis()).await;
} }
} }
@@ -184,8 +197,9 @@ mod app {
&decoder.dest()[0..packet_size], &decoder.dest()[0..packet_size],
) { ) {
Ok(packet) => { Ok(packet) => {
let tc_packet_id = let packet_id = packet.packet_id();
CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet); let psc = packet.psc();
let tc_packet_id = CcsdsPacketIdAndPsc { packet_id, psc };
if let Ok(request) = if let Ok(request) =
postcard::from_bytes::<Request>(packet.packet_data()) postcard::from_bytes::<Request>(packet.packet_data())
{ {
@@ -248,7 +262,7 @@ mod app {
tc_packet_id: CcsdsPacketIdAndPsc, tc_packet_id: CcsdsPacketIdAndPsc,
) -> Result<(), TmSendError> { ) -> Result<(), TmSendError> {
defmt::info!("Received PUS ping telecommand, sending ping reply"); defmt::info!("Received PUS ping telecommand, sending ping reply");
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?; send_tm(tc_packet_id, Response::Ok, *cx.local.seq_count)?;
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1)); *cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
Ok(()) Ok(())
} }
@@ -265,7 +279,7 @@ mod app {
cx.shared cx.shared
.blink_freq .blink_freq
.lock(|blink_freq| *blink_freq = duration); .lock(|blink_freq| *blink_freq = duration);
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?; send_tm(tc_packet_id, Response::Ok, *cx.local.seq_count)?;
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1)); *cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
Ok(()) Ok(())
} }
@@ -273,13 +287,13 @@ mod app {
fn send_tm( fn send_tm(
tc_packet_id: CcsdsPacketIdAndPsc, tc_packet_id: CcsdsPacketIdAndPsc,
response: Response, response: stm32f3::Response,
current_seq_count: u14, current_seq_count: u14,
) -> Result<(), TmSendError> { ) -> Result<(), TmSendError> {
let sp_header = SpHeader::new_for_unseg_tc(PUS_APID, current_seq_count, 0); let sp_header = SpHeader::new_for_unseg_tc(stm32f3::PUS_APID, current_seq_count, 0);
let tm_header = satrs_stm32f3_disco_rtic::TmHeader { let tm_header = TmHeader {
tc_packet_id: Some(tc_packet_id), tc_packet_id: Some(tc_packet_id),
uptime_millis: embassy_time::Instant::now().as_millis() as u32, uptime_millis: Mono::now().duration_since_epoch().to_millis() as u64,
}; };
let mut tm_packet = TmPacket::new(); let mut tm_packet = TmPacket::new();
let tm_size = tm_size(&tm_header, &response); let tm_size = tm_size(&tm_header, &response);
@@ -1,29 +0,0 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs run --chip STM32H743ZITx"
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
rustflags = [
"-C", "linker=flip-link",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
"-C", "link-arg=--nmagic",
# Can be useful for debugging.
# "-Clink-args=-Map=app.map"
]
[build]
# (`thumbv6m-*` is compatible with all ARM Cortex-M chips but using the right
# target improves performance)
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
[alias]
rb = "run --bin"
rrb = "run --release --bin"
[env]
DEFMT_LOG = "info"
File diff suppressed because it is too large Load Diff
@@ -14,37 +14,28 @@ name = "integration"
harness = false harness = false
[dependencies] [dependencies]
embedded-models = { path = "../models", features = ["defmt"] }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
arbitrary-int = "2"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
defmt = "1" defmt = "1"
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] } defmt-rtt = "1"
panic-probe = { version = "1", features = ["print-defmt"] } panic-probe = { version = "1", features = ["print-defmt"] }
cortex-m-semihosting = "0.5.0" embedded-alloc = "0.7"
# TODO: Replace with embassy-hal. static_cell = "2"
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] } rtic = { version = "2", features = ["thumbv7-backend"] }
embedded-alloc = "0.6" spacepackets = { version = "0.17", default-features = false, features = ["defmt"] }
rtic-sync = { version = "1", features = ["defmt-03"] } postcard = "1"
[dependencies.smoltcp] embassy-stm32 = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.6", features = ["stm32h743zi", "memory-x", "defmt", "time-driver-any"]}
version = "0.12"
default-features = false
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
[dependencies.rtic] embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.5", features = ["defmt-timestamp-uptime-ms", "generic-queue-16"] }
version = "2" embassy-net = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.9", features = ["medium-ethernet", "proto-ipv4", "tcp", "udp", "auto-icmp-echo-reply", "dhcpv4", "defmt"] }
features = ["thumbv7-backend"] embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c" }
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]
[dependencies.satrs]
path = "../../satrs"
default-features = false
features = ["defmt", "heapless"]
[dev-dependencies] [dev-dependencies]
defmt-test = "0.4" defmt-test = "0.5"
# cargo build/run # cargo build/run
[profile.dev] [profile.dev]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
use std::path::PathBuf;
use std::{env, fs};
fn main() {
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let cargo_dir = manifest_dir.parent().unwrap().join(".cargo");
let config = cargo_dir.join("config.toml");
let config_template = cargo_dir.join("config.toml.template");
if !config.exists() && config_template.exists() {
fs::create_dir_all(&cargo_dir).unwrap();
fs::copy(&config_template, &config).unwrap();
}
}
@@ -1,8 +0,0 @@
/venv
/.tmtc-history.txt
/log
/.idea/*
!/.idea/runConfigurations
/seqcnt.txt
/tmtc_conf.json
@@ -1,4 +0,0 @@
{
"com_if": "udp",
"tcpip_udp_port": 7301
}
@@ -1,305 +0,0 @@
#!/usr/bin/env python3
"""Example client for the sat-rs example application"""
import struct
import logging
import sys
import time
from typing import Any, Optional, cast
from prompt_toolkit.history import FileHistory, History
from spacepackets.ecss.tm import CdsShortTimestamp
import tmtccmd
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusTm, PusVerificator
from spacepackets.ecss.pus_17_test import Service17Tm
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
from tmtccmd.core.base import BackendRequest
from tmtccmd.core.ccsds_backend import QueueWrapper
from tmtccmd.logging import add_colorlog_console_logger
from tmtccmd.pus import VerificationWrapper
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
from tmtccmd.com import ComInterface
from tmtccmd.config import (
CmdTreeNode,
default_json_path,
SetupParams,
HookBase,
params_to_procedure_conversion,
)
from tmtccmd.config.com import SerialCfgWrapper
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
from tmtccmd.logging.pus import (
RegularTmtcLogWrapper,
RawTmtcTimedLogWrapper,
TimedLogWhen,
)
from tmtccmd.tmtc import (
TcQueueEntryType,
ProcedureWrapper,
TcProcedureType,
FeedWrapper,
SendCbParams,
DefaultPusQueueHelper,
)
from tmtccmd.pus.s5_fsfw_event import Service5Tm
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
from tmtccmd.util.obj_id import ObjectIdDictT
_LOGGER = logging.getLogger()
EXAMPLE_PUS_APID = 0x02
class SatRsConfigHook(HookBase):
def __init__(self, json_cfg_path: str):
super().__init__(json_cfg_path)
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
from tmtccmd.config.com import (
create_com_interface_default,
create_com_interface_cfg_default,
)
assert self.cfg_path is not None
cfg = create_com_interface_cfg_default(
com_if_key=com_if_key,
json_cfg_path=self.cfg_path,
space_packet_ids=None,
)
if cfg is None:
raise ValueError(
f"No valid configuration could be retrieved for the COM IF with key {com_if_key}"
)
if cfg.com_if_key == "serial_cobs":
cfg = cast(SerialCfgWrapper, cfg)
cfg.serial_cfg.serial_timeout = 0.5
return create_com_interface_default(cfg)
def get_command_definitions(self) -> CmdTreeNode:
"""This function should return the root node of the command definition tree."""
return create_cmd_definition_tree()
def get_cmd_history(self) -> Optional[History]:
"""Optionlly return a history class for the past command paths which will be used
when prompting a command path from the user in CLI mode."""
return FileHistory(".tmtc-history.txt")
def get_object_ids(self) -> ObjectIdDictT:
from tmtccmd.config.objects import get_core_object_ids
return get_core_object_ids()
def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node()
root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
return root_node
class PusHandler(SpecificApidHandlerBase):
def __init__(
self,
file_logger: logging.Logger,
verif_wrapper: VerificationWrapper,
raw_logger: RawTmtcTimedLogWrapper,
):
super().__init__(EXAMPLE_PUS_APID, None)
self.file_logger = file_logger
self.raw_logger = raw_logger
self.verif_wrapper = verif_wrapper
def handle_tm(self, packet: bytes, _user_args: Any):
try:
pus_tm = PusTm.unpack(
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
except ValueError as e:
_LOGGER.warning("Could not generate PUS TM object from raw data")
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
raise e
service = pus_tm.service
tm_packet = None
if service == 1:
tm_packet = Service1Tm.unpack(
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
)
res = self.verif_wrapper.add_tm(tm_packet)
if res is None:
_LOGGER.info(
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
)
_LOGGER.warning(
f"No matching telecommand found for {tm_packet.tc_req_id}"
)
else:
self.verif_wrapper.log_to_console(tm_packet, res)
self.verif_wrapper.log_to_file(tm_packet, res)
if service == 3:
_LOGGER.info("No handling for HK packets implemented")
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if pus_tm.subservice == 25:
if len(pus_tm.source_data) < 8:
raise ValueError("No addressable ID in HK packet")
json_str = pus_tm.source_data[8:]
_LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
if service == 5:
tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if service == 17:
tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if tm_packet.subservice == 2:
_LOGGER.info("Received Ping Reply TM[17,2]")
else:
_LOGGER.info(
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
)
if tm_packet is None:
_LOGGER.info(
f"The service {service} is not implemented in Telemetry Factory"
)
tm_packet = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
self.raw_logger.log_tm(pus_tm)
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
byte_string = bytearray(struct.pack("!I", target_id))
byte_string.extend(struct.pack("!I", unique_id))
return byte_string
class TcHandler(TcHandlerBase):
def __init__(
self,
seq_count_provider: FileSeqCountProvider,
verif_wrapper: VerificationWrapper,
):
super(TcHandler, self).__init__()
self.seq_count_provider = seq_count_provider
self.verif_wrapper = verif_wrapper
self.queue_helper = DefaultPusQueueHelper(
queue_wrapper=QueueWrapper.empty(),
tc_sched_timestamp_len=7,
seq_cnt_provider=seq_count_provider,
pus_verificator=verif_wrapper.pus_verificator,
default_pus_apid=EXAMPLE_PUS_APID,
)
def send_cb(self, send_params: SendCbParams):
entry_helper = send_params.entry
if entry_helper.is_tc:
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
pus_tc_wrapper.pus_tc.seq_count = (
self.seq_count_provider.get_and_increment()
)
self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
raw_tc = pus_tc_wrapper.pus_tc.pack()
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
send_params.com_if.send(raw_tc)
elif entry_helper.entry_type == TcQueueEntryType.LOG:
log_entry = entry_helper.to_log_entry()
_LOGGER.info(log_entry.log_str)
def queue_finished_cb(self, info: ProcedureWrapper):
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
q = self.queue_helper
q.queue_wrapper = wrapper.queue_wrapper
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
cmd_path = def_proc.cmd_path
if cmd_path == "/ping":
q.add_log_cmd("Sending PUS ping telecommand")
q.add_pus_tc(PusTelecommand(service=17, subservice=1))
if cmd_path == "/change_blink_freq":
self.create_change_blink_freq_command(q)
def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
q.add_log_cmd("Changing blink frequency")
while True:
blink_freq = int(
input(
"Please specify new blink frequency in ms. Valid Range [2..10000]: "
)
)
if blink_freq < 2 or blink_freq > 10000:
print(
"Invalid blink frequency. Please specify a value between 2 and 10000."
)
continue
break
app_data = struct.pack("!I", blink_freq)
q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
def main():
add_colorlog_console_logger(_LOGGER)
tmtccmd.init_printout(False)
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
parser_wrapper = PreArgsParsingWrapper()
parser_wrapper.create_default_parent_parser()
parser_wrapper.create_default_parser()
parser_wrapper.add_def_proc_args()
params = SetupParams()
post_args_wrapper = parser_wrapper.parse(hook_obj, params)
proc_wrapper = ProcedureParamsWrapper()
if post_args_wrapper.use_gui:
post_args_wrapper.set_params_without_prompts(proc_wrapper)
else:
post_args_wrapper.set_params_with_prompts(proc_wrapper)
params.apid = EXAMPLE_PUS_APID
setup_args = SetupWrapper(
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
)
# Create console logger helper and file loggers
tmtc_logger = RegularTmtcLogWrapper()
file_logger = tmtc_logger.logger
raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
verificator = PusVerificator()
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
# Create primary TM handler and add it to the CCSDS Packet Handler
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
ccsds_handler = CcsdsTmHandler(generic_handler=None)
ccsds_handler.add_apid_handler(tm_handler)
# Create TC handler
seq_count_provider = PusFileSeqCountProvider()
tc_handler = TcHandler(seq_count_provider, verification_wrapper)
tmtccmd.setup(setup_args=setup_args)
init_proc = params_to_procedure_conversion(setup_args.proc_param_wrapper)
tmtc_backend = tmtccmd.create_default_tmtc_backend(
setup_wrapper=setup_args,
tm_handler=ccsds_handler,
tc_handler=tc_handler,
init_procedure=init_proc,
)
tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=hook_obj)
try:
while True:
state = tmtc_backend.periodic_op(None)
if state.request == BackendRequest.TERMINATION_NO_ERROR:
sys.exit(0)
elif state.request == BackendRequest.DELAY_IDLE:
_LOGGER.info("TMTC Client in IDLE mode")
time.sleep(3.0)
elif state.request == BackendRequest.DELAY_LISTENER:
time.sleep(0.8)
elif state.request == BackendRequest.DELAY_CUSTOM:
if state.next_delay.total_seconds() <= 0.4:
time.sleep(state.next_delay.total_seconds())
else:
time.sleep(0.4)
elif state.request == BackendRequest.CALL_NEXT:
pass
except KeyboardInterrupt:
sys.exit(0)
if __name__ == "__main__":
main()
@@ -1,2 +0,0 @@
tmtccmd == 8.0.1
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
@@ -0,0 +1,2 @@
[toolchain]
targets = ["thumbv7em-none-eabihf"]
@@ -5,51 +5,53 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use rtic::app;
use satrs_stm32h7_nucleo_rtic as _; use satrs_stm32h7_nucleo_rtic as _;
use stm32h7xx_hal::{block, prelude::*, timer::Timer}; #[app(device = embassy_stm32, peripherals = false, dispatchers = [SPI1])]
mod app {
use embassy_stm32::gpio;
use cortex_m_rt::entry; #[shared]
struct Shared {}
#[entry] #[local]
fn main() -> ! { struct Local {}
defmt::println!("starting stm32h7 blinky example");
// Get access to the device specific peripherals from the peripheral access crate
let dp = stm32h7xx_hal::stm32::Peripherals::take().unwrap();
// Take ownership over the RCC devices and convert them into the corresponding HAL structs
let rcc = dp.RCC.constrain();
let pwr = dp.PWR.constrain();
let pwrcfg = pwr.freeze();
// Freeze the configuration of all the clocks in the system and
// retrieve the Core Clock Distribution and Reset (CCDR) object
let rcc = rcc.use_hse(8.MHz()).bypass_hse();
let ccdr = rcc.freeze(pwrcfg, &dp.SYSCFG);
// Acquire the GPIOB peripheral
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
#[init]
fn init(_cx: init::Context) -> (Shared, Local) {
let p = embassy_stm32::init(Default::default());
defmt::info!("Hello World!");
// Configure gpio B pin 0 as a push-pull output. // Configure gpio B pin 0 as a push-pull output.
let mut ld1 = gpiob.pb0.into_push_pull_output(); let ld1 = gpio::Output::new(p.PB0, gpio::Level::High, gpio::Speed::Low);
let ld2 = gpio::Output::new(p.PB7, gpio::Level::High, gpio::Speed::Low);
let ld3 = gpio::Output::new(p.PB14, gpio::Level::High, gpio::Speed::Low);
// Configure gpio B pin 7 as a push-pull output. // Schedule the blinking task
let mut ld2 = gpiob.pb7.into_push_pull_output(); blink::spawn(ld1, ld2, ld3).ok();
// Configure gpio B pin 14 as a push-pull output. (Shared {}, Local {})
let mut ld3 = gpiob.pb14.into_push_pull_output(); }
// Configure the timer to trigger an update every second #[task()]
let mut timer = Timer::tim1(dp.TIM1, ccdr.peripheral.TIM1, &ccdr.clocks); async fn blink(
timer.start(1.Hz()); _cx: blink::Context,
mut ld1: gpio::Output<'static>,
// Wait for the timer to trigger an update and change the state of the LED mut ld2: gpio::Output<'static>,
mut ld3: gpio::Output<'static>,
) {
loop { loop {
ld1.toggle(); defmt::info!("high");
ld2.toggle(); ld1.set_high();
ld3.toggle(); ld2.set_high();
block!(timer.wait()).unwrap(); ld3.set_high();
embassy_time::Timer::after_millis(500).await;
defmt::info!("low");
ld1.set_low();
ld2.set_low();
ld3.set_low();
embassy_time::Timer::after_millis(500).await;
}
} }
} }
@@ -1,11 +1,13 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use satrs_stm32h7_nucleo_rtic as _; // global logger + panicking-behavior + memory layout // global logger + panicking-behavior + memory layout
use satrs_stm32h7_nucleo_rtic as _;
#[cortex_m_rt::entry] #[cortex_m_rt::entry]
fn main() -> ! { fn main() -> ! {
loop {
defmt::println!("Hello, world!"); defmt::println!("Hello, world!");
cortex_m::asm::delay(100_000_000);
satrs_stm32h7_nucleo_rtic::exit() }
} }
@@ -1,13 +1,8 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use cortex_m_semihosting::debug; use defmt_rtt as _;
use embassy_stm32 as _;
use defmt_brtt as _; // global logger
// TODO(5) adjust HAL import
use stm32h7xx_hal as _; // memory layout
use panic_probe as _; use panic_probe as _;
// same panicking *behavior* as `panic-probe` but doesn't print a panic message // same panicking *behavior* as `panic-probe` but doesn't print a panic message
@@ -17,14 +12,6 @@ fn panic() -> ! {
cortex_m::asm::udf() cortex_m::asm::udf()
} }
/// Terminates the application and makes a semihosting-capable debug tool exit
/// with status code 0.
pub fn exit() -> ! {
loop {
debug::exit(debug::EXIT_SUCCESS);
}
}
/// Hardfault handler. /// Hardfault handler.
/// ///
/// Terminates the application and makes a semihosting-capable debug tool exit /// Terminates the application and makes a semihosting-capable debug tool exit
@@ -32,9 +19,7 @@ pub fn exit() -> ! {
/// loop. /// loop.
#[cortex_m_rt::exception] #[cortex_m_rt::exception]
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! { unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
loop { panic!("unexpected hard fault");
debug::exit(debug::EXIT_FAILURE);
}
} }
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used // defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
+283 -425
View File
@@ -3,422 +3,215 @@
extern crate alloc; extern crate alloc;
use rtic::app; use rtic::app;
use rtic_monotonics::systick::prelude::*;
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
use satrs::static_subpool;
// global logger + panicking-behavior + memory layout // global logger + panicking-behavior + memory layout
use embassy_stm32::bind_interrupts;
use satrs_stm32h7_nucleo_rtic as _; use satrs_stm32h7_nucleo_rtic as _;
use smoltcp::socket::udp::UdpMetadata;
use smoltcp::socket::{dhcpv4, udp};
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use embedded_alloc::LlffHeap as Heap; use embedded_alloc::LlffHeap as Heap;
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
use stm32h7xx_hal::ethernet;
const DEFAULT_BLINK_FREQ_MS: u32 = 1000; const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
const PORT: u16 = 7301; const PORT: u16 = 7301;
const HEAP_SIZE: usize = 131_072; const HEAP_SIZE: usize = 131_072;
const TC_SOURCE_CHANNEL_DEPTH: usize = 16;
pub type SharedPool = StaticHeaplessMemoryPool<3>;
pub type TcSourceChannel = rtic_sync::channel::Channel<PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
pub type TcSourceTx = rtic_sync::channel::Sender<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
pub type TcSourceRx = rtic_sync::channel::Receiver<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
#[global_allocator] #[global_allocator]
static HEAP: Heap = Heap::empty(); static HEAP: Heap = Heap::empty();
systick_monotonic!(Mono, 1000);
// We place the memory pool buffers inside the larger AXISRAM.
pub const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 32;
pub const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32;
pub const SUBPOOL_MEDIUM_NUM_BLOCKS: u16 = 16;
pub const SUBPOOL_MEDIUM_BLOCK_SIZE: usize = 128;
pub const SUBPOOL_LARGE_NUM_BLOCKS: u16 = 8;
pub const SUBPOOL_LARGE_BLOCK_SIZE: usize = 2048;
// This data will be held by Net through a mutable reference
pub struct NetStorageStatic<'a> {
socket_storage: [SocketStorage<'a>; 8],
}
// MaybeUninit allows us write code that is correct even if STORE is not
// initialised by the runtime
static mut STORE: MaybeUninit<NetStorageStatic> = MaybeUninit::uninit();
static mut UDP_RX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
static mut UDP_RX: [u8; 2048] = [0; 2048];
static mut UDP_TX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
static mut UDP_TX: [u8; 2048] = [0; 2048];
/// Locally administered MAC address /// Locally administered MAC address
const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44]; const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44];
pub struct Net { const TC_QUEUE_DEPTH: usize = 32;
iface: Interface, const TM_QUEUE_DEPTH: usize = 32;
ethdev: ethernet::EthernetDMA<4, 4>,
dhcp_handle: SocketHandle,
}
impl Net { #[app(device = embassy_stm32, peripherals = false)]
pub fn new(
sockets: &mut SocketSet<'static>,
mut ethdev: ethernet::EthernetDMA<4, 4>,
ethernet_addr: HardwareAddress,
) -> Self {
let config = Config::new(ethernet_addr);
let mut iface = Interface::new(
config,
&mut ethdev,
smoltcp::time::Instant::from_millis(Mono::now().duration_since_epoch().to_millis()),
);
// Create sockets
let dhcp_socket = dhcpv4::Socket::new();
iface.update_ip_addrs(|addrs| {
let _ = addrs.push(IpCidr::new(IpAddress::v4(192, 168, 1, 99), 0));
});
let dhcp_handle = sockets.add(dhcp_socket);
Net {
iface,
ethdev,
dhcp_handle,
}
}
/// Polls on the ethernet interface. You should refer to the smoltcp
/// documentation for poll() to understand how to call poll efficiently
pub fn poll<'a>(&mut self, sockets: &'a mut SocketSet) -> bool {
let uptime = Mono::now().duration_since_epoch();
let timestamp = smoltcp::time::Instant::from_millis(uptime.to_millis());
self.iface.poll(timestamp, &mut self.ethdev, sockets)
}
pub fn poll_dhcp<'a>(&mut self, sockets: &'a mut SocketSet) -> Option<dhcpv4::Event<'a>> {
let opt_event = sockets.get_mut::<dhcpv4::Socket>(self.dhcp_handle).poll();
if let Some(event) = &opt_event {
match event {
dhcpv4::Event::Deconfigured => {
defmt::info!("DHCP lost configuration");
self.iface.update_ip_addrs(|addrs| addrs.clear());
self.iface.routes_mut().remove_default_ipv4_route();
}
dhcpv4::Event::Configured(config) => {
defmt::info!("DHCP configuration acquired");
defmt::info!("IP address: {}", config.address);
self.iface.update_ip_addrs(|addrs| {
addrs.clear();
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
});
if let Some(router) = config.router {
defmt::debug!("Default gateway: {}", router);
self.iface
.routes_mut()
.add_default_ipv4_route(router)
.unwrap();
} else {
defmt::debug!("Default gateway: None");
self.iface.routes_mut().remove_default_ipv4_route();
}
}
}
}
opt_event
}
}
pub struct UdpNet {
udp_handle: SocketHandle,
last_client: Option<UdpMetadata>,
tc_source_tx: TcSourceTx,
}
impl UdpNet {
pub fn new<'sockets>(sockets: &mut SocketSet<'sockets>, tc_source_tx: TcSourceTx) -> Self {
// SAFETY: The RX and TX buffers are passed here and not used anywhere else.
let udp_rx_buffer =
smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_RX_META[..] }, unsafe {
&mut UDP_RX[..]
});
let udp_tx_buffer =
smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_TX_META[..] }, unsafe {
&mut UDP_TX[..]
});
let udp_socket = smoltcp::socket::udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
let udp_handle = sockets.add(udp_socket);
Self {
udp_handle,
last_client: None,
tc_source_tx,
}
}
pub fn poll<'sockets>(
&mut self,
sockets: &'sockets mut SocketSet,
shared_pool: &mut SharedPool,
) {
let socket = sockets.get_mut::<udp::Socket>(self.udp_handle);
if !socket.is_open() {
if let Err(e) = socket.bind(PORT) {
defmt::warn!("binding UDP socket failed: {}", e);
}
}
loop {
match socket.recv() {
Ok((data, client)) => {
match shared_pool.add(data) {
Ok(store_addr) => {
if let Err(e) = self.tc_source_tx.try_send(store_addr) {
defmt::warn!("TC source channel is full: {}", e);
}
}
Err(e) => {
defmt::warn!("could not add UDP packet to shared pool: {}", e);
}
}
self.last_client = Some(client);
// TODO: Implement packet wiretapping.
}
Err(e) => match e {
udp::RecvError::Exhausted => {
break;
}
udp::RecvError::Truncated => {
defmt::warn!("UDP packet was truncacted");
}
},
};
}
}
}
#[app(device = stm32h7xx_hal::stm32, peripherals = true)]
mod app { mod app {
use core::ptr::addr_of_mut;
use super::*; use super::*;
use rtic_monotonics::fugit::MillisDurationU32; use arbitrary_int::u14;
use satrs::spacepackets::ecss::tc::PusTcReader; use embassy_net::udp::UdpSocket;
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY}; use embassy_net::StackResources;
use stm32h7xx_hal::gpio::{Output, Pin}; use embassy_stm32::eth;
use stm32h7xx_hal::prelude::*; use embassy_stm32::gpio;
use stm32h7xx_hal::stm32::Interrupt; use embassy_stm32::peripherals;
use embassy_stm32::rng;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_time::Duration;
use embassy_time::Timer;
use embassy_time::WithTimeout as _;
use embedded_models::create_tm_packet;
use embedded_models::stm32h7;
use embedded_models::tm_size;
use embedded_models::TmHeader;
use spacepackets::CcsdsPacketCreationError;
use spacepackets::CcsdsPacketIdAndPsc;
use spacepackets::CcsdsPacketReader;
use spacepackets::SpHeader;
use static_cell::StaticCell;
bind_interrupts!(struct Irqs {
ETH => eth::InterruptHandler;
RNG => rng::InterruptHandler<peripherals::RNG>;
});
type Device = eth::Ethernet<
'static,
peripherals::ETH,
eth::GenericPhy<eth::Sma<'static, peripherals::ETH_SMA>>,
>;
struct BlinkyLeds { struct BlinkyLeds {
led1: Pin<'B', 7, Output>, led1: gpio::Output<'static>,
led2: Pin<'B', 14, Output>, led2: gpio::Output<'static>,
} }
#[local] #[local]
struct Local { struct Local {
net_runner: embassy_net::Runner<'static, Device>,
net_stack: embassy_net::Stack<'static>,
leds: BlinkyLeds, leds: BlinkyLeds,
link_led: Pin<'B', 0, Output>, link_led: gpio::Output<'static>,
net: Net, tc_rx: embassy_sync::channel::Receiver<
udp: UdpNet, 'static,
tc_source_rx: TcSourceRx, NoopRawMutex,
phy: ethernet::phy::LAN8742A<EthernetMAC>, alloc::vec::Vec<u8>,
TC_QUEUE_DEPTH,
>,
tc_tx: embassy_sync::channel::Sender<
'static,
NoopRawMutex,
alloc::vec::Vec<u8>,
TC_QUEUE_DEPTH,
>,
tm_rx: embassy_sync::channel::Receiver<
'static,
NoopRawMutex,
alloc::vec::Vec<u8>,
TM_QUEUE_DEPTH,
>,
tm_tx: embassy_sync::channel::Sender<
'static,
NoopRawMutex,
alloc::vec::Vec<u8>,
TM_QUEUE_DEPTH,
>,
} }
#[shared] #[shared]
struct Shared { struct Shared {
blink_freq: MillisDurationU32, sequence_count: u14,
eth_link_up: bool, blink_freq: embassy_time::Duration,
sockets: SocketSet<'static>,
shared_pool: SharedPool,
} }
#[init] #[init]
fn init(mut cx: init::Context) -> (Shared, Local) { fn init(_cx: init::Context) -> (Shared, Local) {
defmt::println!("Starting sat-rs demo application for the STM32H743ZIT"); defmt::println!("Starting sat-rs demo application for the STM32H743ZIT");
let pwr = cx.device.PWR.constrain(); let mut config = embassy_stm32::Config::default();
let pwrcfg = pwr.freeze(); {
use embassy_stm32::rcc::*;
config.rcc.hsi = Some(HSIPrescaler::Div1);
config.rcc.csi = true;
config.rcc.hsi48 = Some(Default::default()); // needed for RNG
config.rcc.pll1 = Some(Pll {
source: PllSource::Hsi,
prediv: PllPreDiv::Div4,
mul: PllMul::Mul50,
fracn: None,
divp: Some(PllDiv::Div2),
divq: None,
divr: None,
});
config.rcc.sys = Sysclk::Pll1P; // 400 Mhz
config.rcc.ahb_pre = AHBPrescaler::Div2; // 200 Mhz
config.rcc.apb1_pre = APBPrescaler::Div2; // 100 Mhz
config.rcc.apb2_pre = APBPrescaler::Div2; // 100 Mhz
config.rcc.apb3_pre = APBPrescaler::Div2; // 100 Mhz
config.rcc.apb4_pre = APBPrescaler::Div2; // 100 Mhz
config.rcc.voltage_scale = VoltageScale::Scale1;
}
let periphs = embassy_stm32::init(config);
let rcc = cx.device.RCC.constrain(); let link_led = gpio::Output::new(periphs.PB0, gpio::Level::Low, gpio::Speed::Medium);
// Try to keep the clock configuration similar to one used in STM examples: let mut led1 = gpio::Output::new(periphs.PB7, gpio::Level::Low, gpio::Speed::Medium);
// https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/NUCLEO-H743ZI/Examples/GPIO/GPIO_EXTI/Src/main.c let mut led2 = gpio::Output::new(periphs.PB14, gpio::Level::Low, gpio::Speed::Medium);
let ccdr = rcc
.sys_ck(400.MHz())
.hclk(200.MHz())
.use_hse(8.MHz())
.bypass_hse()
.pclk1(100.MHz())
.pclk2(100.MHz())
.pclk3(100.MHz())
.pclk4(100.MHz())
.freeze(pwrcfg, &cx.device.SYSCFG);
// Initialize the systick interrupt & obtain the token to prove that we did
Mono::start(cx.core.SYST, ccdr.clocks.sys_ck().to_Hz());
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
// good for.
cx.core.SCB.enable_icache();
cx.core.DWT.enable_cycle_counter();
let gpioa = cx.device.GPIOA.split(ccdr.peripheral.GPIOA);
let gpiob = cx.device.GPIOB.split(ccdr.peripheral.GPIOB);
let gpioc = cx.device.GPIOC.split(ccdr.peripheral.GPIOC);
let gpiog = cx.device.GPIOG.split(ccdr.peripheral.GPIOG);
let link_led = gpiob.pb0.into_push_pull_output();
let mut led1 = gpiob.pb7.into_push_pull_output();
let mut led2 = gpiob.pb14.into_push_pull_output();
// Criss-cross pattern looks cooler. // Criss-cross pattern looks cooler.
led1.set_high(); led1.set_high();
led2.set_low(); led2.set_low();
let leds = BlinkyLeds { led1, led2 }; let leds = BlinkyLeds { led1, led2 };
let rmii_ref_clk = gpioa.pa1.into_alternate::<11>(); static PACKETS: StaticCell<eth::PacketQueue<4, 4>> = StaticCell::new();
let rmii_mdio = gpioa.pa2.into_alternate::<11>(); // warning: Not all STM32H7 devices have the exact same pins here
let rmii_mdc = gpioc.pc1.into_alternate::<11>(); // for STM32H747XIH, replace p.PB13 for PG12
let rmii_crs_dv = gpioa.pa7.into_alternate::<11>(); let device = eth::Ethernet::new(
let rmii_rxd0 = gpioc.pc4.into_alternate::<11>(); PACKETS.init(eth::PacketQueue::<4, 4>::new()),
let rmii_rxd1 = gpioc.pc5.into_alternate::<11>(); periphs.ETH,
let rmii_tx_en = gpiog.pg11.into_alternate::<11>(); Irqs,
let rmii_txd0 = gpiog.pg13.into_alternate::<11>(); periphs.PA1, // ref_clk
let rmii_txd1 = gpiob.pb13.into_alternate::<11>(); periphs.PA7, // CRS_DV: Carrier Sense
periphs.PC4, // RX_D0: Received Bit 0
let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS); periphs.PC5, // RX_D1: Received Bit 1
periphs.PG13, // TX_D0: Transmit Bit 0
/// Ethernet descriptor rings are a global singleton periphs.PB13, // TX_D1: Transmit Bit 1
#[link_section = ".sram3.eth"] periphs.PG11, // TX_EN: Transmit Enable
static mut DES_RING: MaybeUninit<ethernet::DesRing<4, 4>> = MaybeUninit::uninit(); MAC_ADDRESS,
periphs.ETH_SMA,
let (eth_dma, eth_mac) = ethernet::new( periphs.PA2, // mdio
cx.device.ETHERNET_MAC, periphs.PC1, // mdc
cx.device.ETHERNET_MTL,
cx.device.ETHERNET_DMA,
(
rmii_ref_clk,
rmii_mdio,
rmii_mdc,
rmii_crs_dv,
rmii_rxd0,
rmii_rxd1,
rmii_tx_en,
rmii_txd0,
rmii_txd1,
),
// SAFETY: We do not move the returned DMA struct across thread boundaries, so this
// should be safe according to the docs.
unsafe { DES_RING.assume_init_mut() },
mac_addr,
ccdr.peripheral.ETH1MAC,
&ccdr.clocks,
);
// Initialise ethernet PHY...
let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
lan8742a.phy_reset();
lan8742a.phy_init();
unsafe {
ethernet::enable_interrupt();
cx.core.NVIC.set_priority(Interrupt::ETH, 196); // Mid prio
cortex_m::peripheral::NVIC::unmask(Interrupt::ETH);
}
// unsafe: mutable reference to static storage, we only do this once
let store = unsafe {
let store_ptr = STORE.as_mut_ptr();
// Initialise the socket_storage field. Using `write` instead of
// assignment via `=` to not call `drop` on the old, uninitialised
// value
addr_of_mut!((*store_ptr).socket_storage).write([SocketStorage::EMPTY; 8]);
// Now that all fields are initialised we can safely use
// assume_init_mut to return a mutable reference to STORE
STORE.assume_init_mut()
};
let (tc_source_tx, tc_source_rx) =
rtic_sync::make_channel!(PoolAddr, TC_SOURCE_CHANNEL_DEPTH);
let mut sockets = SocketSet::new(&mut store.socket_storage[..]);
let net = Net::new(&mut sockets, eth_dma, mac_addr.into());
let udp = UdpNet::new(&mut sockets, tc_source_tx);
let mut shared_pool: SharedPool = StaticHeaplessMemoryPool::new(true);
static_subpool!(
SUBPOOL_SMALL,
SUBPOOL_SMALL_SIZES,
SUBPOOL_SMALL_NUM_BLOCKS as usize,
SUBPOOL_SMALL_BLOCK_SIZE,
link_section = ".axisram"
);
static_subpool!(
SUBPOOL_MEDIUM,
SUBPOOL_MEDIUM_SIZES,
SUBPOOL_MEDIUM_NUM_BLOCKS as usize,
SUBPOOL_MEDIUM_BLOCK_SIZE,
link_section = ".axisram"
);
static_subpool!(
SUBPOOL_LARGE,
SUBPOOL_LARGE_SIZES,
SUBPOOL_LARGE_NUM_BLOCKS as usize,
SUBPOOL_LARGE_BLOCK_SIZE,
link_section = ".axisram"
); );
shared_pool let config = embassy_net::Config::dhcpv4(embassy_net::DhcpConfig::default());
.grow(
SUBPOOL_SMALL.get_mut().unwrap(), // Generate random seed.
SUBPOOL_SMALL_SIZES.get_mut().unwrap(), let mut rng = rng::Rng::new(periphs.RNG, Irqs);
SUBPOOL_SMALL_NUM_BLOCKS, let mut seed = [0; 8];
true, rng.fill_bytes(&mut seed);
) let seed = u64::from_le_bytes(seed);
.expect("growing heapless memory pool failed");
shared_pool // Init network stack
.grow( static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
SUBPOOL_MEDIUM.get_mut().unwrap(), let (stack, runner) =
SUBPOOL_MEDIUM_SIZES.get_mut().unwrap(), embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
SUBPOOL_MEDIUM_NUM_BLOCKS,
true,
)
.expect("growing heapless memory pool failed");
shared_pool
.grow(
SUBPOOL_LARGE.get_mut().unwrap(),
SUBPOOL_LARGE_SIZES.get_mut().unwrap(),
SUBPOOL_LARGE_NUM_BLOCKS,
true,
)
.expect("growing heapless memory pool failed");
// Set up global allocator. Use AXISRAM for the heap. // Set up global allocator. Use AXISRAM for the heap.
#[link_section = ".axisram"] #[link_section = ".axisram"]
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
eth_link_check::spawn().expect("eth link check failed"); static TC_CHANNEL: static_cell::ConstStaticCell<
embassy_sync::channel::Channel<NoopRawMutex, alloc::vec::Vec<u8>, TC_QUEUE_DEPTH>,
> = static_cell::ConstStaticCell::new(embassy_sync::channel::Channel::new());
let tc_channel = TC_CHANNEL.take();
let tc_sender = tc_channel.sender();
let tc_receiver = tc_channel.receiver();
static TM_CHANNEL: static_cell::ConstStaticCell<
embassy_sync::channel::Channel<NoopRawMutex, alloc::vec::Vec<u8>, TM_QUEUE_DEPTH>,
> = static_cell::ConstStaticCell::new(embassy_sync::channel::Channel::new());
let tm_channel = TM_CHANNEL.take();
let tm_sender = tm_channel.sender();
let tm_receiver = tm_channel.receiver();
net_lib_task::spawn().expect("spawning net library task failed");
net_app_task::spawn().expect("spawning net application task failed");
blinky::spawn().expect("spawning blink task failed"); blinky::spawn().expect("spawning blink task failed");
udp_task::spawn().expect("spawning UDP task failed"); tc_handler::spawn().expect("spawning TC handler task failed");
tc_source_task::spawn().expect("spawning TC source task failed");
( (
Shared { Shared {
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS), blink_freq: Duration::from_millis(DEFAULT_BLINK_FREQ_MS as u64),
eth_link_up: false, sequence_count: u14::new(0),
sockets,
shared_pool,
}, },
Local { Local {
link_led, link_led,
leds, leds,
net, net_runner: runner,
udp, net_stack: stack,
tc_source_rx, tc_tx: tc_sender,
phy: lan8742a, tc_rx: tc_receiver,
tm_tx: tm_sender,
tm_rx: tm_receiver,
}, },
) )
} }
@@ -430,94 +223,159 @@ mod app {
leds.led1.toggle(); leds.led1.toggle();
leds.led2.toggle(); leds.led2.toggle();
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current); let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
Mono::delay(current_blink_freq).await; Timer::after_millis(current_blink_freq.as_millis()).await;
} }
} }
/// This task checks for the network link. #[task(local=[net_runner])]
#[task(local=[link_led, phy], shared=[eth_link_up])] async fn net_lib_task(cx: net_lib_task::Context) {
async fn eth_link_check(mut cx: eth_link_check::Context) { cx.local.net_runner.run().await;
let phy = cx.local.phy; }
let link_led = cx.local.link_led;
#[task(local = [net_stack, link_led, tc_tx, tm_rx])]
async fn net_app_task(cx: net_app_task::Context) {
pub const MTU: usize = 1500;
// Ensure those are in the data section by making them static.
static RX_UDP_META: static_cell::ConstStaticCell<[embassy_net::udp::PacketMetadata; 8]> =
static_cell::ConstStaticCell::new([embassy_net::udp::PacketMetadata::EMPTY; 8]);
static TX_UDP_META: static_cell::ConstStaticCell<[embassy_net::udp::PacketMetadata; 8]> =
static_cell::ConstStaticCell::new([embassy_net::udp::PacketMetadata::EMPTY; 8]);
static TX_UDP_BUFS: static_cell::ConstStaticCell<[u8; MTU]> =
static_cell::ConstStaticCell::new([0; MTU]);
static RX_UDP_BUFS: static_cell::ConstStaticCell<[u8; MTU]> =
static_cell::ConstStaticCell::new([0; MTU]);
let rx_udp_meta = RX_UDP_META.take();
let rx_udp_bufs = RX_UDP_BUFS.take();
let tx_udp_meta = TX_UDP_META.take();
let tx_udp_bufs = TX_UDP_BUFS.take();
let mut rx_buffer = [0; MTU];
loop { loop {
let link_was_up = cx.shared.eth_link_up.lock(|link_up| *link_up); cx.local.net_stack.wait_link_up().await;
if phy.poll_link() { cx.local.link_led.set_high();
if !link_was_up { defmt::info!("Network link is up");
link_led.set_high();
cx.shared.eth_link_up.lock(|link_up| *link_up = true);
defmt::info!("Ethernet link up");
}
} else if link_was_up {
link_led.set_low();
cx.shared.eth_link_up.lock(|link_up| *link_up = false);
defmt::info!("Ethernet link down");
}
Mono::delay(100.millis()).await;
}
}
#[task(binds=ETH, local=[net], shared=[sockets])] // Ensure DHCP configuration is up before trying connect
fn eth_isr(mut cx: eth_isr::Context) { cx.local.net_stack.wait_config_up().await;
// SAFETY: We do not write the register mentioned inside the docs anywhere else.
unsafe {
ethernet::interrupt_handler();
}
// Check and process ETH frames and DHCP. UDP is checked in a different task.
cx.shared.sockets.lock(|sockets| {
cx.local.net.poll(sockets);
cx.local.net.poll_dhcp(sockets);
});
}
/// This task routes UDP packets. let config = cx.local.net_stack.config_v4();
#[task(local=[udp], shared=[sockets, shared_pool])] defmt::info!("Network task initialized, config: {}", config);
async fn udp_task(mut cx: udp_task::Context) {
let mut udp = UdpSocket::new(
cx.local.net_stack.clone(),
rx_udp_meta,
rx_udp_bufs,
tx_udp_meta,
tx_udp_bufs,
);
defmt::info!("UDP socket bound to port {}", PORT);
udp.bind(PORT).expect("failed to bind UDP socket");
let mut remote_endpoint = None;
loop { loop {
cx.shared.sockets.lock(|sockets| { if !cx.local.net_stack.is_link_up() {
cx.shared.shared_pool.lock(|pool| { defmt::warn!("Network link is down");
cx.local.udp.poll(sockets, pool); cx.local.link_led.set_low();
}) break;
});
Mono::delay(40.millis()).await;
} }
} match udp
.recv_from(&mut rx_buffer)
/// This task handles all the incoming telecommands. .with_timeout(Duration::from_millis(200))
#[task(local=[read_buf: [u8; 1024] = [0; 1024], tc_source_rx], shared=[shared_pool])] .await
async fn tc_source_task(mut cx: tc_source_task::Context) { {
loop { Ok(result) => match result {
let recv_result = cx.local.tc_source_rx.recv().await; Ok((data, meta)) => {
match recv_result { remote_endpoint = Some(meta.endpoint);
Ok(pool_addr) => { defmt::debug!("UDP RX {}, Meta: {}", data, meta);
cx.shared.shared_pool.lock(|pool| { cx.local.tc_tx.send(rx_buffer[0..data].to_vec()).await;
match pool.read(&pool_addr, cx.local.read_buf.as_mut()) {
Ok(packet_len) => {
defmt::info!("received {} bytes in the TC source task", packet_len);
match PusTcReader::new(&cx.local.read_buf[0..packet_len]) {
Ok((packet, _tc_len)) => {
// TODO: Handle packet here or dispatch to dedicated PUS
// handler? Dispatching could simplify some things and make
// the software more scalable..
defmt::info!("received PUS packet: {}", packet);
} }
Err(e) => { Err(e) => {
defmt::info!("invalid TC format, not a PUS packet: {}", e); defmt::warn!("udp receive error: {}", e);
Timer::after_millis(100).await;
}
},
Err(_e) => (),
}
if let Some(endpoint) = remote_endpoint {
while let Ok(packet) = cx.local.tm_rx.try_receive() {
match udp.send_to(&packet, endpoint).await {
Ok(_) => {
defmt::debug!("UDP TX: {} bytes to: {}", packet.len(), endpoint)
}
Err(e) => defmt::warn!("udp send error: {}", e),
} }
} }
if let Err(e) = pool.delete(pool_addr) {
defmt::warn!("deleting TC data failed: {}", e);
} }
} }
Err(e) => {
defmt::warn!("TC packet read failed: {}", e);
} }
} }
#[task(local = [tc_rx, tm_tx], shared=[sequence_count, blink_freq])]
async fn tc_handler(mut cx: tc_handler::Context) {
loop {
let tc = cx.local.tc_rx.receive().await;
match CcsdsPacketReader::new_with_checksum(&tc) {
Ok(packet) => {
let packet_id = packet.packet_id();
let psc = packet.psc();
let tc_packet_id = CcsdsPacketIdAndPsc { packet_id, psc };
if let Ok(request) =
postcard::from_bytes::<stm32h7::Request>(packet.packet_data())
{
let response = match request {
stm32h7::Request::Ping => {
stm32h7::Response::Ok
}
stm32h7::Request::ChangeBlinkFrequency(duration) => {
cx.shared.blink_freq.lock(|current| {
*current = Duration::from_millis(duration.as_millis() as u64)
}); });
} stm32h7::Response::Ok
Err(e) => {
defmt::warn!("TC source reception error: {}", e);
} }
}; };
let sequence_count = cx.shared.sequence_count.lock(|v| {
let current = *v;
*v = v.wrapping_add(u14::new(1));
current
});
// Send Pong/OK response immediately.
if let Err(e) =
send_tm(tc_packet_id, response, sequence_count, cx.local.tm_tx).await
{
defmt::warn!("Failed to send TM response: {}", e);
} }
} }
}
Err(e) => defmt::warn!("Failed to parse received TC packet: {}", e,),
}
defmt::info!("Received from UDP client: {}", tc.as_slice());
}
}
async fn send_tm(
tc_packet_id: CcsdsPacketIdAndPsc,
response: stm32h7::Response,
current_seq_count: u14,
sender: &embassy_sync::channel::Sender<
'static,
NoopRawMutex,
alloc::vec::Vec<u8>,
TM_QUEUE_DEPTH,
>,
) -> Result<(), CcsdsPacketCreationError> {
let sp_header = SpHeader::new_for_unseg_tc(stm32h7::PUS_APID, current_seq_count, 0);
let tm_header = TmHeader {
tc_packet_id: Some(tc_packet_id),
uptime_millis: embassy_time::Instant::now().as_millis(),
};
let tm_size = tm_size(&tm_header, &response);
let mut packet = alloc::vec![0; tm_size];
create_tm_packet(&mut packet, sp_header, tm_header, response)?;
sender.send(packet).await;
Ok(())
}
} }
@@ -1,7 +1,7 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use stm32h7_testapp as _; // memory layout + panic handler use satrs_stm32h7_nucleo_rtic as _; // memory layout + panic handler
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state' // See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
// feature) // feature)
@@ -1,2 +0,0 @@
/settings.json
/.cortex-debug.*
@@ -1,12 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"rust-lang.rust",
"probe-rs.probe-rs-debugger"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}
@@ -1,22 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"preLaunchTask": "${defaultBuildTask}",
"type": "probe-rs-debug",
"request": "launch",
"name": "probe-rs Debugging ",
"flashingConfig": {
"flashingEnabled": true
},
"chip": "STM32H743ZITx",
"coreConfigs": [
{
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-stm32h7-nucleo-rtic",
"rttEnabled": true,
"svdFile": "STM32H743.svd"
}
]
}
]
}
@@ -1,20 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "cargo build",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build"
],
"group": {
"kind": "build",
"isDefault": true
}
},
]
}
+9 -1
View File
@@ -11,9 +11,17 @@ test:
cargo nextest run --all-features cargo nextest run --all-features
cargo test --doc --all-features cargo test --doc --all-features
embedded: embedded: embedded-stm32h7 embedded-stm32f3
cargo check -p satrs --target=thumbv7em-none-eabihf --no-default-features cargo check -p satrs --target=thumbv7em-none-eabihf --no-default-features
[working-directory:"embedded-examples/stm32h7-nucleo-rtic"]
embedded-stm32h7:
cargo build --target=thumbv7em-none-eabihf --release
[working-directory:"embedded-examples/stm32f3-disco-rtic"]
embedded-stm32f3:
cargo build --target=thumbv7em-none-eabihf --release
check-fmt: check-fmt:
cargo fmt --all -- --check cargo fmt --all -- --check
+2 -1
View File
@@ -169,7 +169,7 @@ impl MgmHandlerLis3Mdl {
spi_com: SpiCommunication, spi_com: SpiCommunication,
shared_mgm_set: Arc<Mutex<MgmData>>, shared_mgm_set: Arc<Mutex<MgmData>>,
mode_leaf_helper: ModeLeafHelper, mode_leaf_helper: ModeLeafHelper,
mode_timeout: Duration mode_timeout: Duration,
) -> Self { ) -> Self {
Self { Self {
id, id,
@@ -533,6 +533,7 @@ mod tests {
SpiCommunication::Test(TestSpiInterface::default()), SpiCommunication::Test(TestSpiInterface::default()),
shared_mgm_set, shared_mgm_set,
mode_leaf_helper, mode_leaf_helper,
Duration::from_millis(200),
); );
Self { Self {
assembly_mode_request_tx, assembly_mode_request_tx,
+2 -2
View File
@@ -188,7 +188,7 @@ fn main() {
request_rx: mgm_0_mode_request_rx, request_rx: mgm_0_mode_request_rx,
report_tx: mgm_0_mode_report_tx, report_tx: mgm_0_mode_report_tx,
}, },
Duration::from_millis(1000) Duration::from_millis(1000),
); );
let mut mgm_1_handler = mgm::MgmHandlerLis3Mdl::new( let mut mgm_1_handler = mgm::MgmHandlerLis3Mdl::new(
mgm::MgmId::_1, mgm::MgmId::_1,
@@ -203,7 +203,7 @@ fn main() {
request_rx: mgm_1_mode_request_rx, request_rx: mgm_1_mode_request_rx,
report_tx: mgm_1_mode_report_tx, report_tx: mgm_1_mode_report_tx,
}, },
Duration::from_millis(1000) Duration::from_millis(1000),
); );
let mut mgm_assembly = mgm_assembly::Assembly::new( let mut mgm_assembly = mgm_assembly::Assembly::new(
mgm_assembly::ParentQueueHelper { mgm_assembly::ParentQueueHelper {