EtherCAT主站开发(学习笔记七)

  • 时间:2025-12-11 00:55 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要: 前言 之前针对EtherCAT主站系统的学习了基础配置,主从站初始化以及通信接口的学习,但对于驱动伺服电机还不够,本次就针对子协议层进行学习,今天主要总结COE协议层的学习。COE是基于 CANopen 协议的对象字典(OD)、SDO/PDO 通信、紧急报文(Emergency)、心跳报文等,是 EtherCAT 最基础的应用层协议,几乎所有的伺服从站都支持COE,是控制伺服从站的核心


前言

之前针对EtherCAT主站系统的学习了基础配置,主从站初始化以及通信接口的学习,但对于驱动伺服电机还不够,本次就针对子协议层进行学习,今天主要总结COE协议层的学习。COE是基于 CANopen 协议的对象字典(OD)、SDO/PDO 通信、紧急报文(Emergency)、心跳报文等,是 EtherCAT 最基础的应用层协议,几乎所有的伺服从站都支持COE,是控制伺服从站的核心。

一、ecx_SDOread:



int ecx_SDOread(ecx_contextt *context, uint16 slave, uint16 index, uint8 subindex,
                boolean CA, int *psize, void *p, int timeout)
{
   ec_SDOt *SDOp, *aSDOp;
   uint16 bytesize, Framedatasize;
   int wkc = 0;
   int32 SDOlen;
   uint8 *bp;
   uint8 *hp;
   ec_mbxbuft *MbxIn, *MbxOut;
   uint8 cnt, toggle;
   boolean NotLast;
   MbxIn = NULL;
   MbxOut = NULL;
   wkc = ecx_mbxreceive(context, slave, &MbxIn, 0); // 清空从站待处理的旧邮箱数据
   MbxOut = ecx_getmbx(context); // 获取主站可用的邮箱发送缓冲区
   if (!MbxOut) return wkc; // 缓冲区获取失败,直接返回
   ec_clearmbx(MbxOut); // 清空发送缓冲区,避免旧数据干扰
    SDOp = (ec_SDOt *)MbxOut; // 将邮箱缓冲区转为SDO报文结构体
    // 1. 填充邮箱头部
    SDOp->MbxHeader.length = htoes(0x000a); // 报文长度(10字节,SDO请求固定长度)
    SDOp->MbxHeader.address = htoes(0x0000); // 从站邮箱地址(默认0)
    SDOp->MbxHeader.priority = 0x00; // 通信优先级(默认最低)
    // 2. 生成会话计数(防报文重复)
    cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
    context->slavelist[slave].mbx_cnt = cnt;
    SDOp->MbxHeader.mbxtype = ECT_MBXT_COE + MBX_HDR_SET_CNT(cnt); // 标记为COE报文+会话计        数
    // 3. 填充SDO核心字段
    SDOp->CANOpen = htoes(0x000 + (ECT_COES_SDOREQ << 12)); // 标记为SDO请求
    if (CA) {
    SDOp->Command = ECT_SDO_UP_REQ_CA; // 完整访问模式(读取整个子索引)
    } else {
   SDOp->Command = ECT_SDO_UP_REQ; // 普通读取模式(读取指定子索引)
    }
    SDOp->Index = htoes(index); // 要读取的对象字典索引(如0x6064)
    if (CA && (subindex > 1)) subindex = 1; // 完整访问模式下子索引强制为1
    SDOp->SubIndex = subindex; // 要读取的子索引(如0x00)
    SDOp->ldata[0] = 0; // 数据区初始化为0
    wkc = ecx_mbxsend(context, slave, MbxOut, EC_TIMEOUTTXM); // 发送SDO请求到从站
    MbxOut = NULL; // 释放发送缓冲区引用
    if (wkc > 0) { // 发送成功才继续处理响应
        if (MbxIn) ecx_dropmbx(context, MbxIn); // 清空旧响应
        MbxIn = NULL;
        do {
           wkc = ecx_mbxreceive(context, slave, &MbxIn, timeout); // 等待从站响应
           } while ((wkc > 0) && !MbxIn); // 直到收到有效响应或超时
        aSDOp = (ec_SDOt *)MbxIn; // 将响应缓冲区转为SDO结构体
        if (((aSDOp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_COE) && // 是COE报文
        ((etohs(aSDOp->CANOpen) >> 12) == ECT_COES_SDORES) && // 是SDO响应
        (aSDOp->Index == SDOp->Index)) { // 响应的索引与请求一致
        if ((aSDOp->Command & 0x02) > 0) { // 标记为快速传输
           bytesize = 4 - ((aSDOp->Command >> 2) & 0x03); // 计算实际数据长度(1-4字节)
           if (*psize >= bytesize) { // 用户缓冲区足够大
              memcpy(p, &aSDOp->ldata[0], bytesize); // 拷贝数据到用户缓冲区
              *psize = bytesize; // 返回实际读取的字节数
           } else {
              wkc = 0; // 缓冲区不足,标记失败
              ecx_packeterror(context, slave, index, subindex, 3); // 报错
           }
        }
        else { // 普通帧(分段传输)
       SDOlen = etohl(aSDOp->ldata[0]); // 获取总数据长度
       if (SDOlen <= *psize) { // 用户缓冲区足够
          bp = p; // 数据缓冲区起始指针
          hp = p; // 数据缓冲区当前指针
          Framedatasize = (etohs(aSDOp->MbxHeader.length) - 10); // 第一段数据长度
       if (Framedatasize < SDOlen) { // 需要分段传输
         memcpy(hp, &aSDOp->ldata[1], Framedatasize); // 拷贝第一段数据
         hp += Framedatasize; // 移动指针
         *psize = Framedatasize; // 初始化已读取长度
         NotLast = TRUE;
         toggle = 0x00; // 分段切换位(0/1交替,防丢包)
         while (NotLast) { // 循环读取剩余分段
            // 1. 封装分段读取请求(和步骤2逻辑一致,仅Command改为分段请求)
            MbxOut = ecx_getmbx(context);
            SDOp = (ec_SDOt *)MbxOut;
            // ...(省略重复的头部封装)
            SDOp->Command = ECT_SDO_SEG_UP_REQ + toggle; // 分段读取请求+切换位
            // 2. 发送分段请求
            wkc = ecx_mbxsend(context, slave, MbxOut, EC_TIMEOUTTXM);
            if (wkc > 0) {
               // 3. 接收分段响应
               wkc = ecx_mbxreceive(context, slave, &MbxIn, timeout);
               aSDOp = (ec_SDOt *)MbxIn;
               // 4. 解析分段响应
               if (((aSDOp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_COE) && 
                   ((etohs(aSDOp->CANOpen) >> 12) == ECT_COES_SDORES)) {
                  Framedatasize = etohs(aSDOp->MbxHeader.length) - 3; // 分段数据长度
                  if ((aSDOp->Command & 0x01) > 0) { // 最后一段
                     NotLast = FALSE;
                     // 修正最后一段的有效数据长度
                     if (Framedatasize == 7)
                        Framedatasize = Framedatasize - ((aSDOp->Command & 0x0e) >> 1);
                     memcpy(hp, &(aSDOp->Index), Framedatasize); // 拷贝最后一段
                  } else { // 非最后一段
                     memcpy(hp, &(aSDOp->Index), Framedatasize); // 拷贝当前段
                     hp += Framedatasize; // 移动指针
                  }
                  *psize += Framedatasize; // 更新总长度
               }
               toggle = toggle ^ 0x10; // 切换位翻转(0x00→0x10→0x00...)
            }
         }
      } else { // 无需分段(总长度≤单帧长度)
         memcpy(bp, &aSDOp->ldata[1], SDOlen); // 直接拷贝全部数据
         *psize = SDOlen;
      }
   } else {
      wkc = 0; // 缓冲区不足,标记失败
      ecx_packeterror(context, slave, index, subindex, 3);
   }
}

参数说明

参数作用
ecx_contextt *contextSOEM 上下文结构体,包含主站通信句柄、从站列表、邮箱缓冲区等核心信息
ec_SDOt *SDOpSDO报文结构体指针,封装SDO通信的核心字段(索引、子索引、命令、数据等)
ec_mbxbuft *MbxIn/MbxOut邮箱缓冲区指针,Mbxout为主站发送的报文,MbxIn为从站响应的报文
uint8 cnt/togglecnt是邮箱会话计数(防报文重复),toggle是分段传输的切换位(防丢包)
boolean NotLast分段传输标记, TRUE表示还有后续分段, FALSE表示最后一段
int wkc通信结果返回值(Work Counter),>0 成功,0 失败

函数功能

该接口实现SOEM主站读取从站对象字典(OD)参数的核心函数,支持两种读写方式:快速传输(Expedited)适用于小于4字节短数据,一次报文传输完成,分段传输(Segmented)适用于大于4字节的数据,通过多段报文分块传输,最终将读取到的数据存储到用户指定的缓冲区p。函数大致流程可分为初始化--发送SDO请求--接收从站响应--解析响应数据(快速/分段)--异常处理--资源释放。

二、ecx_SDOwrite:



int ecx_SDOwrite(ecx_contextt *context, uint16 Slave, uint16 Index, uint8 SubIndex,
                 boolean CA, int psize, const void *p, int Timeout)
{
   ec_SDOt *SDOp, *aSDOp;
   int wkc, maxdata;
   ec_mbxbuft *MbxIn, *MbxOut;
   uint8 cnt, toggle;
   int framedatasize;
   boolean NotLast;
   const uint8 *hp;
   MbxIn = NULL;
   MbxOut = NULL;
   wkc = ecx_mbxreceive(context, Slave, &MbxIn, 0); // 清空从站旧邮箱数据
   MbxOut = ecx_getmbx(context); // 获取发送缓冲区
   if (!MbxOut) return wkc; // 缓冲区获取失败,直接返回
   ec_clearmbx(MbxOut); // 清空发送缓冲区
   SDOp = (ec_SDOt *)MbxOut; // 转为SDO报文结构体
   maxdata = context->slavelist[Slave].mbx_l - 0x10; // 计算单帧最大数据长度
   if ((psize <= 4) && !CA) { // 数据≤4字节 + 非完整访问(CA=FALSE)
    // 1. 填充邮箱头部
    SDOp->MbxHeader.length = htoes(0x000a); // 报文长度(10字节,固定)
    SDOp->MbxHeader.address = htoes(0x0000); // 从站邮箱地址
    SDOp->MbxHeader.priority = 0x00; // 通信优先级
    // 2. 生成会话计数(防报文重复)
    cnt = ec_nextmbxcnt(context->slavelist[Slave].mbx_cnt);
    context->slavelist[Slave].mbx_cnt = cnt;
    SDOp->MbxHeader.mbxtype = ECT_MBXT_COE + MBX_HDR_SET_CNT(cnt); // COE报文+会话计数
    // 3. 填充SDO核心字段
    SDOp->CANOpen = htoes(0x000 + (ECT_COES_SDOREQ << 12)); // 标记为SDO请求
    // 关键:Command字段封装快速写+未使用字节数
    SDOp->Command = ECT_SDO_DOWN_EXP | (((4 - psize) << 2) & 0x0c); 
    SDOp->Index = htoes(Index); // 写入的索引(如0x6040)
    SDOp->SubIndex = SubIndex; // 写入的子索引(如0x00)
    // 4. 拷贝用户数据到报文缓冲区
    hp = p;
    memcpy(&SDOp->ldata[0], hp, psize); // 把要写入的数据拷贝到SDO数据区
    // 发送SDO写请求到从站
    wkc = ecx_mbxsend(context, Slave, MbxOut, EC_TIMEOUTTXM);
    MbxOut = NULL;
    if (wkc > 0) { // 发送成功
       if (MbxIn) ecx_dropmbx(context, MbxIn);
       MbxIn = NULL;
       // 等待从站响应
       wkc = ecx_mbxreceive(context, Slave, &MbxIn, Timeout);
       if (wkc > 0) { // 收到响应
          aSDOp = (ec_SDOt *)MbxIn;
          // 验证响应合法性:COE报文 + SDO响应 + 索引/子索引匹配
          if (((aSDOp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_COE) &&
              ((etohs(aSDOp->CANOpen) >> 12) == ECT_COES_SDORES) &&
              (aSDOp->Index == SDOp->Index) &&
              (aSDOp->SubIndex == SDOp->SubIndex)) {
             // 验证通过,写入成功(无需额外操作)
          } else {
             // 响应不合法:处理SDO中止或未知错误
             if (aSDOp->Command == ECT_SDO_ABORT) {
                ecx_SDOerror(context, Slave, Index, SubIndex, etohl(aSDOp->ldata[0]));
             } else {
                ecx_packeterror(context, Slave, Index, SubIndex, 1);
             }
             wkc = 0; // 标记失败
          }
       }
    }
    else {
       framedatasize = psize; // 初始化为总数据长度
       NotLast = FALSE;
       if (framedatasize > maxdata) { // 超过单帧最大长度,需要分段
          framedatasize = maxdata;
          NotLast = TRUE;
       }
        // 1. 填充邮箱头部(长度=协议头+初始化段数据长度)
        SDOp->MbxHeader.length = htoes((uint16)(0x0a + framedatasize));
        SDOp->MbxHeader.address = htoes(0x0000);
        SDOp->MbxHeader.priority = 0x00;
        // 2. 会话计数(和快速传输一致)
        cnt = ec_nextmbxcnt(context->slavelist[Slave].mbx_cnt);
        context->slavelist[Slave].mbx_cnt = cnt;
        SDOp->MbxHeader.mbxtype = ECT_MBXT_COE + MBX_HDR_SET_CNT(cnt);
        SDOp->CANOpen = htoes(0x000 + (ECT_COES_SDOREQ << 12)); // SDO请求
        // 3. 封装Command字段(初始化段)
        if (CA) {
           SDOp->Command = ECT_SDO_DOWN_INIT_CA; // 完整访问初始化
        } else {
           SDOp->Command = ECT_SDO_DOWN_INIT; // 普通初始化
        }
        SDOp->Index = htoes(Index);
        SDOp->SubIndex = SubIndex;
        if (CA && (SubIndex > 1)) {
           SDOp->SubIndex = 1; // 完整访问子索引强制为1
        }
        SDOp->ldata[0] = htoel(psize); // 写入总数据长度(从站据此判断是否接收完整)
        // 4. 拷贝初始化段数据
        hp = p;
        memcpy(&SDOp->ldata[1], hp, framedatasize); // 拷贝第一段数据
        hp += framedatasize; // 移动指针
        psize -= framedatasize; // 剩余数据长度递减
        // 发送初始化段
        wkc = ecx_mbxsend(context, Slave, MbxOut, EC_TIMEOUTTXM);
        MbxOut = NULL;
        if (wkc > 0) {
           // 接收从站对初始化段的响应
           wkc = ecx_mbxreceive(context, Slave, &MbxIn, Timeout);
           if (wkc > 0) {
              aSDOp = (ec_SDOt *)MbxIn;
              // 验证响应合法性(索引/子索引匹配)
              if (((aSDOp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_COE) &&
                  ((etohs(aSDOp->CANOpen) >> 12) == ECT_COES_SDORES) &&
                  (aSDOp->Index == SDOp->Index) &&
                  (aSDOp->SubIndex == SDOp->SubIndex)) {
                maxdata += 7; // 分段传输时单帧最大长度调整(协议头更短)
                toggle = 0; // 分段切换位(防丢包)
                while (NotLast) { // 循环直到最后一段
                   MbxOut = ecx_getmbx(context);
                   if (!MbxOut) break;
                   ec_clearmbx(MbxOut);
                   SDOp = (ec_SDOt *)MbxOut;
                   framedatasize = psize; // 当前段要传输的长度
                   NotLast = FALSE;
                   SDOp->Command = 0x01; // 默认标记为最后一段
                   if (framedatasize > maxdata) { // 还有后续段
                      framedatasize = maxdata;
                      NotLast = TRUE;
                      SDOp->Command = 0x00; // 标记为“还有后续段”
                   }
                   // 处理最后一段的特殊情况(长度<7字节)
                   if (!NotLast && (framedatasize < 7)) {
                      SDOp->MbxHeader.length = htoes(0x0a); // 最小长度
                      // 封装最后一段的“未使用字节数”
                      SDOp->Command = (uint8)(0x01 + ((7 - framedatasize) << 1));
                   } else {
                      SDOp->MbxHeader.length = htoes((uint16)(framedatasize + 3)); // 数据+协议头长度
                   }
                   // 填充头部(会话计数、优先级等)
                   SDOp->MbxHeader.address = htoes(0x0000);
                   SDOp->MbxHeader.priority = 0x00;
                   cnt = ec_nextmbxcnt(context->slavelist[Slave].mbx_cnt);
                   context->slavelist[Slave].mbx_cnt = cnt;
                   SDOp->MbxHeader.mbxtype = ECT_MBXT_COE + MBX_HDR_SET_CNT(cnt);
                   SDOp->CANOpen = htoes(0x000 + (ECT_COES_SDOREQ << 12));
                   SDOp->Command = SDOp->Command + toggle; // 加入切换位
                   // 拷贝当前段数据到报文
                   memcpy(&SDOp->Index, hp, framedatasize);
                   hp += framedatasize; // 移动指针
                   psize -= framedatasize; // 剩余长度递减
                   // 发送当前段
                   wkc = ecx_mbxsend(context, Slave, MbxOut, EC_TIMEOUTTXM);
                   MbxOut = NULL;
                   if (wkc > 0) {
                  // 接收从站对当前段的响应
                  wkc = ecx_mbxreceive(context, Slave, &MbxIn, Timeout);
                      if (wkc > 0) {
                     aSDOp = (ec_SDOt *)MbxIn;
                     // 验证响应合法性
                     if (((aSDOp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_COE) &&
                         ((etohs(aSDOp->CANOpen) >> 12) == ECT_COES_SDORES) &&
                         ((aSDOp->Command & 0xe0) == 0x20)) {
                        // 响应合法,继续
                         } else {
                            // 处理中止/未知错误
                        if (aSDOp->Command == ECT_SDO_ABORT) {
                           ecx_SDOerror(context, Slave, Index, SubIndex, etohl(aSDOp->ldata[0]));
                        } else {
                           ecx_packeterror(context, Slave, Index, SubIndex, 1);
                    }
                        wkc = 0;
                        NotLast = FALSE;
                     }
                  }
               }
               toggle = toggle ^ 0x10; // 切换位翻转(0x00→0x10→0x00...)
            }
             /* unexpected response from slave */
            else
            {
               if (aSDOp->Command == ECT_SDO_ABORT) /* SDO abort frame received */
               {
                  ecx_SDOerror(context, Slave, Index, SubIndex, etohl(aSDOp->ldata[0]));
               }
               else
               {
                  ecx_packeterror(context, Slave, Index, SubIndex, 1); /* Unexpected frame returned */
               }
               wkc = 0;
            }
         }
      }
   }
   if (MbxIn) ecx_dropmbx(context, MbxIn);
   if (MbxOut) ecx_dropmbx(context, MbxOut);
   return wkc;
}

参数说明

参数作用
int maxdata单帧最大可传输数据长度(=从站邮箱大小-协议头长度),用于判断是否需要分段
const uint8 *hp指向用户数据缓冲区的指针,用于逐段拷贝数据
int framedatasize单帧实际传输的数据长度(分段传输时每段的长度)
boolean NotLast分段传输标记,TRUE标识还有后续分段,FALSE表示最后一段
int psize输入参数,用户要写入的数据总长度

函数功能

该函数主要是SOEM中实现EtherCAT主站向从站对象字典(OD)写入参数,同样支持快速传输/分段传输两种模式整体流程可分为初始化--判断传输迷失--封装并发送请求--接收从站响应--分段传输循环--异常处理-资源释放

三、ecx_RxPDO:



int ecx_RxPDO(ecx_contextt *context, uint16 Slave, uint16 RxPDOnumber, int psize, const void *p)
{
   ec_SDOt *SDOp;
   int wkc, maxdata, framedatasize;
   ec_mbxbuft *MbxIn, *MbxOut;
   uint8 cnt;
 
   MbxIn = NULL;
   MbxOut = NULL;
    //读取并丢弃从站邮箱中未处理的旧数据
   wkc = ecx_mbxreceive(context, Slave, &MbxIn, 0);
   if (MbxIn) ecx_dropmbx(context, MbxIn);
    //获取主站可用邮箱发送缓冲区
   MbxOut = ecx_getmbx(context);
   if (!MbxOut) return wkc;//获取失败直接返回
    /清空发送缓冲区
   ec_clearmbx(MbxOut);
    //将邮箱缓冲区转为SDO/PDO通用报文结构体
   SDOp = (ec_SDOt *)MbxOut;
    //计算单帧可传输的最大PDO数据长度
   maxdata = context->slavelist[Slave].mbx_l - 0x08; /* data section=mailbox size - 6 mbx - 2 CoE */
   framedatasize = psize;//初始化为用户要发送的数据长度
   if (framedatasize > maxdata)
   {
      framedatasize = maxdata;//超过最大长度则截断
   }
    //填充邮箱头部 报文总长度=2字节COE头+实际数据长度
   SDOp->MbxHeader.length = htoes((uint16)(0x02 + framedatasize));
   SDOp->MbxHeader.address = htoes(0x0000);//从站邮箱地址,默认0
   SDOp->MbxHeader.priority = 0x00;//通信优先级,默认最低
    //生成会话计数
   cnt = ec_nextmbxcnt(context->slavelist[Slave].mbx_cnt);
   context->slavelist[Slave].mbx_cnt = cnt;
    //标记报文类型为COE+会话计数
   SDOp->MbxHeader.mbxtype = ECT_MBXT_COE + MBX_HDR_SET_CNT(cnt);          /* CoE */
   SDOp->CANOpen = htoes((RxPDOnumber & 0x01ff) + (ECT_COES_RXPDO << 12)); /* number 9bits service upper 4 bits */
   /* copy PDO data to mailbox */
   memcpy(&SDOp->Command, p, framedatasize);
   /* send mailbox RxPDO request to slave */
   wkc = ecx_mbxsend(context, Slave, MbxOut, EC_TIMEOUTTXM);
   MbxOut = NULL;
   return wkc;
}

函数参数

参数作用
RxPDOnumberRxPDO 编号(如 0x1600、0x1601),对应从站对象字典中预配置的 RxPDO 映射表
maxdata单帧可传输的最大 PDO 数据长度(= 从站邮箱大小 - 8 字节协议头)
framedatasize实际要发送的 PDO 数据长度(不超过 maxdata
ECT_COES_RXPDOCOE 协议中 “RxPDO 传输” 的服务码(固定值),标记报文类型为 RxPDO
MBX_HDR_SET_CNT邮箱会话计数(防报文重复,和 SDO 函数逻辑一致)

函数功能

该接口的核心作用是主站通过EtherCAT邮箱,将实时控制数据(如电机速度给定、IO输出信号)发送到指定从站的指定RxPDO通道,大致流程为“初始化--计算传输长度--封装PDO报文--发送报文--返回结果”。

四、ecx_TxPDO:



int ecx_TxPDO(ecx_contextt *context, uint16 slave, uint16 TxPDOnumber, int *psize, void *p, int timeout)
{
   ec_SDOt *SDOp, *aSDOp;
   int wkc;
   ec_mbxbuft *MbxIn, *MbxOut;
   uint8 cnt;
   uint16 framedatasize;
 
   MbxIn = NULL;
   MbxOut = NULL;
    //读取并丢弃从站邮箱中未处理的旧数据
   wkc = ecx_mbxreceive(context, slave, &MbxIn, 0);
   if (MbxIn) ecx_dropmbx(context, MbxIn);
    //获取主站可用的邮箱发送缓冲区
   MbxOut = ecx_getmbx(context);
   if (!MbxOut) return wkc;//获取失败,直接返回
    //清空发送缓冲区
   ec_clearmbx(MbxOut);
    //将邮箱缓冲区转换为SDO/PDO通用报文结构体
   SDOp = (ec_SDOt *)MbxOut;
    //填充邮箱头部:仅2字节COE头(无数据,因为是读取请求)
   SDOp->MbxHeader.length = htoes(0x02);
   SDOp->MbxHeader.address = htoes(0x0000);//从站邮箱地址
   SDOp->MbxHeader.priority = 0x00;//通信优先级
   //生成会话计数
   cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
   context->slavelist[slave].mbx_cnt = cnt;
    //标记报文类型COE+会话计数
   SDOp->MbxHeader.mbxtype = ECT_MBXT_COE + MBX_HDR_SET_CNT(cnt);             /* CoE */
   SDOp->CANOpen = htoes((TxPDOnumber & 0x01ff) + (ECT_COES_TXPDO_RR << 12)); /* number 9bits service upper 4 bits */
    //发送TxPDO读取请求到指定从站
   wkc = ecx_mbxsend(context, slave, MbxOut, EC_TIMEOUTTXM);
   MbxOut = NULL;//释放发送缓冲区引用
   if (wkc > 0)
   {
      if (MbxIn) ecx_dropmbx(context, MbxIn);
      MbxIn = NULL;
      //阻塞等待从站TxPDO响应
      wkc = ecx_mbxreceive(context, slave, &MbxIn, timeout);
      if (wkc > 0) /* succeeded to read slave response ? */
      {
         aSDOp = (ec_SDOt *)MbxIn;
         /* slave response should be CoE, TxPDO */
         if (((aSDOp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_COE) &&
             ((etohs(aSDOp->CANOpen) >> 12) == ECT_COES_TXPDO))
         {
            /* TxPDO response */
            framedatasize = (aSDOp->MbxHeader.length - 2);
            if (*psize >= framedatasize) /* parameter buffer big enough ? */
            {
               /* copy parameter in parameter buffer */
               memcpy(p, &aSDOp->Command, framedatasize);
               /* return the real parameter size */
               *psize = framedatasize;
            }
            /* parameter buffer too small */
            else
            {
               wkc = 0;
               ecx_packeterror(context, slave, 0, 0, 3); /*  data container too small for type */
            }
         }
         /* other slave response */
         else
         {
            if ((aSDOp->Command) == ECT_SDO_ABORT) /* SDO abort frame received */
            {
               ecx_SDOerror(context, slave, 0, 0, etohl(aSDOp->ldata[0]));
            }
            else
            {
               ecx_packeterror(context, slave, 0, 0, 1); /* Unexpected frame returned */
            }
            wkc = 0;
         }
      }
   }
   if (MbxIn) ecx_dropmbx(context, MbxIn);
   if (MbxOut) ecx_dropmbx(context, MbxOut);
   return wkc;
}

参数说明

同上

函数功能

该接口与上面ecx_RxPDO相对应,主站向从站发送TxPDO读取请求,等待并接收从站返回的TxPDO数据(如电机的实际位置、IO输入状态、故障码等),其大致流程为“初始化--封装读取请求--发送请求--接受响应--验证并解析数据--释放资源”。在实际应用中要和ecx_RxPDO配合形成“主站发控制命令--从站返回状态”的闭环控制。

五、



int ecx_readODlist(ecx_contextt *context, uint16 Slave, ec_ODlistt *pODlist)
{
   ec_SDOservicet *SDOp, *aSDOp;
   ec_mbxbuft *MbxIn, *MbxOut;
   int wkc;
   uint16 x, n, i, sp, offset;
   boolean stop;
   uint8 cnt;
   boolean First;
    //初始化OD列表结构体
   pODlist->Slave = Slave;
   pODlist->Entries = 0;
   MbxIn = NULL;
   MbxOut = NULL;
    //读取并丢弃从站邮箱旧数据
   wkc = ecx_mbxreceive(context, Slave, &MbxIn, 0);
   if (MbxIn) ecx_dropmbx(context, MbxIn);
    //获取发送缓冲区
   MbxOut = ecx_getmbx(context);
   if (!MbxOut) return wkc;
   ec_clearmbx(MbxOut);
   SDOp = (ec_SDOservicet *)MbxOut;//转化为SDO拓展服务结构体
    //填充邮箱头部
   SDOp->MbxHeader.length = htoes(0x0008);
   SDOp->MbxHeader.address = htoes(0x0000);
   SDOp->MbxHeader.priority = 0x00;
    //生成会话计数
   cnt = ec_nextmbxcnt(context->slavelist[Slave].mbx_cnt);
   context->slavelist[Slave].mbx_cnt = cnt;
   SDOp->MbxHeader.mbxtype = ECT_MBXT_COE + MBX_HDR_SET_CNT(cnt); /* CoE */
    //填充SDO拓展服务字段
   SDOp->CANOpen = htoes(0x000 + (ECT_COES_SDOINFO << 12)); //标记为SDO信息服务
   SDOp->Opcode = ECT_GET_ODLIST_REQ;//读取OD列表请求 
   SDOp->Reserved = 0;//保留字段
   SDOp->Fragments = 0; //分段标记
   SDOp->wdata[0] = htoes(0x01); /* all objects */
   //发送读取OD列表请求
   wkc = ecx_mbxsend(context, Slave, MbxOut, EC_TIMEOUTTXM);
   MbxOut = NULL;
   /* mailbox placed in slave ? */
   if (wkc > 0)
   {
      x = 0;
      sp = 0;
      First = TRUE;
      offset = 1; /* offset to skip info header in first frame, otherwise set to 0 */
      do
      {
         stop = TRUE; /* assume this is last iteration */
         if (MbxIn) ecx_dropmbx(context, MbxIn);
         MbxIn = NULL;
         //接收并验证
         wkc = ecx_mbxreceive(context, Slave, &MbxIn, EC_TIMEOUTRXM);
         /* got response ? */
         if (wkc > 0)
         {
            aSDOp = (ec_SDOservicet *)MbxIn;
            /* response should be CoE and "get object description list response" */
            if (((aSDOp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_COE) &&
                ((aSDOp->Opcode & 0x7f) == ECT_GET_ODLIST_RES))
            {
               if (First)
               {
                  /* extract number of indexes from mailbox data size */
                  n = (etohs(aSDOp->MbxHeader.length) - (6 + 2)) / 2;
               }
               else
               {
                  /* extract number of indexes from mailbox data size */
                  n = (etohs(aSDOp->MbxHeader.length) - 6) / 2;
               }
               /* check if indexes fit in buffer structure */
               if ((sp + n) > EC_MAXODLIST)
               {
                  n = EC_MAXODLIST + 1 - sp;
                  ecx_SDOinfoerror(context, Slave, 0, 0, 0xf000000); /* Too many entries for master buffer */
                  stop = TRUE;
               }
               /* trim to maximum number of ODlist entries defined */
               if ((pODlist->Entries + n) > EC_MAXODLIST)
               {
                  n = EC_MAXODLIST - pODlist->Entries;
               }
               pODlist->Entries += n;
               /* extract indexes one by one */
               for (i = 0; i < n; i++)
               {
                  pODlist->Index[sp + i] = etohs(aSDOp->wdata[i + offset]);
               }
               sp += n;
               /* check if more fragments will follow */
               if (aSDOp->Fragments > 0)
               {
                  stop = FALSE;
               }
               First = FALSE;
               offset = 0;
            }
            /* got unexpected response from slave */
            else
            {
               if ((aSDOp->Opcode & 0x7f) == ECT_SDOINFO_ERROR) /* SDO info error received */
               {
                  ecx_SDOinfoerror(context, Slave, 0, 0, etohl(aSDOp->ldata[0]));
                  stop = TRUE;
               }
               else
               {
                  ecx_packeterror(context, Slave, 0, 0, 1); /* Unexpected frame returned */
               }
               wkc = 0;
               x += 20;
            }
         }
         x++;
      } while ((x <= 128) && !stop);
   }
   if (MbxIn) ecx_dropmbx(context, MbxIn);
   if (MbxOut) ecx_dropmbx(context, MbxOut);
   return wkc;
}

参数说明

参数作用
ec_ODlistt *pODlist输出参数存储读取到的OD列表
ec_SDOservicetSDO拓展服务结构体(复用邮箱缓冲区),用于封装“读取OD列表”的请求/响应
ECT_GET_ODLIST_REQ“读取 OD 列表请求” 操作码(主站发送)
ECT_GET_ODLIST_RES“读取 OD 列表响应” 操作码(从站返回)
Fragments分段标记:>0 表示还有后续分段,=0 表示最后一段
offset数据偏移量:第一段响应需跳过 6 字节协议头,后续分段偏移量为 0
EC_MAXODLISTOD 列表的最大索引数(SOEM 宏定义,默认 2048),防止缓冲区溢出
First分段标记: TRUE表示处理第一段响应, FALSE表示处理后续分段

函数功能

该接口实现主站向从站发送“读取对象字典列表”请求,接受并解析从站返回的所有支持的对象字典索引,存储到ec_ODlistt结构体中,大致流程为“初始化--封装请求--发送请求--循环接收分段响应--解析索引--释放资源”。

总结

今天主要学习了COE层的主要函数,涵盖了SDO(非周期性读写)、PDO(实施周期数据传输)、OD列表读取(从站适配),后续可以针对从站进行数据通信,控制伺服电机的运动。

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部