Tutorial - How to use a device, example with encoder
This page explains the different steps to use an EtherCAT device driver. For more informations on EtherCAT specifications please read this page.
Step 0 : Install and configure PID
Before starting to use a device, you have to install PID, ethercatcpp-core and all others ethercatcpp packages you need to use the device. In this example, we need only ethercatcpp-core because Beckhoff terminals are included in this package. Then you can use an exiting package or create a package with :
pid cd
pid create package=my-package url=git@gite.lirmm.fr:own/my-package.gitYou have to declare dependency for all devices drivers packages you need to use. In the root CMakelists.txt file of your package, after the package declaration, you have to write something like:
PID_Dependency(ethercatcpp-<name> VERSION 2.1)Now, package dependencies have been declared, you have to declare your application dependency in the CMakeLists.txt files:
PID_Component(your-app-name
...
DEPEND ethercatcpp/core)And then, don’t forget to include ethercatcpp core header in your code:
#include <ethercatcpp/core.h> Step 1 : EtherCAT configurations
Step 1.1 Master creation
The first step when you want to use an EtherCAT device is to create the master. Then you have to link this master with an network interface in your computer. If you want to use a redundant mode, you can link your master with an other network interface.
ethercatcpp::Master ethercat_master; // Master creation
ethercat_master.set_primary_interface ( "primary_interface_id" ); //Link primary interface to master
ethercat_master.set_redundant_interface ( "redundant_interface_id" ); //Link redundant interface to masterprimary_interface_id and redundant_interface_id are the network interface names on your computer (for instance: eth0, eth1).
Step 1.2 Bus creation
Now master is created, you have to describe your EtherCAT hardware bus. This simply consists in adding devices to the bus in same order as hardware: regarding hardware bus topology first element connected to your computer has to be the first element added to the bus and so on. If this order condition is not respected, your EtherCAT network will never work. Indeed device addressing in an ethercat bus is directly bound the position of slave devices in hadrware topology.
In our example, for a decoder device Beckhoff EL5101:
...
EK1100 ethercat_head; // create an EK1100 device (EtherCAT head)
EL5101 encoder; // create an EL5101 device (encoder)
// Linking device to bus in hardware order !!
ethercat_master.add(ethercat_head);//first device in hardware topology
ethercat_master.add(encoder); //second device in hardware topology
ethercat_master.init();That’s it there is no much more to do from user perspective: sequence of calls to add function must reflect the sequence of devices in hardware topology, starting from the primary interface. When the init() function is called, the master checks that all devices are trully available on the BUS, performs some internal configuration, then configure each device.
If anything went wrong during call to init() and exception is thrown, preventing the program to run. Otherwise, communciation can start.
Step 2 : Device usage
To use your EtherCAT device, you have to use a temporized cyclic loop. In cyclic loop, start to set configurations and commands, launch the next cycle and then get devices status and datas.
Step 2.1 : Main cyclic loop
An EtherCAT bus works as a cyclic loop. You can create the main cyclic loop the way you want. In the following example we use the synchro library defined in package pid-threading. It provides a Period object used to do that:
#include <pid/synchro.h>
...
pid::Period period(1ms);
while (not stop) {
...
if (ethercat_master.next_Cycle()) {//communicating with ethercat devices
//if no problem during communication
...
}
period.sleep();
}Remember to add the dependency to pid-threading in the CMakeLists.txt of your project:
PID_Dependency(pid-threading VERSION 0.9) # latest version of pid-threading at that timeand add the synchro component as a dependency of your component:
PID_Component(your-component-name
...
DEPEND ethercatcpp/core
pid/synchro
)This pattern simply implements the communication over the BUS. You can put it in a separate thread if you want BUT be aware that calls to ethercatcpp functions are not thread safe. In other words, any call to a device belonging to the same ethercat bus must be made in the same thread. This is the responsibility of the user to implement data sharing between this thread and other parts of your application.
It may also be important to enforce a real-time scheduling policy for the thread running this loop, in order to ensure communication will not be preempted by other running process on your system and so better ensure that real-time constraints are respected for ethercat communication. To do this you can use the realtime library of the pid-os-utilities package:
#include <pid/synchro.h>
#include <pid/real_time.h>
...
auto memory_locker = pid::make_current_thread_real_time();
pid::Period period(1ms);
while (not stop) {
...
if (ethercat_master.next_Cycle()) {//communicating with ethercat devices
//if no problem during communication
...
}
period.sleep();
}The line auto memory_locker = pid::make_current_thread_real_time(); simply converts the current thread as a real time one.
Remember to add the dependency to pid-os-utilities in the CMakeLists.txt of your project:
PID_Dependency(pid-os-utilities VERSION 3.2) # latest version of pid-threading at that timeand add the realtime component as a dependency of your component:
PID_Component(your-component-name
...
DEPEND ethercatcpp/core
pid/synchro
pid/realtime
) Step 2.2 : Device usage
Now communication is ready it is time to interact with the ethercat devices available on the bus. Interaction with devices follow this pattern:
...
while (not stop) {
//SET / CONFIGURE all devices
// => set the command buffer
if (ethercat_master.next_Cycle()) {//communicating with ethercat devices
// GET / UPDATE all devices datas
// => get the status buffer
}
period.sleep();
}So for instance with the devices used in this example:
...
while (not stop) {
//SET config
EL5101_1.enable_Latch_C(false);
EL5101_1.enable_Latch_Ext_Pos(false);
EL5101_1.enable_Counter_offset(false);
EL5101_1.enable_Latch_Ext_Neg(false);
EL5101_1.set_Counter_Offset_Value(0);
if (ethercat_master.next_Cycle()) {//communicating with ethercat devices
EL5101_1.print_All_Datas();
}
period.sleep();
}Step 3 Closing EtherCAT
When communication has to end, the EtherCAT communication must be “closed” properly and eventually prior specific actions have to be performed on devices. This is controlled using the end() function:
- exit the communication loop (in the example when variable
stopis set to true) - call the
end()funciton of master object:
ethercat_master.end();Step 4 example: Beckhoff EL5101 Encoder device
Here is the complete example:
#include <ethercatcpp/core.h>
#include <pid/signal_manager.h>
#include <pid/log.h>
#include <pid/real_time.h>
#include <pid/synchro.h>
#include <chrono>
using std::chrono_literals;
int main(int argc, char* argv[]) {
auto memory_locker = pid::make_current_thread_real_time();
// Master creation
ethercatcpp::Master master;
// Adding network interface (replace by adequet id)
master.set_primary_interface("eth0");
// Device definition
ethercatcpp::EK1100 EK1100;
ethercatcpp::EL5101 EL5101;
// Linking device to bus in hardware order !!
master.add(EK1100);
master.add(EL5101);
// Initilize the network
master.init();
bool stop = false;
pid::SignalManager::add(pid::SignalManager::Interrupt, "SigInt stop",
[&stop]() { stop = true; });
const auto period = std::chrono::duration<double>(control_period);
pid::Period period(1ms);
pid_log << "Starting periodic loop" << pid::endl;
while (not stop) {
// SET config => set the command buffer
EL5101.enable_Latch_On(ethercatcpp::EL5101::latch_pin_C, false);
EL5101.enable_Latch_On(ethercatcpp::EL5101::latch_pin_ext_pos, false);
EL5101.enable_Latch_On(ethercatcpp::EL5101::latch_pin_ext_neg, false);
EL5101.enable_Counter_offset(false);
EL5101.set_Counter_Offset_Value(0);
// If cycle is correct read data
if (master.next_Cycle()) {
pid_log << "EL5101 state:" << pid::endl;
EL5101.print_All_Datas();
}
period.sleep();
}
pid::SignalManager::remove(pid::SignalManager::Interrupt, "SigInt stop");
//Cleanly terminate communication
master.end();
}To be capable of interrupting the communication loop we use the signal-manager library of pid-os-utilities: when we enter CTRL+C (user interrupt signal) in terminal the stop variable is set to true and then the loop exits.
To use this library you need to add it as a dependency of your component:
PID_Component(your-component-name
...
DEPEND ethercatcpp/core
pid/synchro
pid/realtime
pid/signal-manager
)