linuxcnc-ethercat

Adding New Drivers to LinuxCNC-Ethercat

Before writing a new driver, consider opening a new issue in the issue tracker. Use a subject like “Add support for Fooco AB-15”, and then say that you’re intending to write it. Ideally include a link to the manufacturer’s website, and maybe a very short description of the hardware.

Writing Drivers

Drivers are written in C and live in src/devices/. Follow the naming scheme that already exists. Each driver needs to live in src/devices and include a types[] array that defines the specific devices supported by this driver:

static lcec_typelist_t types[] = {
    {"EL7041", LCEC_BECKHOFF_VID, 0x1B813052, 0, NULL, lcec_el7041_init},
    {"EL7041-1000", LCEC_BECKHOFF_VID, 0x1B813052, 0, NULL, lcec_el7041_init},
    {"EP7041", LCEC_BECKHOFF_VID, 0x1B813052, 0, NULL, lcec_el7041_init},
    {NULL},
};
ADD_TYPES(types);

The ADD_TYPES(types); line is mandatory; it does the behind-the-scenes work to make sure that the driver is linked in and available.

You shouldn’t have to edit any other files; the Makefile and all of the LinuxCNC-Ethercat support code should pick up your new driver automatically.

When a manufacturer makes several similar devices, try to produce a single driver that covers them all, or at least can be trivially extended to handle them in the future. See the lcec_el3xxx.c driver for one example of how to do this. If you need to distinguish between different types of devices within your code, then consider using the flags field in types[] (again, see lcec_el3xxx.c). This field is purely for driver use, so use it however works best for you.

See the PDOs and syncs doc for a discussion of the various ways of mapping PDO entries in LinuxCNC-Ethercat.

If your device forgets configuration that _init wrote via SDO or SII when it loses power (module lists, PDO assignment, feature bits), add a proc_reinit hook so LinuxCNC-Ethercat can re-apply it when the device returns to the bus. See Runtime slave re-initialization.

HAL pin and parameter access

LinuxCNC is migrating HAL to typed getter/setter accessors (upstream PR #4099; the old direct-dereference API disappears at the announced API break). LinuxCNC-Ethercat supports both APIs at once through the compatibility layer in src/lcec_hal_compat.h (already included via lcec.h). New driver code must:

See any converted driver (for example lcec_deasda.c, which uses both pin and param macros) for how this looks in practice.

Process data access

PDO offsets from lcec_pdo_init() index into the image returned by lcec_slave_pd(slave):

static void lcec_foo_read(lcec_slave_t *slave, long period) {
  lcec_foo_data_t *hal_data = (lcec_foo_data_t *)slave->hal_data;
  uint8_t *pd = lcec_slave_pd(slave);

  LCEC_PIN_U32_SET(hal_data->status, EC_READ_U16(&pd[hal_data->status_os]));
}

Do not use slave->master->process_data. A Sync Unit can run its drivers from its own HAL thread on a private copy of its domain, and only lcec_slave_pd() points at the right one. lcec logs an error for any slave in such a unit whose driver never calls it. The period passed to proc_read / proc_write is the slave’s Sync Unit cycle.

Runtime re-initialization

A slave that is power-cycled (or loses its cable) while LinuxCNC runs comes back through INIT with its volatile configuration gone: SII writes, and any SDO writes that were not registered with the master, must be re-applied or the slave reaches OP with stale config. runtime-reinit.md describes the mechanism; the driver-side rule is simple:

Every configuration write your driver makes must be reachable at runtime re-init. The review litmus test is “if this slave power-cycles mid-run, what breaks?” Preference order for any given write:

  1. Master-registered configuration (lcec_write_sdo* / ecrt_slave_config_* at _init time): the master owns it and replays it on every INIT->PREOP transition by itself. Nothing else to do; prefer this whenever possible.
  2. The proc_reinit typelist hook, for what the master cannot own: SII writes, dynamically computed values, or writes that need sequencing against other steps. When the slave returns, the master holds it in PREOP and lcec runs proc_reinit before releasing it (needs libethercat with EC_HAVE_REINIT_HOLD; without it the slave behaves as before and lcec logs a warning).
  3. Never: a direct write that exists only in _init.

The clean way to satisfy this is one apply_config()-style function holding every device-visible write, called from both _init and proc_reinit; _init additionally does the one-time HAL/pin/PDO registration. See lcec_leadshine_ec.c for a reference implementation.

Style points

Contributing Drivers

The best way to contribute a new driver is to sent a Github pull request with your new driver, along with any information needed to use the driver. A few things that would be good to add in the pull request: