EtherCAT-SOEM官方教程+测试

这篇文章主要是翻译SOEM的官方教程,英语读着不顺的直接来看这个啦~~

1 官方文档解析

1.1 功能简介

SOEM是一个提供给用户应用程序的library以便用EtherCAT格式收取和发送数据,应用程序应自行提供如下方法:

  1. 读取和写入SOEM发送和接受到的过程数据;
  2. 保持本地IO数据和全局Iomap同步;
  3. 检测SOEM发送来的错误;
  4. 管理SOEM报告的错误;

1.2 基本操作

1)新建SOEM任务线程osal_thread_create

osal_thread_create(&thread1, 128000, &ecatcheck, (void*) &ctime);

各参数对应含义如下:

//************************************

// Method:    osal_thread_create

// FullName:  创建SOEM任务线程

// Returns:   创建成功返回1,否则为0

// Parameter: void * * thandle  void类型指针,指向线程

// Parameter: int stacksize     线程堆栈尺寸,表示与此应用的堆栈尺寸相同时为0,这时主线程与创建的线程有一样长度的堆栈。并且其长度会根据需要自动变长

// Parameter: void * func       线程函数所指向的起始地址的函数

// Parameter: void * param      给线程函数传递的参数

//************************************

2)初始化ec_init(char* ifname)

在应用程序启动之后,我们需要设置网卡作为EtherCAT以太网接口。在一个简单的设置中,我们调用ec_init(ifname),如果SOEM支持电缆冗余,我们调用ec_init_redundant,它将打开第二个端口作为备份。如果您在nicdrv.c中选择了一个专用NIC,则可以像发送name一样发送NULL。如果成功,它将返回>0。

SOEM是一个轻量级的ethercat主站,用于嵌入式系统,它只支持运行时配置。它请求一个地址为0的BRD(宽频带读),网络中所有正常的从站都会响应这个请求,因此我们将得到一个与网络中从服务器数量相等的工作计数器。ec_config_init还为支持它的从服务器设置邮箱。当ec_config_init完成时,它将请求所有从站进入PRE_OP状态。所有读取和配置的数据都存储在一个全局结构体数组ec_slave中,该数组充当键值的占位符,详细信息请参考ec_slave。

/* initialise SOEM, bind socket to ifname */
   if (ec_init(ifname))

这个函数主要是初始化SOEM,将网卡和socket绑定起来。其中ifname是个const char*类型的字符串,可以运行slaveinfo.c来获取。比如我的电脑就是:

 \Device\NPF_{9A10D941-1301-4A51-A856-024B1399EA32}

而网卡和socket绑定是指,针对多网口计算机,这个通讯过程是一对多(从站对计算机网卡)的,所以要配置真正连接从站的网卡进行绑定。

 /* find and auto-config slaves */
   if ( ec_config_init(FALSE) > 0 )
   {
      rprintp("%d slaves found and configured.\n",ec_slavecount);

SOEM现在已经发现并配置了它所连接的网络结构。现在我们可以验证所有的从站都如预期的那样存在。这些定义可以由脱机.h文件中的外部工具生成。这些定义可以替换为一个保存从属号的结构。

#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");  

通过上面的步骤,我们就把网络部分配置好了。 如果从站支持邮箱功能,也会自动把它配置好. 接下来,我们将创建一个IOmap,并配置SyncManager(SM,同步管理器)和FMMU来链接EtherCAT主站和从站. IO映射是自动完成的,SOEM力求使逻辑过程映像尽可能紧凑。 It is done by trying to fit Bit oriented slaves together in single bytes. 下面的例子是八个从站,我们从图上可以看到他们的排列顺序。 During mapping SOEM also calculates an expected WKC for the IO mapped together.在映射的过程中,SOEM还计算了IO映射后的理论工作计数器(expected WKC)。之后通过与实际比较可以检测是否发生error。

  • 输出模块被放在了IOmap的最前面;
  • 输入模块紧随其后

映射结束后SOEM会请求从站进入SAFE_OP状态(这个请求过程被放置在了映射函数的结尾部分,但仅仅是请求而不是强制,因此我们后面还需要扫一遍看看)

char IOmap[128];
...
      ec_config_map(&IOmap);
...
EtherCAT-SOEM官方教程+测试_第1张图片 memory layout, mapping between physical and logical

 

要进入状态OP,我们需要向outputs发送有效的数据。 EtherCAT帧处理分为ec_send_processdata和ec_receive_processdata两个函数完成。

  • ec_send_processdata 发送NIC上的帧,并将帧保存在堆栈上,以便接收和获取。
  • ec_receive_processdata(EC_TIMEOUTRET) 试图获取堆栈上的帧。我们发送一个参数表示我们将尝试获取帧多长时间。ec_receive_processdata返回工作计数器
 /* 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(OP状态).

Application

IO data is accessed through the IOmap, the ec_slave struct keep 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:

我们之后要访问IO数据就可以通过IOmap搞定,ec_slave这个结构体的inputs或是outputs指针一直指向IOmap数组中自己的输入或者输出的起始字节Istartbit或是Ostartbit存放第一个起始字节的起始位。即使SOEM是把从站数据紧密结合为帧结构以便发送,我们也可以通过这样的方式在字节层面上找到我们需要的IO数据。用上图中的slave 8 作为例子来看一下(这些都是在slave结构体里面有的)。

  • Slave:8
    1. Name:EL2622
    2. Output size: 2bits
    3. Input size: 0bits
    4. Configured address: 1008
    5. Outputs address: 18cf6
    6. Inputs address: 0
    7. FMMU0 Ls:2 Ll: 1 Lsb:4 Leb:5 Ps:f00 Psb:0 Ty:2 Act:1

这里先看一下SOEM的ec_slave 结构(在文件ethercatmain.h文件中,下面省略了一部分):

/** for list of ethercat slaves detected */
typedef struct ec_slave
{
   /** state of slave从站状态 */
   uint16           state;

   /** Configured address */
   uint16           configadr;
   
   /** output bits */
   uint16           Obits;
   /** output bytes, if Obits < 8 then Obytes = 0 */
   uint32           Obytes;
   /** output pointer in IOmap buffer */
   uint8            *outputs;
   /** startbit in first output byte */
   uint8            Ostartbit;

   /** input bits */
   uint16           Ibits;
   /** input bytes, if Ibits < 8 then Ibytes = 0 */
   uint32           Ibytes;
   /** input pointer in IOmap buffer */
   uint8            *inputs;
   /** startbit in first input byte */
   uint8            Istartbit;
   
   /** FMMU structure */
   ec_fmmut         FMMU[EC_MAXFMMU];
   /** FMMU0 function */
   uint8            FMMU0func;
   /** FMMU1 function */
   uint8            FMMU1func;
   /** FMMU2 function */
   uint8            FMMU2func;
   /** FMMU3 function */
   uint8            FMMU3func;
   
   ...

} ec_slavet;

从站结构体中还有个FMMU结构体,找到定义:

/** record for FMMU */
PACKED_BEGIN
typedef struct PACKED ec_fmmu
{
   uint32  LogStart;	//Ls:逻辑地址起点
   uint16  LogLength;	//Ll:逻辑地址长度
   uint8   LogStartbit;	//Lsb:逻辑地址开始位置
   uint8   LogEndbit;	//Leb:逻辑地址结束位置
   uint16  PhysStart;	//Ps:物理地址起点
   uint8   PhysStartBit;//Psb:物理地址开始位置
   uint8   FMMUtype;	//Ty:类型
   uint8   FMMUactive;	//Act:active
   uint8   unused1;
   uint16  unused2;
}  ec_fmmut;
PACKED_END

outputs: 18cf6 是slave 8 在IOmap的物理起始地址(非逻辑哦)
FMMU.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.
Ostartbit告诉我们咋在被组合起来的字节里面找到本从站对应的那个bit或者几个bit的起始位置。当然啦,如果从站的输入或者输出刚好占了byte的整数倍,那也就不用什么偏移地址了,所以这时这个值时0。

下面是获取不同的数据类型的一些例子(前为译,我觉得应该是读写从站IO端子模块上特定channel上的值):

当需要考虑内存对齐时,设置输出int16类型的值, 该函数的三个参数:

  • slave number in ethercat network(从站的编号)
  • module index as index internal to the slave in case more than one channel(模块的编号,可以理解做端口所在编号比如EL1008的第一个端口)
  • 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);

也可以这么操作,感觉就很帅了。问题是当你并不知道用户将要安装哪些从站模块的时候,这样的做法没什么实际用途。

typedef struct PACKED
{
        int16      outvalue1;
        int16      outvalue2;
} out_EL4132t;

out_EL4132t             *out_EL4132;
...
   /* connect struct pointers to slave I/O pointers */
   out_EL4132 = (out_EL4132t*) ec_slave[3].outputs;
   out_EL4132->outvalue2 = 0x3FFF;
   
...      

异常和错误处理:

Identify and manage errors. The key is the Working Counter, CRC errors and errors local to the slave causing a state change can be detected by loss of Working Counter since the syncmanagers won't get updated. When returning Working Counter don't match Expected Working Counter something is wrong, then it is up to an error handler to act, locate the erroneous slave and decide what action to perform. The error may not be fatal. Some basic code from simple_test.

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 > 0)
          { 
             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_slave[slave].islost = TRUE;
              printf("ERROR : slave %d lost\n",slave);   
            }
          }
       }
       if (ec_slave[slave].islost)
       {
         if(!ec_slave[slave].state)
         {
           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");
}  

有问题请留言。。。

你可能感兴趣的:(EtherCAT,SOEM)