Tutorial¶
General¶
The SOEM is a library that provides the user application with the means to send and receive EtherCAT frames. It is up to the application to provide means for:
Reading and writing process data to be sent/received by SOEM
Keeping local IO data synchronized with the global IOmap
Detecting errors reported by SOEM
Managing errors reported by SOEM
The following sections show some basic examples on how to get the SOEM up and running, as well as making use of the process data and checking for errors. Since all code is local to the application or global variables, it is possible to tweak and optimize when possible.
The following example shows how to add a main function that will be called by startup code. In this example main’s only purpose is to spawn a new task that executes SOEM:
int main (void)
{
rprintp("SOEM (Simple Open EtherCAT Master)\nSimple test\n");
task_spawn ("simpletest", simpletest, 9, 8192, NULL);
Configuration¶
Followed by start of the application we need to set up the NIC to be
used as EtherCAT Ethernet interface. In a simple setup we call ec_init(ifname)
and if SOEM comes with support for cable redundancy we
call ec_init_redundant
that will open a second port as backup. You can
send NULL as ifname if you have a dedicated NIC selected in the
nicdrv.c. It returns >0 if succeeded:
/* initialise SOEM, bind socket to ifname */
if (ec_init(ifname) > 0)
SOEM is a light weight ethercat master library used in embedded
systems, It supports only runtime configuration. It requests a BRD
(Broad Cast Read) of address 0, all fully functional slaves in the
network will respond to this request, and therefore we will get a
working counter equal to the number of slaves in the network.
ec_config_init
also sets up the mailboxes for slaves that support it.
When ec_config_init
finishes it will have requested all slaves to
state PRE_OP. All data read and configured are stored in a global
array which acts as a placeholder for key values, consult ec_slave
for
detailed information:
/* find and auto-config slaves */
if ( ec_config_init(FALSE) > 0 )
{
rprintp("%d slaves found and configured.\n",ec_slavecount);
SOEM has now discovered and configured the network it is connected to. Now we can verify that all slaves are present as expected. These definitions could be generated by an external tool in an offline .h file. The definitions could be replaced by a struct keeping slave number:
#define EK1100_1 1
#define EL4001_1 2
...
#define EL2622_3 8
#define EL2622_4 9
#define NUMBER_OF_SLAVES 9
snippet
...
uint32 network_configuration(void)
{
/* Do we got expected number of slaves from config */
if (ec_slavecount < NUMBER_OF_SLAVES)
return 0;
/* Verify slave by slave that it is correct*/
if (strcmp(ec_slave[EK1100_1].name,"EK1100"))
return 0;
else if (strcmp(ec_slave[EL4001_1].name,"EL4001"))
return 0;
...
else if (strcmp(ec_slave[EL2622_4].name,"EL2622"))
return 0;
return 1;
}
simpletest
...
if (network_configuration())
...
else
rprintp("Mismatch of network units!\n");
We now have the network up and configured. Mailboxes are up for slaves that support it. Next we will create an IOmap and configure the SyncManager’s and FMMU’s to link the EtherCAT master and the slaves. The IO mapping is done automatically, SOEM strives to keep the logical process image as compact as possible. It is done by trying to fit Bit oriented slaves together in single bytes. Below is an example of 8 slaves and how they are ordered. During mapping SOEM also calculates an expected WKC for the IO mapped together. That is the primary key to detect errors.
Outputs are placed together in the beginning of the IOmap
Inputs follow
When the mapping is done SOEM requests slaves to enter SAFE_OP:
char IOmap[128];
int usedmem;
...
usedmem = ec_config_map(&IOmap);
if (usedmem <= sizeof(IOmap))
...
memory layout, mapping between physical and logical
To enter state OP we need to send valid data to outputs. The EtherCAT
frame handling is split into ec_send_processdata
and
ec_receive_processdata
.
ec_send_processdata
sends the frame on the NIC and saves the frame on the stack for receive to fetch.ec_receive_processdata(EC_TIMEOUTRET)
tries to fetch the frames on the stack. We send an argument for how long we will try to fetch the frame.ec_receive_processdata
returns the working counter:/* send one valid process data to make outputs in slaves happy*/ ec_send_processdata(); wkc = ec_receive_processdata(EC_TIMEOUTRET); ... ec_writestate(0); /* wait for all slaves to reach OP state */ ec_statecheck(0, EC_STATE_OPERATIONAL, EC_TIMEOUTSTATE);
Now we have a system up and running, all slaves are in state operational.
Custom Configuration¶
PDO Assign and PDO Config¶
Do custom configuration with PDO Assign or PDO Config. SOEM support
custom configuration during start via a PreOP to SafeOP configuration
hook. It can be done per slave and should be set before calling the
configuration and mapping of process data, e.g. the call to
ec_config_map
. Setting the configuration hook ensure that the custom
configuration will be applied when calling recover and re-
configuration of a slave, as described below:
int EL7031setup(uint16 slave)
{
int retval;
uint16 u16val;
retval = 0;
/* Map velocity PDO assignment via Complete Access*/
uint16 map_1c12[4] = {0x0003, 0x1601, 0x1602, 0x1604};
uint16 map_1c13[3] = {0x0002, 0x1a01, 0x1a03};
retval += ec_SDOwrite(slave, 0x1c12, 0x00, TRUE, sizeof(map_1c12), &map_1c12, EC_TIMEOUTSAFE);
retval += ec_SDOwrite(slave, 0x1c13, 0x00, TRUE, sizeof(map_1c13), &map_1c13, EC_TIMEOUTSAFE);
/* set some motor parameters, just as example */
u16val = 1200; // max motor current in mA
retval += ec_SDOwrite(slave, 0x8010, 0x01, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTSAFE);
u16val = 150; // motor coil resistance in 0.01ohm
retval += ec_SDOwrite(slave, 0x8010, 0x04, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTSAFE);
/* set other necessary parameters as needed */
...
printf("EL7031 slave %d set, retval = %d\n", slave, retval);
return 1;
}
void simpletest(char *ifname)
{
...
/* Detect slave beckhoff EL7031 from vendor ID and product code */
if((ec_slave[slc].eep_man == 0x00000002) && (ec_slave[slc].eep_id == 0x1b773052))
{
printf("Found %s at position %d\n", ec_slave[slc].name, slc);
/* link slave specific setup to preop->safeop hook */
ec_slave[slc].PO2SOconfig = EL7031setup;
}
...
Legacy versus overlapping IOmap¶
IOmap options legacy versus overlapping. Overlapping IOmap was introduced to handle the TI ESC that doesn’t support RW access to non- interleaved input and output process data of multiple slaves. The difference is that legacy IOmapping will send IOmap as is on the EtherCAT network while the overlapping will re-use logic addressing per slave to replace RxPDO process data coming from the Master with TxPDO process data generated by the slave sent back to the master.
Overview of legacy pdo map Legacy IOmapping Overview of overlapping pdo map Overlapping IOmapping
EtherCAT slave groups¶
Slave groups can be used to group slaves into separate logic groups within an EtherCAT network. Each group will have its own logic address space mapped to an IOmap address and make it possible to send and receive process data at different update rate.
Below is an example on how to assign a slave to a group. OBS! A slave can only be member in one group:
for (cnt = 1; cnt <= ec_slavecount; cnt++)
{
if ( <some condition> )
{
ec_slave[cnt].group = X;
}
else
{
ec_slave[cnt].group = Y;
}
}
Alternative 1, configure all slave groups at once, call ec_config_map
or ec_config_map_group
with arg 0. This option will share IOmap and
store the group IOmap data at offset EC_LOGGROUPOFFSET
:
`ec_config_map_group`_(&`IOmap`_, 0);
Alternative 2, configure the slave groups one by one, call
ec_config_map
or ec_config_map_group
with arg X, Y. This option will
use different, supplied by the user, IOmaps:
ec_config_map_group(&IOmap1, X);
ec_config_map_group(&IOmap2, Y);
To exchange process data for given group(s) the user must call send/recv process data per group. The send and receive stack of process data don’t consider groups, so the application has to send and receive the process data for one group before sending/receiving process data for another group:
ec_send_processdata_group(X);
ec_receive_processdata_group(X, EC_TIMEOUTRET);
ec_send_processdata_group(Y);
ec_receive_processdata_group(Y, EC_TIMEOUTRET);
Application¶
Accessing data through IOmap¶
IOmap is the fastest mechanism for accessing slaves’ IO data. Using
this mechanism, the ec_slave
struct keeps pointers to the start byte
in the IO map on slave level together with start bit within the start
byte. This way we can bit mask IO on bit level even though SOEM has
combined slave data to minimize the frame size to be sent. We’ll use
slave 8 in the picture above as an example. From a printout from
ec_slave
we have the following:
Slave:8
Name:EL2622
Output size: 2bits
Input size: 0bits
Configured address: 1008
Outputs address: 18cf6
Inputs address: 0
FMMU0 Ls:2 Ll: 1 Lsb:4 Leb:5 Ps:f00 Psb:0 Ty:2 Act:1
The Outputs address: 18cf6 is the pointer to slave 8’s start byte. The
FMMU’s Lsb:4 (LogicalStartBit) - ec_slave.Ostartbit
telling us how to
mask for the individual bits in the combined byte. The same goes for
byte addressed slaves, but byte slaves only need the byte start
address since they are byte aligned, the start bit will be 0.
Some example on how to access different types of data
Set an output int 16 value when memory alignment needs to be considered, arguments is:
slave number in ethercat network
module index as index internal to the slave in case more than one channel
value to write:
#define EL4001_1 2 ... void set_output_int16 (uint16 slave_no, uint8 module_index, int16 value) { uint8 *data_ptr; data_ptr = ec_slave[slave_no].outputs; /* Move pointer to correct module index*/ data_ptr += module_index * 2; /* Read value byte by byte since all targets can't handle misaligned * addresses */ *data_ptr++ = (value >> 0) & 0xFF; *data_ptr++ = (value >> 8) & 0xFF; } ... set_output_int16(EL4001_1,0,slave_EL4001_1.out1);
Target can handle non aligned pointers to the IOmap:
wkc = ec_receive_processdata(EC_TIMEOUTRET);
expectedWKC = (ec_group[0].outputsWKC * 2) + ec_group[0].inputsWKC;
if( inOP && ((wkc < expectedWKC) || ec_group[currentgroup].docheckstate))
{
if (needlf)
{
needlf = FALSE;
printf("\n");
}
/* one ore more slaves are not responding */
ec_group[currentgroup].docheckstate = FALSE;
ec_readstate();
for (slave = 1; slave <= ec_slavecount; slave++)
{
if ((ec_slave[slave].group == currentgroup) && (ec_slave[slave].state != EC_STATE_OPERATIONAL))
{
ec_group[currentgroup].docheckstate = TRUE;
if (ec_slave[slave].state == (EC_STATE_SAFE_OP + EC_STATE_ERROR))
{
printf("ERROR : slave %d is in SAFE_OP + ERROR, attempting ack.\n", slave);
ec_slave[slave].state = (EC_STATE_SAFE_OP + EC_STATE_ACK);
ec_writestate(slave);
}
else if(ec_slave[slave].state == EC_STATE_SAFE_OP)
{
printf("WARNING : slave %d is in SAFE_OP, change to OPERATIONAL.\n", slave);
ec_slave[slave].state = EC_STATE_OPERATIONAL;
ec_writestate(slave);
}
else if(ec_slave[slave].state > EC_STATE_NONE)
{
if (ec_reconfig_slave(slave, EC_TIMEOUTMON))
{
ec_slave[slave].islost = FALSE;
printf("MESSAGE : slave %d reconfigured\n",slave);
}
}
else if(!ec_slave[slave].islost)
{
/* re-check state */
ec_statecheck(slave, EC_STATE_OPERATIONAL, EC_TIMEOUTRET);
if (ec_slave[slave].state == EC_STATE_NONE)
{
ec_slave[slave].islost = TRUE;
printf("ERROR : slave %d lost\n",slave);
}
}
}
if (ec_slave[slave].islost)
{
if(ec_slave[slave].state == EC_STATE_NONE)
{
if (ec_recover_slave(slave, EC_TIMEOUTMON))
{
ec_slave[slave].islost = FALSE;
printf("MESSAGE : slave %d recovered\n",slave);
}
}
else
{
ec_slave[slave].islost = FALSE;
printf("MESSAGE : slave %d found\n",slave);
}
}
}
if(!ec_group[currentgroup].docheckstate)
printf("OK : all slaves resumed OPERATIONAL.\n");
}
Accessing SDOs and PDOs¶
There are multiple ways a slave can communicate with the master. CANopen over EtherCAT (CoE) is a (slow but flexible) asynchronous mechanism for transferring data via mailboxes. SOEM provides the `ecx_SDOread()`_ and `ecx_SDOwrite()`_ functions for reading and writing a CoE SDO (Service Data Object) given the corresponding index and subindex.
SOEM does not provide specific functions for accessing CoE PDOs (Process Data Objects). On most slaves, however, it is possible to use the same functions available for SDOs. In the seldom case in which the PDO object has been marked in the CoE dictionary as “PDO only”, only IOmap access is allowed. Note that a list of the PDO mappings can be retrieved through the “slaveinfo<interface> -map” command.