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