J1900-side DC-Synchron: EL6695 secondary firmware sync_mode=2 via CoE, DC telemetry, verified 180s zero sync errors

This commit is contained in:
Tony Cao 2026-08-10 01:56:41 +08:00
parent cdb90c3e9a
commit 253f4b3cf7

View File

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