J1900-side DC-Synchron: EL6695 secondary firmware sync_mode=2 via CoE, DC telemetry, verified 180s zero sync errors
This commit is contained in:
parent
cdb90c3e9a
commit
253f4b3cf7
180
src/main.rs
180
src/main.rs
|
|
@ -98,6 +98,9 @@ struct Config {
|
|||
/// payload changes (new TwinCAT frame), then process immediately.
|
||||
/// Phase-locks J1900 processing to the primary master's frame arrival.
|
||||
spin: bool,
|
||||
/// Enable DC SYNC0 on the EL6695 secondary (cycle = --cycle-us) and print
|
||||
/// per-second DC phase/drift telemetry in the report line.
|
||||
dc: bool,
|
||||
}
|
||||
|
||||
fn parse_args() -> Result<Config, String> {
|
||||
|
|
@ -116,6 +119,7 @@ fn parse_args() -> Result<Config, String> {
|
|||
el2262: false,
|
||||
el1252_every: 1,
|
||||
spin: false,
|
||||
dc: false,
|
||||
};
|
||||
let mut args = std::env::args().skip(1).peekable();
|
||||
if let Some(a) = args.peek() {
|
||||
|
|
@ -146,6 +150,7 @@ fn parse_args() -> Result<Config, String> {
|
|||
"--prio" => cfg.prio = val("--prio")?.parse().map_err(|_| "bad --prio")?,
|
||||
"--quiet" => cfg.quiet = true,
|
||||
"--spin" => cfg.spin = true,
|
||||
"--dc" => cfg.dc = true,
|
||||
"--el2202" => cfg.el2202 = true,
|
||||
"--el2202-dual" => {
|
||||
cfg.el2202 = true;
|
||||
|
|
@ -525,6 +530,148 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
println!(" >>> OP achieved! <<<");
|
||||
|
||||
// ---- Optional DC SYNC0 on the EL6695 secondary ----
|
||||
// ethercrab init already distributed the system time (offset/propagation
|
||||
// delay/static drift). Here we only start the SYNC0 unit on --cycle-us
|
||||
// grid and verify the clock actually advances.
|
||||
let mut dc_ref: Option<(u64, u64)> = None; // (dc_time, mono_time) at setup
|
||||
if cfg.dc {
|
||||
let t1 = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||
.receive::<u64>(&maindevice)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let t2 = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||
.receive::<u64>(&maindevice)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
let adv = t2.wrapping_sub(t1);
|
||||
println!("DC system time: {} -> {} (adv {} ns in 100ms)", t1, t2, adv);
|
||||
if adv < 50_000_000 || adv > 500_000_000 {
|
||||
println!("DC: clock not advancing sanely; SYNC0 setup skipped");
|
||||
} else {
|
||||
// Diagnostics: which DC register groups actually exist?
|
||||
for (reg, name) in [
|
||||
(0x0900u16, "rx_time_port0(u32)"),
|
||||
(0x0920u16, "sys_time_offset(u64)"),
|
||||
(0x0928u16, "prop_delay(u32)"),
|
||||
(0x0930u16, "drift... (u16)"),
|
||||
(0x0980u16, "sync_activation(u8)"),
|
||||
(0x0981u16, "sync_active_byte(u8)"),
|
||||
(0x09A0u16, "sync0_cycle(u32)"),
|
||||
(0x09AEu16, "sync0_status(u8)"),
|
||||
] {
|
||||
match ethercrab::Command::fprd(station, reg)
|
||||
.receive_slice(&maindevice, 8)
|
||||
.await
|
||||
{
|
||||
Ok(pdu) => {
|
||||
let raw: &[u8] = &pdu;
|
||||
println!(" DC probe 0x{:04X} {}: {:02X?}", reg, name, &raw[..8.min(raw.len())]);
|
||||
}
|
||||
Err(e) => println!(" DC probe 0x{:04X} {}: ERR {:?}", reg, name, e),
|
||||
}
|
||||
}
|
||||
// CoE view: 0x1C32 (SM2 outputs) / 0x1C33 (SM3 inputs) sync params
|
||||
for idx in [0x1C32u16, 0x1C33] {
|
||||
let n = sd.sdo_read::<u8>(idx, 0).await.unwrap_or(0xFF);
|
||||
println!(" CoE 0x{:04X}: {} subentries", idx, n);
|
||||
for (sub, name) in [(0x01u8, "sync_mode"), (0x04, "modes_supported"), (0x0B, "sm_event_missed"), (0x0C, "cycle_exceeded")] {
|
||||
match sd.sdo_read::<u16>(idx, sub).await {
|
||||
Ok(x) => println!(" CoE 0x{:04X}:{:02X} {} = 0x{:04X}", idx, sub, name, x),
|
||||
Err(e) => println!(" CoE 0x{:04X}:{:02X} {} ERR {:?}", idx, sub, name, e),
|
||||
}
|
||||
}
|
||||
match sd.sdo_read::<u32>(idx, 0x02).await {
|
||||
Ok(x) => println!(" CoE 0x{:04X}:02 cycle_time_ns = {}", idx, x),
|
||||
Err(e) => println!(" CoE 0x{:04X}:02 cycle_time_ns ERR {:?}", idx, e),
|
||||
}
|
||||
match sd.sdo_read::<u8>(idx, 0x20).await {
|
||||
Ok(x) => println!(" CoE 0x{:04X}:20 sync_error = {}", idx, x),
|
||||
Err(e) => println!(" CoE 0x{:04X}:20 sync_error ERR {:?}", idx, e),
|
||||
}
|
||||
}
|
||||
let start = t2.wrapping_add(100_000_000);
|
||||
let w1 = ethercrab::Command::fpwr(station, regs::DC_SYNC_START_TIME)
|
||||
.send(&maindevice, start)
|
||||
.await;
|
||||
println!(" DC write start_time(0x0990): {:?}", w1.as_ref().map(|_| "OK").map_err(|e| format!("{:?}", e)));
|
||||
let w2 = ethercrab::Command::fpwr(station, regs::DC_SYNC0_CYCLE)
|
||||
.send(&maindevice, cycle_ns as u32)
|
||||
.await;
|
||||
println!(" DC write sync0_cycle(0x09A0): {:?}", w2.as_ref().map(|_| "OK").map_err(|e| format!("{:?}", e)));
|
||||
let w3 = regs::rmw_u8(
|
||||
&maindevice,
|
||||
station,
|
||||
regs::DC_SYNC_ACTIVATION,
|
||||
regs::DC_SYNC0_ACTIVATE,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
println!(" DC rmw activation(0x0980): {:?}", w3.as_ref().map(|v| format!("0x{:02X}", v)).map_err(|e| format!("{:?}", e)));
|
||||
// rmw needs a read (fails here); try a blind full-byte write instead
|
||||
let w4 = ethercrab::Command::fpwr(station, regs::DC_SYNC_ACTIVATION)
|
||||
.send(&maindevice, regs::DC_SYNC0_ACTIVATE)
|
||||
.await;
|
||||
println!(" DC blind write activation(0x0980)=0x01: {:?}", w4.as_ref().map(|_| "OK").map_err(|e| format!("{:?}", e)));
|
||||
std::thread::sleep(Duration::from_millis(150));
|
||||
// Try selecting DC-Synchron SYNC0 via firmware object (mode 2).
|
||||
// May be rejected in OP; worth observing the abort code either way.
|
||||
let mut coe_dc_ok = true;
|
||||
for idx in [0x1C32u16, 0x1C33] {
|
||||
match sd.sdo_write::<u16>(idx, 0x01, 2).await {
|
||||
Ok(_) => println!(" CoE write 0x{:04X}:01 sync_mode=2 OK", idx),
|
||||
Err(e) => {
|
||||
coe_dc_ok = false;
|
||||
println!(" CoE write 0x{:04X}:01 sync_mode=2 ERR {:?}", idx, e);
|
||||
}
|
||||
}
|
||||
match sd.sdo_read::<u16>(idx, 0x01).await {
|
||||
Ok(x) => println!(" CoE-after 0x{:04X}:01 sync_mode = {}", idx, x),
|
||||
Err(e) => {
|
||||
coe_dc_ok = false;
|
||||
println!(" CoE-after 0x{:04X}:01 sync_mode ERR {:?}", idx, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if coe_dc_ok {
|
||||
let t_dc = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||
.receive::<u64>(&maindevice)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
dc_ref = Some((t_dc, rt::now_ns()));
|
||||
println!("DC-Synchron: firmware sync_mode=2 accepted, DC telemetry on");
|
||||
}
|
||||
let act = ethercrab::Command::fprd(station, regs::DC_SYNC_ACTIVATION)
|
||||
.receive::<u8>(&maindevice)
|
||||
.await;
|
||||
let cyc0 = ethercrab::Command::fprd(station, regs::DC_SYNC0_CYCLE)
|
||||
.receive::<u32>(&maindevice)
|
||||
.await;
|
||||
let t3 = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||
.receive::<u64>(&maindevice)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
let (al, alc) = bridge::drive_state(&maindevice, station, 0x0008).await;
|
||||
println!(" AL after DC writes: {}", bridge::verdict(al, alc));
|
||||
println!(
|
||||
"DC SYNC0: act={:?} cycle={:?} start=+100ms (t3={} start_delta={}ns)",
|
||||
act.as_ref().map(|v| format!("0x{:02X}", v)).map_err(|e| format!("{:?}", e)),
|
||||
cyc0.as_ref().map_err(|e| format!("{:?}", e)),
|
||||
t3,
|
||||
t3.wrapping_sub(start) as i64
|
||||
);
|
||||
if matches!(act, Ok(a) if a & regs::DC_SYNC0_ACTIVATE != 0)
|
||||
&& matches!(cyc0, Ok(c) if c == cycle_ns as u32)
|
||||
{
|
||||
dc_ref = Some((t3, rt::now_ns()));
|
||||
println!("DC SYNC0: active on EL6695 secondary");
|
||||
} else {
|
||||
println!("DC SYNC0: readback mismatch; running without DC telemetry");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Optional EL2202 oscilloscope waveform (toggle one DO per cycle) ----
|
||||
const EL2202_OUT_ADDR: u16 = 0x0F00;
|
||||
let mut el2202: Option<(u16, u16)> = None;
|
||||
|
|
@ -1109,8 +1256,23 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
let proc_val = tc_seq.wrapping_mul(7).wrapping_add(3);
|
||||
let stale_pct = if cyc > 0 { (rx_stale * 100 / cyc) } else { 0 };
|
||||
let mut dc_info = String::new();
|
||||
if let Some((dc0, mono0)) = dc_ref {
|
||||
if let Ok(dc_now) = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||
.receive::<u64>(&maindevice)
|
||||
.await
|
||||
{
|
||||
let mono_now = rt::now_ns();
|
||||
let drift = (dc_now.wrapping_sub(dc0)) as i64 - (mono_now - mono0) as i64;
|
||||
dc_info = format!(
|
||||
" dcph={} dcdr={}ns",
|
||||
dc_now % cycle_ns,
|
||||
drift
|
||||
);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"S tms={} cyc={} raw={} proc={} txe={} rxe={} late={} stale={}% mono={} cerr={} mism={} e0={} e1={}",
|
||||
"S tms={} cyc={} raw={} proc={} txe={} rxe={} late={} stale={}% mono={} cerr={} mism={} e0={} e1={}{}",
|
||||
start.elapsed().as_millis(),
|
||||
cyc,
|
||||
tc_seq,
|
||||
|
|
@ -1124,6 +1286,7 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
|||
payload_mismatch,
|
||||
pos_edges,
|
||||
pos1_edges,
|
||||
dc_info,
|
||||
);
|
||||
last_report = Instant::now();
|
||||
}
|
||||
|
|
@ -1138,6 +1301,21 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
|||
let _ = f.await;
|
||||
}
|
||||
|
||||
// ---- DC post-run: re-read firmware sync counters accumulated during the run ----
|
||||
if cfg.dc {
|
||||
println!("\n=== DC post-run sync counters (0x1C32 SM2 / 0x1C33 SM3) ===");
|
||||
for idx in [0x1C32u16, 0x1C33] {
|
||||
let mode = sd.sdo_read::<u16>(idx, 0x01).await.unwrap_or(0xFFFF);
|
||||
let missed = sd.sdo_read::<u16>(idx, 0x0B).await.unwrap_or(0xFFFF);
|
||||
let exceeded = sd.sdo_read::<u16>(idx, 0x0C).await.unwrap_or(0xFFFF);
|
||||
let serr = sd.sdo_read::<u8>(idx, 0x20).await.unwrap_or(0xFF);
|
||||
println!(
|
||||
" 0x{:04X}: sync_mode={} sm_event_missed={} cycle_exceeded={} sync_error={}",
|
||||
idx, mode, missed, exceeded, serr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Final report ----
|
||||
println!("\n=== timing stats ===");
|
||||
jitter_st.report(1000, "us");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user