### Install LinuxCNC EtherCAT via APT
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/README.html
Configures the repository, adds the signing key, and installs the driver and master packages.
```bash
# 1. Install the archive signing key
curl -fsSL https://linuxcnc-ethercat.github.io/apt/linuxcnc-ethercat-apt.gpg \
-o /usr/share/keyrings/linuxcnc-ethercat-apt.gpg
# 2. Detect the codename and add the apt source
. /etc/os-release
case "$VERSION_CODENAME" in
bullseye|bookworm|trixie) ;;
*) echo "Unsupported codename: $VERSION_CODENAME (supported: bullseye, bookworm, trixie)" >&2; exit 1 ;;
esac
echo "deb [signed-by=/usr/share/keyrings/linuxcnc-ethercat-apt.gpg] \
https://linuxcnc-ethercat.github.io/apt/ $VERSION_CODENAME main" \
> /etc/apt/sources.list.d/linuxcnc-ethercat.list
# 3. Install
apt update
apt install -y linux-headers-$(uname -r) ethercat-master linuxcnc-ethercat
```
--------------------------------
### Initialize CoE Command
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__conf__icmds_8c_source.html
Sets up the SDO configuration structure when starting a CoE command.
```c
static void icmdTypeCoeIcmdStart(LCEC_CONF_XML_INST_T *inst, int next, const char **attr) {
LCEC_CONF_ICMDS_STATE_T *state = (LCEC_CONF_ICMDS_STATE_T *)inst;
state->currSdoConf = ADD_OUTPUT_BUFFER(state->outputBuf, LCEC_CONF_SDOCONF_T);
if (state->currSdoConf == NULL) {
XML_StopParser(inst->parser, 0);
return;
}
state->currSdoConf->confType = lcecConfTypeSdoConfig;
state->currSdoConf->index = 0xffff;
state->currSdoConf->subindex = 0xff;
while (*attr) {
const char *name = *(attr++);
const char *val = *(attr++);
// parse CompleteAccess
if (strcmp(name, "CompleteAccess") == 0) {
if (atoi(val)) {
```
--------------------------------
### Implement XML start handler
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__conf__util_8c.html
Handles the start of an XML element by matching the current state and element name to trigger the appropriate handler.
```c
98 {
99 LCEC_CONF_XML_INST_T *inst = (LCEC_CONF_XML_INST_T *)data;
100 const LCEC_CONF_XML_HANLDER_T *state;
101
102 for (state = inst->states; state->el != NULL; state++) {
103 if (inst->state == state->state_from && (strcmp(el, state->el) == 0)) {
104 if (state->start_handler != NULL) {
105 state->start_handler(inst, state->state_to, attr);
106 }
107 inst->state = state->state_to;
108 return;
109 }
110 }
111
112 fprintf(stderr, "%s: ERROR: unexpected node %s found\n", modname, el);
113 XML_StopParser(inst->parser, 0);
114}
```
--------------------------------
### Define Test Setup and Assertion Macros
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/tests_8h_source.html
Macros for initializing test counters and performing assertions on integers, strings, and null pointers.
```c
#define TESTSETUP int pass = 0, fail = 0
#define TESTINT(call, want) \
do { \
int got; \
if (got = call, got != want) { \
fprintf(stderr, "fail at %s:%d: %s, got %d, want %d\n", __FILE__, __LINE__, #call, got, want); \
fail++; \
} else { \
pass++; \
} \
} while (0)
#define TESTSTRING(call, want) \
do { \
const char *got; \
if (got = call, strcmp(got, want)) { \
fprintf(stderr, "fail at %s:%d: %s, got \"%s\", want \"%s\"\n", __FILE__, __LINE__, #call, got, want); \
fail++; \
} else { \
pass++; \
} \
} while (0)
#define TESTNOTNULL(call) \
do { \
void *got; \
if (got = call, got == NULL) { \
fprintf(stderr, "fail at %s:%d: %s, got %p==NULL\n", __FILE__, __LINE__, #call, got); \
fail++; \
} else { \
pass++; \
} \
} while (0)
```
--------------------------------
### XML Parser Initialization and Handling
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__conf__util_8c_source.html
Functions to initialize an XML parser instance and handle start elements using the Expat library.
```c
int initXmlInst(LCEC_CONF_XML_INST_T *inst, const LCEC_CONF_XML_HANLDER_T *states) {
// create xml parser
inst->parser = XML_ParserCreate(NULL);
if (inst->parser == NULL) {
return 1;
}
// setup data
inst->states = states;
inst->state = 0;
XML_SetUserData(inst->parser, inst);
// setup handlers
XML_SetElementHandler(inst->parser, xml_start_handler, xml_end_handler);
return 0;
}
static void xml_start_handler(void *data, const char *el, const char **attr) {
LCEC_CONF_XML_INST_T *inst = (LCEC_CONF_XML_INST_T *)data;
const LCEC_CONF_XML_HANLDER_T *state;
for (state = inst->states; state->el != NULL; state++) {
if (inst->state == state->state_from && (strcmp(el, state->el) == 0)) {
if (state->start_handler != NULL) {
state->start_handler(inst, state->state_to, attr);
}
```
--------------------------------
### Setup Slave PDOs, DC, and Watchdog
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__main_8c_source.html
Configures process data objects, distributed clocks, and watchdog settings for an EtherCAT slave.
```c
slave->regs = lcec_allocate_pdo_entry_reg(LCEC_MAX_PDO_REG_COUNT);
if (slave->regs == NULL) {
rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "failure allocating PDO entries for slave %s.%s\n", master->name, slave->name);
goto fail2;
}
// setup pdos
if (slave->proc_init != NULL) {
rtapi_print_msg(RTAPI_MSG_DBG, LCEC_MSG_PFX "proc_init for slave %s.%s\n", master->name, slave->name);
if ((slave->proc_init(lcec_comp_id, slave)) != 0) {
rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "failure in proc_init for slave %s.%s\n", master->name, slave->name);
goto fail2;
}
}
// configure dc for this slave
if (slave->dc_conf != NULL) {
ecrt_slave_config_dc(slave->config, slave->dc_conf->assignActivate, slave->dc_conf->sync0Cycle, slave->dc_conf->sync0Shift,
slave->dc_conf->sync1Cycle, slave->dc_conf->sync1Shift);
rtapi_print_msg(RTAPI_MSG_DBG,
LCEC_MSG_PFX "configuring DC for slave %s.%s: assignActivate=x%x sync0Cycle=%d sync0Shift=%d sync1Cycle=%d sync1Shift=%d\n",
master->name, slave->name, slave->dc_conf->assignActivate, slave->dc_conf->sync0Cycle, slave->dc_conf->sync0Shift,
slave->dc_conf->sync1Cycle, slave->dc_conf->sync1Shift);
}
// Configure the slave's watchdog times.
if (slave->wd_conf != NULL) {
ecrt_slave_config_watchdog(slave->config, slave->wd_conf->divider, slave->wd_conf->intervals);
}
// configure slave
if (slave->sync_info != NULL) {
rtapi_print_msg(RTAPI_MSG_DBG, LCEC_MSG_PFX "sync_info setup for slave %s.%s\n", master->name, slave->name);
if (ecrt_slave_config_pdos(slave->config, EC_END, slave->sync_info)) {
rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s\n", master->name, slave->name);
goto fail2;
}
}
```
--------------------------------
### Configure EtherCAT Master XML
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/rtdrv.html
Example XML configuration for defining a DRV400E slave device within the EtherCAT master setup.
```xml
...
...
```
--------------------------------
### EL5032 Initialization Logic
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__el5032_8c_source.html
Initializes the slave device, allocates HAL memory, and sets up PDO callbacks.
```c
static int lcec_el5032_init(int comp_id, lcec_slave_t *slave) {
lcec_master_t *master = slave->master;
lcec_el5032_data_t *hal_data;
int i;
lcec_el5032_chan_t *chan;
int err;
// initialize callbacks
slave->proc_read = lcec_el5032_read;
// alloc hal memory
hal_data = LCEC_HAL_ALLOCATE(lcec_el5032_data_t);
slave->hal_data = hal_data;
// initialize sync info
slave->sync_info = lcec_el5032_syncs;
// initialize global data
hal_data->last_operational = 0;
// initialize pins
for (i = 0; i < LCEC_EL5032_CHANS; i++) {
chan = &hal_data->chans[i];
// initialize POD entries
lcec_pdo_init(slave, 0x6000 + (i << 4), 0x01, &chan->warn_os, &chan->warn_bp);
lcec_pdo_init(slave, 0x6000 + (i << 4), 0x02, &chan->error_os, &chan->error_bp);
lcec_pdo_init(slave, 0x6000 + (i << 4), 0x03, &chan->ready_os, &chan->ready_bp);
lcec_pdo_init(slave, 0x6000 + (i << 4), 0x0d, &chan->diag_os, &chan->diag_bp);
lcec_pdo_init(slave, 0x6000 + (i << 4), 0x0e, &chan->tx_state_os, &chan->tx_state_bp);
```
--------------------------------
### Configure RTelligent EtherCAT Slave in XML
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/rtec.html
Example XML configuration for an ECT60 slave device within the LinuxCNC EtherCAT master setup.
```xml
...
...
```
--------------------------------
### Generic EL70x1 Initialization Routine
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__el70x1_8c.html
Configures SDO parameters, allocates HAL memory, and initializes PDO mappings for EL70x1 devices.
```c
270 {
271 lcec_master_t *master = slave->master;
272 lcec_slave_modparam_t *p;
273 lcec_el70x1_data_t *hal_data;
274 int err;
275
276 // initialize callbacks
277 slave->proc_read = lcec_el70x1_read;
278 slave->proc_write = lcec_el70x1_write;
279
280 // set to position mode
281 if (lcec_write_sdo8(slave, 0x8012, 0x01, 3) != 0) {
282 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s sdo position mode\n", master->name, slave->name);
283 return -1;
284 }
285
286 // set config patameters
287 for (p = slave->modparams; p != NULL && p->id >= 0; p++) {
288 switch (p->id) {
289 case LCEC_EL70x1_PARAM_MAX_CURR:
290 if (lcec_write_sdo16(slave, 0x8010, 0x01, p->value.u32) != 0) {
291 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s sdo maxCurrent\n", master->name, slave->name);
292 return -1;
293 }
294 break;
295 case LCEC_EL70x1_PARAM_RED_CURR:
296 if (lcec_write_sdo16(slave, 0x8010, 0x02, p->value.u32) != 0) {
297 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s sdo redCurrent\n", master->name, slave->name);
298 return -1;
299 }
300 break;
301 case LCEC_EL70x1_PARAM_NOM_VOLT:
302 if (lcec_write_sdo16(slave, 0x8010, 0x03, p->value.u32) != 0) {
303 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s sdo nomVoltage\n", master->name, slave->name);
304 return -1;
305 }
306 break;
307 case LCEC_EL70x1_PARAM_COIL_RES:
308 if (lcec_write_sdo16(slave, 0x8010, 0x04, p->value.u32) != 0) {
309 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s sdo coilRes\n", master->name, slave->name);
310 return -1;
311 }
312 break;
313 case LCEC_EL70x1_PARAM_MOTOR_EMF:
314 if (lcec_write_sdo16(slave, 0x8010, 0x05, p->value.u32) != 0) {
315 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s sdo motorEMF\n", master->name, slave->name);
316 return -1;
317 }
318 break;
319 }
320 }
321
322 // alloc hal memory
323 hal_data = LCEC_HAL_ALLOCATE(lcec_el70x1_data_t);
324 slave->hal_data = hal_data;
325
326 // initialize POD entries
327 lcec_pdo_init(slave, 0x6010, 0x01, &hal_data->stm_ready_to_enable_pdo_os, &hal_data->stm_ready_to_enable_pdo_bp);
328 lcec_pdo_init(slave, 0x6010, 0x02, &hal_data->stm_ready_pdo_os, &hal_data->stm_ready_pdo_bp);
329 lcec_pdo_init(slave, 0x6010, 0x03, &hal_data->stm_warning_pdo_os, &hal_data->stm_warning_pdo_bp);
330 lcec_pdo_init(slave, 0x6010, 0x04, &hal_data->stm_error_pdo_os, &hal_data->stm_error_pdo_bp);
331 lcec_pdo_init(slave, 0x6010, 0x05, &hal_data->stm_move_pos_pdo_os, &hal_data->stm_move_pos_pdo_bp);
332 lcec_pdo_init(slave, 0x6010, 0x06, &hal_data->stm_move_neg_pdo_os, &hal_data->stm_move_neg_pdo_bp);
333 lcec_pdo_init(slave, 0x6010, 0x07, &hal_data->stm_torque_reduced_pdo_os, &hal_data->stm_torque_reduced_pdo_bp);
334 lcec_pdo_init(slave, 0x6010, 0x0c, &hal_data->stm_din1_pdo_os, &hal_data->stm_din1_pdo_bp);
335 lcec_pdo_init(slave, 0x6010, 0x0d, &hal_data->stm_din2_pdo_os, &hal_data->stm_din2_pdo_bp);
336 lcec_pdo_init(slave, 0x6010, 0x0e, &hal_data->stm_sync_err_pdo_os, &hal_data->stm_sync_err_pdo_bp);
337 lcec_pdo_init(slave, 0x6010, 0x10, &hal_data->stm_tx_toggle_pdo_os, &hal_data->stm_tx_toggle_pdo_bp);
338
339 lcec_pdo_init(slave, 0x7010, 0x01, &hal_data->stm_ena_pdo_os, &hal_data->stm_ena_pdo_bp);
340 lcec_pdo_init(slave, 0x7010, 0x02, &hal_data->stm_reset_pdo_os, &hal_data->stm_reset_pdo_bp);
341 lcec_pdo_init(slave, 0x7010, 0x03, &hal_data->stm_reduce_torque_pdo_os, &hal_data->stm_reduce_torque_pdo_bp);
342
343 lcec_pdo_init(slave, 0x7010, 0x11, &hal_data->stm_pos_raw_pdo_os, NULL);
344
345 // export pins
346 if ((err = lcec_pin_newf_list(hal_data, slave_pins, LCEC_MODULE_NAME, master->name, slave->name)) != 0) {
347 return err;
348 }
349
350 // export pins
351 if ((err = lcec_param_newf_list(hal_data, slave_params, LCEC_MODULE_NAME, master->name, slave->name)) != 0) {
352 return err;
353 }
354
355 // initialize variables
356 hal_data->stm_pos_scale = 1.0;
```
--------------------------------
### Commit Message Format Examples
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/CONTRIBUTING.html
Short examples of valid commit message prefixes and subjects.
```text
* `build: Add missing dependency`
* `ci: fix Github workflow action`
* `docs: add instructions for installing on Raspberry Pi 3s`
* `style: fix C formatting in el2xxx.c`
* `fix(ep2316): add missing PDO`
* `feat(el7941): add new module`
```
--------------------------------
### Implement initialization logic
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__stmds5k_8c_source.html
Initializes the slave, allocates HAL memory, and reads initial SDO parameters from the drive.
```c
static int lcec_stmds5k_init(int comp_id, lcec_slave_t *slave) {
lcec_master_t *master = slave->master;
lcec_stmds5k_data_t *hal_data;
int err;
uint8_t sdo_buf[4];
double sdo_torque_reference;
double sdo_speed_max_rpm;
double sdo_speed_max_rpm_sp;
LCEC_CONF_MODPARAM_VAL_T *pval;
int enc_bits;
const lcec_stmds5k_extenc_conf_t *extenc_conf;
// get encoder bits
pval = lcec_modparam_get(slave, LCEC_STMDS5K_PARAM_MULTITURN);
enc_bits = (pval != NULL && pval->bit) ? 32 : 24;
// get extenc config
extenc_conf = NULL;
pval = lcec_modparam_get(slave, LCEC_STMDS5K_PARAM_EXTENC);
if (pval != NULL) {
extenc_conf = lcec_stmds5k_get_extenc_conf(pval->u32);
}
// initialize callbacks
slave->proc_read = lcec_stmds5k_read;
slave->proc_write = lcec_stmds5k_write;
// alloc hal memory
hal_data = LCEC_HAL_ALLOCATE(lcec_stmds5k_data_t);
slave->hal_data = hal_data;
// read sdos
// B18 : torque reference
if (lcec_read_sdo(slave, 0x2212, 0x00, sdo_buf, 4)) {
return -EIO;
}
sdo_torque_reference = (double)EC_READ_S32(sdo_buf) * STMDS5K_TORQUE_REF_DIV;
// C01 : max rpm
if (lcec_read_sdo(slave, 0x2401, 0x00, sdo_buf, 4)) {
return -EIO;
}
sdo_speed_max_rpm = (double)EC_READ_S32(sdo_buf);
// D02 : setpoint max rpm
if (lcec_read_sdo(slave, 0x2602, 0x00, sdo_buf, 4)) {
return -EIO;
}
sdo_speed_max_rpm_sp = (double)EC_READ_S32(sdo_buf);
if (sdo_speed_max_rpm_sp > 1e-20 || sdo_speed_max_rpm_sp < -1e-20) {
hal_data->speed_max_rpm_sp_rcpt = 1.0 / sdo_speed_max_rpm_sp;
} else {
hal_data->speed_max_rpm_sp_rcpt = 0.0;
}
```
--------------------------------
### Install the CiA 402 HAL component
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/cia402.html
Command to compile and install the CiA 402 HAL component for LinuxCNC.
```bash
sudo halcompile --install hal-cia402.comp
```
--------------------------------
### Initialize LinuxCNC-Ethercat
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__main_8c.html
Main entry point that initializes HAL, parses configuration, and sets up EtherCAT masters and slaves.
```c
126 {
127 int slave_count;
128 lcec_master_t *master;
129 lcec_slave_t *slave;
130 char name[HAL_NAME_LEN + 1];
131 lcec_slave_sdoconf_t *sdo_config;
132 lcec_slave_idnconf_t *idn_config;
133 int pdo_entry_count = 0;
134
135#ifndef __KERNEL
136 struct sigaction handler;
137 sigemptyset(&handler.sa_mask);
138 handler.sa_flags = SA_NODEFER | SA_RESETHAND;
139 handler.sa_handler = sigsegv_handler;
140
141 sigaction(SIGSEGV, &handler, NULL);
142 sigaction(SIGBUS, &handler, NULL);
143 sigaction(SIGFPE, &handler, NULL);
144 sigaction(SIGKILL, &handler, NULL);
145#endif
146
147 // connect to the HAL
148 if ((lcec_comp_id = hal_init(LCEC_MODULE_NAME)) < 0) {
149 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "hal_init() failed\n");
150 goto fail0;
151 }
152
153 /* Probe HAL initf support. New linuxcnc => deferred RT-context activation
154 via lcec.activate funct (clean DC phasing). Old linuxcnc => fall back to
155 legacy inline activation here in rtapi_app_main, with PLL trim covering
156 the dirty start. */
157 initf_supported = (&hal_init_funct_to_thread != NULL);
158 if (!initf_supported) {
159 rtapi_print_msg(RTAPI_MSG_WARN, LCEC_MSG_PFX
160 "linuxcnc lacks initf support; using legacy inline activation. "
161 "DC phasing will trim via PLL. Upgrade linuxcnc for clean activation.\n");
162 }
163
164 // parse configuration
165 if ((slave_count = lcec_parse_config()) < 0) {
166 goto fail1;
167 }
168
169 // init global hal data
170 if ((global_hal_data = lcec_init_master_hal(LCEC_MODULE_NAME, 1)) == NULL) {
171 goto fail2;
172 }
173
174 // initialize masters
175 for (master = first_master; master != NULL; master = master->next) {
176 // request ethercat master
177 if (!(master->master = ecrt_request_master(master->index))) {
178 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "requesting master %s (index %d) failed\n", master->name, master->index);
179 goto fail2;
180 }
181
182#ifdef __KERNEL__
183 // register callbacks
184 ecrt_master_callbacks(master->master, lcec_request_lock, lcec_release_lock, master);
185#endif
186
187 // create domain
188 if (!(master->domain = ecrt_master_create_domain(master->master))) {
189 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "master %s domain creation failed\n", master->name);
190 goto fail2;
191 }
192
193 // initialize slaves
194 for (slave = master->first_slave; slave != NULL; slave = slave->next) {
195 // read slave config
196
197 rtapi_print_msg(RTAPI_MSG_DBG, LCEC_MSG_PFX "calling ecrt_master_slave_config for slave %s.%s\n", master->name, slave->name);
198 if (!(slave->config = ecrt_master_slave_config(master->master, 0, slave->index, slave->vid, slave->pid))) {
199 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to read slave %s.%s configuration\n", master->name, slave->name);
200 goto fail2;
201 }
202
203 // initialize sdos
204 if (slave->sdo_config != NULL) {
205 for (sdo_config = slave->sdo_config; sdo_config->index != 0xffff;
206 sdo_config = (lcec_slave_sdoconf_t *)&sdo_config->data[sdo_config->length]) {
207 if (sdo_config->subindex == LCEC_CONF_SDO_COMPLETE_SUBIDX) {
208 if (ecrt_slave_config_complete_sdo(slave->config, sdo_config->index, &sdo_config->data[0], sdo_config->length) != 0) {
209 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "failed to configure slave %s.%s sdo %04x (complete)\n", master->name,
210 slave->name, sdo_config->index);
211 }
212 } else {
213 if (lcec_write_sdo(slave, sdo_config->index, sdo_config->subindex, &sdo_config->data[0], sdo_config->length) != 0) {
214 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "failed to configure slave %s.%s sdo %04x:%02x\n", master->name, slave->name,
215 sdo_config->index, sdo_config->subindex);
216 }
217 }
218 }
219 }
220
221 // initialize idns
222 if (slave->idn_config != NULL) {
223 for (idn_config = slave->idn_config; idn_config->state != 0;
224 idn_config = (lcec_slave_idnconf_t *)&idn_config->data[idn_config->length]) {
225 if (ecrt_slave_config_idn(
226 slave->config, idn_config->drive, idn_config->idn, idn_config->state, &idn_config->data[0], idn_config->length) != 0) {
227 rtapi_print_msg(RTAPI_MSG_ERR, LCEC_MSG_PFX "fail to configure slave %s.%s drive %d idn %c-%d-%d (state %d, length %u)\n",
```
--------------------------------
### Main Initialization Routine
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__conf_8c_source.html
Initializes the HAL component, registers pins, sets up signal handling, and parses the configuration file.
```c
107 int main(int argc, char **argv) {
108 int ret = 1;
109 char *filename;
110 int done;
111 char buffer[BUFFSIZE];
112 FILE *file;
113 LCEC_CONF_NULL_T *end;
114 char *shmem_ptr;
115 LCEC_CONF_HEADER_T *header;
116 uint64_t u;
117 LCEC_CONF_XML_STATE_T state;
118
119 // initialize component
120 hal_comp_id = hal_init(modname);
121 if (hal_comp_id < 1) {
122 fprintf(stderr, "%s: ERROR: hal_init failed\n", modname);
123 goto fail0;
124 }
125
126 // allocate hal memory
127 conf_hal_data = LCEC_HAL_ALLOCATE(LCEC_CONF_HAL_T);
128 if (conf_hal_data == NULL) {
129 fprintf(stderr, "%s: ERROR: unable to allocate HAL shared memory\n", modname);
130 goto fail1;
131 }
132
133 // register pins
134 if (hal_pin_u32_newf(HAL_OUT, &(conf_hal_data->master_count), hal_comp_id, "%s.conf.master-count", LCEC_MODULE_NAME) != 0) {
135 fprintf(stderr, "%s: ERROR: unable to register pin %s.conf.master-count\n", modname, LCEC_MODULE_NAME);
136 goto fail1;
137 }
138 if (hal_pin_u32_newf(HAL_OUT, &(conf_hal_data->slave_count), hal_comp_id, "%s.conf.slave-count", LCEC_MODULE_NAME) != 0) {
139 fprintf(stderr, "%s: ERROR: unable to register pin %s.conf.slave-count\n", modname, LCEC_MODULE_NAME);
140 goto fail1;
141 }
142 *(conf_hal_data->master_count) = 0;
143 *(conf_hal_data->slave_count) = 0;
144
145 // initialize signal handling
146 exitEvent = eventfd(0, 0);
147 if (exitEvent == -1) {
148 fprintf(stderr, "%s: ERROR: unable to create exit event\n", modname);
149 goto fail1;
150 }
151 signal(SIGINT, exitHandler);
152 signal(SIGTERM, exitHandler);
153
154 // get config file name
155 if (argc != 2) {
156 fprintf(stderr, "%s: ERROR: invalid arguments\n", modname);
157 goto fail2;
158 }
159 filename = argv[1];
160
161 // open file
162 file = fopen(filename, "r");
163 if (file == NULL) {
164 fprintf(stderr, "%s: ERROR: unable to open config file %s\n", modname, filename);
165 goto fail2;
166 }
167
168 // create xml parser
169 memset(&state, 0, sizeof(state));
170 if (initXmlInst((LCEC_CONF_XML_INST_T *)&state, xml_states)) {
171 fprintf(stderr, "%s: ERROR: Couldn't allocate memory for parser\n", modname);
172 goto fail3;
173 }
174
175 initOutputBuffer(&state.outputBuf);
176 for (done = 0; !done;) {
177 // read block
178 int len = fread(buffer, 1, BUFFSIZE, file);
179 if (ferror(file)) {
180 fprintf(stderr, "%s: ERROR: Couldn't read from file %s\n", modname, filename);
181 goto fail4;
182 }
183
184 // check for EOF
185 done = feof(file);
186
187 // parse current block
188 if (!XML_Parse(state.xml.parser, buffer, len, done)) {
189 fprintf(stderr, "%s: ERROR: Parse error at line %u: %s\n", modname, (unsigned int)XML_GetCurrentLineNumber(state.xml.parser),
190 XML_ErrorString(XML_GetErrorCode(state.xml.parser)));
191 goto fail4;
192 }
193 }
194
195 // set end marker
196 end = ADD_OUTPUT_BUFFER(&state.outputBuf, LCEC_CONF_NULL_T);
197 if (end == NULL) {
198 goto fail4;
199 }
200 end->confType = lcecConfTypeNone;
201
202 // setup shared mem for config
203 shmem_id = rtapi_shmem_new(LCEC_CONF_SHMEM_KEY, hal_comp_id, sizeof(LCEC_CONF_HEADER_T) + state.outputBuf.len);
204 if (shmem_id < 0) {
205 fprintf(stderr, "%s: ERROR: couldn't allocate user/RT shared memory\n", modname);
206 goto fail4;
207 }
208 if (lcec_rtapi_shmem_getptr(shmem_id, (void **)&shmem_ptr) < 0) {
209 fprintf(stderr, "%s: ERROR: couldn't map user/RT shared memory\n", modname);
210 goto fail5;
211 }
212
213 // setup header
214 header = (LCEC_CONF_HEADER_T *)shmem_ptr;
215 shmem_ptr += sizeof(LCEC_CONF_HEADER_T);
216 header->magic = LCEC_CONF_SHMEM_MAGIC;
```
--------------------------------
### int lcec_cia402_add_input_sync(lcec_slave_t *slave, lcec_syncs_t *syncs, lcec_class_cia402_options_t *options)
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__class__cia402_8h.html
Sets up the first batch of input PDOs for syncing. This should be called after all output PDOs have been configured.
```APIDOC
## int lcec_cia402_add_input_sync(lcec_slave_t *slave, lcec_syncs_t *syncs, lcec_class_cia402_options_t *options)
### Description
Sets up the first batch of input PDOs for syncing. This function should be called after all output PDOs are configured. Once this returns, the caller should proceed to call `lcec_syncs_add_pdo_info` and `lcec_syncs_add_pdo_entry` as required.
### Parameters
- **slave** (lcec_slave_t *) - Pointer to the slave structure.
- **syncs** (lcec_syncs_t *) - Pointer to the sync manager structure.
- **options** (lcec_class_cia402_options_t *) - Pointer to the CiA 402 configuration options.
### Returns
- **int** - Returns 0 on success, or -1 if the number of configured input PDO entries exceeds the hardware limit defined in `options->txpdolimit`.
```
--------------------------------
### Get ModParam Value
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec_8h_source.html
Retrieves an XML modParam value for a specified slave.
```c
LCEC_CONF_MODPARAM_VAL_T * lcec_modparam_get(lcec_slave_t *slave, int id) __attribute__((nonnull))
```
--------------------------------
### Configure Master DC Sync
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/distributed-clocks.html
Example of the master tag configuration for DC synchronization.
```xml
....
```
--------------------------------
### Main entry point for lcec_configgen
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__configgen_8c.html
This function handles command-line argument parsing, iterates through detected slaves to build master configurations, and prints the final XML structure.
```c
1093 {
1094 // Parse Go-style "-flag=value" / "-flag" args (matches existing tool's CLI).
1095 for (int i = 1; i < argc; i++) {
1096 char *a = argv[i];
1097 if (a[0] != '-') {
1098 usage(argv[0]);
1099 return 1;
1100 }
1101 a++;
1102 if (a[0] == '-') a++; // accept --flag too
1103 char *eq = strchr(a, '=');
1104 char *val = NULL;
1105 if (eq) {
1106 *eq = 0;
1107 val = eq + 1;
1108 }
1109 if (!strcmp(a, "typedb"))
1110 flag_typedb = parse_bool(val);
1111 else if (!strcmp(a, "extra_cia_modparams"))
1112 flag_extra_cia_modparams = parse_bool(val);
1113 else if (!strcmp(a, "generic_pdos"))
1114 flag_generic_pdos = parse_bool(val);
1115 else if (!strcmp(a, "h") || !strcmp(a, "help")) {
1116 usage(argv[0]);
1117 return 0;
1118 } else {
1119 fprintf(stderr, "%s: unknown flag -%s\n", argv[0], a);
1120 return 1;
1121 }
1122 }
1123
1124 compile_all_regexes();
1125 read_slaves();
1126
1127 master_config_t **masters = NULL;
1128 size_t mc = 0, mcap = 0;
1129 int dev_seq = 0;
1130
1131 for (size_t i = 0; i < slaves_count; i++) {
1132 ec_slave_t *s = &slaves_arr[i];
1133 master_config_t *mm = find_master(masters, mc, s->master);
1134 if (!mm) {
1135 if (mc == mcap) {
1136 mcap = mcap ? mcap * 2 : 4;
1137 masters = realloc(masters, mcap * sizeof(*masters));
1138 }
1139 mm = xmalloc(sizeof(*mm));
1140 mm->idx = xstrdup(s->master);
1141 masters[mc++] = mm;
1142 }
1143
1144 read_sdos(s);
1145
1146 slave_config_t *sc = xmalloc(sizeof(*sc));
1147 sc->idx = xstrdup(s->slave);
1148 sc->type = xstrdup(infer_type(s));
1149 dev_seq++;
1150 sc->name = xasprintf("D%d", dev_seq);
1151
1152 config_modparams_from_driver(s, sc);
1153
1154 if (!strcmp(sc->type, "basic_cia402") || (flag_extra_cia_modparams && is_cia402(s))) {
1155 cia_enable_modparams(s, sc);
1156 }
1157
1158 if (!strcmp(sc->type, "generic") || !strcmp(sc->type, "basic_cia402")) {
1159 sc->vid = xstrdup(s->vendor_id);
1160 sc->pid = xstrdup(s->product_id);
1161 sc->comment = xstrdup(s->device_name);
1162 } else {
1163 sc->vid = xstrdup("");
1164 sc->pid = xstrdup("");
1165 sc->comment = xstrdup("");
1166 }
1167
1168 if (!strcmp(sc->type, "generic") && flag_generic_pdos) {
1169 build_pdos(s, sc);
1170 }
1171
1172 fixup_pin_names(sc);
1173
1174 if (mm->slave_count == mm->slave_cap) {
1175 mm->slave_cap = mm->slave_cap ? mm->slave_cap * 2 : 4;
1176 mm->slaves = realloc(mm->slaves, mm->slave_cap * sizeof(*mm->slaves));
1177 }
1178 mm->slaves[mm->slave_count++] = sc;
1179 }
1180
1181 // Sort masters by idx for deterministic output.
1182 qsort(masters, mc, sizeof(*masters), master_cmp);
1183
1184 printf("\n");
1185 for (size_t i = 0; i < mc; i++) {
1186 printf(" \n", masters[i]->idx);
1187 for (size_t j = 0; j < masters[i]->slave_count; j++) emit_slave(stdout, masters[i]->slaves[j]);
1188 printf(" \n");
1189 }
1190 printf("\n");
1191 return 0;
1192}
```
--------------------------------
### int lcec_cia402_add_output_sync(lcec_slave_t *slave, lcec_syncs_t *syncs, lcec_class_cia402_options_t *options)
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__class__cia402_8h.html
Sets up the first batch of output PDOs for syncing. This should be called after lcec_cia402_init_sync(), but before registering any device-specific PDOs.
```APIDOC
## int lcec_cia402_add_output_sync(lcec_slave_t *slave, lcec_syncs_t *syncs, lcec_class_cia402_options_t *options)
### Description
Sets up the first batch of output PDOs for syncing. This function must be called after `lcec_cia402_init_sync()` and before registering any device-specific PDOs.
### Parameters
- **slave** (lcec_slave_t *) - The slave device structure.
- **syncs** (lcec_syncs_t *) - The synchronization structure.
- **options** (lcec_class_cia402_options_t *) - Configuration options for the CiA 402 slave.
### Returns
- **int** - Returns 0 on success, or -1 if the number of configured output PDO entries exceeds the hardware limit.
```
--------------------------------
### Generic Slave Initialization
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec__generic_8c_source.html
Initializes the slave, sets up callbacks, and exports HAL pins based on PDO configuration.
```c
int lcec_generic_init(int comp_id, lcec_slave_t *slave) {
lcec_master_t *master = slave->master;
lcec_generic_pin_t *hal_data = (lcec_generic_pin_t *)slave->hal_data;
int i, j;
int err;
// initialize callbacks
slave->proc_read = lcec_generic_read;
slave->proc_write = lcec_generic_write;
// initialize pins
for (i = 0; i < slave->generic_pdo_entry_count; i++, hal_data++) {
// PDO mapping
lcec_pdo_init(slave, hal_data->pdo_idx, hal_data->pdo_sidx, &hal_data->pdo_os, &hal_data->pdo_bp);
switch (hal_data->type) {
case HAL_BIT:
if (hal_data->bitLength == 1) {
// single bit pin
err = lcec_pin_newf(
hal_data->type, hal_data->dir, &hal_data->pin[0], "%s.%s.%s.%s", LCEC_MODULE_NAME, master->name, slave->name, hal_data->name);
if (err != 0) {
return err;
}
} else {
// bit pin array
for (j = 0; j < LCEC_CONF_GENERIC_MAX_SUBPINS && j < hal_data->bitLength; j++) {
err = lcec_pin_newf(hal_data->type, hal_data->dir, &hal_data->pin[j], "%s.%s.%s.%s-%d", LCEC_MODULE_NAME, master->name,
slave->name, hal_data->name, j);
if (err != 0) {
return err;
}
}
}
break;
case HAL_S32:
case HAL_U32:
// check data size
if (hal_data->bitLength > 32) {
rtapi_print_msg(RTAPI_MSG_WARN, LCEC_MSG_PFX "unable to export pin %s.%s.%s.%s: invalid process data bitlen!\n", LCEC_MODULE_NAME,
master->name, slave->name, hal_data->name);
continue;
}
// export pin
err = lcec_pin_newf(
hal_data->type, hal_data->dir, &hal_data->pin[0], "%s.%s.%s.%s", LCEC_MODULE_NAME, master->name, slave->name, hal_data->name);
if (err != 0) {
return err;
}
break;
case HAL_FLOAT:
// check data size
if ((hal_data->bitLength > 32) && (hal_data->subType != lcecPdoEntTypeFloatDoubleIeee)) {
rtapi_print_msg(RTAPI_MSG_WARN, LCEC_MSG_PFX "unable to export pin %s.%s.%s.%s: invalid process data bitlen!\n", LCEC_MODULE_NAME,
master->name, slave->name, hal_data->name);
continue;
}
// export pin
err = lcec_pin_newf(
hal_data->type, hal_data->dir, &hal_data->pin[0], "%s.%s.%s.%s", LCEC_MODULE_NAME, master->name, slave->name, hal_data->name);
if (err != 0) {
return err;
}
break;
default:
rtapi_print_msg(RTAPI_MSG_WARN, LCEC_MSG_PFX "unsupported pin type %d!\n", hal_data->type);
}
}
return 0;
}
```
--------------------------------
### Get PDO entry count
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/doxygen/lcec_8h.html
Returns the current number of entries registered in a lcec_pdo_entry_reg_t structure.
```c
677{ return reg->current; }
```
--------------------------------
### Sample Temperature and Resistance Values
Source: https://linuxcnc-ethercat.github.io/linuxcnc-ethercat/el3xxx.html
Example output values for different sensor configurations on an EL3204 module.
```text
8 float OUT 20.84 lcec.0.D16.temp-0-temperature
8 float OUT 987.125 lcec.0.D16.temp-1-temperature
8 float OUT 19.7 lcec.0.D16.temp-2-temperature
8 float OUT 995.0469 lcec.0.D16.temp-3-temperature
```