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.
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.
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:
lcec_pin_newf() / lcec_pin_newf_list() and
params with lcec_param_newf() / lcec_param_newf_list(). Never
call hal_pin_*_new*() or hal_param_*_new*() directly.LCEC_PIN_* macros
(LCEC_PIN_U32_SET(), LCEC_PIN_BIT_GET(), or the type-dispatching
LCEC_PIN_SET() / LCEC_PIN_GET()). Never dereference a pin
pointer.lcec_param_*_t typedefs
(lcec_param_s32_t, etc.) and access them only through the
LCEC_PARAM_* macros.hal_u32_t *, etc.); the compat layer handles the opaque new-API
reference types internally.See any converted driver (for example
lcec_deasda.c, which uses both pin
and param macros) for how this looks in practice.
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.
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:
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.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)._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.
clang-format on your code. There’s a default
format specifier in the tree. Feel free to argue
about better defaults in a new issue on Github.static whenever possible. Most drivers
don’t need to export anything into the global C namespace for other
code to link against.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:
documentation/ directory that explains how to
use the new driver, including any parameters or configuration
needed. Some devices are so trivial that this doesn’t matter
(digital in/out boards, for example), while others are probably
unusable without documentation.documentation/devices/ that say which
devices the driver supports. These can be created automatically by
scripts/update-devicelist.sh, but you’ll want to edit the
resulting file(s). Please include your Github and/or forum handles
so people can contact you if they have issues or questions in the
future.