Hello,

I'm working on a proof of concept application using the IgH EtherCAT master however I'm having some problems. Specifically I'm seeing unmatched and skipped datagram packet warnings coming out of the kernel module:
====================================================
Dec 11 13:26:31 lyot kernel: [878188.716201] EtherCAT WARNING: Datagram ffff88032e383558 (domain0-0-main) was SKIPPED 21 times. Dec 11 13:26:31 lyot kernel: [878189.443671] EtherCAT WARNING 0: 20 datagrams UNMATCHED! Dec 11 13:26:32 lyot kernel: [878189.769889] EtherCAT WARNING: Datagram ffff88032e383558 (domain0-0-main) was SKIPPED 21 times. Dec 11 13:26:32 lyot kernel: [878190.444575] EtherCAT WARNING 0: 20 datagrams UNMATCHED!
====================================================
Based on the mailing list and the documentation PDF I believe that the problem is related to attempting to run the cyclic task too quickly. I'm running the cyclic task in userland at ~20Hz (50ms delays) on Ubuntu 14.04 with the 3.16.0-55 kernel. The master is relying on the generic NIC driver to control a 4 port Intel 85271EB PCIe card. Even slowing way down to 5 seconds I still see the occasional "SKIPPED 1 times" message. The manual suggests that the theoretical cyclic rate should be many orders of magnitude faster. I realize that the theoretical rate is theoretical, but I would have hoped a cyclic task of 20Hz~200Hz to be achievable with the generic NIC driver.

In short my application (after setting up the master and domain) is doing:

   ecrt_master_receive
   ecrt_domain_process
   // read process data
   // calculations
   // write process data
   ecrt_domain_queue
   ecrt_master_send


So my question are:
Has anyone else seen similar problems with unmatched/skipped datagrams even at very low cyclic task rates? What sort of frequencies should I expect on a non rt-kernel, with a userland app, and the generic NIC driver? Does anything look obviously wrong with my attached code? (Be gentle I'm primarily a non real time Java programmer).

In case it helps my end goal is to replace the discontinued Spectracom TSync PCIe PTP DIO card with a 'block' of Beckhoff modules that provide the same input timestamping, and output match time functionality. I'm currently working with an EK1100 (EtherCat coupler), EL1252 (24v input w/time stamp), EL2252 (24v output w/time stamp), and an EL6688 (PTP module). When all done everything will be controlled by a userland app running on a CentOS 7 system with standard RHEL/CentOS 7 kernel. At this point I'm still just trying to familiarize myself with the API and get a basic proof of concept application running.

I have been partly successful reading the input states, and rising/falling latch times from the EL1252. I have not yet had success with the output module EL2252. I'll put out another email asking for help with that one. I haven't even tried to work with the EL6688 module.


Thanks in advance


--
-john

To be or not to be, that is the question
                2b || !2b
(0b10)*(0b1100010) || !(0b10)*(0b1100010)
        0b11000100 || !0b11000100
        0b11000100 ||  0b00111011
               0b11111111
255, that is the answer.

#include <iostream>
#include <chrono>
#include <thread>

#include "EtherCat.h"
#include "Timer.h"

#define BECKHOFF 0x00000002
      
EtherCat::EtherCat(const unsigned short& masterNum) {
  master = ecrt_request_master(masterNum);
  if (NULL == master) {
    std::cerr << "ERROR:\tFailed to obtain master" << std::endl;
    exit(-1);
  } else {
    std::cout << "OK:\tMaster obtained" << std::endl;
  }

  domain = ecrt_master_create_domain(master);
  if (NULL == domain) {
    std::cerr << "ERROR:\tFailed to obtain domain" << std::endl;
    exit(-1);
  } else {
    std::cout << "OK:\tDomain obtained" << std::endl;
  }
}

EtherCat::~EtherCat() {
}

ec_master_t* EtherCat::getMaster() {
  return master;
}

ec_domain_t* EtherCat::getDomain() {
  return domain;
}

void EtherCat::start(void (*f)(EtherCat* ec), const unsigned int& micros) {
  unsigned int error = ecrt_master_activate(master);
  if (error) {
    std::cerr << "ERROR:\tFailed to activate master" << std::endl;
    exit(-1);
  } else std::cout << "OK:\tMaster activated" << std::endl;

  auto delayTime = std::chrono::microseconds(micros);
  auto nextTick = std::chrono::high_resolution_clock::now() + delayTime;

  Timer timer = Timer();
  while (!done) {
    timer.tick();
    //auto taskStartTime = std::chrono::high_resolution_clock::now();
    ecrt_master_receive(getMaster());
    ecrt_domain_process(getDomain());
    uint8_t* pd = getProcessData();
    for (auto &slaveEntry: slaves) 
     slaveEntry.second->readProcessData(pd);
     
    f(this);
    for (auto &slaveEntry: slaves) 
     slaveEntry.second->writeProcessData(pd);

    ecrt_domain_queue(getDomain());
    ecrt_master_send(getMaster());

    auto now = std::chrono::high_resolution_clock::now();
    auto sleepTime = nextTick - now;
    //std::this_thread::sleep_for(sleepTime);
    std::this_thread::sleep_for(delayTime);
    //timer.printStats();
  }

  ecrt_master_deactivate(master);
  std::cout << "OK:\tMaster deactivated" << std::endl;
}

void EtherCat::stop() {
  done = true;
} 

void EtherCat::configureSlave(const std::string& name, 
    EtherCatSlave& slave) {
  std::cout << "DEBUG:\tPreparing to configure slave '" << name;
  auto vid = slave.getVendorId();
  auto pid = slave.getProductId();
  auto pos = slave.getPositionId();
  std::cout << "' in position " << pos << std::endl;
  ec_slave_config_t* cfg = ecrt_master_slave_config(master, 0, pos, vid, pid);
  if (NULL == cfg) {
    std::cerr << "ERROR:\tFailed to obtain slave config " << name << std::endl;
    exit(-1);
  } else {
    std::cout << "OK:\tConfig obtained for slave '" << name << "'" << std::endl;
    slaveCfgs[name] = cfg;
  }

  {
    ec_slave_config_state_t state;
    ecrt_slave_config_state(cfg, &state);
    std::string opState;
    switch (state.al_state) {
      case 1: opState = "INITIALISED";
      case 2: opState = "PRE-OPERATIONAL";
      case 4: opState = "SAFE-OPERATIONAL";
      case 8: opState = "OPERATIONAL";
    }
    std::cout << "\tstate is " << opState <<  std::endl;
    //std::cout << "\t\t\tconfig obtained" <<  std::endl;
  }

  int error;
  
  error = ecrt_slave_config_pdos(cfg, EC_END, slave.getSyncs());
  if (error) {
    std::cerr << "ERROR:\tFailed to configure slave PDOs" << std::endl;
    exit(-1);
  } else {
    std::cout << "OK:\tSlave '" << name << "' pdos configured" << std::endl;
  }
  
  auto pdos = slave.getPdoEntryRegs();
  error = ecrt_domain_reg_pdo_entry_list(domain, pdos);
  if (error) {
    std::cerr << "ERROR:\tFailed to register PDOs with domain";
    std::cerr << " Error code: " << error << std::endl;
    exit(-1);
  } else {
    std::cout << "OK:\tPDOs registered with domain" << std::endl;
  }
  slaves[name] = &slave;
}

EtherCatSlave* EtherCat::getSlave(const std::string& name) {
  return slaves[name];
}

uint8_t* EtherCat::getProcessData() {
  return ecrt_domain_data(domain);
}


#include <sstream>
#include <bitset>
#include "InputModule.h"

InputModule::InputModule(uint16_t position) : 
    EtherCatSlave(InputModule::BECKHOFF_ID, InputModule::EL1252_ID, position) {
  int i = 0;
  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x6000, 0x01, 
      &offsetInput[0], &bitPosInput[0]};

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x6000, 0x02, 
      &offsetInput[1], &bitPosInput[1]},

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x1d09, 0xae, 
      &offsetStatus[0], &bitPosStatus[0]},

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x1d09, 0xaf, 
      &offsetStatus[1], &bitPosStatus[1]},

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x1d09, 0xb0, 
      &offsetLatchPos[0], &bitPosLatchPos[0]},

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x1d09, 0xc0, 
      &offsetLatchPos[1], &bitPosLatchPos[1]},

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x1d09, 0xb8, 
      &offsetLatchNeg[0], &bitPosLatchNeg[0]},

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL1252_ID, 0x1d09, 0xc8, 
      &offsetLatchNeg[1], &bitPosLatchNeg[1]},

  pdoRegs[i++] = { };

}

InputModule::~InputModule() { }

const ec_sync_info_t* InputModule::getSyncs() const {
  return syncs;
}

const ec_pdo_entry_reg_t* InputModule::getPdoEntryRegs() const {
  return pdoRegs;
}

void InputModule::readProcessData(uint8_t* processData) {
  for (int i = 0; i < 2; i++) {
    input[i] = EC_READ_BIT(processData + offsetInput[i], offsetInput[i]);
    status[i] = EC_READ_U8(processData + offsetStatus[i]);
    posLatch[i] = EC_READ_U64(processData + offsetLatchPos[i]);
    negLatch[i] = EC_READ_U64(processData + offsetLatchNeg[i]);
  }
  //TODO
}

void InputModule::writeProcessData(uint8_t* processData) { }

bool InputModule::getInput(unsigned short& channel) {
  return input[channel];
}

uint8_t InputModule::getStatus(unsigned short& channel) {
  return status[channel];
}

uint64_t InputModule::getPosLatch(unsigned short& channel) {
  return posLatch[channel];
}

uint64_t InputModule::getNegLatch(unsigned short& channel) {
  return negLatch[channel];
}

std::string InputModule::toString() const {
  std::stringstream os;
  for (int i = 0; i < 2; i++) {
    if (i == 0) os << "el1252";
    else os << "      ";
    os << " "<< i << ": ";
    os << "in=" << (input[i] ? std::string("true"): std::string("false"));
    std::bitset<8> statusBits(status[i]);
    os << "\tstatus=" << statusBits;
    os << "\tnegLatch=" << negLatch[i];
    os << "\tposLatch=" << posLatch[i];
    if (i == 0) os << std::endl;
  }
  return os.str();
}

std::ostream& operator<<(std::ostream& os, InputModule& x) {
  os << x.toString();
  return os;
}

const ec_pdo_entry_info_t InputModule::pdoEntries[] = {
  {0x6000, 0x01, 1}, /* Input */
  {0x6000, 0x02, 1}, /* Input */
  {0x0000, 0x00, 6},
  {0x1d09, 0xae, 8}, /* Status1 */
  {0x1d09, 0xaf, 8}, /* Status2 */
  {0x1d09, 0xb0, 64}, /* LatchPos1 */
  {0x1d09, 0xb8, 64}, /* LatchNeg1 */
  {0x1d09, 0xc0, 64}, /* LatchPos2 */
  {0x1d09, 0xc8, 64}, /* LatchNeg2 */
};

const ec_pdo_info_t InputModule::pdos[] = {
  /* Channel 1 */
  {0x1a00, 1, const_cast<ec_pdo_entry_info_t*>(pdoEntries + 0)}, 
  /* Channel 2 */
  {0x1a01, 2, const_cast<ec_pdo_entry_info_t*>(pdoEntries + 1)}, 
  /* Reserved */
  {0x1a02, 0, NULL}, 
  /* Latch */
  {0x1a13, 6, const_cast<ec_pdo_entry_info_t*>(pdoEntries + 3)}, 
};

const ec_sync_info_t InputModule::syncs[] = {
  {0, EC_DIR_INPUT, 3, const_cast<ec_pdo_info_t*>(pdos + 0), EC_WD_DISABLE},
  {1, EC_DIR_INPUT, 1, const_cast<ec_pdo_info_t*>(pdos + 3), EC_WD_DISABLE},
  {2, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
  {0xff}
};


#include <unistd.h>
#include <signal.h>
#include <iostream>
#include <bitset>
#include <chrono>
#include <thread>
#include <string.h>

#include "ecrt.h"
#include "EtherCat.h"
#include "InputModule.h"
#include "OutputModule.h"
#include "slaves.h"

static EtherCat* ec;

static void checkMasterStatus(ec_master_t* master);
static void checkDomainStatus(ec_domain_t* domain);
static void doCyclicTask(EtherCat* ec);
static void term(int sigNum);

static const std::string INPUT_MODULE = "input";
static const std::string OUTPUT_MODULE = "output";

  unsigned int micros = 5000000; // 5s
//unsigned int micros = 500000;  // 0.5s
//unsigned int micros = 50000;   // 50ms
//unsigned int micros = 5000;    // 5ms
//unsigned int micros = 500;     // 0.5ms

static InputModule inputModule(1);
static OutputModule outputModule(2);


int main(int argc, char** argv) {
  std::cout << "Starting main..." << std::endl;
  //sleep(5);

  //EtherCat* ec = new EtherCat();
  ec = new EtherCat();

  ec_master_t* master = ec->getMaster();
  ec_domain_t* domain = ec->getDomain();

  //InputModule inputModule = InputModule(1);
  //OutputModule outputModule = OutputModule(2);
  ec->configureSlave(INPUT_MODULE, inputModule);
  ec->configureSlave(OUTPUT_MODULE, outputModule);

  checkMasterStatus(master);
  checkDomainStatus(domain);

  signal(SIGABRT, &term);
  signal(SIGTERM, &term);
  signal(SIGINT, &term);

  ec->start(doCyclicTask, micros);
  // stop will from from a ctrl+c signal

  delete ec;
  ec = NULL;

  std::cout << "Leaving main..." << std::endl;
  return 0;
}

static void doCyclicTask(EtherCat* ec) {
  static unsigned int counter = 0;
  static unsigned int blink = false;
  static uint64_t lastPosLatch[2] =  {0, 0};
  static bool lastInput[2] =  {false, false};

  //std::cout <<"." << std::flush;
  //TODO process

  static uint8_t* processData = ec->getProcessData();

  /*
  if (counter++ % (1000000/micros) == 0) {
    counter=1;
    blink = !blink;
    //std::cout << "Blink:\t" << (blink ? "true": "false") << std::endl;

    //std::bitset<8> bits(*processData);
    //std::cout << bits << std::endl;
    //std::cout << offset0 << bitPos0 << offset1 << bitPos1<< std::endl;
    //std::bitset<8> moreBits(*(processData + 1));
    //std::cout << moreBits << std::endl;
    //std::cout << inputModule.toString() << std::endl;
    //std::cout << outputModule.toString() << std::endl;
    //for (unsigned short i = 0; i < 2; i++) {

    //outputModule.setTriState(0, blink);
    //outputModule.setTriState(1, !blink);
  }
  */

  /*
  for (unsigned short i = 0; i < 2; i++) {
  //for (unsigned short i = 0; i < 1; i++) {
  //for (unsigned short i = 1; i < 2; i++) {
    bool header = false;

    if (inputModule.getInput(i) != lastInput[i]) {
      std::cout << "el1252[" << i << "]";
      header = true;
      if (lastInput[i]) std::cout << " transition to low ";
      else std::cout << " transition to high";
      lastInput[i] = inputModule.getInput(i);
    }

    if (0 != inputModule.getPosLatch(i)) {
      if (0 != lastPosLatch[i]) {
        if (inputModule.getPosLatch(i) != lastPosLatch[i]) {
          if (!header) {
            std::cout << "el1252[" << i << "]";
            header = true;
          }
          std::cout << " duration: ";
          std::cout << inputModule.getPosLatch(i)- lastPosLatch[i] ;
          //std::cout << "\t" << lastPosLatch[i];
          //std::cout << "\t" <<  inputModule.getPosLatch(i);

        }
      }
      lastPosLatch[i] = inputModule.getPosLatch(i);
    }
    if (header) std::cout << std::endl;
  }
  */

  unsigned short channel = 1;
  bool input1 = inputModule.getInput(channel);
  if (input1 && (input1 != lastInput[channel])) {
    uint64_t time = inputModule.getPosLatch(channel);
    std::cout << "button press detected @ ";
    std::cout << time;
    std::cout << std::endl;

    // add 1 second
    time += 1000000000;

    outputModule.schedule(time, blink, !blink);
    blink = !blink;

  }

  for (unsigned short i = 0; i < 2; i++) {

    lastInput[i] = inputModule.getInput(i);
    lastPosLatch[i] = inputModule.getPosLatch(i);
  }

  ecrt_domain_queue(ec->getDomain());
  ecrt_master_send(ec->getMaster());

}

static void term(int signum) {
  std::cerr << "Received sig: " << signum << std::endl;
  if (NULL != ec) ec->stop();
}

static void checkMasterStatus(ec_master_t* master) {
  ec_master_state_t ms;
  ecrt_master_state(master, &ms);
  std::bitset<4> states(ms.al_states);
  std::cout << "Master:\tlink is " << (ms.link_up ? "up" : "down");
  std::cout << " with " << ms.slaves_responding << " slave(s)." << std::endl;
  std::cout << "       \tapplication layer states: 0b" << states << std::endl;
}

static void checkDomainStatus(ec_domain_t* domain) {
  ec_domain_state_t ds;
  ecrt_domain_state(domain, &ds);
  std::cout << "Domain:\tWC    " << ds.working_counter << std::endl;
  std::cout << "       \tState " << ds.wc_state << std::endl; 
}

static void parseProcessData(uint8_t* data) {
  int i;
  for (i = 0; i < 2; i++) {
    //unsigned int in = EC_READ_BIT(data + el1252.offset_in[i], 
        //el1252.offset_in[i]);
    //unsigned int status = EC_READ_BIT(data + el1252.offset_status[i], 
        //el1252.offset_status[i]);
    //unsigned int poslatch= EC_READ_BIT(data + el1252.offset_latch_pos[i], 
        //el1252.offset_latch_pos[i]);
    //unsigned int neglatch= EC_READ_BIT(data + el1252.offset_latch_neg[i], 
        //el1252.offset_latch_neg[i]);
    //printf("el1252[%i]:  in=%s\tstatus=%s\tlatchpos=%s\tlatchneg=%s\n", i,
        //in ? "true": "false", 
        //status ? "true": "false",
        //poslatch? "true": "false",
        //neglatch? "true": "false");
  }
}

#include <sstream>
#include <bitset>
#include "OutputModule.h"

OutputModule::OutputModule(uint16_t position) : 
    EtherCatSlave(OutputModule::BECKHOFF_ID, OutputModule::EL2252_ID, 
    position) {
  int i = 0;
  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL2252_ID, 0x1d09, 0x81, 
      &offsetActivate, &bitPosActivate};

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL2252_ID, 0x1d09, 0x90, 
      &offsetStartTime, &bitPosStartTime};

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL2252_ID, 0x7000, 0x01, 
      &offsetOutput[0], &bitPosOutput[0]};

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL2252_ID, 0x7000, 0x02, 
      &offsetTriState[0], &bitPosTriState[0]};

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL2252_ID, 0x7010, 0x01, 
      &offsetOutput[1], &bitPosOutput[1]};

  pdoRegs[i++] = { 0, position, BECKHOFF_ID, EL2252_ID, 0x7010, 0x02, 
      &offsetTriState[1], &bitPosTriState[1]};

  pdoRegs[i++] = { };

 state = IDLE;
}

const ec_sync_info_t* OutputModule::getSyncs() const {
  return syncs;
}

const ec_pdo_entry_reg_t* OutputModule::getPdoEntryRegs() const {
  return pdoRegs;
}

void OutputModule::readProcessData(uint8_t* processData) { }

void OutputModule::writeProcessData(uint8_t* processData) {
  switch (state) {
    case IDLE: 
      break;
    case WRITE_READY:
      EC_WRITE_U64(processData + offsetStartTime, time);
      for (int i = 0; i < 2; i++) {
        EC_WRITE_BIT(processData + offsetOutput[i], offsetOutput[i], output[i]);
        EC_WRITE_BIT(processData + offsetTriState[i], offsetTriState[i], 0);
      }
      EC_WRITE_U8(processData + offsetActivate, 0);
      std::cout << "State transitioned from WRITE_READY --> WRITE_STARTED";
      std::cout << std::endl;
      state = WRITE_STARTED;
      break;
    case WRITE_STARTED:
      std::cout << "State transitioned from WRITE_STARTED --> IDLE";
      std::cout << std::endl;
      EC_WRITE_U8(processData + offsetActivate, 3);
      state = IDLE;
      break;
  }
}

void OutputModule::schedule(uint64_t startTime, bool output0, bool output1) {
  std::cout << "@ " << startTime << " El2252[0] will become " << output0;
  std::cout << " and El2252[1] will become " << output1 << std::endl;
  this->startTime = startTime;
  this->output[0] = output0;
  this->output[1] = output1;
  this->state = WRITE_READY;
}

OutputModule::~OutputModule() { }

const ec_pdo_entry_info_t OutputModule::pdoEntries[] = {
  {0x1d09, 0x81, 8}, /* Activate */
  {0x1d09, 0x90, 64}, /* StartTime */
  {0x7000, 0x01, 1}, /* Output */
  {0x7000, 0x02, 1}, /* TriState */
  {0x7010, 0x01, 1}, /* Output */
  {0x7010, 0x02, 1}, /* TriState */
  {0x0000, 0x00, 4},
};

const ec_pdo_info_t OutputModule::pdos[] = {
  /* DC Sync Activate */
  {0x1602, 1, const_cast<ec_pdo_entry_info_t*>(pdoEntries + 0)}, 
  /* DC Sync Start */
  {0x1603, 1, const_cast<ec_pdo_entry_info_t*>(pdoEntries + 1)}, 
  /* Channel 1 */
  {0x1600, 2, const_cast<ec_pdo_entry_info_t*>(pdoEntries + 2)}, 
  /* Channel 2 */
  {0x1601, 3, const_cast<ec_pdo_entry_info_t*>(pdoEntries + 4)}, 
  /* Reserved */
  {0x1604, 0, NULL}, 
};

const ec_sync_info_t OutputModule::syncs[] = {
  {0, EC_DIR_INPUT, 1, const_cast<ec_pdo_info_t*>(pdos + 0), EC_WD_DISABLE},
  {1, EC_DIR_INPUT, 1, const_cast<ec_pdo_info_t*>(pdos + 1), EC_WD_DISABLE},
  {2, EC_DIR_OUTPUT, 3, const_cast<ec_pdo_info_t*>(pdos + 2), EC_WD_ENABLE},
  {3, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
  {4, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
  {0xff}
};

std::string OutputModule::toString() const {
  std::stringstream os;
  for (int i = 0; i < 2; i++) {
    if (0 == i) os << "el2252 ";
    else os << "       ";
    os << i << " : ";
    //os << "in=" << input[i] ? std::string("true"): std::string("false");
    //std::bitset<8> statusBits(status[i]);
    //os << "\tstatus=" << statusBits;
    //os << "\tnegLatch=" << negLatch[i];
    //os << "\tposLatch=" << posLatch[i];
    if (i == 0) os << std::endl;
  }
  return os.str();
}

std::ostream& operator<<(std::ostream & os, OutputModule& x) {
  os << x.toString();
  return os;
}


_______________________________________________
etherlab-users mailing list
etherlab-users@etherlab.org
http://lists.etherlab.org/mailman/listinfo/etherlab-users

Reply via email to