最近入手一块正点原子的ATK-RK3506开发板,在开发板上运行SOEM来控制EtherCAT从站IO。
1.1 编译SOEM
使用正点原子提供的虚拟机开发环境,根据《13【正点原子】基于Buildroot系统_交叉编译器安装与使用参考手册V1.1》中的说明,安装相应的交叉编译器。
./atk-DLRK3506-toolchain-arm-buildroot-linux-gnueabihf-x86_64-20240320-v1.0.run
参考《16【正点原子】adb工具使用说明V1.1》安装adb工具。交叉编译器和adb工具安装完成后,在$HOME/.bashrc中添加下述配置,便于后续的开发。

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

将下载依次执行下列指令,完成SOEM代码的编译。
source $ATK_RK3506
cd SOEM
mkdir -p build
cd build
cmake ..
make
编写以下代码,将其替换掉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;
}
重新对其进行编译。

1.2 运行SOEM程序
将输出结果中的slaveinfo二进制可执行文件命名为led_ctrl。使用adb工具将编译出来的led_ctrl和simple_test发送到开发板上。

连接开发板和EtherCAT从站

执行led_ctrl的效果如下,实现对EtherCAT上LED灯的控制。

执行simple_test后,可以查看PDO数据的实时更新。

1.3 总结
在RK3506上运行SOEM是可以的,可以作为入门EtherCAT开发的选择。