Skip to content

Wrong bits in DLStatus Register used for determining network topology #372

Description

@jeffective

Working on DC for my project, it seems that EtherCrab uses a different method for determining the topology of the network than SOEM and IGH EtherCAT Master, and based on my understanding of the relevent documentation, EtherCrab's method is (slightly) wrong. The consequence of this difference is minor and is unlikely to ever present an issue to users.

The topology is determined by the number of "active links" at a subdevice. An "active link" is a port where the subdevice will send frames, instead of internally forwarding the frames to its next port.

EtherCrab solely uses the "physical link" bits of the DL status register. IGH and SOEM both use port active = (rx_signal_detected AND NOT loop_internal).

EtherCrab:

dl_status.link_port0,

IGH:
https://gitlab.com/etherlab.org/ethercat/-/blob/stable-1.6/master%2Ffsm_slave_scan.c?ref_type=heads&blame=1#L522

SOEM:
https://github.com/OpenEtherCATsociety/SOEM/blob/2f73eaa803f91f8332b5c8b047ba03a1210c9a80/src/ec_config.c#L280

And the most informative information on the DL Status register is part of the Beckhoff Hardware Datasheet Section 1 - Technology 4.1 Link Status:
https://download.beckhoff.com/download/document/io/ethercat-development-products/ethercat_esc_datasheet_sec1_technology_2i3.pdf

My understanding is that the EtherCrab method will ignore the outcome of "enhanced link detection". I will be following SOEM, IGH. Here is a zig code example:

Zig code snippet
/// DL Status Register
///
/// The DL Status register is used to indicate the state of the DL ports and state
/// of the interface between the DL-user and the DL.
///
/// - portx_physical_link indicates the presence of carrier signals
///   on the port (green LED on port typically)
/// - portx_loop_internal indicates there is no connection on that port and the autoforwarder will forward to the next port
/// - portx_rx_signal_detected indicates link established after enhanced link detection
///
/// Ref: IEC 61158-4-12:2019 6.1.4
/// Ref: Beckhoff Hardware Data Sheet Section 1 - Technology 4.1 Link status
pub const DLStatus = packed struct {
    dls_user_operational: bool,
    dls_user_watchdog_ok: bool,
    extended_link_detection: bool,
    _reserved: u1 = 0,

    port0_physical_link: bool,
    port1_physical_link: bool,
    port2_physical_link: bool,
    port3_physical_link: bool,

    port0_loop_internal: bool,
    /// also called "communication established"
    port0_rx_signal_detected: bool,

    port1_loop_internal: bool,
    /// also called "communication established"
    port1_rx_signal_detected: bool,

    port2_loop_internal: bool,
    /// also called "communication established"
    port2_rx_signal_detected: bool,

    port3_loop_internal: bool,
    /// also called "communication established"
    port3_rx_signal_detected: bool,

    pub fn topology(self: DLStatus) Topology {
        // cannot only use physical_link. Must use rx_signal_detected
        // because of enhanced link detection.
        // rx_signal_detected is considerd "communication established"
        // and is considered "most important".
        //
        // Loop internal is considered for the case that
        // user force closes a port.
        const port0_active = self.port0_rx_signal_detected and !self.port0_loop_internal;
        const port1_active = self.port1_rx_signal_detected and !self.port1_loop_internal;
        const port2_active = self.port2_rx_signal_detected and !self.port2_loop_internal;
        const port3_active = self.port3_rx_signal_detected and !self.port3_loop_internal;

        const n_active_ports: u3 =
            @as(u3, @intFromBool(port0_active)) +
            @as(u3, @intFromBool(port1_active)) +
            @as(u3, @intFromBool(port2_active)) +
            @as(u3, @intFromBool(port3_active));

        return switch (n_active_ports) {
            // 0 is unlikely to ever happen, and 0 actually means port 0 is open, because
            // port 0 is always open even if no ports are active.
            // Instead of considering this a protocol violation we
            // can just treat it like 1 port is active.
            //
            // "If all ports are closed (either manually or
            // automatically, e.g., because no port has a communication
            // link), port 0 is automatically opened as the recovery port"
            //
            // Ref: Beckhoff Hardware Data Sheet Section 1 - Technology 4.1 Link status
            0 => return .end,
            1 => return .end,
            2 => return .line,
            3 => return .wye,
            4 => return .cross,
            else => unreachable,
        };
    }
};

pub const Topology = enum {
    /// one active link, (the end of a module stack)
    end,
    /// two active links, one before and one after, (middle of module stack)
    line,
    /// three active links, for example: Beckhoff EK1100
    wye,
    /// four active links, for example: Beckhoff EK1122
    cross,
};

I can imagine your method causing a failure if:

  1. user force closes a port using the DLControl Register LoopControlSettings set to always_closed but there is still something physically connected on that port, causing link status to be true but no forwarding will actually occur.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions