Ethercat学习-从站FOE固件更新(TwinCAT主站)

news2024/11/26 15:35:55

文章目录

      • 简介
      • 协议说明
        • 1.读请求
        • 2.写请求
        • 3.数据
        • 4.应答
        • 5.错误码
        • 6.忙
      • 数据传输流程
        • 1.读流程
        • 2.写流程
        • 3.忙操作
      • 代码实现
        • 1.源码生成与移植
        • 2.代码解析
          • 1.FOE_ServiceInd
          • 2.FOE_Read
          • 3.FOE_Write
          • 4.FOE_Ack
          • 5.FOE_Data
          • 6.FOE_Error
          • 7.FOE_Busy
          • 其他
      • TwinCAT测试

简介

FOE(File Access over Ethercat),用于节点之间的文件传输。协议类似于TFTP协议,感觉和TFTP协议没有太大的区别,只是一个是UDP传输,一个是Ethercat传输。从机在通过FOE进行固件更新的时候,作为服务端,主机作为客户端,有主机发起固件的上传和下载

协议说明

FOE帧格式:

目标地址源地址类型Frame HeaderDatagram HeaderMailbox HeaderFOE HeaderdataFCS
6 bytes6 bytes2 bytes (0x88A4)2 bytes10 bytes6 bytes6 bytesN bytes4bytes

FOE 的帧格式与COE的帧格式差不多,也是邮箱数据。FOE Header6个字节,第一个字节是opcode,第二个字节为预留。opcode的取值范围是1~6,表示数据包的不同功能。

1.读请求

opcodereservedpasswordfile name
1byte (0x01)1byte4byten byte

opcode为0x01的时候表示读请求,password可以作为一个判断,只有当密钥正确的时候才进行操作。file name 表示待读取文件的名字

2.写请求

opcodereservedpasswordfile name
1byte (0x02)1byte4byten byte

opcode为0x02的时候表示写请求,password可以作为一个判断,只有当密钥正确的时候才进行操作。file name 表示待写入文件的名字。

3.数据

opcodereservedPacketNumberdata
1byte (0x03)1byte4byten byte

opcode为0x03的时候表示数据包,PacketNumber表示包计数,每次发送加1。

4.应答

opcodereservedPacketNumber
1byte (0x04)1byte4byte

opcode为0x04的时候表示数据包,PacketNumber表示所接收数据包的中的PacketNumber。

5.错误码

opcodereservedErrorCodeError Text
1byte (0x05)1byte4byten byte

opcode为0x05的时候表示错误码,ErrorCode表示所接收数据包的中的ErrorCode。Error Text对错误的描述,也可以不写。

6.忙

opcodereservedDoneEntireBusy Text
1byte (0x06)1byte2byte2 byten byte

opcode为0x06这个在网上看,有的朋友说是表示传输百分比内容的,但是根据源码还是官方文档分析,感觉是重传请求。

数据传输流程

1.读流程

在这里插入图片描述

如图所示,是一个成功读写的流程,客户机发起读请求;服务器在接收到读请求后经过password、文件名等判断后,开始返回第一个数据包,数据包的包计数为1;客户机在接收到数据包的时候,返回应答包,packetno等于接收到的数据包的packetno 。当服务端接收到最后一包数据的应答包的时候结束发送数据。

关于数据传输过程中的包数据长度,图中给出的是Full Maibox;在从站源码中有一个变量 u16FoeMaxSendBlockSize;当剩余数据大于等于u16FoeMaxSendBlockSize时传输的包长度为u16FoeMaxSendBlockSize,当剩余数据小于u16FoeMaxSendBlockSize时传输的数据长度等于剩余数据的长度。所以当客户机接收到数据后发现数据长度小于u16FoeMaxSendBlockSize时就职到当前数据时最后一包数据了。

/*  u16SendMbxSize: 主站配置的邮箱的大小 
    SIZEOF(TFOEHEADER) FOE 头的大小  6
    MBX_HEADER_SIZE: 邮箱头的大小  6 byte
*/
u16FoeMaxSendBlockSize = (u16SendMbxSize - SIZEOF(TFOEHEADER) - MBX_HEADER_SIZE);

在这里插入图片描述

如图所示,是一个读请求操作失败的流程,客户机发出读请求,服务器在接收到读请求后对密钥、文件名进行判断,如果不正确,则直接返回错误码终止传输。另外在数据传输的过程中也如果出现问题,也可以发送错误码终止传输。

2.写流程

在这里插入图片描述

如图所示,客户机发出写请求,服务器在接收到写请求后,判断密钥是否符合要求,如果符合,直接返回应答包,应答包的PacketNo等于0。客户机在收到应答包之后就会继续发送数据,当服务端接收的数据小于u16FoeMaxSendBlockSize时,就知道当前包时最后一包数据了,返回应答包,然后结束传输。

在这里插入图片描述

如图所示,是一个写请求失败的流程,服务器在收到写请求后,对密钥进行判断,如果不符合要求,直接回复错误码终止传输。

3.忙操作

在这里插入图片描述

如图所示,是一个在写流程的过程收到一个重传请求的操作。客户机发起写请求,服务器收到后回复应答包,然后开始写流程。中间服务器在收到一包数据后,因为一些状况(例如发现数据错误、数据保存文件失败)没有返回应答包,而是返回了Busy请求,客户端在接收到请求后,将上一包数据重新发送,然后继续传输。

代码实现

1.源码生成与移植

从站源码创建和移植部分不说了,重点是在SSC中勾选BOOTSTRAPMODE_SUPPORTED和FOE_SUPPORTED。FOE 进行固件更新可以再OP模式下进行,但是最好是在BootStrap模式下进行,在该状态下只能进行FOE通信,更安全一些。生成的文件中有几个文件bootloaderappl.c、bootloaderappl.h、bootmode.c、bootmode.h。我主要对bootloaderappl.c中的函数进行修改,bootmode中的函数我没有去做修改。

2.代码解析

1.FOE_ServiceInd

FOE_ServiceInd在接收到FOE数据的时候会调用,输入参数为接收到的数据包。

UINT8 FOE_ServiceInd(TFOEMBX MBXMEM * pFoeInd)
{
  ...
  初始化状态,计算接收的FOE数的长度
  ...
    switch ( SWAPWORD(pFoeInd->FoeHeader.OpCode) )
    {
    case ECAT_FOE_OPCODE_RRQ:
         如果是读请求,调用FOE_Read处理,返回状态值。
    case ECAT_FOE_OPCODE_WRQ:
         如果是写请求,调用FOE_Write处理,返回状态值。
    case ECAT_FOE_OPCODE_DATA:
        如果是数据包,调用FOE_Data处理,返回状态值
    case ECAT_FOE_OPCODE_ACK:
        如果是应答包,调用FOE_Ack处理,返回状态值
    case ECAT_FOE_OPCODE_ERR:
       如果是错误码,调用FOE_Error处理,返回状态值
    case ECAT_FOE_OPCODE_BUSY:
      如果是重发请求,调用FOE_Busy处理,返回状态值
    if ( nextState <= FOE_MAXDATA )
    {
       
    }
    else if ( nextState <= FOE_MAXBUSY )
    {
      ...
         根据状态值,对数据进行填充,然后发送
      ....

    return 0;
}

因此我们需要实现的就是FOE_Read、FOE_Write、FOE_Data、FOE_Ack、FOE_Error、FOE_Busy这几个函数。

2.FOE_Read

FOE_Read调用的pAPPL_FoeRead函数;FOE_Read里面主要就是判断一下文件名和密钥,然后读取第一包数据包

/**
 * @******************************************************************************: 
 * @func: [recv_RRQ]
 * @description: 接收到读请求信号调用函数
 * @note: 
 * @author: gxf
 * @param [UINT16 MBXMEM *] pName 请求的文件名
 * @param [UINT16] nameSize 请求的文件名长度
 * @param [UINT32] password 密钥
 * @param [UINT16] maxBlockSize 读取数据长度
 * @param [UINT16] *pData 读取数据的存储地址
 * @return [UINT16] 返回读取的数据的长度
 * @==============================================================================: 
 */
UINT16 recv_RRQ(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password, UINT16 maxBlockSize, UINT16 *pData)
{
    UINT16 i = 0;
    UINT16 readsize = 0;
    CHAR aReadFileName[MAX_FILE_NAME_SIZE];

    /* 判断文件名长度 */
    if ((nameSize + 1) > MAX_FILE_NAME_SIZE)
    {
        return ECAT_FOE_ERRCODE_DISKFULL;
    }
    if(password != PSWD)
    {   
        /* 密钥无效 */
        return ECAT_FOE_ERRCODE_NORIGHTS;
    }
    /* 拷贝文件名到数组中 */
    MBXSTRCPY(aReadFileName, pName, nameSize);
    aReadFileName[nameSize] = '\0';
    /* 判断文件名 获取文件地址 文件长度 */
    if(strcmp("app1",aReadFileName) == 0)
    {
        u32Appaddr = APP1_ADDR;
        u32FileSize = flash_read32(APP1_LEN_ADDR);
        if(u32FileSize>196608) u32FileSize = 196608;
    }else if(strcmp("app2",aReadFileName) == 0){
        u32Appaddr = APP2_ADDR;
        u32FileSize = flash_read32(APP2_LEN_ADDR);
        if(u32FileSize>196608) u32FileSize = 196608;
    }else{
        /* 文件未找到 */
        return ECAT_FOE_ERRCODE_NOTFOUND;
    }
    /* 判断文件大小 */
    if (u32FileSize == 0)
    {
        return ECAT_FOE_ERRCODE_NOTFOUND;
    }
    readsize = maxBlockSize;
    if (u32FileSize < readsize)
    {
        readsize = (UINT16)u32FileSize;
    }
    flash_read8_multiple(u32Appaddr, (uint8_t *)pData, readsize);
    return readsize;
}

3.FOE_Write

FOE_Write调用的pAPPL_FoeWrite函数;FOE_Write主要就是判断一下文件名和密钥;然后做一些文件写入的准备比如打开文件,我这里是操作flash,所以是擦除flash。如果没有错误,在FOE_ServiceInd函数中会自动帮我们返回第一包ACK。

/**
 * @******************************************************************************: 
 * @func: [recv_WRQ]
 * @description: 接收到写请求的调用函数
 * @note: 
 * @author: gxf
 * @param [UINT16 MBXMEM *] pName 待写文件的名字
 * @param [UINT16] nameSize 名字长度
 * @param [UINT32] password 密钥
 * @return [*] 0:正确;其他:错误
 * @==============================================================================: 
 */
UINT16 recv_WRQ(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password)
{
    if(password != PSWD)
    {   
        /* 密钥无效 */
        return ECAT_FOE_ERRCODE_NORIGHTS;
    }
    /* 如果当前运行的是APP2 ,则更新APP1 所在的位置 */
    if(flash_read32(APP_FLAG) ==  APP2_ADDR)
    {
        u32Appaddr = APP1_ADDR;
    }else{
        u32Appaddr = APP2_ADDR;
    }
    /* 擦除flash等待写入 */
    if(u32Appaddr == APP1_ADDR)
    {
        if(flash_erase_sector_multiple(4,5) != 0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;
    }else{
        if(flash_erase_sector_multiple(6,7) != 0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;        
    }
    nFileWriteOffset = 0;
    u32FileSize = 0;
    return 0;

}
4.FOE_Ack

FOE_Ack调用的是pAPPL_FoeReadData,它主要就是读取数据,然后存到指定的地址中

/**
 * @******************************************************************************: 
 * @func: [recv_ACK]
 * @description: 接收到应答信号的调用函数
 * @note: 
 * @author: gxf
 * @param [UINT32] offset 读取的地址的偏移量(相对于首次读取)
 * @param [UINT16] maxBlockSize 读取的数据长度
 * @param [UINT16] *pData 读取数据的存储地址
 * @return [UINT16] 返回读取的数据的长度
 * @==============================================================================: 
 */
UINT16 recv_ACK(UINT32 offset, UINT16 maxBlockSize, UINT16 *pData)
{
    UINT16 readsize = 0;
    
    /* 如果文件大小小于指针的偏移量,说明文件已经读完了*/
    if (u32FileSize < offset)
    {
        return 0;
    }

    /* 剩余未读取 */
    readsize = (UINT16)(u32FileSize - offset);
    /* 读取大小 */
    if (readsize > maxBlockSize)
    {
        readsize = maxBlockSize;
    }
    /* 读取文件大小 */
    flash_read8_multiple(u32Appaddr+offset, (uint8_t *)pData, readsize);
    return readsize;
}
5.FOE_Data

FOE_Data调用的pAPPL_FoeWriteData,主要就是向文件中写入数据

/**
 * @******************************************************************************: 
 * @func: [recv_DATA]
 * @description: 接收到数据包的调用函数
 * @note: 
 * @author: gxf
 * @param [UINT16 MBXMEM *] pData 带写入数据的地址
 * @param [UINT16] Size 写入数据的长度
 * @param [BOOL] bDataFollowing 是否还有数据数据
 * @return [*] 0:正确;其他:错误
 * @==============================================================================: 
 */
UINT16 recv_DATA(UINT16 MBXMEM * pData, UINT16 Size, BOOL bDataFollowing)
{

    if ((nFileWriteOffset + Size) > MAX_FILE_SIZE)
    {
        return ECAT_FOE_ERRCODE_DISKFULL;
    }

    if (Size)
    {
        /* 写flash */
        if(flash_write8_multiple(u32Appaddr+nFileWriteOffset,(uint8_t *)pData,Size)!=0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;
    }
    /* 后面是否还有数据 */
    if (bDataFollowing)
    {
        nFileWriteOffset += Size;
    }
    else
    {
        /* 最后一包数据 计算总的接收文件长度 */
        u32FileSize = nFileWriteOffset + Size;
        nFileWriteOffset = 0;
        /* 保存下次上电运行的APP地址 和 APP的长度*/
        UINT32 app1length = flash_read32(APP1_LEN_ADDR);
        UINT32 app2length = flash_read32(APP2_LEN_ADDR);
        UINT32 appflag    = flash_read32(APP_FLAG);
        if(u32Appaddr == APP1_ADDR) 
            app1length = u32FileSize;
        else
            app2length = u32FileSize;
        if(flash_erase_sector(2) != 0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;  
        if(flash_write32(APP_FLAG,appflag) != 0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;
        if(flash_write32(APP1_LEN_ADDR,app1length) != 0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;
        if(flash_write32(APP2_LEN_ADDR,app2length) != 0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;
        if(flash_write32(UPDATE_APP_FLAG,u32Appaddr) != 0)
            return ECAT_FOE_ERRCODE_FLASH_ERROR;
            
    }

    return 0;
}
6.FOE_Error

FOE_Error调用了pAPPL_FoeError,由于是测试程序,所以这个函数我没有去修改,当接收到错误包的时候,我们可以进行一些关闭文件等操作。

7.FOE_Busy

FOE_Busy调用了FOE_Ack,重新发送上一包数据。

UINT16 FOE_Busy(UINT16 done, UINT32 fileOffset, UINT16 MBXMEM * pData)
{
    return FOE_Ack(fileOffset, pData);
}
其他

上面的几个函数都是bootloaderappl.c中的,只是函数名字我改了。在MainInit函数中做一下赋值,如下:

UINT16 MainInit(void)
{
   ...
    pAPPL_FoeRead = recv_RRQ;
    pAPPL_FoeReadData = recv_ACK;
    pAPPL_FoeError = NULL;
    pAPPL_FoeWrite = recv_WRQ;
    pAPPL_FoeWriteData = recv_DATA;

    pAPPL_MainLoop = NULL;
/*ECATCHANGE_END(V5.12) ECAT8*/
    ...
}

TwinCAT测试

在这里插入图片描述

测试很简单,我是使用TwinCAT作为主机进行测试的。OP模式下和Bootstrap模式下都可以进行上传和下载。图中标记的Download是将PC端的文件写入从站的。Upload是将从站中的文件读取到PC端。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429227.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React从入门到入土系列3-使用React构建你的应用

这是我自己系统整理的React系列博客&#xff0c;主要参考2023年3月开放的最新版本react官网内容&#xff0c;欢迎你阅读本系列内容&#xff0c;希望能有所收货。 本文是该系列的第3篇文章&#xff0c;阅读完本文后你将收获&#xff1a; 如何使用React逐步构建你的应用了解prop…

《分布式商业》

读完《分布式商业&#xff1a;数字化时代的新商业变革》&#xff0c;说实话&#xff0c;如果读者没有技术研发背景&#xff0c;读完此书&#xff0c;是很难在技术和商业层面引起共鸣。我甚至觉得&#xff0c;这本书就是写给技术类岗位的同学看的&#xff1b;而写这本书的人&…

Softing工业边缘产品的新功能助力工业数据集成到IT解决方案中

Softing的edgeConnector和edgeAggregator产品在3.40版本中新增了一系列功能&#xff0c;使数据集成到IT解决方案变得更加容易。 基于Docker的edgeConnector产品系列支持访问SIMATIC S7、SINUMERIK 840D和Modbus TCP等控制器中的过程数据。同样基于Docker的edgeAggregator产品可…

柔性数组【结构体和动态内存的结合】

全文目录前言柔性数组的定义语法柔性数组的特点柔性数组的使用柔性数组的优势前言 很多人可能没有听过柔性数组这个概念&#xff0c;但是在C99中柔性数组是确实存在的。我个人感觉有点像动态内存和结构体的结合。 柔性数组的定义语法 结构中的最后一个元素允许是未知大小的数…

一起学 WebGL:绘制三角形

大家好&#xff0c;我是前端西瓜哥。画了好几节课的点&#xff0c;这次我们来画三角形了。 三角形可太重要了&#xff0c;再复杂的三维模型都是由一个个小三角形组合而成&#xff0c;越多越精细越真实。 绘制三角形 这次绘制三角形&#xff0c;要绘制的点就有三个了&#xf…

C语言之 单链表1(simply linked list)

单链表 链表优点&#xff1a; 1.按需申请空间&#xff0c;需要就申请&#xff0c;不需要就释放 2.头部或中间插入数据&#xff0c;不需要挪动数据 3.不存在空间浪费 缺点&#xff1a; 1.每次存放一个数据&#xff0c;到要存一个指针去链接后面的数据节点 2.不支持随机访问&a…

让你的ChatGPT更加强大——200+小白用来解锁ChatGPT高级功能的提示(Prompts)

让你的ChatGPT更加强大——200小白用来解锁ChatGPT高级功能的提示&#xff08;Prompts&#xff09;使用说明标签筛选关键词搜索展示区复制语言切换常见问题为什么提示词用英文&#xff1f;中文搜索出错输出虚假信息提示词不好用为什么执着于 ChatGPT&#xff1f;最后参考博客其…

Vue.js 2.0 单文件组件

Vue.js 2.0 单文件组件介绍 在很多Vue项目中&#xff0c;我们使用 Vue.component 来定义全局组件&#xff0c;紧接着用 new Vue({ el: #container }) 在每个页面内指定一个容器元素。 这种方案在只是使用 JavaScript 增强某个视图的中小型项目中表现得很好。然而在更复杂的项…

对象树、QT的坐标系、信号和槽机制

目录 1、QT中什么是对象树 2、QT的坐标系 3、信号和槽机制 3.1、信号槽的理解 3.2、信号槽的工作原理 3.3、信号槽的使用 3.3.1、系统的信号和槽 3.3.2、自定义信号和槽函数 3.3.3、信号和槽函数之间的参数传递 3.3.4、信号和槽的注意 1、QT中什么是对象树 在创建 QO…

重构·改善既有代码的设计.04之重构手法(下)完结

1. 前言 本文是代码重构系列的最后一篇啦。前面三篇《重构改善既有代码的设计.01之入门基础》、《重构改善既有代码的设计.02之代码的“坏味道”》、《重构改善既有代码的设计.03之重构手法&#xff08;上&#xff09;》介绍了基础入门&#xff0c;代码异味&#xff0c;还有部…

如何在ubuntu上搭建minio

由于腾讯的对象存储服务器&#xff08;COS&#xff09;的半年免费试用期已过&#xff0c;所以寻思鼓捣一下minio&#xff0c;试着在自己的服务器上搭建一套开源的minio对象存储系统。 单机部署基本上有以下两种方式。 一、直接安装 最基础的一种单机安装&#xff0c;这里不做…

记录-js基础练习题

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 隔行换色(%): window.onload function() {var aLi document.getElementsByTagName(li);for(var i 0; i < aLi.length; i){if(i%2 1){aLi[i].style.background #bfa;}} } <ul><li>…

微搭低代码学习之基础操作

低代码开发平台&#xff0c;是一种方便产生应用程序的平台软件&#xff0c;软件会开发环境让用户以图形化接口以及配置编写程序&#xff0c;而不是用传统的程序设计作法。此平台可能是针对 某些种类的应用而设计开发的&#xff0c;例如数据库、业务过程、以及用户界面。这类平台…

STM32锁芯片,解锁方法(J-LINK)

在烧写STM32时&#xff0c;不小心把usb口给弄掉了&#xff0c;然后就尴尬了&#xff0c;芯片被锁了。 烧写的时候报错&#xff0c;如下&#xff1a; 然后就搜了一下&#xff0c;大致有两个方法&#xff0c; 第一种需要通过接3.3V到板子上BOOT0重新上电第二种是直接通过软件…

Golang语言Windows环境搭建(Visual Studio Code)

一、Golang语言简介 二、Windows环境搭建 1、软件下载 Golang语言安装包官网下载地址&#xff1a;https://golang.google.cn/dl/ Visual Studio Code编辑器下载&#xff1a;https://code.visualstudio.com/ 2、Golang安装及环境变量配置 2.1 Golang语言安装包 双击安装…

可配置物料-文章资料分享

可配置物料项目一般很少用到&#xff0c;用到就是要命&#xff0c;推荐一下之前查资料收集的资料&#xff0c;分享给大家。感谢这位大佬收集的文章&#xff01; Variant Configuration (LO-VC) - Product Lifecycle Management - Support Wikihttps://archive.sap.com/document…

5V的LDO电源的WCCA分析-可靠性分析计算过程

WCCA(WorstCase Circuit Analysis)分析方法是一种电路可靠性分析设计技术&#xff0c;用来评估电路中各个器件同时发生变化时的性能&#xff0c;用于保证设计电路在整个生命周期的都可以可靠工作。通过WCCA分析&#xff0c;验证在上述参数在其容差范围内发生变化时&#xff0c;…

C++模板基础(八)

数值模板参数与模板模板参数 ● 模板可以接收&#xff08;编译期常量&#xff09;数值作为模板参数 – template class Str; template<int a> int fun(int x) {return x a; }int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);std::cout << fun…

相机SD卡无法读取提示格式化 相机SD卡无法读取怎么修复

相机SD卡中储存着的照片和视频&#xff0c;承载着我们美好的回忆。因为相机SD卡的容量有限&#xff0c;我们会定期对SD卡中的数据进行云盘备份&#xff0c;然后清理相机SD卡中的数据。在打开相机SD卡时&#xff0c;可能会遇到SD卡无法读取的情况。那么&#xff0c;相机SD卡无法…

Umi‘s Friends 冠名 VC MeetUP 酒会,圆满举办!

香港加密新政的整体目的是令虚拟资产交易明确化和合法化&#xff0c;通过不断完善的监管框架&#xff0c;促进香港虚拟资产行业的可持续和负责任地发展。在加强合规和持牌经营的监管思路下&#xff0c;长期审慎合规经营的老牌机构和项目&#xff0c;显然将获得先发优势。随着香…