1、EtherCAT主站
安路今年推出一款的FPSoC DR190M,组合了硬核处理器系统和FPGA,通过高带宽总线进行二者的互联。多核 ARM/RISC-V 处理器系统与安路FPGA可编程逻辑相结合于一颗芯片中,提供了应用类ARM/RISC-V处理器的性能 与生态系统,并且具备安路FPGA的灵活性、低功耗、可扩展 SoC平台。ARM/RISC-V CPU是处理器系统的核心,同时系统还包含on-chip RAM,内存接口和丰富的外设互联接口,定位复杂嵌入式系统、低功耗和高性能芯片市场。
DR190M芯片的PS端有两个以太网控制器,基于以太网的功能,可以实现EtherCAT主站控制功能。米尔-安路MYD-YM90X开发套件的芯片就是这款芯片。

1.1 编译SOEM
SOEM(Simple Opensource EtherCAT Master)协议栈是很便于使用的开源EtherCAT Master主站协议栈。下载SOEM源码。

在DR190M开发环境中配置交叉编译需要使用的编译器。配置脚本的内容下,路径需要根据具体的开发环境进行修改
#!/bin/sh
export PATH=$PATH:<具体的工具链安放位置>/MYD-YM90X-Distribution-L6.1.54-V1.0.0//toolchains/aarch64-linux/bin
export CROSS_COMPILE=aarch64-linux-gnu
export ARCH=arm64
export PREFIX=aarch64-linux-gnu
export AS=aarch64-linux-gnu-as
export LD=aarch64-linux-gnu-ld
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
export AR=aarch64-linux-gnu-ar
export NM=aarch64-linux-gnu-nm
export STRIP=aarch64-linux-gnu-strip
export OBJCOPY=aarch64-linux-gnu-objcopy
export OBJDUMP=aarch64-linux-gnu-objdump
使用source指令导入上述脚本,使开发环境配置生效。
source xxx.sh
解压下载的SOEM源代码。

修改其中的示例代码slaveinfo用于后续的演示。
#include <stdlib.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <math.h>
#include "osal.h"
#include "ethercattype.h"
#include "nicdrv.h"
#include "ethercatbase.h"
#include "ethercatmain.h"
#include "ethercatdc.h"
#include "ethercatcoe.h"
#include "ethercatfoe.h"
#include "ethercatconfig.h"
#include "ethercatprint.h"
#include "ethercat.h"
static char IOmap[1024];
volatile uint8_t mode = 0;
static uint8_t slave = 1;
static volatile boolean inOP;
volatile int wkc;
int expectedWKC;
uint8 currentgroup = 0;
uint16 output_fsm=0;
void show_slave_info()
{
for (int cnt = 1; cnt <= ec_slavecount; cnt++)
{
printf("\\nSlave:%d\\n Name:%s\\n Output size: %dbits\\n Input size: %dbits\\n State: %d\\n Delay: %d[ns]\\n Has DC: %d\\n",cnt, ec_slave[cnt].name, ec_slave[cnt].Obits,ec_slave[cnt].Ibits, ec_slave[cnt].state,ec_slave[cnt].pdelay, ec_slave[cnt].hasdc);
printf(" Activeports:%d.%d.%d.%d\\n", (ec_slave[cnt].activeports & 0x01) > 0 ,(ec_slave[cnt].activeports & 0x02) > 0 ,(ec_slave[cnt].activeports & 0x04) > 0 ,(ec_slave[cnt].activeports & 0x08)>0 );
printf(" Configured address: %x\\n", ec_slave[cnt].configadr);
printf(" Outputs address: %p\\n", ec_slave[cnt].outputs);
printf(" Inputs address: %p\\n", ec_slave[cnt].inputs);
for (int j = 0; j < ec_slave[cnt].FMMUunused; j++)
{
printf(" FMMU%1d Ls:%x Ll:%4d Lsb:%d Leb:%d Ps:%x Psb:%d Ty:%x Act:%x\\n", j, \\
(int) ec_slave[cnt].FMMU[j].LogStart, ec_slave[cnt].FMMU[j].LogLength, \\
ec_slave[cnt].FMMU[j].LogStartbit, ec_slave[cnt].FMMU[j].LogEndbit, \\
ec_slave[cnt].FMMU[j].PhysStart, ec_slave[cnt].FMMU[j].PhysStartBit, \\
ec_slave[cnt].FMMU[j].FMMUtype, ec_slave[cnt].FMMU[j].FMMUactive);
}
printf(" FMMUfunc 0:%d 1:%d 2:%d 3:%d\\n", ec_slave[cnt].FMMU0func, ec_slave[cnt].FMMU1func, \\
ec_slave[cnt].FMMU2func, ec_slave[cnt].FMMU3func);
}
}
int main(void)
{
const char *ifname = "eth0";
ecx_context.manualstatechange = 1;
printf("Test LED \\r\\n");
if (ecx_init(&ecx_context, ifname))
{
printf("EtherCAT Init NIC\\r\\n");
wkc = ecx_config_init(&ecx_context, FALSE);
if (wkc > 0)
{
printf("wkc is %d\\r\\n",wkc);
ecx_context.slavelist[slave].state = EC_STATE_PRE_OP;
ecx_writestate(&ecx_context, slave);
ecx_statecheck(&ecx_context, slave, EC_STATE_PRE_OP, 5000);
if (ecx_context.slavelist[slave].state != EC_STATE_PRE_OP)
{
ecx_readstate(&ecx_context);
for(int i = 1; i <= *(ecx_context.slavecount) ; i++)
{
if(ecx_context.slavelist[i].state != EC_STATE_PRE_OP)
{
}
}
}
ecx_configdc(&ecx_context);
ecx_config_map_group(&ecx_context, &IOmap, currentgroup);
expectedWKC = (ec_group[0].outputsWKC * 2) + ec_group[0].inputsWKC;
ecx_context.slavelist[slave].state = EC_STATE_SAFE_OP;
ecx_writestate(&ecx_context, slave);
ecx_statecheck(&ecx_context, slave, EC_STATE_SAFE_OP, 5000);
if (ecx_context.slavelist[slave].state != EC_STATE_SAFE_OP)
{
ecx_readstate(&ecx_context);
for(int i = 1; i <= *(ecx_context.slavecount) ; i++)
{
if(ecx_context.slavelist[i].state != EC_STATE_SAFE_OP)
{
}
}
}
ecx_readstate(&ecx_context);
show_slave_info();
inOP = FALSE;
expectedWKC = (ec_group[0].outputsWKC * 2) + ec_group[0].inputsWKC;
ecx_context.slavelist[slave].state = EC_STATE_OPERATIONAL;
ecx_send_processdata(&ecx_context);
wkc = ecx_receive_processdata(&ecx_context, EC_TIMEOUTRET);
ecx_writestate(&ecx_context, slave);
if (ecx_context.slavelist[slave].state == EC_STATE_OPERATIONAL)
{
inOP = TRUE;
ec_send_processdata();
wkc = ec_receive_processdata(EC_TIMEOUTRET);
while (inOP)
{
switch(output_fsm){
case 0:
output_fsm=1;
*(ecx_context.slavelist[0].outputs)=1;
break;
case 1:
output_fsm=2;
*(ecx_context.slavelist[0].outputs)=2;
break;
case 2:
output_fsm=3;
*(ecx_context.slavelist[0].outputs)=4;
break;
case 3:
output_fsm=4;
*(ecx_context.slavelist[0].outputs)=8;
break;
case 4:
output_fsm=5;
*(ecx_context.slavelist[0].outputs)=16;
break;
case 5:
output_fsm=6;
*(ecx_context.slavelist[0].outputs)=32;
break;
case 6:
output_fsm=7;
*(ecx_context.slavelist[0].outputs)=64;
break;
case 7:
output_fsm=0;
*(ecx_context.slavelist[0].outputs)=128;
break;
}
ec_send_processdata();
wkc = ec_receive_processdata(EC_TIMEOUTRET);
osal_usleep(1000000);
}
}
else
{
ecx_close(&ecx_context);
}
}
else
{
printf("wkc is %d\\r\\n",wkc);
}
}
else
{
printf("ecx_config_init failed\\r\\n");
}
printf("End Program\\r\\n");
return 0;
}
接下来对SOEM源码进行编译。依次执行下列指令,完成SOEM代码的编译。
cd SOEM
mkdir -p build
cd build
cmake ..
make
1.2 运行示例程序
复制编译出的SOEM/build/test/linux/slaveinfo可执行文件到DR190M开发板。在开发板的eth0口连接一个EtherCAT从站。执行slaveinfo,可以看到EtherCAT从站上的LED循环变化。

将编译示例中的simple_test可执行文件复制到开发板,运行示例程序,可以看到PDO数据的输入输出状态,拨动EtherCAT从站的开关状态,可以看到输入数据的变化。
1.3 总结
在米尔-安路MYD-YM90X开发板上移植SOEM,可以实现对EtherCAT从站的IO控制,安路的DR190M芯片可以作为EtherCAT主站实现的一种方案.在实际测试时,连接EtherCAT从站的网口应使用开发板左侧的网口,右侧的网口在使用时有问题(原因应该是由于其是基于FPGA实现的,使用时不正常)。