### 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 ```