目录

  • 18 FOE知识储备
      • 18.1 FoE读写操作说明
      • 18.2 支持忙状态的FoE读取操作流程说明
      • 18.3 FoE 错误码
  • 实战
      • SSC
      • 接口
      • 实现读取接口
      • 读取测试
      • 实现写入接口
      • 写入测试
  • 后记

18 FOE知识储备

18.1 FoE读写操作说明

如果最后一个FoE数据服务原语中包含的数据恰好填满邮箱(由参数Size = FullSize指示),则必须使用一个空的数据服务原语来终止FoE写入或FoE读取服务。
在这里插入图片描述
写请求类似,无需多言
在这里插入图片描述
最后一条 Size < FullSize 的 FoE Data 指示用于表示 FoE 服务的结束。

18.2 支持忙状态的FoE读取操作流程说明

FoE服务定义详见ETG.1000.5标准,相关协议规范参见ETG.1000.6标准。
针对带有Busy状态的FoE读取请求,必须使用图11所示的通信序列
在这里插入图片描述
若文件的最后一个数据段恰好填满邮箱,则必须额外发送一个不包含数据的FoE Data.req(PacketNo=3,Size=0)。最后一个FoE Data必须满足Size < FullSize的条件。

对于带有Busy状态的FoE_Write请求,其处理方式应严格遵循ETG.1000.5标准的规定。这意味着从设备(SubDevice)必须始终以FoE_ACK.req作为对FoE_Write.ind的响应——即使内部处理需要额外时间来检查主设备(MainDevice)的写入请求。在FoE_Data.ind之后,若出现错误(例如文件不存在),从设备可发送FoE_Busy.req或FoE_Error.req。

18.3 FoE 错误码

FoE错误码在ETG.1000.6标准[5]中定义。FoE错误码基于TFTP错误码衍生而来。现有多种实现方案中:
部分实现直接使用TFTP错误码(不含0x8000偏移量),两种错误码格式均可任意选用。
表56列出了FoE应使用的现有错误码及新增错误码

在这里插入图片描述

实战

SSC

打开FOE支持
在这里插入图片描述

接口

生成代码之后,工程内多出来foeappl.c和ethercatfoe.c两个源文件以及相关头文件。

appinterface.h中多出来一部分接口代码如下:


/////////////////////////////////////////////////////////////////////////////////////////
/**
\param     pName         Pointer to the name of the file (the pointer is null if the function is called due to a previous busy state)
\param     nameSize      Length of the file name (the value is 0 if the function is called due to a previous busy state)
\param     password      Password for the file read (the value is 0 if the function is called due to a previous busy state)
\param     maxBlockSize  Maximum size of a data block (copied to pData)
\param     pData         Destination pointer for the first FoE fragment
                        


\return                  block size:
                            < FOE_MAXBUSY-101    (0x7F95)
                         busy:
                            FOE_MAXBUSY-100 (0%)    (0x7FFA - 0x64)
                            ...
                            FOE_MAXBUSY (100%) (0x7FFA)
                         error:
                            ECAT_FOE_ERRCODE_NOTDEFINED (0x8000)
                            ECAT_FOE_ERRCODE_NOTFOUND (0x8001)
                            ECAT_FOE_ERRCODE_ACCESS    (0x8002)
                            ECAT_FOE_ERRCODE_DISKFULL (0x8003)
                            ECAT_FOE_ERRCODE_ILLEGAL (0x8004)
                            ECAT_FOE_ERRCODE_EXISTS    (0x8006)
                            ECAT_FOE_ERRCODE_NOUSER    (0x8007)

\brief    The function is called when a file read request was received. The Foe fragments shall always have the length of "maxBlockSize" till the last file fragment.
\brief    In case that the file size is a multiple of "maxBlockSize" 0 shall be returned after the last fragment.
\brief	   The function pointer is reset in MainInit() so it shall be set afterwards
*////////////////////////////////////////////////////////////////////////////////////////
PROTO UINT16 (*pAPPL_FoeRead)(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password, UINT16 maxBlockSize, UINT16 *pData);


/////////////////////////////////////////////////////////////////////////////////////////
/**
\param     offset        File offset which shall be transmitted next
\param     maxBlockSize  Maximum size of a data block (copied to pData)
\param     pData         Destination pointer for the next foe fragment



\return                  block size:
                            < FOE_MAXBUSY-101    (0x7F95)
                        busy:
                            FOE_MAXBUSY-100 (0%)    (0x7FFA - 0x64)
                            ...
                            FOE_MAXBUSY (100%) (0x7FFA)
                        error:
                            ECAT_FOE_ERRCODE_NOTDEFINED (0x8000)
                            ECAT_FOE_ERRCODE_NOTFOUND (0x8001)
                            ECAT_FOE_ERRCODE_ACCESS    (0x8002)
                            ECAT_FOE_ERRCODE_DISKFULL (0x8003)
                            ECAT_FOE_ERRCODE_ILLEGAL (0x8004)
                            ECAT_FOE_ERRCODE_EXISTS    (0x8006)
                            ECAT_FOE_ERRCODE_NOUSER    (0x8007)

\brief    The function is called to transmit FoE read data 2 .. n (the slave received an acknowledge on a previous accepted file read request). The Foe fragments shall always have the length of "maxBlockSize" till the last file fragment.
\brief    In case that the file size is a multiple of "maxBlockSize" 0 shall be returned after the last fragment.
\brief	   The function pointer is reset in MainInit() so it shall be set afterwards
*////////////////////////////////////////////////////////////////////////////////////////
PROTO UINT16(*pAPPL_FoeReadData)(UINT32 offset, UINT16 maxBlockSize, UINT16 *pData);


/////////////////////////////////////////////////////////////////////////////////////////
/**
\param     errorCode     Error code send by the EtherCAT master


\brief    The function is called when the master has send an FoE Abort.
\brief	   The function pointer is reset in MainInit() so it shall be set afterwards
*////////////////////////////////////////////////////////////////////////////////////////
PROTO void(*pAPPL_FoeError)(UINT32 errorCode);



/////////////////////////////////////////////////////////////////////////////////////////
/**
\param     pName         Pointer to the name of the file
\param     nameSize      Length of the file name
\param     password      Password for the file read

\return                 okay, or an error code
                        0 (okay)
                        ECAT_FOE_ERRCODE_NOTDEFINED (0x8000)
                        ECAT_FOE_ERRCODE_NOTFOUND (0x8001)
                        ECAT_FOE_ERRCODE_ACCESS    (0x8002)
                        ECAT_FOE_ERRCODE_DISKFULL (0x8003)
                        ECAT_FOE_ERRCODE_ILLEGAL (0x8004)
                        ECAT_FOE_ERRCODE_EXISTS    (0x8006)
                        ECAT_FOE_ERRCODE_NOUSER    (0x8007)

\brief    This function is called on a received FoE write request.
\brief    (no busy response shall be returned by this function. If the slave requires some time to handle the incoming data the function pAPPL_FoeData() shall return a busy)
\brief	   The function pointer is reset in MainInit() so it shall be set afterwards
*////////////////////////////////////////////////////////////////////////////////////////
PROTO UINT16 (*pAPPL_FoeWrite)(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password);


/////////////////////////////////////////////////////////////////////////////////////////
/**
\param     pData            Received file data
\param 	   Size             Length of received file data
\param 	   bDataFollowing   TRUE if more FoE Data requests are following

\return                     okay, busy or an error code
                            0 (okay)
                            FOE_MAXBUSY-100 (0%)    (0x7FFa - 100)
                            FOE_MAXBUSY (100%) (0x7FFA)
                            ECAT_FOE_ERRCODE_NOTDEFINED (0x8000)
                            ECAT_FOE_ERRCODE_ACCESS    (0x8002)
                            ECAT_FOE_ERRCODE_DISKFULL (0x8003)
                            ECAT_FOE_ERRCODE_ILLEGAL (0x8004)
                            ECAT_FOE_ERRCODE_NOUSER    (0x8007)

\brief    This function is called on a FoE Data request
\brief	   The function pointer is reset in MainInit() so it shall be set afterwards
*////////////////////////////////////////////////////////////////////////////////////////
PROTO UINT16(*pAPPL_FoeWriteData)(UINT16 MBXMEM * pData, UINT16 Size, BOOL bDataFollowing);

其中,pAPPL_FoeRead是EtehrCAT Master发起的读取请求,Slave需要再这里返回第一包数据。如果数据量比较大,一包返回不完全,那么协议栈就会调用pAPPL_FoeReadData,Slave在这里返回剩余包。

写数据的流程类似

pAPPL_FoeError的功能待探索

上面都是函数指针,用户需要自己实现这些函数并将指针指向相应的函数。

实现读取接口

SSC-Device.c中增加如下代码,主要有三部分内容:
1,test_str测试字符串
2,APPL_FoeRead,EtherCAT Master第一次请求回调函数,包含密码、文件名、最大包长度等数据,需要Slave返回第一包数据
3,APPL_FoeReadData,Slave在第一包数据返回中没有返回全部数据的,在这个函数中返回后续数据

uint8_t test_str[] =
"Tech changes life.                        \n\
Phones,AI,data-fast but risky.             \n\
Distract,stress,no privacy.                \n\
Online>real life?Balance is key.           \n\
Less screen,more talk.Digital detox helps. \n\
Use tech wise,stay human.                  \n";

UINT16 APPL_FoeRead(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password, UINT16 maxBlockSize, UINT16 *pData)
{
  SEGGER_RTT_printf(0, "%s pName = %s, nameSize = %d, password = %04x, maxBlockSize = %d\n", 
                        __FUNCTION__, pName, nameSize, password, maxBlockSize);

  //检查文件名称长度是否为4, (test.txt)
  if(nameSize != 4)
    return ECAT_FOE_ERRCODE_NOTFOUND;

  //检查文件名称内容是否为test
  if(memcmp("test", pName, 4))
    return ECAT_FOE_ERRCODE_NOTFOUND;

  //检查密码是否为0x043C
  if(password != 0x043C)
    return ECAT_FOE_ERRCODE_ACCESS;
  
  if(sizeof(test_str) > maxBlockSize)
  {//一包发不完,还有后续数据包;注意如果上述两者相等,APPL_FoeReadData还会被调用一次,要返回0
    memcpy(pData, test_str, maxBlockSize);
    return maxBlockSize;
  } 
  else 
  {//一包发的完
    memcpy(pData, test_str, sizeof(test_str));
    return sizeof(test_str);
  }
}
UINT16 APPL_FoeReadData(UINT32 offset, UINT16 maxBlockSize, UINT16 *pData)
{
  SEGGER_RTT_printf(0, "%s offset = %d, maxBlockSize = %d\n", __FUNCTION__, offset, maxBlockSize);
  if(sizeof(test_str) - offset > maxBlockSize)
  {//一包发不完,还有后续数据包;注意如果上述两者相等,APPL_FoeReadData还会被调用一次,要返回0
    memcpy(pData, test_str + offset, maxBlockSize);
    return maxBlockSize;
  }
  else
  {//一包发的完
    memcpy(pData, test_str + offset, sizeof(test_str) - offset);
    return sizeof(test_str) - offset;
  }
}

在其它可见函数指针和实例的文件中将函数指针指向上述函数

  pAPPL_FoeRead = APPL_FoeRead;
  pAPPL_FoeReadData = APPL_FoeReadData;

读取测试

文件名为test,后缀不关心,我们这里是文本文件所以设置为txt
在这里插入图片描述文件名为test,文件密码为0x0000043C,参考代码中的校验过程
在这里插入图片描述
获取数据如下
在这里插入图片描述
调试打印输出如下:
可以看出,数据长度超过了两整包,第三包发送的是一个半包,EtherCAT Master收到半包或者空包之后认为结束

00> APPL_FoeRead pName = test, nameSize = 4, password = 043C, maxBlockSize = 116
00> APPL_FoeReadData offset = 116, maxBlockSize = 116
00> APPL_FoeReadData offset = 232, maxBlockSize = 116

实现写入接口

直接干,通过现象看调用过程
测试代码如下

UINT16 APPL_FoeWrite(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password)
{
  SEGGER_RTT_printf(0, "%s pName = %s, nameSize = %d, password = %04x\n", 
                      __FUNCTION__, pName, nameSize, password);
  return 0;
}
UINT16 APPL_FoeWriteData(UINT16 MBXMEM * pData, UINT16 Size, BOOL bDataFollowing)
{
  SEGGER_RTT_printf(0, "%s Size = %d bDataFollowing = %d\n", 
                      __FUNCTION__, Size, bDataFollowing);
  return 0;
}

对接接口

  pAPPL_FoeWrite = APPL_FoeWrite;
  pAPPL_FoeWriteData = APPL_FoeWriteData;

写入测试

把刚才的文件发回去
在这里插入图片描述
在这里插入图片描述
日志如下
注意:这里的pName似乎没有字符串终止符号,我在打印输出的时候做了%04s的限制
注意:APPL_FoeWrite 函数里没有数据传输,只传递文件名文件长度密码,数据传输在APPL_FoeWriteData 中。
用户自己对接好数据接收即可

00> APPL_FoeWrite pName = test, nameSize = 4, password = 6666
00> APPL_FoeWriteData Size = 116 bDataFollowing = 1
00> APPL_FoeWriteData Size = 116 bDataFollowing = 1
00> APPL_FoeWriteData Size = 32 bDataFollowing = 0

后记

测试数据包是maxBlockSize 整数倍情况,修改上面读取数据内容长度为116 * 3

uint8_t test_str[116 * 3] =........

执行读取操作,读取执行4次,最后一次为空包

00> APPL_FoeRead pName = test, nameSize = 4, password = 043C, maxBlockSize = 116
00> APPL_FoeReadData offset = 116, maxBlockSize = 116
00> APPL_FoeReadData offset = 232, maxBlockSize = 116
00> APPL_FoeReadData offset = 348, maxBlockSize = 116

最后一次的现场情况
在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐