update STM32H7 example code
This commit is contained in:
@@ -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"
|
||||
+479
-697
File diff suppressed because it is too large
Load Diff
@@ -17,34 +17,40 @@ harness = false
|
||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
cortex-m-rt = "0.7"
|
||||
defmt = "1"
|
||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
||||
defmt-rtt = "1"
|
||||
# defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||
cortex-m-semihosting = "0.5.0"
|
||||
# cortex-m-semihosting = "0.5.0"
|
||||
# TODO: Replace with embassy-hal.
|
||||
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
|
||||
embedded-alloc = "0.6"
|
||||
rtic-sync = { version = "1", features = ["defmt-03"] }
|
||||
# stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
|
||||
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.6", features = ["stm32h743zi", "memory-x", "defmt", "time-driver-any"]}
|
||||
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.5", features = ["defmt-timestamp-uptime-ms", "generic-queue-16"] }
|
||||
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"] }
|
||||
embedded-alloc = "0.7"
|
||||
static_cell = "2"
|
||||
# rtic-sync = { version = "1", features = ["defmt-03"] }
|
||||
rtic = { version = "2", features = ["thumbv7-backend"] }
|
||||
|
||||
[dependencies.smoltcp]
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
|
||||
# [dependencies.smoltcp]
|
||||
# version = "0.13"
|
||||
# default-features = false
|
||||
# features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
|
||||
|
||||
[dependencies.rtic]
|
||||
version = "2"
|
||||
features = ["thumbv7-backend"]
|
||||
# [dependencies.rtic]
|
||||
# version = "2"
|
||||
# features = ["thumbv7-backend"]
|
||||
|
||||
[dependencies.rtic-monotonics]
|
||||
version = "2"
|
||||
features = ["cortex-m-systick"]
|
||||
# [dependencies.rtic-monotonics]
|
||||
# version = "2"
|
||||
# features = ["cortex-m-systick"]
|
||||
|
||||
[dependencies.satrs]
|
||||
path = "../../satrs"
|
||||
default-features = false
|
||||
features = ["defmt", "heapless"]
|
||||
# [dependencies.satrs]
|
||||
# path = "../../satrs"
|
||||
# default-features = false
|
||||
# features = ["defmt", "heapless"]
|
||||
|
||||
[dev-dependencies]
|
||||
defmt-test = "0.4"
|
||||
defmt-test = "0.5"
|
||||
|
||||
# cargo build/run
|
||||
[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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["thumbv7em-none-eabihf"]
|
||||
@@ -5,51 +5,53 @@
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
use rtic::app;
|
||||
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]
|
||||
fn main() -> ! {
|
||||
defmt::println!("starting stm32h7 blinky example");
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
// Get access to the device specific peripherals from the peripheral access crate
|
||||
let dp = stm32h7xx_hal::stm32::Peripherals::take().unwrap();
|
||||
#[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.
|
||||
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);
|
||||
|
||||
// Take ownership over the RCC devices and convert them into the corresponding HAL structs
|
||||
let rcc = dp.RCC.constrain();
|
||||
// Schedule the blinking task
|
||||
blink::spawn(ld1, ld2, ld3).ok();
|
||||
|
||||
let pwr = dp.PWR.constrain();
|
||||
let pwrcfg = pwr.freeze();
|
||||
(Shared {}, Local {})
|
||||
}
|
||||
|
||||
// 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);
|
||||
#[task()]
|
||||
async fn blink(
|
||||
_cx: blink::Context,
|
||||
mut ld1: gpio::Output<'static>,
|
||||
mut ld2: gpio::Output<'static>,
|
||||
mut ld3: gpio::Output<'static>,
|
||||
) {
|
||||
loop {
|
||||
defmt::info!("high");
|
||||
ld1.set_high();
|
||||
ld2.set_high();
|
||||
ld3.set_high();
|
||||
embassy_time::Timer::after_millis(500).await;
|
||||
|
||||
// Acquire the GPIOB peripheral
|
||||
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
|
||||
|
||||
// Configure gpio B pin 0 as a push-pull output.
|
||||
let mut ld1 = gpiob.pb0.into_push_pull_output();
|
||||
|
||||
// Configure gpio B pin 7 as a push-pull output.
|
||||
let mut ld2 = gpiob.pb7.into_push_pull_output();
|
||||
|
||||
// Configure gpio B pin 14 as a push-pull output.
|
||||
let mut ld3 = gpiob.pb14.into_push_pull_output();
|
||||
|
||||
// Configure the timer to trigger an update every second
|
||||
let mut timer = Timer::tim1(dp.TIM1, ccdr.peripheral.TIM1, &ccdr.clocks);
|
||||
timer.start(1.Hz());
|
||||
|
||||
// Wait for the timer to trigger an update and change the state of the LED
|
||||
loop {
|
||||
ld1.toggle();
|
||||
ld2.toggle();
|
||||
ld3.toggle();
|
||||
block!(timer.wait()).unwrap();
|
||||
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_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]
|
||||
fn main() -> ! {
|
||||
defmt::println!("Hello, world!");
|
||||
|
||||
satrs_stm32h7_nucleo_rtic::exit()
|
||||
loop {
|
||||
defmt::println!("Hello, world!");
|
||||
cortex_m::asm::delay(100_000_000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use cortex_m_semihosting::debug;
|
||||
|
||||
use defmt_brtt as _; // global logger
|
||||
|
||||
// TODO(5) adjust HAL import
|
||||
use stm32h7xx_hal as _; // memory layout
|
||||
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32 as _;
|
||||
use panic_probe as _;
|
||||
|
||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||
@@ -17,14 +12,6 @@ fn panic() -> ! {
|
||||
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.
|
||||
///
|
||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
||||
@@ -32,9 +19,7 @@ pub fn exit() -> ! {
|
||||
/// loop.
|
||||
#[cortex_m_rt::exception]
|
||||
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
||||
loop {
|
||||
debug::exit(debug::EXIT_FAILURE);
|
||||
}
|
||||
panic!("unexpected hard fault");
|
||||
}
|
||||
|
||||
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
||||
|
||||
@@ -3,422 +3,158 @@
|
||||
extern crate alloc;
|
||||
|
||||
use rtic::app;
|
||||
use rtic_monotonics::systick::prelude::*;
|
||||
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
|
||||
use satrs::static_subpool;
|
||||
// global logger + panicking-behavior + memory layout
|
||||
use embassy_stm32::bind_interrupts;
|
||||
use satrs_stm32h7_nucleo_rtic as _;
|
||||
use smoltcp::socket::udp::UdpMetadata;
|
||||
use smoltcp::socket::{dhcpv4, udp};
|
||||
|
||||
use core::mem::MaybeUninit;
|
||||
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 PORT: u16 = 7301;
|
||||
|
||||
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]
|
||||
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
|
||||
const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44];
|
||||
|
||||
pub struct Net {
|
||||
iface: Interface,
|
||||
ethdev: ethernet::EthernetDMA<4, 4>,
|
||||
dhcp_handle: SocketHandle,
|
||||
}
|
||||
|
||||
impl Net {
|
||||
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)]
|
||||
#[app(device = embassy_stm32, peripherals = false)]
|
||||
mod app {
|
||||
use core::ptr::addr_of_mut;
|
||||
|
||||
use super::*;
|
||||
use rtic_monotonics::fugit::MillisDurationU32;
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
|
||||
use stm32h7xx_hal::gpio::{Output, Pin};
|
||||
use stm32h7xx_hal::prelude::*;
|
||||
use stm32h7xx_hal::stm32::Interrupt;
|
||||
use embassy_net::udp::UdpSocket;
|
||||
use embassy_net::StackResources;
|
||||
use embassy_stm32::eth;
|
||||
use embassy_stm32::gpio;
|
||||
use embassy_stm32::peripherals;
|
||||
use embassy_stm32::rng;
|
||||
use embassy_time::Duration;
|
||||
use embassy_time::Timer;
|
||||
use embassy_time::WithTimeout as _;
|
||||
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 {
|
||||
led1: Pin<'B', 7, Output>,
|
||||
led2: Pin<'B', 14, Output>,
|
||||
led1: gpio::Output<'static>,
|
||||
led2: gpio::Output<'static>,
|
||||
}
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
net_runner: embassy_net::Runner<'static, Device>,
|
||||
net_stack: embassy_net::Stack<'static>,
|
||||
leds: BlinkyLeds,
|
||||
link_led: Pin<'B', 0, Output>,
|
||||
net: Net,
|
||||
udp: UdpNet,
|
||||
tc_source_rx: TcSourceRx,
|
||||
phy: ethernet::phy::LAN8742A<EthernetMAC>,
|
||||
link_led: gpio::Output<'static>,
|
||||
}
|
||||
|
||||
#[shared]
|
||||
struct Shared {
|
||||
blink_freq: MillisDurationU32,
|
||||
eth_link_up: bool,
|
||||
sockets: SocketSet<'static>,
|
||||
shared_pool: SharedPool,
|
||||
blink_freq: embassy_time::Duration,
|
||||
//eth_link_up: bool,
|
||||
}
|
||||
|
||||
#[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");
|
||||
|
||||
let pwr = cx.device.PWR.constrain();
|
||||
let pwrcfg = pwr.freeze();
|
||||
let mut config = embassy_stm32::Config::default();
|
||||
{
|
||||
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();
|
||||
// Try to keep the clock configuration similar to one used in STM examples:
|
||||
// https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/NUCLEO-H743ZI/Examples/GPIO/GPIO_EXTI/Src/main.c
|
||||
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();
|
||||
let link_led = gpio::Output::new(periphs.PB0, gpio::Level::Low, gpio::Speed::Medium);
|
||||
let mut led1 = gpio::Output::new(periphs.PB7, gpio::Level::Low, gpio::Speed::Medium);
|
||||
let mut led2 = gpio::Output::new(periphs.PB14, gpio::Level::Low, gpio::Speed::Medium);
|
||||
|
||||
// Criss-cross pattern looks cooler.
|
||||
led1.set_high();
|
||||
led2.set_low();
|
||||
let leds = BlinkyLeds { led1, led2 };
|
||||
|
||||
let rmii_ref_clk = gpioa.pa1.into_alternate::<11>();
|
||||
let rmii_mdio = gpioa.pa2.into_alternate::<11>();
|
||||
let rmii_mdc = gpioc.pc1.into_alternate::<11>();
|
||||
let rmii_crs_dv = gpioa.pa7.into_alternate::<11>();
|
||||
let rmii_rxd0 = gpioc.pc4.into_alternate::<11>();
|
||||
let rmii_rxd1 = gpioc.pc5.into_alternate::<11>();
|
||||
let rmii_tx_en = gpiog.pg11.into_alternate::<11>();
|
||||
let rmii_txd0 = gpiog.pg13.into_alternate::<11>();
|
||||
let rmii_txd1 = gpiob.pb13.into_alternate::<11>();
|
||||
|
||||
let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS);
|
||||
|
||||
/// Ethernet descriptor rings are a global singleton
|
||||
#[link_section = ".sram3.eth"]
|
||||
static mut DES_RING: MaybeUninit<ethernet::DesRing<4, 4>> = MaybeUninit::uninit();
|
||||
|
||||
let (eth_dma, eth_mac) = ethernet::new(
|
||||
cx.device.ETHERNET_MAC,
|
||||
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"
|
||||
static PACKETS: StaticCell<eth::PacketQueue<4, 4>> = StaticCell::new();
|
||||
// warning: Not all STM32H7 devices have the exact same pins here
|
||||
// for STM32H747XIH, replace p.PB13 for PG12
|
||||
let device = eth::Ethernet::new(
|
||||
PACKETS.init(eth::PacketQueue::<4, 4>::new()),
|
||||
periphs.ETH,
|
||||
Irqs,
|
||||
periphs.PA1, // ref_clk
|
||||
periphs.PA7, // CRS_DV: Carrier Sense
|
||||
periphs.PC4, // RX_D0: Received Bit 0
|
||||
periphs.PC5, // RX_D1: Received Bit 1
|
||||
periphs.PG13, // TX_D0: Transmit Bit 0
|
||||
periphs.PB13, // TX_D1: Transmit Bit 1
|
||||
periphs.PG11, // TX_EN: Transmit Enable
|
||||
MAC_ADDRESS,
|
||||
periphs.ETH_SMA,
|
||||
periphs.PA2, // mdio
|
||||
periphs.PC1, // mdc
|
||||
);
|
||||
|
||||
shared_pool
|
||||
.grow(
|
||||
SUBPOOL_SMALL.get_mut().unwrap(),
|
||||
SUBPOOL_SMALL_SIZES.get_mut().unwrap(),
|
||||
SUBPOOL_SMALL_NUM_BLOCKS,
|
||||
true,
|
||||
)
|
||||
.expect("growing heapless memory pool failed");
|
||||
shared_pool
|
||||
.grow(
|
||||
SUBPOOL_MEDIUM.get_mut().unwrap(),
|
||||
SUBPOOL_MEDIUM_SIZES.get_mut().unwrap(),
|
||||
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");
|
||||
let config = embassy_net::Config::dhcpv4(embassy_net::DhcpConfig::default());
|
||||
|
||||
// Generate random seed.
|
||||
let mut rng = rng::Rng::new(periphs.RNG, Irqs);
|
||||
let mut seed = [0; 8];
|
||||
rng.fill_bytes(&mut seed);
|
||||
let seed = u64::from_le_bytes(seed);
|
||||
|
||||
// Init network stack
|
||||
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
|
||||
let (stack, runner) =
|
||||
embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
|
||||
|
||||
// Set up global allocator. Use AXISRAM for the heap.
|
||||
#[link_section = ".axisram"]
|
||||
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
||||
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
|
||||
|
||||
eth_link_check::spawn().expect("eth link check failed");
|
||||
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");
|
||||
udp_task::spawn().expect("spawning UDP task failed");
|
||||
tc_source_task::spawn().expect("spawning TC source task failed");
|
||||
|
||||
(
|
||||
Shared {
|
||||
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
|
||||
eth_link_up: false,
|
||||
sockets,
|
||||
shared_pool,
|
||||
blink_freq: Duration::from_millis(DEFAULT_BLINK_FREQ_MS as u64),
|
||||
},
|
||||
Local {
|
||||
link_led,
|
||||
leds,
|
||||
net,
|
||||
udp,
|
||||
tc_source_rx,
|
||||
phy: lan8742a,
|
||||
net_runner: runner,
|
||||
net_stack: stack,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -430,94 +166,86 @@ mod app {
|
||||
leds.led1.toggle();
|
||||
leds.led2.toggle();
|
||||
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=[link_led, phy], shared=[eth_link_up])]
|
||||
async fn eth_link_check(mut cx: eth_link_check::Context) {
|
||||
let phy = cx.local.phy;
|
||||
let link_led = cx.local.link_led;
|
||||
#[task(local=[net_runner])]
|
||||
async fn net_lib_task(cx: net_lib_task::Context) {
|
||||
cx.local.net_runner.run().await;
|
||||
}
|
||||
|
||||
#[task(local = [net_stack, link_led])]
|
||||
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 {
|
||||
let link_was_up = cx.shared.eth_link_up.lock(|link_up| *link_up);
|
||||
if phy.poll_link() {
|
||||
if !link_was_up {
|
||||
link_led.set_high();
|
||||
cx.shared.eth_link_up.lock(|link_up| *link_up = true);
|
||||
defmt::info!("Ethernet link up");
|
||||
cx.local.net_stack.wait_link_up().await;
|
||||
cx.local.link_led.set_high();
|
||||
defmt::info!("Network link is up");
|
||||
|
||||
// Ensure DHCP configuration is up before trying connect
|
||||
cx.local.net_stack.wait_config_up().await;
|
||||
|
||||
let config = cx.local.net_stack.config_v4();
|
||||
defmt::info!("Network task initialized, config: {}", config);
|
||||
|
||||
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");
|
||||
loop {
|
||||
if !cx.local.net_stack.is_link_up() {
|
||||
defmt::warn!("Network link is down");
|
||||
cx.local.link_led.set_low();
|
||||
break;
|
||||
}
|
||||
} 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])]
|
||||
fn eth_isr(mut cx: eth_isr::Context) {
|
||||
// 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.
|
||||
#[task(local=[udp], shared=[sockets, shared_pool])]
|
||||
async fn udp_task(mut cx: udp_task::Context) {
|
||||
loop {
|
||||
cx.shared.sockets.lock(|sockets| {
|
||||
cx.shared.shared_pool.lock(|pool| {
|
||||
cx.local.udp.poll(sockets, pool);
|
||||
})
|
||||
});
|
||||
Mono::delay(40.millis()).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// This task handles all the incoming telecommands.
|
||||
#[task(local=[read_buf: [u8; 1024] = [0; 1024], tc_source_rx], shared=[shared_pool])]
|
||||
async fn tc_source_task(mut cx: tc_source_task::Context) {
|
||||
loop {
|
||||
let recv_result = cx.local.tc_source_rx.recv().await;
|
||||
match recv_result {
|
||||
Ok(pool_addr) => {
|
||||
cx.shared.shared_pool.lock(|pool| {
|
||||
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) => {
|
||||
defmt::info!("invalid TC format, not a PUS packet: {}", e);
|
||||
}
|
||||
match udp
|
||||
.recv_from(&mut rx_buffer)
|
||||
.with_timeout(Duration::from_millis(400))
|
||||
.await
|
||||
{
|
||||
Ok(result) => match result {
|
||||
Ok((data, meta)) => {
|
||||
defmt::info!("udp rx {} bytes from {}", data, meta);
|
||||
match udp.send_to(&rx_buffer[0..data], meta).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
defmt::warn!("udp send error: {}", e);
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
if let Err(e) = pool.delete(pool_addr) {
|
||||
defmt::warn!("deleting TC data failed: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
defmt::warn!("TC packet read failed: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
Err(e) => {
|
||||
defmt::warn!("udp receive error: {}", e);
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
},
|
||||
Err(_e) => (),
|
||||
}
|
||||
Err(e) => {
|
||||
defmt::warn!("TC source reception error: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
@@ -169,7 +169,7 @@ impl MgmHandlerLis3Mdl {
|
||||
spi_com: SpiCommunication,
|
||||
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||
mode_leaf_helper: ModeLeafHelper,
|
||||
mode_timeout: Duration
|
||||
mode_timeout: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
|
||||
@@ -188,7 +188,7 @@ fn main() {
|
||||
request_rx: mgm_0_mode_request_rx,
|
||||
report_tx: mgm_0_mode_report_tx,
|
||||
},
|
||||
Duration::from_millis(1000)
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let mut mgm_1_handler = mgm::MgmHandlerLis3Mdl::new(
|
||||
mgm::MgmId::_1,
|
||||
@@ -203,7 +203,7 @@ fn main() {
|
||||
request_rx: mgm_1_mode_request_rx,
|
||||
report_tx: mgm_1_mode_report_tx,
|
||||
},
|
||||
Duration::from_millis(1000)
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let mut mgm_assembly = mgm_assembly::Assembly::new(
|
||||
mgm_assembly::ParentQueueHelper {
|
||||
|
||||
Reference in New Issue
Block a user