|
|
|
@ -0,0 +1,316 @@
|
|
|
|
|
/** \file
|
|
|
|
|
* \brief Example code for Simple Open EtherCAT master
|
|
|
|
|
*
|
|
|
|
|
* Usage: simple_ng IFNAME1
|
|
|
|
|
* IFNAME1 is the NIC interface name, e.g. 'eth0'
|
|
|
|
|
*
|
|
|
|
|
* This is a minimal test.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "ethercat.h"
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
ecx_contextt context;
|
|
|
|
|
char * iface;
|
|
|
|
|
uint8 group;
|
|
|
|
|
int roundtrip_time;
|
|
|
|
|
|
|
|
|
|
/* Used by the context */
|
|
|
|
|
uint8 map[4096];
|
|
|
|
|
ecx_portt port;
|
|
|
|
|
ec_slavet slavelist[EC_MAXSLAVE];
|
|
|
|
|
int slavecount;
|
|
|
|
|
ec_groupt grouplist[EC_MAXGROUP];
|
|
|
|
|
uint8 esibuf[EC_MAXEEPBUF];
|
|
|
|
|
uint32 esimap[EC_MAXEEPBITMAP];
|
|
|
|
|
ec_eringt elist;
|
|
|
|
|
ec_idxstackT idxstack;
|
|
|
|
|
boolean ecaterror;
|
|
|
|
|
int64 DCtime;
|
|
|
|
|
ec_SMcommtypet SMcommtype[EC_MAX_MAPT];
|
|
|
|
|
ec_PDOassignt PDOassign[EC_MAX_MAPT];
|
|
|
|
|
ec_PDOdesct PDOdesc[EC_MAX_MAPT];
|
|
|
|
|
ec_eepromSMt eepSM;
|
|
|
|
|
ec_eepromFMMUt eepFMMU;
|
|
|
|
|
} Fieldbus;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
fieldbus_initialize(Fieldbus *fieldbus, char *iface)
|
|
|
|
|
{
|
|
|
|
|
ecx_contextt *context;
|
|
|
|
|
|
|
|
|
|
/* Let's start by 0-filling `fieldbus` to avoid surprises */
|
|
|
|
|
memset(fieldbus, 0, sizeof(*fieldbus));
|
|
|
|
|
|
|
|
|
|
fieldbus->iface = iface;
|
|
|
|
|
fieldbus->group = 0;
|
|
|
|
|
fieldbus->roundtrip_time = 0;
|
|
|
|
|
fieldbus->ecaterror = FALSE;
|
|
|
|
|
|
|
|
|
|
/* Initialize the ecx_contextt data structure */
|
|
|
|
|
context = &fieldbus->context;
|
|
|
|
|
context->port = &fieldbus->port;
|
|
|
|
|
context->slavelist = fieldbus->slavelist;
|
|
|
|
|
context->slavecount = &fieldbus->slavecount;
|
|
|
|
|
context->maxslave = EC_MAXSLAVE;
|
|
|
|
|
context->grouplist = fieldbus->grouplist;
|
|
|
|
|
context->maxgroup = EC_MAXGROUP;
|
|
|
|
|
context->esibuf = fieldbus->esibuf;
|
|
|
|
|
context->esimap = fieldbus->esimap;
|
|
|
|
|
context->esislave = 0;
|
|
|
|
|
context->elist = &fieldbus->elist;
|
|
|
|
|
context->idxstack = &fieldbus->idxstack;
|
|
|
|
|
context->ecaterror = &fieldbus->ecaterror;
|
|
|
|
|
context->DCtime = &fieldbus->DCtime;
|
|
|
|
|
context->SMcommtype = fieldbus->SMcommtype;
|
|
|
|
|
context->PDOassign = fieldbus->PDOassign;
|
|
|
|
|
context->PDOdesc = fieldbus->PDOdesc;
|
|
|
|
|
context->eepSM = &fieldbus->eepSM;
|
|
|
|
|
context->eepFMMU = &fieldbus->eepFMMU;
|
|
|
|
|
context->FOEhook = NULL;
|
|
|
|
|
context->EOEhook = NULL;
|
|
|
|
|
context->manualstatechange = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
fieldbus_roundtrip(Fieldbus *fieldbus)
|
|
|
|
|
{
|
|
|
|
|
ecx_contextt *context;
|
|
|
|
|
ec_timet start, end, diff;
|
|
|
|
|
int wkc;
|
|
|
|
|
|
|
|
|
|
context = &fieldbus->context;
|
|
|
|
|
|
|
|
|
|
start = osal_current_time();
|
|
|
|
|
ecx_send_processdata(context);
|
|
|
|
|
wkc = ecx_receive_processdata(context, EC_TIMEOUTRET);
|
|
|
|
|
end = osal_current_time();
|
|
|
|
|
osal_time_diff(&start, &end, &diff);
|
|
|
|
|
fieldbus->roundtrip_time = diff.sec * 1000000 + diff.usec;
|
|
|
|
|
|
|
|
|
|
return wkc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static boolean
|
|
|
|
|
fieldbus_start(Fieldbus *fieldbus)
|
|
|
|
|
{
|
|
|
|
|
ecx_contextt *context;
|
|
|
|
|
ec_groupt *grp;
|
|
|
|
|
ec_slavet *slave;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
context = &fieldbus->context;
|
|
|
|
|
grp = fieldbus->grouplist + fieldbus->group;
|
|
|
|
|
|
|
|
|
|
printf("Initializing SOEM on '%s'... ", fieldbus->iface);
|
|
|
|
|
if (! ecx_init(context, fieldbus->iface)) {
|
|
|
|
|
printf("no socket connection\n");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
printf("done\n");
|
|
|
|
|
|
|
|
|
|
printf("Finding autoconfig slaves... ");
|
|
|
|
|
if (ecx_config_init(context, FALSE) <= 0) {
|
|
|
|
|
printf("no slaves found\n");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
printf("%d slaves found\n", fieldbus->slavecount);
|
|
|
|
|
|
|
|
|
|
printf("Sequential mapping of I/O... ");
|
|
|
|
|
ecx_config_map_group(context, fieldbus->map, fieldbus->group);
|
|
|
|
|
printf("mapped %dO+%dI bytes from %d segments",
|
|
|
|
|
grp->Obytes, grp->Ibytes, grp->nsegments);
|
|
|
|
|
if (grp->nsegments > 1) {
|
|
|
|
|
/* Show how slaves are distrubuted */
|
|
|
|
|
for (i = 0; i < grp->nsegments; ++i) {
|
|
|
|
|
printf("%s%d", i == 0 ? " (" : "+", grp->IOsegment[i]);
|
|
|
|
|
}
|
|
|
|
|
printf(" slaves)");
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
|
|
printf("Configuring distributed clock... ");
|
|
|
|
|
ecx_configdc(context);
|
|
|
|
|
printf("done\n");
|
|
|
|
|
|
|
|
|
|
printf("Waiting for all slaves in safe operational... ");
|
|
|
|
|
ecx_statecheck(context, 0, EC_STATE_SAFE_OP, EC_TIMEOUTSTATE * 4);
|
|
|
|
|
printf("done\n");
|
|
|
|
|
|
|
|
|
|
printf("Send a roundtrip to make outputs in slaves happy... ");
|
|
|
|
|
fieldbus_roundtrip(fieldbus);
|
|
|
|
|
printf("done\n");
|
|
|
|
|
|
|
|
|
|
printf("Setting operational state..");
|
|
|
|
|
/* Act on slave 0 (a virtual slave used for broadcasting) */
|
|
|
|
|
slave = fieldbus->slavelist;
|
|
|
|
|
slave->state = EC_STATE_OPERATIONAL;
|
|
|
|
|
ecx_writestate(context, 0);
|
|
|
|
|
/* Poll the result ten times before giving up */
|
|
|
|
|
for (i = 0; i < 10; ++i) {
|
|
|
|
|
printf(".");
|
|
|
|
|
fieldbus_roundtrip(fieldbus);
|
|
|
|
|
ecx_statecheck(context, 0, EC_STATE_OPERATIONAL, EC_TIMEOUTSTATE / 10);
|
|
|
|
|
if (slave->state == EC_STATE_OPERATIONAL) {
|
|
|
|
|
printf(" all slaves are now operational\n");
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf(" failed,");
|
|
|
|
|
ecx_readstate(context);
|
|
|
|
|
for (i = 1; i <= fieldbus->slavecount; ++i) {
|
|
|
|
|
slave = fieldbus->slavelist + i;
|
|
|
|
|
if (slave->state != EC_STATE_OPERATIONAL) {
|
|
|
|
|
printf(" slave %d is 0x%04X (AL-status=0x%04X %s)",
|
|
|
|
|
i, slave->state, slave->ALstatuscode,
|
|
|
|
|
ec_ALstatuscode2string(slave->ALstatuscode));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
fieldbus_stop(Fieldbus *fieldbus)
|
|
|
|
|
{
|
|
|
|
|
ecx_contextt *context;
|
|
|
|
|
ec_slavet *slave;
|
|
|
|
|
|
|
|
|
|
context = &fieldbus->context;
|
|
|
|
|
/* Act on slave 0 (a virtual slave used for broadcasting) */
|
|
|
|
|
slave = fieldbus->slavelist;
|
|
|
|
|
|
|
|
|
|
printf("Requesting init state on all slaves... ");
|
|
|
|
|
slave->state = EC_STATE_INIT;
|
|
|
|
|
ecx_writestate(context, 0);
|
|
|
|
|
printf("done\n");
|
|
|
|
|
|
|
|
|
|
printf("Close socket... ");
|
|
|
|
|
ecx_close(context);
|
|
|
|
|
printf("done\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static boolean
|
|
|
|
|
fieldbus_dump(Fieldbus *fieldbus)
|
|
|
|
|
{
|
|
|
|
|
ec_groupt *grp;
|
|
|
|
|
uint32 n;
|
|
|
|
|
int wkc, expected_wkc;
|
|
|
|
|
|
|
|
|
|
grp = fieldbus->grouplist + fieldbus->group;
|
|
|
|
|
|
|
|
|
|
wkc = fieldbus_roundtrip(fieldbus);
|
|
|
|
|
expected_wkc = grp->outputsWKC * 2 + grp->inputsWKC;
|
|
|
|
|
printf("%6d usec WKC %d", fieldbus->roundtrip_time, wkc);
|
|
|
|
|
if (wkc < expected_wkc) {
|
|
|
|
|
printf(" wrong (expected %d)\n", expected_wkc);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf(" O:");
|
|
|
|
|
for (n = 0; n < grp->Obytes; ++n) {
|
|
|
|
|
printf(" %02X", grp->outputs[n]);
|
|
|
|
|
}
|
|
|
|
|
printf(" I:");
|
|
|
|
|
for (n = 0; n < grp->Ibytes; ++n) {
|
|
|
|
|
printf(" %02X", grp->inputs[n]);
|
|
|
|
|
}
|
|
|
|
|
printf(" T: %lld\r", (long long) fieldbus->DCtime);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
fieldbus_check_state(Fieldbus *fieldbus)
|
|
|
|
|
{
|
|
|
|
|
ecx_contextt *context;
|
|
|
|
|
ec_groupt *grp;
|
|
|
|
|
ec_slavet *slave;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
context = &fieldbus->context;
|
|
|
|
|
grp = context->grouplist + fieldbus->group;
|
|
|
|
|
grp->docheckstate = FALSE;
|
|
|
|
|
ecx_readstate(context);
|
|
|
|
|
for (i = 1; i <= fieldbus->slavecount; ++i) {
|
|
|
|
|
slave = context->slavelist + i;
|
|
|
|
|
if (slave->group != fieldbus->group) {
|
|
|
|
|
/* This slave is part of another group: do nothing */
|
|
|
|
|
} else if (slave->state != EC_STATE_OPERATIONAL) {
|
|
|
|
|
grp->docheckstate = TRUE;
|
|
|
|
|
if (slave->state == EC_STATE_SAFE_OP + EC_STATE_ERROR) {
|
|
|
|
|
printf("* Slave %d is in SAFE_OP+ERROR, attempting ACK\n", i);
|
|
|
|
|
slave->state = EC_STATE_SAFE_OP + EC_STATE_ACK;
|
|
|
|
|
ecx_writestate(context, i);
|
|
|
|
|
} else if(slave->state == EC_STATE_SAFE_OP) {
|
|
|
|
|
printf("* Slave %d is in SAFE_OP, change to OPERATIONAL\n", i);
|
|
|
|
|
slave->state = EC_STATE_OPERATIONAL;
|
|
|
|
|
ecx_writestate(context, i);
|
|
|
|
|
} else if(slave->state > EC_STATE_NONE) {
|
|
|
|
|
if (ecx_reconfig_slave(context, i, EC_TIMEOUTRET)) {
|
|
|
|
|
slave->islost = FALSE;
|
|
|
|
|
printf("* Slave %d reconfigured\n", i);
|
|
|
|
|
}
|
|
|
|
|
} else if(! slave->islost) {
|
|
|
|
|
ecx_statecheck(context, i, EC_STATE_OPERATIONAL, EC_TIMEOUTRET);
|
|
|
|
|
if (slave->state == EC_STATE_NONE) {
|
|
|
|
|
slave->islost = TRUE;
|
|
|
|
|
printf("* Slave %d lost\n", i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (slave->islost) {
|
|
|
|
|
if(slave->state != EC_STATE_NONE) {
|
|
|
|
|
slave->islost = FALSE;
|
|
|
|
|
printf("* Slave %d found\n", i);
|
|
|
|
|
} else if (ecx_recover_slave(context, i, EC_TIMEOUTRET)) {
|
|
|
|
|
slave->islost = FALSE;
|
|
|
|
|
printf("* Slave %d recovered\n", i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! grp->docheckstate) {
|
|
|
|
|
printf("All slaves resumed OPERATIONAL\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
main(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
Fieldbus fieldbus;
|
|
|
|
|
|
|
|
|
|
if (argc != 2) {
|
|
|
|
|
printf("Usage: simple_ng IFNAME1\n"
|
|
|
|
|
"IFNAME1 is the NIC interface name, e.g. 'eth0'\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fieldbus_initialize(&fieldbus, argv[1]);
|
|
|
|
|
if (fieldbus_start(&fieldbus)) {
|
|
|
|
|
int i, min_time, max_time;
|
|
|
|
|
min_time = max_time = 0;
|
|
|
|
|
for (i = 1; i <= 10000; ++i) {
|
|
|
|
|
printf("Iteration %4d:", i);
|
|
|
|
|
if (! fieldbus_dump(&fieldbus)) {
|
|
|
|
|
fieldbus_check_state(&fieldbus);
|
|
|
|
|
} else if (i == 1) {
|
|
|
|
|
min_time = max_time = fieldbus.roundtrip_time;
|
|
|
|
|
} else if (fieldbus.roundtrip_time < min_time) {
|
|
|
|
|
min_time = fieldbus.roundtrip_time;
|
|
|
|
|
} else if (fieldbus.roundtrip_time > max_time) {
|
|
|
|
|
max_time = fieldbus.roundtrip_time;
|
|
|
|
|
}
|
|
|
|
|
osal_usleep(5000);
|
|
|
|
|
}
|
|
|
|
|
printf("\nRoundtrip time (usec): min %d max %d\n", min_time, max_time);
|
|
|
|
|
fieldbus_stop(&fieldbus);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|