Re-implementation of rustbootd's ecat_el6695_* examples as a single binary with fixes found in review and on hardware: - DC-follow PLL hardened against period-2 hunting: slew-limited anchor (+/-50us/cycle), bistable-trap snap re-anchor, re-prime on stale deadline - drift-free absolute-grid ticker mode; probe mode for timestamp forensics - bounded-memory online stats (histograms), graceful SIGINT/SIGTERM shutdown with full report, error-streak abort - timestamp plausibility filter comparing against the previous raw sample (avoids the deadlock after a startup outlier) - XFC scope waveform options: --el2202 (with --el2202-dual), --el2262, --el1252 latch timestamp readback with per-channel edge statistics - register access unified in regs.rs: named bit constants everywhere, read-modify-write for enable/activation bytes - vendored patched ethercrab 0.7.1 (sdo_write_complete, send_raw_coe) Verified on J1900 (PREEMPT_RT 6.6.135): 600k cycles/600s exact 1 kHz, tx/rx zero errors, phase_err p50=164us std=5us; EL2202<->EL1252 loopback edge interval mean 2000.24us std=24.65us.
200 lines
6.7 KiB
Rust
200 lines
6.7 KiB
Rust
//! Demonstrate releasing of a global `PduLoop`, then of the PDU loop and all TX/RX handles so they
|
|
//! can be reused.
|
|
//!
|
|
//! The process data loop does 256 iterations then moves on to the next scenario.
|
|
|
|
// For Windows
|
|
#![allow(unused)]
|
|
|
|
use env_logger::Env;
|
|
use ethercrab::{
|
|
MainDevice, MainDeviceConfig, PduStorage, Timeouts, error::Error, std::ethercat_now,
|
|
};
|
|
use futures_lite::StreamExt;
|
|
use std::time::Duration;
|
|
|
|
/// Maximum number of SubDevices that can be stored. This must be a power of 2 greater than 1.
|
|
const MAX_SUBDEVICES: usize = 16;
|
|
/// Maximum PDU data payload size - set this to the max PDI size or higher.
|
|
const MAX_PDU_DATA: usize = PduStorage::element_size(1100);
|
|
/// Maximum number of EtherCAT frames that can be in flight at any one time.
|
|
const MAX_FRAMES: usize = 16;
|
|
/// Maximum total PDI length.
|
|
const PDI_LEN: usize = 64;
|
|
|
|
static PDU_STORAGE: PduStorage<MAX_FRAMES, MAX_PDU_DATA> = PduStorage::new();
|
|
|
|
#[cfg(not(windows))]
|
|
fn main() -> Result<(), Error> {
|
|
smol::block_on(async {
|
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
|
|
|
let interface = std::env::args()
|
|
.nth(1)
|
|
.expect("Provide network interface as first argument.");
|
|
|
|
log::info!("Starting release demo...");
|
|
log::info!("Run with RUST_LOG=ethercrab=debug or =trace for debug information");
|
|
|
|
let (tx, rx, pdu_loop) = PDU_STORAGE.try_split().expect("can only split once");
|
|
|
|
// ---
|
|
// ---
|
|
// First cycle: normal TX/RX
|
|
// ---
|
|
// ---
|
|
|
|
let maindevice =
|
|
MainDevice::new(pdu_loop, Timeouts::default(), MainDeviceConfig::default());
|
|
|
|
let tx_rx_handle =
|
|
smol::spawn(ethercrab::std::tx_rx_task(&interface, tx, rx).expect("spawn TX/RX task"));
|
|
|
|
process_loop(&maindevice).await;
|
|
|
|
// SAFETY: Any groups created with the current `maindevice` MUST be dropped before this line.
|
|
// They cannot be reused with a new `MainDevice` instance and must be initialised again.
|
|
let pdu_loop = unsafe { maindevice.release() };
|
|
|
|
// ---
|
|
// ---
|
|
// Second cycle: reuse TX/RX task and PDU loop, but create a new `MainDevice`.
|
|
// ---
|
|
// ---
|
|
|
|
log::info!("PduLoop was released, starting new MainDevice...");
|
|
|
|
// Now make a new MainDevice with the same PDU loop
|
|
let maindevice =
|
|
MainDevice::new(pdu_loop, Timeouts::default(), MainDeviceConfig::default());
|
|
|
|
process_loop(&maindevice).await;
|
|
|
|
// SAFETY: Any groups created with the current `maindevice` MUST be dropped before this line.
|
|
// They cannot be reused with a new `MainDevice` instance and must be initialised again.
|
|
let pdu_loop = unsafe { maindevice.release_all() };
|
|
|
|
// ---
|
|
// ---
|
|
// Third cycle: stop the previous TX/RX task and create a new one with the now-released TX/RX
|
|
// handles.
|
|
// ---
|
|
// ---
|
|
|
|
let (tx, rx) = tx_rx_handle.await.expect("Failed to stop TX/RX task");
|
|
|
|
log::info!(
|
|
"PduLoop, PduTx and PduRx were released, starting new TX/RX task and making new MainDevice..."
|
|
);
|
|
|
|
// Now spawn a new TX/RX task. You could use a different network interface here, for example.
|
|
let tx_rx_handle =
|
|
smol::spawn(ethercrab::std::tx_rx_task(&interface, tx, rx).expect("spawn TX/RX task"));
|
|
|
|
let maindevice =
|
|
MainDevice::new(pdu_loop, Timeouts::default(), MainDeviceConfig::default());
|
|
|
|
process_loop(&maindevice).await;
|
|
|
|
log::info!("Third PDU loop with second TX/RX task shutdown complete");
|
|
|
|
// SAFETY: Any groups created with the current `maindevice` MUST be dropped before this line.
|
|
// They cannot be reused with a new `MainDevice` instance and must be initialised again.
|
|
let pdu_loop = unsafe { maindevice.release_all() };
|
|
|
|
let (tx, rx) = tx_rx_handle.await.expect("TX/RX task error");
|
|
|
|
// ---
|
|
// ---
|
|
// Fourth cycle: do the same with `io_uring` (Linux only)
|
|
// ---
|
|
// ---
|
|
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
|
{
|
|
use ethercrab::std::tx_rx_task_io_uring;
|
|
|
|
log::info!("Linux only: reuse TX/RX with io_uring");
|
|
|
|
// NOTE: This is a suboptimal TX/RX thread spawn. See the `io-uring` example for how to do it properly.
|
|
let tx_rx_handle = std::thread::spawn(move || tx_rx_task_io_uring(&interface, tx, rx));
|
|
|
|
let maindevice =
|
|
MainDevice::new(pdu_loop, Timeouts::default(), MainDeviceConfig::default());
|
|
|
|
process_loop(&maindevice).await;
|
|
|
|
// SAFETY: Any groups created with the current `maindevice` MUST be dropped before this line.
|
|
// They cannot be reused with a new `MainDevice` instance and must be initialised again.
|
|
let _pdu_loop = unsafe { maindevice.release_all() };
|
|
|
|
let (tx, rx) = tx_rx_handle
|
|
.join()
|
|
.expect("io_uring TX/RX thread")
|
|
.expect("Could not recover TX/RX hadnles");
|
|
|
|
// Handles are ready for reuse
|
|
assert!(!tx.should_exit());
|
|
assert!(!rx.should_exit());
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
async fn process_loop(maindevice: &MainDevice<'_>) {
|
|
let group = maindevice
|
|
.init_single_group::<MAX_SUBDEVICES, PDI_LEN>(ethercat_now)
|
|
.await
|
|
.expect("Init")
|
|
.into_op(maindevice)
|
|
.await
|
|
.expect("PRE-OP -> OP");
|
|
|
|
log::info!("Discovered {} SubDevices", group.len());
|
|
|
|
for subdevice in group.iter(maindevice) {
|
|
let io = subdevice.io_raw();
|
|
|
|
log::info!(
|
|
"-> SubDevice {:#06x} {} inputs: {} bytes, outputs: {} bytes",
|
|
subdevice.configured_address(),
|
|
subdevice.name(),
|
|
io.inputs().len(),
|
|
io.outputs().len()
|
|
);
|
|
}
|
|
|
|
let mut tick_interval = smol::Timer::interval(Duration::from_millis(5));
|
|
|
|
for _ in 0..u8::MAX {
|
|
group.tx_rx(maindevice).await.expect("TX/RX");
|
|
|
|
// Increment every output byte for every SubDevice by one
|
|
for subdevice in group.iter(maindevice) {
|
|
let mut o = subdevice.outputs_raw_mut();
|
|
|
|
for byte in o.iter_mut() {
|
|
*byte = byte.wrapping_add(1);
|
|
}
|
|
}
|
|
|
|
tick_interval.next().await;
|
|
}
|
|
|
|
let _ = group
|
|
.into_safe_op(maindevice)
|
|
.await
|
|
.expect("OP -> SAFE-OP")
|
|
.into_pre_op(maindevice)
|
|
.await
|
|
.expect("SAFE-OP -> PRE-OP")
|
|
.into_init(maindevice)
|
|
.await
|
|
.expect("PRE-OP -> INIT");
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn main() {
|
|
eprintln!("This example only supports non-Windows OSes");
|
|
}
|