SOEM是一个提供给用户应用程序的library以便用EtherCAT格式收取和发送数据,应用程序应自行提供如下方法:
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 给线程函数传递的参数
//************************************
在应用程序启动之后,我们需要设置网卡作为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。
映射结束后SOEM会请求从站进入SAFE_OP状态(这个请求过程被放置在了映射函数的结尾部分,但仅仅是请求而不是强制,因此我们后面还需要扫一遍看看)
char IOmap[128];
...
ec_config_map(&IOmap);
...
要进入状态OP,我们需要向outputs发送有效的数据。 EtherCAT帧处理分为ec_send_processdata和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);
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结构体里面有的)。
这里先看一下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类型的值, 该函数的三个参数:
#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");
}
有问题请留言。。。