Ethercat学习-从站FOE固件更新(QT上位机)

news2024/11/28 1:45:31

文章目录

      • 简介
      • 1、源码简介
        • 1、ec_FOEread
        • 2、ec_FOEwrite
        • 3、ec_FOEdefinehook
      • 2、程序思路
      • 3、修改实现
        • 1、ecx_FOEwrite_gxf
        • 2、ecx_FOEread_gxf
      • 4、其他
      • 5、结果
      • 6、源码连接

简介

FOE协议与下位机程序实现过程之前文章有提到,这里不做介绍了。这里主要介绍1、QT上位机通过FOE读写下位机的数据;2、QT上位机读写ESC的EEPROM。

1、源码简介

SOEM源码中和foe相关的文件为ethercatfoe.c、ethercatfoe.h。主要包含了下面三个函数。

/* 读请求 参数:请求文件名、密钥、本地用于存储的内存的大小、本地文件的地址、每次通信超时时间 */
int ec_FOEread(uint16 slave, char *filename, uint32 password, int *psize, void *p, int timeout)
/* 写请求 参数:请求文件名、密钥、文件大小、本地文件的地址、每次通信超时时间*/
int ec_FOEwrite(uint16 slave, char *filename, uint32 password, int psize, void *p, int timeout)
/* 定义回调函数 参数:回调函数的地址 */
int ec_FOEdefinehook(void *hook)

1、ec_FOEread

FOE读的整体实现思路如下:

  1. 清空邮箱,防止现有数据影响接下来的判断。

  2. 向下位机发送读请求帧,包含了文件名、密钥等的数据。

  3. 读取下位机的回复的数据帧;

  4. 将数据帧的内容拷贝到本地内存中,判断内存是否越界,计算已接受数据的大小

  5. 如果定义了回调函数,则会调用回调函数。回调函数包含了从机编号、包计数、已接收文件总长度这三个参数

  6. 循环,直到接收完成。

    /** FoE read, 代码片段.
     *
     * @param[in]  context        = context struct
     * @param[in]     slave      = 从机编号.
     * @param[in]     filename   = 请求的文件名.
     * @param[in]     password   = 密钥.
     * @param[in,out] psize      = 本地存储文件的内存的大小.
     * @param[out]    p          = 本地存储文件的内存的地址
     * @param[in]     timeout    = 超时时间us
     * @return Workcounter from last slave response
     */
    int ecx_FOEread(ecx_contextt *context, uint16 slave, char *filename, uint32 password, int *psize, void *p, int timeout)
    {
        .......
       /* 将邮箱清空,为FOE传输做准备 */
       ec_clearmbx(&MbxIn);
       wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, 0);
       ec_clearmbx(&MbxOut);
        ......
       /* 封装数据,发送读请求 */
       wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);
       if (wkc > 0) 
       {
          do
          {
                   .......
                   /* 一系列判断 */
                   if(aFOEp->OpCode == ECT_FOE_DATA)
                   {
                       /* 获取数据的长度和包编号 */
                      segmentdata = etohs(aFOEp->MbxHeader.length) - 0x0006;
                      packetnumber = etohl(aFOEp->PacketNumber);
                       /* 判断包编号和总计接收的长度是否超过了内存 */
                      if ((packetnumber == ++prevpacket) && (dataread + segmentdata <= buffersize))
                      {
                         /* 数据转存,内存偏移,接收计数累加 */
                         memcpy(p, &aFOEp->Data[0], segmentdata);
                         dataread += segmentdata;
                         p = (uint8 *)p + segmentdata;
                          /* 这个maxdata是邮箱的大小,也是每包数据的最大长度,相等说明不是最后一包数据,继续接收*/
                         if (segmentdata == maxdata)
                         {
                            worktodo = TRUE;
                         }
                         ........
                         /* 回复ack给下位机,如果有回调函数则调用回调 */
                         wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);
                         if (wkc <= 0)
                         {
                            worktodo = FALSE;
                         }
                         if (context->FOEhook)
                         {
                            context->FOEhook(slave, packetnumber, dataread);
                         }
                         ..........
    }
    

2、ec_FOEwrite

FOE写的整体实现思路如下:

  1. 清空邮箱,防止现有数据影响接下来的判断。

  2. 向下位机发送写请求帧,包含了文件名、密钥等的数据。

  3. 读取下位机的回复的应答帧;

  4. 判断应答帧的包计数,如果定义了回调函数,则会调用回调函数,回调函数包含了从机编号、包计数、已接收文件总长度这三个参数

  5. 继续发送数据包

  6. 循环,直到发送完成。

    /** FoE write, 代码片段.
     *
     * @param[in]  context        = context struct
     * @param[in]  slave      = 从站编号.
     * @param[in]  filename   = 待发送的文件名.
     * @param[in]  password   = 密钥.
     * @param[in]  psize      = 待发送的文件的大小.
     * @param[out] p          = 待发送文件的地址
     * @param[in]  timeout    = Timeout per mailbox cycle in us, standard is EC_TIMEOUTRXM
     * @return Workcounter from last slave response
     */
    int ecx_FOEwrite(ecx_contextt *context, uint16 slave, char *filename, uint32 password, int psize, void *p, int timeout)
    {
       ........
       /* 清空邮箱为FOE通信做准备 */
       ec_clearmbx(&MbxIn);
       wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, 0);
       ec_clearmbx(&MbxOut);
       ......
       /* 封装数据发送写请求 */
       wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);
       if (wkc > 0) 
       {
          do
          {
             worktodo = FALSE;
             /* clean mailboxbuffer */
             ec_clearmbx(&MbxIn);
             /* 读取从站回复 */
             wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, timeout);
             if (wkc > 0) /* succeeded to read slave response ? */
             {
                
                if ((aFOEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_FOE)
                {
                   switch (aFOEp->OpCode)
                   {
                      case ECT_FOE_ACK:
                      {
                         packetnumber = etohl(aFOEp->PacketNumber);
                         if (packetnumber == sendpacket)
                         {
                             /* 如果定义了回调函数,可以调用回调函数 */
                            if (context->FOEhook)
                            {
                               context->FOEhook(slave, packetnumber, psize);
                            }
                            tsize = psize;
                            if (tsize > maxdata)
                            {
                               tsize = maxdata;
                            }
                            if(tsize || dofinalzero)
                            {
                               worktodo = TRUE;
                               dofinalzero = FALSE;
                               segmentdata = tsize;
                               psize -= segmentdata;
                               /* 判断是否为最后一帧 */
                               if (!psize && (segmentdata == maxdata))
                               {
                                  dofinalzero = TRUE;
                               }
                               ...........
                               /* 封装数据,发送数据帧 */
                               wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);
                               if (wkc <= 0)
                               {
                                  worktodo = FALSE;
                               }
                            }
                         }
                         else
                         {
                            /* FoE error */
                            wkc = -EC_ERR_TYPE_FOE_PACKETNUMBER;
                         }
                         break;
                      }
    }
    
    

3、ec_FOEdefinehook

用与定义回调函数,就是将我们回调函数入口地址传递给一个变量。我们自己定义的回调函数需要为下面的形式

(*FOEhook)(uint16 slave, int packetnumber, int datasize);

2、程序思路

首先,通过看SOEMd的源码发现,在调用ec_FOEread的时候,我们需要先申请一片内存,将内存地址传递进去,用来存放读取的文件,那么当文件很大的时候,我们申请的内存就需要足够大。同样的调用ec_FOEwrite的时候,我们也需要申请一片内存,将要写的数据全部读取到内存中,然后将内存的地址传递给ec_FOEwrite,当文件很大的时候,我们就需要定义的内存足够大。这种实现方式也算是用空间换时间吧,将文件一次性放到内存中,然后再统一操作数据。还有一点就是ec_FOEread和ec_FOEwrite我们只需要调用一次,将参数传递进去后,就开始进行一个while循环,知道我们所有的操作结束。

本次是用QT来进行FOE数据传输的,在数据传输部分,个人偏向于边读便发送,或者边写便发送的方式。另外在数据收发的过程中,还要保证界面继续刷新,因此需要对ec_FOEread和ec_FOEwrite进行修改。

3、修改实现

1、ecx_FOEwrite_gxf

                   /* 对比源码修改的部分 */                    

                    if (packetnumber == sendpacket)
                     {
                        /* 在收到ack 说明写请求成功,发送自定义信号量,并延时10ms 来更新界面 psize是剩余的                           文件大小 */
                         if(psize != 0)
                             emit foewrite(psize);
                         Sleep(10);
                        /***********************************************************/
                        tsize = psize;
                        if (tsize > maxdata)
                        {
                           tsize = maxdata;
                        }
                        if(tsize || dofinalzero)
                        {
                           worktodo = TRUE;
                           dofinalzero = FALSE;
                           segmentdata = tsize;
                           psize -= segmentdata;
                           /* if last packet was full size, add a zero size packet as final */
                           /* EOF is defined as packetsize < full packetsize */
                           if (!psize && (segmentdata == maxdata))
                           {
                              dofinalzero = TRUE;
                           }
                           FOEp->MbxHeader.length = htoes((uint16)(0x0006 + segmentdata));
                           FOEp->MbxHeader.address = htoes(0x0000);
                           FOEp->MbxHeader.priority = 0x00;
                           /* get new mailbox count value */
                           cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
                           context->slavelist[slave].mbx_cnt = cnt;
                           FOEp->MbxHeader.mbxtype = ECT_MBXT_FOE + MBX_HDR_SET_CNT(cnt); /* FoE */
                           FOEp->OpCode = ECT_FOE_DATA;
                           sendpacket++;
                           FOEp->PacketNumber = htoel(sendpacket);
                           /* 屏蔽掉了下面发送地址偏移的代码,因此每次发送的数据地址都是固定的,只需要定义一                               个固定的地址用于存放数据,每次在发送前将数据填充到该地址就可以了,这样我们就不                               需要一个很大的内存了 */
                           memcpy(&FOEp->Data[0], p, segmentdata);
//                           p = (uint8 *)p + segmentdata;

2、ecx_FOEread_gxf

                  /* 对比源码修改部分 */
                 
                 segmentdata = etohs(aFOEp->MbxHeader.length) - 0x0006;
                  packetnumber = etohl(aFOEp->PacketNumber);
                  /* 这里就不需要判断接收数据总大小是否大于申请的内存的大小了,因为 p 的地址不偏移了,每次接收完数据后,根据自定义的信号量去固定的内存地址拷贝已读取到的数据并保存到文件,不需要太大的内存了 */
//                  if ((packetnumber == ++prevpacket) && (dataread + segmentdata <= buffersize))
                  if ((packetnumber == ++prevpacket) && (segmentdata <= maxdata))
                  {
                     memcpy(p, &aFOEp->Data[0], segmentdata);
                     dataread += segmentdata;
                      /* 发送数据的地址不偏移 */
//                     p = (uint8 *)p + segmentdata;
                     if (segmentdata == maxdata)
                     {
                        worktodo = TRUE;
                     }

                    /* 发送自定义信号量,更新界面 dataread已接收的文件大小 */
                     emit foeread(dataread);
                     Sleep(10);
                     FOEp->MbxHeader.length = htoes(0x0006);
                     FOEp->MbxHeader.address = htoes(0x0000);

这里只是简单的介绍了一下程序的实现思路,可能有点绕。

4、其他

测试的例程在连接从站后,直接请求从站的状态从Init跳转到了Bootstrap。在Bootstrap的时候值能通过FOE进行通信。还有一个地方需要注意,在Bootstrap状态的时候,通信的邮箱是boot邮箱,虽然地址、大小一般都和标准邮箱(COE用的邮箱)一样,但是还是需要进行配置的。boot邮箱的配置在EEPROM中,因此在进入Bootstrap的时候会去EEPROM中读取boot邮箱的配置。如果和标准邮箱的配置一样且标准邮箱已经配置过了,就不需要再进行配置了,否则,需要对boot邮箱进行配置。下图就是XML中Boot邮箱的地址、大小的配置信息。需要再SSC-Tool中勾选了BOOTSTRAPMODE_SUPPORTED选项,xml中才会产生。

在这里插入图片描述

5、结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6、源码连接

FOE主站源码:https://github.com/IJustLoveMyself/csdn-example/tree/main/example4

配合测试的STM32F405从站源码:https://github.com/IJustLoveMyself/csdn-example/tree/main/example5

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

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

相关文章

Java开发 - 让你少走弯路的Redis的主从复制

前言 大家举举手&#xff0c;让我看看还有多少人不会配置Redis的主从&#xff0c;主主这些的。故事发生在前段时间&#xff0c;小伙伴看到了博主的MySQL主从&#xff0c;就问博主有没有Redis的主从配置教程&#xff0c;本以为网上到处都是教程的博主打开网页一搜&#xff0c;好…

SpringCloud:分布式缓存之Redis主从

1.搭建主从架构 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 2.主从数据同步原理 2.1.全量同步 主从第一次建立连接时&#xff0c;会执行全量同步&#xff0c;将master节点的所有数据…

VSCode+Git+TortoiseGit+Gitee

目录 一、VSCode 1、VSCode(visual studio code)下载安装 2、VSCode使用技巧和经验 2.1、设置字体: 2.2、快捷方式 2.3、安装插件 二、Git下载安装 三、TortoiseGit 1、TortoiseGit 简介 2、下载安装Git及Tortoisegit 3、Tortoisegit拉取gitee仓库到本地 4、Git拉取…

Linux 终端安装并使用tmux管理远程会话 tmux使用教程

文章目录 1 Tmux简介1.1 会话与窗口1.2 tmux功能 2 tmux安装2.1 源码安装2.2 命令行安装 3 基本用法&#xff08;命令行&#xff09;3.1 创建窗口3.2 分离会话 切换会话3.3 连接会话3.4 关闭会话并杀死进行对会话进行重命名 4 Tmux 的快捷键5 窗口操作与窗格操作参考 1 Tmux简介…

Ctfshow基础二刷(1)

前言&#xff1a; 前两天的信安给我整emo了&#xff0c;头一回打正经比赛&#xff0c;结果发现基础太差&#xff0c;代码审计烂得一踏糊涂。 寻思寻思&#xff0c;从头整一遍基础。又买了安恒出的新书。争取7号去吉林打省队选拔不给导儿丢脸吧呜呜 文件包含 web78: 这题一…

前端gojs中禁用指定节点的选中效果

代码思路 适用于禁用某些节点的选中状态&#xff0c;选中节点时判断该节点要不要禁用 点击节点的时候&#xff0c;判断节点要不要禁用选中效果 如果禁用&#xff0c;就在选中时&#xff0c;把选中节点重置为最近一次非禁用的节点 diagram.select&#xff1a;选中节点 diagram.…

INCA使用记录(一):INCA新建工程及观测标定

目录 1、概述 2、INCA实用方法 2.1、新建工程-添加A2L 2.2、添加工作空间 2.3、添加实验选项 ​2.4、添加硬件配置 2.5、添加工程elf 2.6、初始化工程 2.7、测量与观测参数 2.8、更换A2L之后如何更新工程 1、概述 INCA作为汽车行业常用的一种XCP处理工具&#xff0c;对…

javascript基础十一:JavaScript中执行上下文和执行栈是什么?

一、执行上下文 简单的来说&#xff0c;执行上下文是对Javascript代码执行环境的一种抽象概念&#xff0c;只要有Javascript代码运行&#xff0c;那么它就一定是运行在执行上下文中 执行上下文的类型分为三种&#xff1a; 全局执行上下文&#xff1a;只有一个&#xff0c;浏…

基于MPC的自适应巡航控制(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Kyligence x 明道云|低代码平台助力中小企业实现存量背景下的创新增长

国内大部分制造企业在经历疫情后&#xff0c;终于迎来了市场端的消费需求的恢复和增长&#xff0c;但如何在激烈的竞争中以更少投入&#xff0c;获得更高回报&#xff0c;在市场上获得一席生存之地&#xff0c;成为了悬在众多企业头上的达摩克利斯之剑。在市场野蛮生长阶段时&a…

使用PYQT5和VTK实现一个六轴跟随的电路板转动动画效果

实现过程&#xff1a; 关于六轴&#xff1a; 线下有一个带有六轴姿态传感器的硬件设备&#xff0c;将采集到的三轴加速度和角速度的值每隔1秒通过串口发送给电脑&#xff0c;电脑上位机使用的是pyqt5&#xff0c;在python中调用serial模块进行串口数据的接收&#xff0c;接收…

专业是要选软工还是人工智能?

大家好&#xff0c;我是帅地。 在帅地的训练营里&#xff0c;也有不少 26 届的学员&#xff0c;不过大一即将过去&#xff0c;部分学校是到了大一后面或者大二才开始细分专业方向的&#xff0c;包括一些想要转专业的同学&#xff0c;也需要选择一个细分的方向&#xff0c;而且…

10:mysql----存储引擎--进阶篇

目录 1:MySQL体系结构 2:存储引擎简介 3:存储引擎特点 4:存储引擎选择 1:MySQL体系结构 连接层 : 最上层是一些客户端和链接服务&#xff0c;主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。 服务层 :…

抽象轻松JavaScript

想象一样&#xff0c;现在有一个苹果&#xff0c;两个苹果&#xff0c;一箱苹果在你面前 看&#xff0c;上面的三种苹果&#xff0c;&#xff08;我写的是苹果就是苹果&#xff09; 语境1 例如你现在要搬运苹果&#xff01; 那么现在上面有苹果&#xff0c;一个&#xff0c;两…

阿里云的数据库架构如何设计,以实现高可用性和容灾性?

阿里云的数据库架构如何设计&#xff0c;以实现高可用性和容灾性&#xff1f;   在当今的数字化时代&#xff0c;数据库作为应用程序的核心组件之一&#xff0c;对于企业的正常运行至关重要。这篇文章将为您解析阿里云如何设计其数据库架构&#xff0c;以实现高可用性和容灾性…

加法器种类介绍

二进制加法器 二进制加法器接收加数A和B,以及进位Ci,输出和S,以及进位输出Co.二进制加法器的真值表如下&#xff1a; 逻辑表达式&#xff1a; S A ⊕ B ⊕ C i SA⊕B⊕C_i SA⊕B⊕Ci​ C o A B B C i A C i C_oABBC_iAC_i Co​ABBCi​ACi​ 从实现的角度&#xff0c;可以…

好兄弟,一天面了4家公司,堪称Offer收割机...

好兄弟一天面了4家公司&#xff0c;堪称Offer收割机… 面试感受 先说一个字 是真的 “ 累 ” 安排的太满的后果可能就是一天只吃一顿饭&#xff0c;一直奔波在路上 不扯这个了&#xff0c;给大家说说面试吧&#xff0c;我工作大概两年多的时间&#xff0c;大家可以参考下 在…

开关电源DCDC并联均流输出8V(XL4015)-2011年全国电赛题

2011年全国电赛题-开关电源模块并联供电系统&#xff0c;两路XL4015芯片做DCDC模块输出8V&#xff0c;采用主从均流法&#xff0c;可实现多种比例精确分配电流&#xff0c;效率在80%以上。 题目 设计并制作一个由两个额定输出功率均为 16W 的 8V DC/DC 模块构成的并联供电系统…

【事务】@Transactional 注解参数详解

文章目录 前言一、参数详解1.1、isolation&#xff08;事务隔离级别&#xff09;1.2、propagation&#xff08;事务传播机制&#xff09;1.3、readOnly&#xff08;事务读写性&#xff09;1.4、noRollbackFor 和 noRollbackForClassName&#xff08;遇到时不回滚&#xff09;1.…

PHPMySQL基础(四):模拟登录Login功能案例

PHP&MySQL基础&#xff08;一&#xff09;:创建数据库并通过PHP进行连接_长风沛雨的博客-CSDN博客 PHP&MySQL基础&#xff08;二&#xff09;:通过PHP对MySQL进行增、删、改、查_长风沛雨的博客-CSDN博客 PHP&MySQL基础&#xff08;三&#xff09;:处理查询SQL返…