Difference between revisions of "EtherCAT Master"
(→Demo) |
|||
(27 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
= Introduction = | = Introduction = | ||
− | In the context of the [https://www.i-mech.eu I-MECH project], funded by the European Commission under the ECSEL programme, we have ported the [https://github.com/OpenEtherCATsociety/SOEM SOEM EtherCAT library] on ERIKA. | + | In the context of the [https://www.i-mech.eu I-MECH project], funded by the European Commission under the ECSEL programme, we have ported the [https://github.com/OpenEtherCATsociety/SOEM SOEM EtherCAT library] on ERIKA. <br> |
This page explains how to setup and run the provided demo. | This page explains how to setup and run the provided demo. | ||
Line 14: | Line 14: | ||
== Demo == | == Demo == | ||
− | The provided demo assumes the Intel i210 | + | The provided demo assumes the Intel i210 network interface to be connected to a Beckhoff EL2004 EtherCAT slave. |
+ | The demo has been tested on Advantech ARK-3520P-U7A1E. | ||
To run the provided EtherCAT master demo, follow the next steps: | To run the provided EtherCAT master demo, follow the next steps: | ||
<ol> | <ol> | ||
− | <li> Download the EtherCAT | + | <li> Download the SOEM EtherCAT master stack from https://github.com/OpenEtherCATsociety/SOEM |
− | <li> Download the bare-metal x86 toolchain available at this link [http://erika-enterprise.com/download/erika3_x86_64_xtools.tar.gz http://erika-enterprise.com/download/erika3_x86_64_xtools.tar.gz] | + | <li> Download the bare-metal x86 toolchain available at this link [http://erika-enterprise.com/download/erika3_x86_64_xtools.tar.gz http://erika-enterprise.com/download/erika3_x86_64_xtools.tar.gz] and extract the downloaded archive on a local directory |
+ | <li> (optional) Put the directory containing the toolchain binaries in the <code>PATH</code> environment variable before running RT-Druid. | ||
<li> Run the RT-Druid tool | <li> Run the RT-Druid tool | ||
<li> Create a new project by clicking on <code>New</code> → <code>RT-Druid v3 Oil and C/C++ Project</code> as shown in the next Figure: | <li> Create a new project by clicking on <code>New</code> → <code>RT-Druid v3 Oil and C/C++ Project</code> as shown in the next Figure: | ||
Line 27: | Line 29: | ||
<li> Check the box for using an existing template and select <code>x86-64</code> → <code>Xen</code> → <code>EtherCAT SOEM demo</code>as shown in the next Figure: | <li> Check the box for using an existing template and select <code>x86-64</code> → <code>Xen</code> → <code>EtherCAT SOEM demo</code>as shown in the next Figure: | ||
[[File:Eclipse_ethercat_template.jpg|thumb|center|Figure 3: Selecting the EtherCAT template.]] | [[File:Eclipse_ethercat_template.jpg|thumb|center|Figure 3: Selecting the EtherCAT template.]] | ||
+ | <li> Right click the project and select <code>Properties</code> as shown in the following Figure: | ||
+ | [[File:Eclipse_project_properties.png|thumb|center|Figure 4: Eclipse project properties.]] | ||
+ | <li> Click <code>Oil</code> → <code>Generator properties</code>, enable project specific settings and specify the directory containing the SOEM library for ERIKA3: | ||
+ | [[File:Eclipse_ethercat_path.jpg|thumb|center|Figure 5: Setting the path of the SOEM library.]] | ||
+ | <li> If you didn't put the toolchain in the <code>PATH</code>directory, also set the <code>GNU Compiler prefix</code> property to <code>/full/path/x-tools/x86_64-unknown-elf/bin/x86_64-unknown-elf-</code>. | ||
+ | <li> Edit the <code>main.c</code> file and set the <code>SLAVE_NB</code> variable for the Bechkhoff EL2004 slave (note that the SOEM library numbers slaves starting from <code>1</code>). | ||
+ | <li> Follow the instructions at [[ERIKA3_on_the_Xen_hypervisor|this page]] for building and running the Xen iso image. | ||
</ol> | </ol> | ||
+ | == Motion control of CiA 402 compliant devices == | ||
+ | |||
+ | CiA® 402 refers to CAN in Automation (CiA) CANopen® Drives and Motion Control Profile 402, which standardizes the functional behavior of controllers for servo drives, frequency inverters, and stepper motors. | ||
+ | |||
+ | The profile describes the Finite State Automation (FSA) of the device, where each state specifies the internal/external behavior and the accepted commands: | ||
+ | |||
+ | [[File:CiA402_state_machine.png|thumb|center|Figure 6:FSA of CiA402.]] | ||
+ | |||
+ | The generic CanOpen specification for the automation provides an Object Dictionary, a grouping of objects accessible via the network in an ordered pre-defined fashion. Each object within the dictionary is addressed using a 16-bit index. | ||
+ | |||
+ | The CiA 402 extends the basic dictionary with specific objects and among these we can find ''Controlword'' and ''Statusword.'' | ||
+ | |||
+ | The '''Controlword''' is used to set the required states in the state machine. Object ID is '''0x6040''' and bits are defined as follows: | ||
+ | |||
+ | <table class="wikitable"> | ||
+ | <tr> | ||
+ | <th>Bit</th> | ||
+ | <th>Name</th> | ||
+ | <th>Abbreviation</th> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>0</td> | ||
+ | <td>Switch On</td> | ||
+ | <td>SO</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>1</td> | ||
+ | <td>Enable Voltage</td> | ||
+ | <td>EV</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>2</td> | ||
+ | <td>Quick Stop</td> | ||
+ | <td>QS</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>3</td> | ||
+ | <td>Enable Operation</td> | ||
+ | <td>EO</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>4-6</td> | ||
+ | <td>Operation mode specific</td> | ||
+ | <td>OMS</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>7</td> | ||
+ | <td>Fault Reset</td> | ||
+ | <td>F</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>8</td> | ||
+ | <td>Halt</td> | ||
+ | <td>H</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>9</td> | ||
+ | <td>Operation Mode Specific</td> | ||
+ | <td>OMS</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>10</td> | ||
+ | <td>Reserved</td> | ||
+ | <td>R</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>11-15</td> | ||
+ | <td>Manufacturer specific</td> | ||
+ | <td>MS</td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | |||
+ | The following bits are required when transitioning between states: | ||
+ | |||
+ | <table class="wikitable"><tr><th style="text-align: center;" colspan="2" rowspan="2" class="confluenceTh"><span>Transition</span></th><th style="text-align: center;" rowspan="2" class="confluenceTh"><span>Value (h)</span></th><th style="text-align: center;" colspan="7" class="confluenceTh"><p style="text-align: center;">BITS</p></th></tr><tr><th style="text-align: center;" colspan="1" class="confluenceTh"><span>0</span><br style="text-align: center;"/><span>(SO)</span></th><th style="text-align: center;" colspan="1" class="confluenceTh"><span>1</span><br style="text-align: center;"/><span>(EV)</span></th><th style="text-align: center;" colspan="1" class="confluenceTh"><span>2</span><br style="text-align: center;"/><span>(QS)</span></th><th style="text-align: center;" colspan="1" class="confluenceTh"><span>3</span><br style="text-align: center;"/><span>(EO)</span></th><th style="text-align: center;" colspan="1" class="confluenceTh"><span>4-6</span><br style="text-align: center;"/><span>(OMS)</span></th><th style="text-align: center;" colspan="1" class="confluenceTh"><span>7</span><br style="text-align: center;"/><span>(F)</span></th><th style="text-align: center;" colspan="1" class="confluenceTh"><span>8-15</span></th></tr><tr><th colspan="1" class="confluenceTh">15</th><th colspan="1" class="confluenceTh"><span>Fault → Ready to switch on</span></th><th colspan="1" class="confluenceTh">0x86</th><td colspan="1" class="confluenceTd">0</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">0</td><td colspan="1" class="confluenceTd">xxx</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">xxxxxxxx</td></tr><tr><th colspan="1" class="confluenceTh"><p>8/9/0</p></th><th class="confluenceTh"><p>Non fault → Ready to switch on</p></th><th colspan="1" class="confluenceTh">0x06</th><td class="confluenceTd"><p>0</p></td><td colspan="1" class="confluenceTd"><p>1</p></td><td colspan="1" class="confluenceTd"><p>1</p></td><td colspan="1" class="confluenceTd"><p>0</p></td><td colspan="1" class="confluenceTd"><p>xxx</p></td><td colspan="1" class="confluenceTd"><p>0</p></td><td colspan="1" class="confluenceTd"><p>xxxxxxxx</p></td></tr><tr><th class="confluenceTh">3/5</th><th class="confluenceTh">→ Switched on<sup>(1)</sup></th><th colspan="1" class="confluenceTh">0x07</th><td class="confluenceTd">1</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">0</td><td colspan="1" class="confluenceTd">xxx</td><td colspan="1" class="confluenceTd">0</td><td colspan="1" class="confluenceTd">xxxxxxxx</td></tr><tr><th class="confluenceTh">4/4a</th><th class="confluenceTh">→ Operation enabled</th><th colspan="1" class="confluenceTh"><p>0x0F/0x1F</p></th><td class="confluenceTd">1</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">1</td><td colspan="1" class="confluenceTd">xxx</td><td colspan="1" class="confluenceTd">0</td><td colspan="1" class="confluenceTd">xxxxxxxx</td></tr></table> | ||
+ | |||
+ | <sup>(1)</sup> The switched on state is often bypassed by the master | ||
+ | |||
+ | The '''Statusword''' provides information about the operational status of the drive and is sent from the slave to the master. Object ID is '''0x6041''' and defined bits are: | ||
+ | |||
+ | <table class="wikitable"> | ||
+ | <tr> | ||
+ | <th>Bit</th> | ||
+ | <th>Name</th> | ||
+ | <th>Abbreviation</th> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>0</td> | ||
+ | <td>Ready to Switch On</td> | ||
+ | <td>RTSO</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>1</td> | ||
+ | <td>Switched On</td> | ||
+ | <td>SO</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>2</td> | ||
+ | <td>Operation Enable</td> | ||
+ | <td>OE</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>3</td> | ||
+ | <td>Fault</td> | ||
+ | <td>F</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>4</td> | ||
+ | <td>Voltage Enabled</td> | ||
+ | <td>VE</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>5</td> | ||
+ | <td>Quick Stop</td> | ||
+ | <td>QS</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>6</td> | ||
+ | <td>Switch On Disabled</td> | ||
+ | <td>SOD</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>7</td> | ||
+ | <td>Warning</td> | ||
+ | <td>W</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>8</td> | ||
+ | <td>Manufacturer Specific</td> | ||
+ | <td>MA</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>9</td> | ||
+ | <td>Remote</td> | ||
+ | <td>RM</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>10</td> | ||
+ | <td>Target Reached</td> | ||
+ | <td>TR</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>11</td> | ||
+ | <td>Internal Limit Active</td> | ||
+ | <td>ILA</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>12-13</td> | ||
+ | <td>Operation Mode Specific</td> | ||
+ | <td>OMS</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>14-15</td> | ||
+ | <td>Manifacturer Specific</td> | ||
+ | <td>MS</td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | |||
+ | The following table outlines the Statusword bits and the represented state: | ||
+ | |||
+ | <table class="wikitable"><tr><th class="confluenceTh">State</th><th class="confluenceTh">Statusword Bits</th><th class="confluenceTh">Functional Meaning</th></tr><tr><td class="confluenceTd">Not ready to switch on</td><td class="confluenceTd">xxxx xxxx x0xx 0000</td><td class="confluenceTd">No CiA mode of operation selected</td></tr><tr><td class="confluenceTd">Switch on disabled<sup>(1)</sup></td><td class="confluenceTd">xxxx xxxx x100 0000</td><td class="confluenceTd">No motor torque - drive disabled</td></tr><tr><td class="confluenceTd">Ready to switch on</td><td class="confluenceTd">xxxx xxxx x01x 0001</td><td class="confluenceTd">No motor torque - drive disabled</td></tr><tr><td class="confluenceTd">Switched on</td><td class="confluenceTd">xxxx xxxx x01x 0011</td><td class="confluenceTd">No motor toque - drive disabled</td></tr><tr><td class="confluenceTd">Operation Enabled</td><td class="confluenceTd">xxxx xxxx x01x 0111</td><td class="confluenceTd">Motor has torque - drive enabled</td></tr><tr><td colspan="1" class="confluenceTd">Fault reaction active<sup>(1)</sup></td><td colspan="1" class="confluenceTd">xxxx xxxx x0xx 1111</td><td colspan="1" class="confluenceTd">Motor is losing torque - drive disabling</td></tr><tr><td colspan="1" class="confluenceTd">Fault</td><td colspan="1" class="confluenceTd">xxxx xxxx x0xx 1000</td><td colspan="1" class="confluenceTd">No motor torque - drive disabled</td></tr><tr><td colspan="1" class="confluenceTd">Quick stop active<sup>(2)</sup></td><td colspan="1" class="confluenceTd">xxxx xxxx x00x 0111</td><td colspan="1" class="confluenceTd">Not implemented</td></tr></table> | ||
+ | |||
+ | <sup>(1)</sup> these states are automatically transitioned through | ||
+ | |||
+ | <sup>(2)</sup> quick stop active is not currently implemented | ||
+ | |||
+ | == Controlling a CiA 402 drive with Erika + SOEM == | ||
+ | |||
+ | Using the SOEM EtherCat library, Erika can control the position of a digital servo motor. | ||
+ | Before executing Erika, it is useful to discover the mapping of the fundamentals SDOs (Controlword, Statusword, Current Position and Target Position) into the PDO in order to access them as simple memory in the IOmap provided by the SOEM stack. The latter provides the "slaveinfo" utility which prints the mapping of SDOs into the PDOs. The application must be executed on linux with the ''-map'' arguments | ||
+ | |||
+ | sudo ./slaveinfo <eth_interface> -map | ||
+ | |||
+ | |||
+ | This will print the mapping of SDOs into PDOs. A possible mapping could be the following: | ||
+ | |||
+ | PDO mapping according to CoE : | ||
+ | SM2 outputs | ||
+ | addr b index: sub bitl data_type name | ||
+ | [0x0000.0] 0x607A:0x00 0x20 INTEGER32 Target position | ||
+ | [0x0004.0] 0x60FE:0x01 0x20 UNSIGNED32 Physical outputs | ||
+ | [0x0008.0] 0x6040:0x00 0x10 UNSIGNED16 Controlword | ||
+ | SM3 inputs | ||
+ | addr b index: sub bitl data_type name | ||
+ | [0x000A.0] 0x6064:0x00 0x20 INTEGER32 Position actual value | ||
+ | [0x000E.0] 0x60FD:0x00 0x20 UNSIGNED32 Digital inputs | ||
+ | [0x0012.0] 0x6041:0x00 0x10 UNSIGNED16 Statusword | ||
+ | |||
+ | These fields can be easily accessed using the function ''set_output_int16'' and ''get_input_int16'' implemente in the file <code>oshw/erika/oshw.c</code>: | ||
+ | |||
+ | void set_output_int16 (uint16_t slave_nb, uint8_t module_index, int16_t value) | ||
+ | { | ||
+ | uint8_t *data_ptr; | ||
+ | |||
+ | data_ptr = ec_slave[slave_nb].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; | ||
+ | } | ||
+ | |||
+ | void get_input_int16(uint16_t slave_nb, uint8_t module_index, int16_t *value) | ||
+ | { | ||
+ | uint8_t *data_ptr; | ||
+ | |||
+ | data_ptr = ec_slave[slave_nb].inputs; | ||
+ | /* Move pointer to correct module index*/ | ||
+ | data_ptr += module_index * 2; | ||
+ | /* Read value byte by byte since all targets can't handle misaligned | ||
+ | addresses */ | ||
+ | *value |= ((*data_ptr++) & 0xFF); | ||
+ | *value |= ((*data_ptr) << 8) & 0xff00; | ||
+ | } | ||
+ | |||
+ | After collecting such preliminary infos, the motor can be controlled following these steps: | ||
+ | <ol> | ||
+ | <li> '''Bring the slave in OP state''': After performing the NIC i210 init functions, Distributed Clock setup and PDO mapping, the SOEM stack shall let the slave reach the ''OPERATIONAL state'' as required by CiA402. | ||
+ | </li> | ||
+ | |||
+ | ec_config_map(&IOmap); /*map slave's PDO into IOmap.inputs and IOmap.outputs */ | ||
+ | ec_configdc(); /* Configure the Distributed Clock */ | ||
+ | |||
+ | set_operational(); /* CiA402 requires OP state */ | ||
+ | print_slave_info(); | ||
+ | |||
+ | After the initialization functions, Erika will enter in an endless control loop. | ||
+ | |||
+ | <li>'''Read the Statusword''': at the beginning of the control loop, after the transmission/reception of the periodic PDO, Erika shall read the Statusword in order to acquire the current state of the drive. The function | ||
+ | ''get_input_int16'' can be used: </li> | ||
+ | |||
+ | #define SLAVE_NB (1) | ||
+ | |||
+ | int16_t statusword = 0; | ||
+ | /* The input address for get_input_int16 must be devided by two. 0xA | ||
+ | is the starting address of inputs*/ | ||
+ | get_input_int16(SLAVE_NB, (0x12 - 0xA) >> 1, (int16_t*)&statusword); | ||
+ | |||
+ | |||
+ | <li> '''CiA402 FSA implementation''': after the acquisition of the Statusword, Erika shall set the drive into the '''Operation Enable''' state. | ||
+ | According to the specification, a simple FSA implementation could be the following:</li> | ||
+ | |||
+ | #define STATUS_WORD_MASK(x) (x &= 0x6F) | ||
+ | #define MODES_OF_OPERATION_INDEX (0x6060) | ||
+ | |||
+ | typedef enum{ | ||
+ | control_switch_on = 0, | ||
+ | control_enable_voltage = 1, | ||
+ | control_quick_stop = 2, | ||
+ | control_enable_operation = 3, | ||
+ | control_fault_reset = 7, | ||
+ | control_4 = 4, | ||
+ | control_5 = 5, | ||
+ | control_6 = 6, | ||
+ | control_8 = 8 | ||
+ | }control_bit_t; | ||
+ | |||
+ | typedef enum{ | ||
+ | /*xxxx xxxx x0xx 0000*/ Not_ready_to_switch_on = 0x0, | ||
+ | /*xxxx xxxx x1xx 0000*/ Switch_on_disabled = 1 << 6, | ||
+ | /*xxxx xxxx x01x 0001*/ Ready_to_switch_on = (1 << 5) | 1, | ||
+ | /*xxxx xxxx x01x 0011*/ Switch_on = (1 << 5) | 3, | ||
+ | /*xxxx xxxx x01x 0111*/ Operation_enabled = (1 << 5) | 7, | ||
+ | /*xxxx xxxx x00x 0111*/ Quick_stop_active = 0x7, | ||
+ | /*xxxx xxxx x0xx 1111*/ Fault_reaction_active = 0x0f, | ||
+ | /*xxxx xxxx x0xx 1000*/ Fault = 1 << 3 | ||
+ | }statusword_state_t; | ||
+ | |||
+ | STATUS_WORD_MASK(statusword); | ||
+ | |||
+ | switch(statusword){ | ||
+ | case (Not_ready_to_switch_on): { | ||
+ | /* Now the FSM should automatically go to Switch_on_disabled*/ | ||
+ | break; | ||
+ | } | ||
+ | case (Switch_on_disabled): { | ||
+ | /* Automatic transition (2)*/ | ||
+ | controlword = 0; | ||
+ | controlword |= (1 << control_enable_voltage) | ||
+ | | (1 << control_quick_stop); | ||
+ | break; | ||
+ | } | ||
+ | case (Ready_to_switch_on): { | ||
+ | /* Switch on command for transition (3) */ | ||
+ | controlword |= 1 << control_switch_on; | ||
+ | break; | ||
+ | } | ||
+ | case (Switch_on): { | ||
+ | /* Enable operation command for transition (4) */ | ||
+ | controlword |= 1 << control_enable_operation; | ||
+ | break; | ||
+ | } | ||
+ | case (Operation_enabled): { | ||
+ | /* Setting modes of operation | ||
+ | * Value Description | ||
+ | -128...-2 Reserved | ||
+ | -1 No mode | ||
+ | 0 Reserved | ||
+ | 1 Profile position mode | ||
+ | 2 Velocity (not supported) | ||
+ | 3 Profiled velocity mode | ||
+ | 4 Torque profiled mode | ||
+ | 5 Reserved | ||
+ | 6 Homing mode | ||
+ | 7 Interpolated position mode | ||
+ | 8 Cyclic Synchronous position | ||
+ | ...127 Reserved*/ | ||
+ | |||
+ | uint16_t mode = 8; /* Setting Cyclic Synchronous position */ | ||
+ | int mode_size = sizeof(mode); | ||
+ | SDO_result = ec_SDOwrite(SLAVE_NB, MODES_OF_OPERATION_INDEX, 0, | ||
+ | 0, mode_size, &mode, EC_TIMEOUTRXM); | ||
+ | break; | ||
+ | } | ||
+ | case (Quick_stop_active): { | ||
+ | break; | ||
+ | } | ||
+ | case (Fault_reaction_active): { | ||
+ | break; | ||
+ | } | ||
+ | case (Fault): { | ||
+ | /* Returning to Switch on Disabled */ | ||
+ | controlword = (1 << control_fault_reset); | ||
+ | break; | ||
+ | } | ||
+ | default:{ | ||
+ | OSEE_PRINT("Unrecognized status\n"); | ||
+ | break;} | ||
+ | } | ||
+ | |||
+ | Please note that in the '''Operation Enabled''' state, the Mode of Operation is selected through the '''0x6060''' SDO. | ||
+ | |||
+ | <li>'''Writing the Controlword''': after setting properly the Controlword, it can be written using: </li> | ||
+ | set_output_int16(SLAVE_NB, 8 >> 1, controlword); | ||
+ | |||
+ | <li>'''Getting ''Current position'' and setting ''Target position'' ''': in the ''Operation Enable'' state, PDOs' mapping can be | ||
+ | used to get the current position of the drive and then set the target position:</li> | ||
+ | |||
+ | int16_t value_high = 0; | ||
+ | int16_t value_low = 0; | ||
+ | int32_t value = 0; | ||
+ | /* Getting value */ | ||
+ | get_input_int16(SLAVE_NB, (0xA - 0xA) >> 1, (int16_t*) &value_low); | ||
+ | get_input_int16(SLAVE_NB, (0xC - 0xA) >> 1, (int16_t*) &value_high); | ||
+ | value = ((value_high << 16) & 0xffff0000) | (value_low & 0x00ffff); | ||
+ | value += 400; | ||
+ | |||
+ | /* Setting output */ | ||
+ | set_output_int16(SLAVE_NB, 0x0 >> 1, (int16_t)(value & 0xffff) ); | ||
+ | set_output_int16(SLAVE_NB, 0x2 >> 1, (int16_t)((value >> 16) & 0xffff)); | ||
+ | </ol> | ||
+ | |||
+ | |||
+ | |||
+ | All the steps described above have been tested on the ELMO's Gold duet servo drive: | ||
+ | [[File:Gold_duet_40.jpg|thumb|center|Figure 6: ELMO Gold Duet 40]] | ||
[[Category:Tutorial]] | [[Category:Tutorial]] |
Latest revision as of 10:03, 27 May 2019
Contents
Introduction
In the context of the I-MECH project, funded by the European Commission under the ECSEL programme, we have ported the SOEM EtherCAT library on ERIKA.
This page explains how to setup and run the provided demo.
Requirements
For running the demo, an x86-64 platform with the following characteristics is needed:
- Supporting hardware-assisted virtualization (i.e. VT-x, VT-D, EPT) and capable of running the Xen hypervisor in HVM mode.
- With an Intel i210 network interface.
Demo
The provided demo assumes the Intel i210 network interface to be connected to a Beckhoff EL2004 EtherCAT slave. The demo has been tested on Advantech ARK-3520P-U7A1E.
To run the provided EtherCAT master demo, follow the next steps:
- Download the SOEM EtherCAT master stack from https://github.com/OpenEtherCATsociety/SOEM
- Download the bare-metal x86 toolchain available at this link http://erika-enterprise.com/download/erika3_x86_64_xtools.tar.gz and extract the downloaded archive on a local directory
- (optional) Put the directory containing the toolchain binaries in the
PATH
environment variable before running RT-Druid. - Run the RT-Druid tool
- Create a new project by clicking on
New
→RT-Druid v3 Oil and C/C++ Project
as shown in the next Figure: - Name the new project (e.g.,
mytest
) and select the Cross-GCC as shown in the next Figure: - Check the box for using an existing template and select
x86-64
→Xen
→EtherCAT SOEM demo
as shown in the next Figure: - Right click the project and select
Properties
as shown in the following Figure: - Click
Oil
→Generator properties
, enable project specific settings and specify the directory containing the SOEM library for ERIKA3: - If you didn't put the toolchain in the
PATH
directory, also set theGNU Compiler prefix
property to/full/path/x-tools/x86_64-unknown-elf/bin/x86_64-unknown-elf-
. - Edit the
main.c
file and set theSLAVE_NB
variable for the Bechkhoff EL2004 slave (note that the SOEM library numbers slaves starting from1
). - Follow the instructions at this page for building and running the Xen iso image.
Motion control of CiA 402 compliant devices
CiA® 402 refers to CAN in Automation (CiA) CANopen® Drives and Motion Control Profile 402, which standardizes the functional behavior of controllers for servo drives, frequency inverters, and stepper motors.
The profile describes the Finite State Automation (FSA) of the device, where each state specifies the internal/external behavior and the accepted commands:
The generic CanOpen specification for the automation provides an Object Dictionary, a grouping of objects accessible via the network in an ordered pre-defined fashion. Each object within the dictionary is addressed using a 16-bit index.
The CiA 402 extends the basic dictionary with specific objects and among these we can find Controlword and Statusword.
The Controlword is used to set the required states in the state machine. Object ID is 0x6040 and bits are defined as follows:
Bit | Name | Abbreviation |
---|---|---|
0 | Switch On | SO |
1 | Enable Voltage | EV |
2 | Quick Stop | QS |
3 | Enable Operation | EO |
4-6 | Operation mode specific | OMS |
7 | Fault Reset | F |
8 | Halt | H |
9 | Operation Mode Specific | OMS |
10 | Reserved | R |
11-15 | Manufacturer specific | MS |
The following bits are required when transitioning between states:
Transition | Value (h) | BITS | |||||||
---|---|---|---|---|---|---|---|---|---|
0 (SO) | 1 (EV) | 2 (QS) | 3 (EO) | 4-6 (OMS) | 7 (F) | 8-15 | |||
15 | Fault → Ready to switch on | 0x86 | 0 | 1 | 1 | 0 | xxx | 1 | xxxxxxxx |
8/9/0 | Non fault → Ready to switch on | 0x06 | 0 | 1 | 1 | 0 | xxx | 0 | xxxxxxxx |
3/5 | → Switched on(1) | 0x07 | 1 | 1 | 1 | 0 | xxx | 0 | xxxxxxxx |
4/4a | → Operation enabled | 0x0F/0x1F | 1 | 1 | 1 | 1 | xxx | 0 | xxxxxxxx |
(1) The switched on state is often bypassed by the master
The Statusword provides information about the operational status of the drive and is sent from the slave to the master. Object ID is 0x6041 and defined bits are:
Bit | Name | Abbreviation |
---|---|---|
0 | Ready to Switch On | RTSO |
1 | Switched On | SO |
2 | Operation Enable | OE |
3 | Fault | F |
4 | Voltage Enabled | VE |
5 | Quick Stop | QS |
6 | Switch On Disabled | SOD |
7 | Warning | W |
8 | Manufacturer Specific | MA |
9 | Remote | RM |
10 | Target Reached | TR |
11 | Internal Limit Active | ILA |
12-13 | Operation Mode Specific | OMS |
14-15 | Manifacturer Specific | MS |
The following table outlines the Statusword bits and the represented state:
State | Statusword Bits | Functional Meaning |
---|---|---|
Not ready to switch on | xxxx xxxx x0xx 0000 | No CiA mode of operation selected |
Switch on disabled(1) | xxxx xxxx x100 0000 | No motor torque - drive disabled |
Ready to switch on | xxxx xxxx x01x 0001 | No motor torque - drive disabled |
Switched on | xxxx xxxx x01x 0011 | No motor toque - drive disabled |
Operation Enabled | xxxx xxxx x01x 0111 | Motor has torque - drive enabled |
Fault reaction active(1) | xxxx xxxx x0xx 1111 | Motor is losing torque - drive disabling |
Fault | xxxx xxxx x0xx 1000 | No motor torque - drive disabled |
Quick stop active(2) | xxxx xxxx x00x 0111 | Not implemented |
(1) these states are automatically transitioned through
(2) quick stop active is not currently implemented
Controlling a CiA 402 drive with Erika + SOEM
Using the SOEM EtherCat library, Erika can control the position of a digital servo motor. Before executing Erika, it is useful to discover the mapping of the fundamentals SDOs (Controlword, Statusword, Current Position and Target Position) into the PDO in order to access them as simple memory in the IOmap provided by the SOEM stack. The latter provides the "slaveinfo" utility which prints the mapping of SDOs into the PDOs. The application must be executed on linux with the -map arguments
sudo ./slaveinfo <eth_interface> -map
This will print the mapping of SDOs into PDOs. A possible mapping could be the following:
PDO mapping according to CoE : SM2 outputs addr b index: sub bitl data_type name [0x0000.0] 0x607A:0x00 0x20 INTEGER32 Target position [0x0004.0] 0x60FE:0x01 0x20 UNSIGNED32 Physical outputs [0x0008.0] 0x6040:0x00 0x10 UNSIGNED16 Controlword SM3 inputs addr b index: sub bitl data_type name [0x000A.0] 0x6064:0x00 0x20 INTEGER32 Position actual value [0x000E.0] 0x60FD:0x00 0x20 UNSIGNED32 Digital inputs [0x0012.0] 0x6041:0x00 0x10 UNSIGNED16 Statusword
These fields can be easily accessed using the function set_output_int16 and get_input_int16 implemente in the file oshw/erika/oshw.c
:
void set_output_int16 (uint16_t slave_nb, uint8_t module_index, int16_t value) { uint8_t *data_ptr; data_ptr = ec_slave[slave_nb].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; }
void get_input_int16(uint16_t slave_nb, uint8_t module_index, int16_t *value) { uint8_t *data_ptr; data_ptr = ec_slave[slave_nb].inputs; /* Move pointer to correct module index*/ data_ptr += module_index * 2; /* Read value byte by byte since all targets can't handle misaligned addresses */ *value |= ((*data_ptr++) & 0xFF); *value |= ((*data_ptr) << 8) & 0xff00; }
After collecting such preliminary infos, the motor can be controlled following these steps:
- Bring the slave in OP state: After performing the NIC i210 init functions, Distributed Clock setup and PDO mapping, the SOEM stack shall let the slave reach the OPERATIONAL state as required by CiA402.
- Read the Statusword: at the beginning of the control loop, after the transmission/reception of the periodic PDO, Erika shall read the Statusword in order to acquire the current state of the drive. The function get_input_int16 can be used:
- CiA402 FSA implementation: after the acquisition of the Statusword, Erika shall set the drive into the Operation Enable state. According to the specification, a simple FSA implementation could be the following:
- Writing the Controlword: after setting properly the Controlword, it can be written using:
- Getting Current position and setting Target position : in the Operation Enable state, PDOs' mapping can be used to get the current position of the drive and then set the target position:
ec_config_map(&IOmap); /*map slave's PDO into IOmap.inputs and IOmap.outputs */ ec_configdc(); /* Configure the Distributed Clock */ set_operational(); /* CiA402 requires OP state */ print_slave_info();
After the initialization functions, Erika will enter in an endless control loop.
#define SLAVE_NB (1) int16_t statusword = 0; /* The input address for get_input_int16 must be devided by two. 0xA is the starting address of inputs*/ get_input_int16(SLAVE_NB, (0x12 - 0xA) >> 1, (int16_t*)&statusword);
#define STATUS_WORD_MASK(x) (x &= 0x6F) #define MODES_OF_OPERATION_INDEX (0x6060) typedef enum{ control_switch_on = 0, control_enable_voltage = 1, control_quick_stop = 2, control_enable_operation = 3, control_fault_reset = 7, control_4 = 4, control_5 = 5, control_6 = 6, control_8 = 8 }control_bit_t; typedef enum{ /*xxxx xxxx x0xx 0000*/ Not_ready_to_switch_on = 0x0, /*xxxx xxxx x1xx 0000*/ Switch_on_disabled = 1 << 6, /*xxxx xxxx x01x 0001*/ Ready_to_switch_on = (1 << 5) | 1, /*xxxx xxxx x01x 0011*/ Switch_on = (1 << 5) | 3, /*xxxx xxxx x01x 0111*/ Operation_enabled = (1 << 5) | 7, /*xxxx xxxx x00x 0111*/ Quick_stop_active = 0x7, /*xxxx xxxx x0xx 1111*/ Fault_reaction_active = 0x0f, /*xxxx xxxx x0xx 1000*/ Fault = 1 << 3 }statusword_state_t; STATUS_WORD_MASK(statusword); switch(statusword){ case (Not_ready_to_switch_on): { /* Now the FSM should automatically go to Switch_on_disabled*/ break; } case (Switch_on_disabled): { /* Automatic transition (2)*/ controlword = 0; controlword |= (1 << control_enable_voltage) | (1 << control_quick_stop); break; } case (Ready_to_switch_on): { /* Switch on command for transition (3) */ controlword |= 1 << control_switch_on; break; } case (Switch_on): { /* Enable operation command for transition (4) */ controlword |= 1 << control_enable_operation; break; } case (Operation_enabled): { /* Setting modes of operation * Value Description -128...-2 Reserved -1 No mode 0 Reserved 1 Profile position mode 2 Velocity (not supported) 3 Profiled velocity mode 4 Torque profiled mode 5 Reserved 6 Homing mode 7 Interpolated position mode 8 Cyclic Synchronous position ...127 Reserved*/ uint16_t mode = 8; /* Setting Cyclic Synchronous position */ int mode_size = sizeof(mode); SDO_result = ec_SDOwrite(SLAVE_NB, MODES_OF_OPERATION_INDEX, 0, 0, mode_size, &mode, EC_TIMEOUTRXM); break; } case (Quick_stop_active): { break; } case (Fault_reaction_active): { break; } case (Fault): { /* Returning to Switch on Disabled */ controlword = (1 << control_fault_reset); break; } default:{ OSEE_PRINT("Unrecognized status\n"); break;} }
Please note that in the Operation Enabled state, the Mode of Operation is selected through the 0x6060 SDO.
set_output_int16(SLAVE_NB, 8 >> 1, controlword);
int16_t value_high = 0; int16_t value_low = 0; int32_t value = 0; /* Getting value */ get_input_int16(SLAVE_NB, (0xA - 0xA) >> 1, (int16_t*) &value_low); get_input_int16(SLAVE_NB, (0xC - 0xA) >> 1, (int16_t*) &value_high); value = ((value_high << 16) & 0xffff0000) | (value_low & 0x00ffff); value += 400; /* Setting output */ set_output_int16(SLAVE_NB, 0x0 >> 1, (int16_t)(value & 0xffff) ); set_output_int16(SLAVE_NB, 0x2 >> 1, (int16_t)((value >> 16) & 0xffff));
All the steps described above have been tested on the ELMO's Gold duet servo drive: