ethercat-linux/vendor/ethercrab/examples/release.rs
Tony Cao 21b2d3bf99 J1900 <-> TwinCAT 1 kHz verification over EL6695 bridge
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.
2026-07-22 08:55:41 +08:00

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");
}