#ethercat #automation #fieldbus #beckhoff #soem

no-std ethercrab

An EtherCAT master in pure Rust that is no_std compatible

19 releases

0.5.0 Jul 28, 2024
0.4.4 Oct 3, 2024
0.4.3 Sep 26, 2024
0.4.2 May 27, 2024
0.1.0 Jan 2, 2023

#147 in Network programming

Download history 156/week @ 2024-07-01 62/week @ 2024-07-08 63/week @ 2024-07-15 126/week @ 2024-07-22 255/week @ 2024-07-29 47/week @ 2024-08-05 55/week @ 2024-08-12 15/week @ 2024-08-19 1/week @ 2024-09-02 3/week @ 2024-09-09 18/week @ 2024-09-16 171/week @ 2024-09-23 182/week @ 2024-09-30 38/week @ 2024-10-07 23/week @ 2024-10-14

415 downloads per month

MIT/Apache

745KB
15K SLoC

EtherCrab

Build Status Crates.io Docs.rs Matrix chat

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 SubDevices every tick. It is tested on an EK1100 with output modules but may work on other basic SubDevices.

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}, MainDevice, MainDeviceConfig, PduStorage, Timeouts
};
use std::{sync::Arc, time::Duration};
use tokio::time::MissedTickBehavior;

/// Maximum number of SubDevices that can be stored. This must be a power of 2 greater than 1.
const MAX_SUBDEVICES: 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 SubDevice, 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 maindevice = Arc::new(MainDevice::new(
        pdu_loop,
        Timeouts {
            wait_loop_delay: Duration::from_millis(2),
            mailbox_response: Duration::from_millis(1000),
            ..Default::default()
        },
        MainDeviceConfig::default(),
    ));

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

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

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

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

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

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

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

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

        log::info!(
            "-> SubDevice {:#06x} {} inputs: {} bytes, outputs: {} bytes",
            subdevice.configured_address(),
            subdevice.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(&maindevice).await.expect("TX/RX");

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

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

        tick_interval.tick().await;
    }
}

Community

We're on Matrix!

Current and future features

  • async API
  • Usable in no_std contexts with no allocator required, as long as an async executor is available.
  • Autoconfigure SubDevices from their EEPROM (SII) data during startup
    • Supports configuration using CoE data
  • Safely usable in multi-threaded Linux systems with e.g. smol, tokio or std::thread and block_on.
  • Support for io_uring on Linux systems to improve performance and latency
  • Support for SDO read/writes to configure SubDevices
  • Distributed clocks
    • Detection of delays between SubDevices in topology
    • Static drift compensation on startup
    • Cyclic synchronisation during OP
  • Basic support for CiA402/DS402 drives
    • A higher level DS402 API for torque, position and velocity control of common servo drives in a more abstract way.
  • Integration with LinuxCNC as a HAL component using the linuxcnc-hal crate.
  • Load SubDevice configurations from ESI XML files

Sponsors

GitHub Sponsors

Thank you to everyone who has donated test equipment, time or money to the EtherCrab project! Would you like to be in this list? Then please consider becoming a Github sponsor!

  • @nealsjoe generously donated an EK1100 with several IO modules for testing with.
  • Trisk Bio generously donated some additional Beckhoff modules and some optical ethernet gear.
  • Smark sent a $200 one time donation. Thank you!

License

Licensed under either of

at your option.

Dependencies

~9–21MB
~307K SLoC