///|
/// ESM extensions for Device Emulation and OpOnly flag handling.
/// Implements ETG.1500 #103 (Device Emulation shall) and §5.3.4 (OpOnly).
///|
/// Read the Type register (0x0000) from a slave and check if
/// the Device Emulation flag (bit 7 of byte 0) is set.
pub fn[N : @hal.Nic] read_device_emulation(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> Bool raise @hal.EcError {
let pdu = read_register_fp(nic, station, reg_type, 1, timeout)
if pdu.data.length() < 1 {
return false
}
(pdu.data[0] & b'\x80') != b'\x00'
}
///|
/// Request an ESM state transition with Device Emulation awareness.
/// For slaves with Device Emulation:
/// - Clear Error Indication in AL Control (bit 4) before transition.
/// - Write AL Control with Acknowledge bit when in error state.
/// For normal slaves, behaves identically to request_state.
pub fn[N : @hal.Nic] request_state_aware(
nic : N,
station : UInt,
target : EsmState,
device_emulation? : Bool = false,
timeout : @hal.Duration,
) -> EsmState raise @hal.EcError {
if device_emulation {
// For Device Emulation slaves: first acknowledge any error indication
// by writing current state with bit 4 (Error Indication Ack) set
let current_pdu = read_register_fp(nic, station, reg_al_status, 2, timeout)
if current_pdu.data.length() >= 2 {
let current_status = current_pdu.data[0]
// Check bit 4 of AL Status (Error Indication)
if (current_status & b'\x10') != b'\x00' {
// Acknowledge error: write current state | 0x10 to AL Control
let ack_byte = (current_status & b'\x0F') | b'\x10'
let ack_buf = Bytes::from_array([ack_byte, b'\x00'])
let _ = write_register_fp(
nic, station, reg_al_control, ack_buf, timeout,
)
}
}
}
// Proceed with normal state request
request_state(nic, station, target, timeout)
}
///|
/// Enable or disable a specific Sync Manager.
/// Used for OpOnly handling: disable output SM when not in Op state.
///
/// SM activate register is at: SM_base + SM_index * 8 + 6
pub fn[N : @hal.Nic] set_sm_enable(
nic : N,
station : UInt,
sm_index : Int,
enable : Bool,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let sm_activate_addr = reg_sm_base +
sm_index.reinterpret_as_uint() * sm_entry_size.reinterpret_as_uint() +
6U
let value : Byte = if enable { b'\x01' } else { b'\x00' }
let _ = write_register_fp(
nic,
station,
sm_activate_addr,
Bytes::from_array([value]),
timeout,
)
}
///|
/// Apply OpOnly policy for a slave during ESM state transitions.
/// When op_only is true and target state is not Op, disables the output
/// process data SMs (typically SM2). When transitioning to Op, enables them.
pub fn[N : @hal.Nic] apply_op_only_policy(
nic : N,
station : UInt,
target : EsmState,
op_only : Bool,
output_sm_indices : Array[Int],
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
if !op_only {
return
}
let enable = target is Op
for i = 0; i < output_sm_indices.length(); i = i + 1 {
set_sm_enable(nic, station, output_sm_indices[i], enable, timeout)
}
}
///|
/// SM Watchdog configuration snapshot read from ESC registers.
pub(all) struct SmWatchdogConfig {
/// Watchdog divider value (register 0x0400). Default 0x09C2 = 100µs base.
divider : UInt
/// PDI watchdog time (register 0x0410). In watchdog base-time multiples.
pdi_time : UInt
/// Process Data watchdog time (register 0x0420). In watchdog base-time multiples.
pdata_time : UInt
/// Process Data watchdog status (register 0x0440). Bit 0: 0=expired, 1=active.
pdata_status : Bool
/// Process Data watchdog counter (register 0x0442). Increments on each event.
pdata_counter : UInt
} derive(Eq, Debug)
///|
/// Read SM Watchdog configuration from ESC registers.
pub fn[N : @hal.Nic] read_sm_watchdog_config(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> SmWatchdogConfig raise @hal.EcError {
let div_pdu = read_register_fp(nic, station, reg_watchdog_divider, 2, timeout)
let divider = if div_pdu.data.length() >= 2 {
div_pdu.data[0].to_uint() | (div_pdu.data[1].to_uint() << 8)
} else {
0U
}
let pdi_pdu = read_register_fp(
nic, station, reg_watchdog_time_pdi, 2, timeout,
)
let pdi_time = if pdi_pdu.data.length() >= 2 {
pdi_pdu.data[0].to_uint() | (pdi_pdu.data[1].to_uint() << 8)
} else {
0U
}
let pd_pdu = read_register_fp(
nic, station, reg_watchdog_time_pdata, 2, timeout,
)
let pdata_time = if pd_pdu.data.length() >= 2 {
pd_pdu.data[0].to_uint() | (pd_pdu.data[1].to_uint() << 8)
} else {
0U
}
let status_pdu = read_register_fp(
nic, station, reg_watchdog_status_pdata, 2, timeout,
)
let pdata_status = if status_pdu.data.length() >= 1 {
(status_pdu.data[0] & b'\x01') != b'\x00'
} else {
false
}
let counter_pdu = read_register_fp(
nic, station, reg_watchdog_counter_pdata, 1, timeout,
)
let pdata_counter = if counter_pdu.data.length() >= 1 {
counter_pdu.data[0].to_uint()
} else {
0U
}
{ divider, pdi_time, pdata_time, pdata_status, pdata_counter }
}
///|
/// Write SM Watchdog configuration to ESC registers.
/// Configures watchdog divider (0x0400), PDI watchdog time (0x0410),
/// and process data watchdog time (0x0420).
pub fn[N : @hal.Nic] write_sm_watchdog_config(
nic : N,
station : UInt,
divider : UInt,
pdi_time : UInt,
pdata_time : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let div_buf = Bytes::from_array([
(divider & 0xFFU).to_byte(),
((divider >> 8) & 0xFFU).to_byte(),
])
let _ = write_register_fp(
nic, station, reg_watchdog_divider, div_buf, timeout,
)
let pdi_buf = Bytes::from_array([
(pdi_time & 0xFFU).to_byte(),
((pdi_time >> 8) & 0xFFU).to_byte(),
])
let _ = write_register_fp(
nic, station, reg_watchdog_time_pdi, pdi_buf, timeout,
)
let pd_buf = Bytes::from_array([
(pdata_time & 0xFFU).to_byte(),
((pdata_time >> 8) & 0xFFU).to_byte(),
])
let _ = write_register_fp(
nic, station, reg_watchdog_time_pdata, pd_buf, timeout,
)
}
///|
/// Per-port error counters from ESC DL registers.
pub(all) struct PortErrorCounters {
/// Frame errors (invalid FCS / length) detected on this port.
frame_errors : UInt
/// Physical layer errors (invalid symbols) detected on this port.
physical_errors : UInt
/// Forwarded RX errors (previously forwarded bad frames) on this port.
forwarded_rx_errors : UInt
/// Lost link events on this port (link-down transitions).
lost_link_count : UInt
} derive(Eq, Debug)
///|
/// DL/Physical layer error counters snapshot (ESC registers 0x0300–0x0313).
pub(all) struct DlErrorCounters {
/// Per-port error counters [port0, port1, port2, port3].
ports : FixedArray[PortErrorCounters]
/// EtherCAT Processing Unit error counter (register 0x030C).
ecat_pu_errors : UInt
/// PDI interface error counter (register 0x030D).
pdi_errors : UInt
} derive(Eq, Debug)
///|
/// Read DL/Physical layer error counters from ESC registers using configured station addressing.
/// Reads registers 0x0300–0x030D (RX Error, Forwarded RX Error, EPU, PDI)
/// and 0x0310–0x0313 (Lost Link).
pub fn[N : @hal.Nic] read_dl_error_counters(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> DlErrorCounters raise @hal.EcError {
// Read RX Error Counters (0x0300–0x0307, 8 bytes: frame+physical per port)
let rx_pdu = read_register_fp(nic, station, reg_rx_error_counter, 8, timeout)
// Read Forwarded RX Error Counters (0x0308–0x030B, 4 bytes) + EPU (0x030C) + PDI (0x030D)
let fwd_pdu = read_register_fp(
nic, station, reg_fwd_rx_error_counter, 6, timeout,
)
// Read Lost Link Counters (0x0310–0x0313, 4 bytes)
let lost_pdu = read_register_fp(
nic, station, reg_lost_link_counter, 4, timeout,
)
decode_dl_error_counters(rx_pdu.data, fwd_pdu.data, lost_pdu.data)
}
///|
/// Read DL/Physical layer error counters using auto-increment (AP) addressing.
pub fn[N : @hal.Nic] read_dl_error_counters_ap(
nic : N,
position : UInt,
timeout : @hal.Duration,
) -> DlErrorCounters raise @hal.EcError {
let rx_pdu = read_register_ap(nic, position, reg_rx_error_counter, 8, timeout)
let fwd_pdu = read_register_ap(
nic, position, reg_fwd_rx_error_counter, 6, timeout,
)
let lost_pdu = read_register_ap(
nic, position, reg_lost_link_counter, 4, timeout,
)
decode_dl_error_counters(rx_pdu.data, fwd_pdu.data, lost_pdu.data)
}
///|
fn decode_dl_error_counters(
rx_data : Bytes,
fwd_data : Bytes,
lost_data : Bytes,
) -> DlErrorCounters {
let ports : FixedArray[PortErrorCounters] = FixedArray::make(4, {
frame_errors: 0U,
physical_errors: 0U,
forwarded_rx_errors: 0U,
lost_link_count: 0U,
})
for i = 0; i < 4; i = i + 1 {
let frame = if rx_data.length() > i * 2 {
rx_data[i * 2].to_uint()
} else {
0U
}
let physical = if rx_data.length() > i * 2 + 1 {
rx_data[i * 2 + 1].to_uint()
} else {
0U
}
let fwd = if fwd_data.length() > i { fwd_data[i].to_uint() } else { 0U }
let lost = if lost_data.length() > i { lost_data[i].to_uint() } else { 0U }
ports[i] = {
frame_errors: frame,
physical_errors: physical,
forwarded_rx_errors: fwd,
lost_link_count: lost,
}
}
let ecat_pu = if fwd_data.length() > 4 { fwd_data[4].to_uint() } else { 0U }
let pdi = if fwd_data.length() > 5 { fwd_data[5].to_uint() } else { 0U }
{ ports, ecat_pu_errors: ecat_pu, pdi_errors: pdi }
}