Skip to content

All PDOs in Sync Manager Mapped to Domain Despite Single PDO Entry Registration

When registering a single PDO entry to a domain using ecrt_domain_reg_pdo_entry_list(), all PDOs assigned to the same sync manager are included in the domain’s process data image, contrary to the expectation that only the explicitly registered PDO entry should be mapped.

Steps to Reproduce

  1. Use the IgH EtherCAT Master (EtherLab) with a Beckhoff EL1809 slave (16-channel digital input, Vendor ID: 0x00000002, Product Code: 0x07113052).
  2. Configure the slave with 16 PDO entries (0x6000:0x01 to 0x60f0:0x01), each 1 bit, assigned to 16 PDOs (0x1a00 to 0x1a0f) in a single input sync manager (index 0).
  3. Register only one PDO entry (0x6000:0x01) to the domain using ecrt_domain_reg_pdo_entry_list().
  4. Activate the master with ecrt_master_activate().
  5. Inspect the domain’s process data image (via ecrt_domain_data()) or check the working counter/domain size.

Example Code

Below is a modified version of the EtherLab user example that reproduces the issue:

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h> /* clock_gettime() */
#include <sys/mman.h> /* mlockall() */
#include <sched.h> /* sched_setscheduler() */

/****************************************************************************/



#include "ecrt.h"

/****************************************************************************/



#define PERIOD_NS   (1000000)
#define MAX_SAFE_STACK (8 * 1024)
#define NSEC_PER_SEC (1000000000)
#define FREQUENCY (NSEC_PER_SEC / PERIOD_NS)

// EtherCAT


static ec_master_t *master = NULL;
static ec_master_state_t master_state = {};
static ec_domain_t *domain1 = NULL;
static ec_domain_state_t domain1_state = {};
static ec_slave_config_t *sc_slave_1 = NULL;
static ec_slave_config_state_t sc_slave_1_state = {};

// process data


static uint8_t *domain1_pd = NULL;

#define Slave1Pos  0, 1
#define Beckhoff_EL1809 0x00000002, 0x07113052

static const ec_pdo_entry_info_t slave_1_pdo_entries[] = {
    {0x6000, 0x01, 1}, /* Input */
    {0x6010, 0x01, 1}, /* Input */
    {0x6020, 0x01, 1}, /* Input */
    {0x6030, 0x01, 1}, /* Input */
    {0x6040, 0x01, 1}, /* Input */
    {0x6050, 0x01, 1}, /* Input */
    {0x6060, 0x01, 1}, /* Input */
    {0x6070, 0x01, 1}, /* Input */
    {0x6080, 0x01, 1}, /* Input */
    {0x6090, 0x01, 1}, /* Input */
    {0x60a0, 0x01, 1}, /* Input */
    {0x60b0, 0x01, 1}, /* Input */
    {0x60c0, 0x01, 1}, /* Input */
    {0x60d0, 0x01, 1}, /* Input */
    {0x60e0, 0x01, 1}, /* Input */
    {0x60f0, 0x01, 1}, /* Input */
};

static const ec_pdo_info_t slave_1_pdos[] = {
    {0x1a00, 1, slave_1_pdo_entries + 0}, /* Channel 1 */
    {0x1a01, 1, slave_1_pdo_entries + 1}, /* Channel 2 */
    {0x1a02, 1, slave_1_pdo_entries + 2}, /* Channel 3 */
    {0x1a03, 1, slave_1_pdo_entries + 3}, /* Channel 4 */
    {0x1a04, 1, slave_1_pdo_entries + 4}, /* Channel 5 */
    {0x1a05, 1, slave_1_pdo_entries + 5}, /* Channel 6 */
    {0x1a06, 1, slave_1_pdo_entries + 6}, /* Channel 7 */
    {0x1a07, 1, slave_1_pdo_entries + 7}, /* Channel 8 */
    {0x1a08, 1, slave_1_pdo_entries + 8}, /* Channel 9 */
    {0x1a09, 1, slave_1_pdo_entries + 9}, /* Channel 10 */
    {0x1a0a, 1, slave_1_pdo_entries + 10}, /* Channel 11 */
    {0x1a0b, 1, slave_1_pdo_entries + 11}, /* Channel 12 */
    {0x1a0c, 1, slave_1_pdo_entries + 12}, /* Channel 13 */
    {0x1a0d, 1, slave_1_pdo_entries + 13}, /* Channel 14 */
    {0x1a0e, 1, slave_1_pdo_entries + 14}, /* Channel 15 */
    {0x1a0f, 1, slave_1_pdo_entries + 15}, /* Channel 16 */
};

static const ec_sync_info_t slave_1_syncs[] = {
    {0, EC_DIR_INPUT, 16, slave_1_pdos + 0, EC_WD_DISABLE},
    {0xff}
};

static unsigned int offset1;
static unsigned int bit1;

const static ec_pdo_entry_reg_t domain1_regs[] = {
    {Slave1Pos, Beckhoff_EL1809, 0x6000, 1, &offset1, &bit1},
    {}
};

static unsigned int counter = 0;

void check_domain1_state(void)
{
    ec_domain_state_t ds;
    ecrt_domain_state(domain1, &ds);
    if (ds.working_counter != domain1_state.working_counter) {
        printf("Domain1: WC %u.\n", ds.working_counter);
    }
    if (ds.wc_state != domain1_state.wc_state) {
        printf("Domain1: State %u.\n", ds.wc_state);
    }
    domain1_state = ds;
}

void check_master_state(void)
{
    ec_master_state_t ms;
    ecrt_master_state(master, &ms);
    if (ms.slaves_responding != master_state.slaves_responding) {
        printf("%u slave(s).\n", ms.slaves_responding);
    }
    if (ms.al_states != master_state.al_states) {
        printf("AL states: 0x%02X.\n", ms.al_states);
    }
    if (ms.link_up != master_state.link_up) {
        printf("Link is %s.\n", ms.link_up ? "up" : "down");
    }
    master_state = ms;
}

void check_slave_config_states(void)
{
    ec_slave_config_state_t s;
    ecrt_slave_config_state(sc_slave_1, &s);
    if (s.al_state != sc_slave_1_state.al_state) {
        printf("Slave1: State 0x%02X.\n", s.al_state);
    }
    if (s.online != sc_slave_1_state.online) {
        printf("Slave1: %s.\n", s.online ? "online" : "offline");
    }
    if (s.operational != sc_slave_1_state.operational) {
        printf("Slave1: %soperational.\n", s.operational ? "" : "Not ");
    }
    sc_slave_1_state = s;
}

void cyclic_task()
{
    ecrt_master_receive(master);
    ecrt_domain_process(domain1);
    check_domain1_state();
    if (counter) {
        counter--;
    } else {
        counter = FREQUENCY;
        printf("Domain Data %i %i.\n", domain1_pd[0], domain1_pd[1]);
        check_master_state();
        check_slave_config_states();
    }
    ecrt_domain_queue(domain1);
    ecrt_master_send(master);
}

void stack_prefault(void)
{
    unsigned char dummy[MAX_SAFE_STACK];
    memset(dummy, 0, MAX_SAFE_STACK);
}

int main(int argc, char **argv)
{
    struct timespec wakeup_time;
    int ret = 0;

    master = ecrt_request_master(0);
    if (!master) {
        return -1;
    }

    domain1 = ecrt_master_create_domain(master);
    if (!domain1) {
        return -1;
    }

    if (!(sc_slave_1 = ecrt_master_slave_config(master, Slave1Pos, Beckhoff_EL1809))) {
        fprintf(stderr, "Failed to get slave configuration.\n");
        return -1;
    }

    printf("Configuring PDOs...\n");
    if (ecrt_slave_config_pdos(sc_slave_1, EC_END, slave_1_syncs)) {
        fprintf(stderr, "Failed to configure PDOs.\n");
        return -1;
    }

    if (ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs)) {
        fprintf(stderr, "PDO entry registration failed!\n");
        return -1;
    }

    printf("Activating master...\n");
    if (ecrt_master_activate(master)) {
        return -1;
    }

    if (!(domain1_pd = ecrt_domain_data(domain1))) {
        return -1;
    }

    struct sched_param param = {};
    param.sched_priority = sched_get_priority_max(SCHED_FIFO);
    printf("Using priority %i.\n", param.sched_priority);
    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler failed");
    }

    if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
        fprintf(stderr, "Warning: Failed to lock memory: %s\n", strerror(errno));
    }

    stack_prefault();

    printf("Starting RT task with dt=%u ns.\n", PERIOD_NS);

    clock_gettime(CLOCK_MONOTONIC, &wakeup_time);
    wakeup_time.tv_sec += 1;
    wakeup_time.tv_nsec = 0;

    while (1) {
        ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &wakeup_time, NULL);
        if (ret) {
            fprintf(stderr, "clock_nanosleep(): %s\n", strerror(ret));
            break;
        }

        cyclic_task();

        wakeup_time.tv_nsec += PERIOD_NS;
        while (wakeup_time.tv_nsec >= NSEC_PER_SEC) {
            wakeup_time.tv_nsec -= NSEC_PER_SEC;
            wakeup_time.tv_sec++;
        }
    }

    return ret;
}
Edited by Jannik Becher