Crate ethercrab

source ·
Expand description

A performant, async-first EtherCAT master written in pure Rust.

Are you looking to use Rust in your next EtherCAT deployment? Commercial support for EtherCrab is available! Send me a message at james@wapl.es to get started with a free consulting call.

§Crate features

  • std (enabled by default) - exposes the std module, containing helpers to run the TX/RX loop on desktop operating systems.
  • defmt - enable logging with the defmt crate.
  • log - enable logging with the log crate. This is enabled by default when the std feature is enabled.
  • serde - enable serde impls for some public items.

For no_std targets, it is recommended to add this crate with

cargo add --no-default-features --features defmt

§Examples

This example increments the output bytes of all detected slaves every tick. It is tested on an EK1100 with output modules but may work on other basic slave devices.

Run with e.g.

Linux

RUST_LOG=debug cargo run --example ek1100 --release -- eth0

Windows

$env:RUST_LOG="debug" ; cargo run --example ek1100 --release -- '\Device\NPF_{FF0ACEE6-E8CD-48D5-A399-619CD2340465}'
use env_logger::Env;
use ethercrab::{
    error::Error, std::{ethercat_now, tx_rx_task}, Client, ClientConfig, PduStorage, SlaveGroup,
    Timeouts, slave_group
};
use std::{sync::Arc, time::Duration};
use tokio::time::MissedTickBehavior;

/// Maximum number of slaves that can be stored. This must be a power of 2 greater than 1.
const MAX_SLAVES: usize = 16;
/// Maximum PDU data payload size - set this to the max PDI size or higher.
const MAX_PDU_DATA: usize = 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();

#[tokio::main]
async fn main() -> Result<(), Error> {
    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 EK1100 demo...");
    log::info!("Ensure an EK1100 is the first slave, with any number of modules connected after");
    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");

    let client = Arc::new(Client::new(
        pdu_loop,
        Timeouts {
            wait_loop_delay: Duration::from_millis(2),
            mailbox_response: Duration::from_millis(1000),
            ..Default::default()
        },
        ClientConfig::default(),
    ));

    tokio::spawn(tx_rx_task(&interface, tx, rx).expect("spawn TX/RX task"));

    let mut group = client
        .init_single_group::<MAX_SLAVES, PDI_LEN>(ethercat_now)
        .await
        .expect("Init");

    log::info!("Discovered {} slaves", group.len());

    for slave in group.iter(&client) {
        // Special case: if an EL3004 module is discovered, it needs some specific config during
        // init to function properly
        if slave.name() == "EL3004" {
            log::info!("Found EL3004. Configuring...");

            slave.sdo_write(0x1c12, 0, 0u8).await?;
            slave.sdo_write(0x1c13, 0, 0u8).await?;

            slave.sdo_write(0x1c13, 1, 0x1a00u16).await?;
            slave.sdo_write(0x1c13, 2, 0x1a02u16).await?;
            slave.sdo_write(0x1c13, 3, 0x1a04u16).await?;
            slave.sdo_write(0x1c13, 4, 0x1a06u16).await?;
            slave.sdo_write(0x1c13, 0, 4u8).await?;
        }
    }

    let mut group = group.into_op(&client).await.expect("PRE-OP -> OP");

    for slave in group.iter(&client) {
        let (i, o) = slave.io_raw();

        log::info!(
            "-> Slave {:#06x} {} inputs: {} bytes, outputs: {} bytes",
            slave.configured_address(),
            slave.name(),
            i.len(),
            o.len()
        );
    }

    let mut tick_interval = tokio::time::interval(Duration::from_millis(5));
    tick_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);

    loop {
        group.tx_rx(&client).await.expect("TX/RX");

        // Increment every output byte for every slave device by one
        for mut slave in group.iter(&client) {
            let (_i, o) = slave.io_raw_mut();

            for byte in o.iter_mut() {
                *byte = byte.wrapping_add(1);
            }
        }

        tick_interval.tick().await;
    }
}

Re-exports§

Modules§

  • Experimental DS402 state machine.
  • EtherCrab error types.
  • A group of slave devices.
  • Items required for running in std environments.

Structs§

  • The main EtherCAT master instance.
  • Configuration passed to Client.
  • The core EtherCrab network communications driver.
  • EtherCAT frame receive adapter.
  • Stores PDU frames that are currently being prepared to send, in flight, or being received and processed.
  • EtherCAT frame transmit adapter.
  • An EtherCAT frame that is ready to be sent over the network.
  • Slave device metadata. See SlaveRef for richer behaviour.
  • Slave identity information (vendor ID, product ID, etc).
  • Process Data Image (PDI) segments for a given slave device.
  • A wrapper around a Slave and additional state for richer behaviour.
  • Timeout configuration for the EtherCrab master.
  • A wrapped version of a Reads exposing a builder API used to send/receive data over the wire.
  • A wrapped version of a Writes exposing a builder API used to send/receive data over the wire.

Enums§

Traits§

Derive Macros§