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

目录

前言

一、ecx_init:

参数说明

函数功能

二、ecx_statecheck:

参数说明

函数功能

三、ecx_send_processdata_group / ecx_send_processdata:

参数说明

函数功能

四、ecx_receive_processdata_group / ecx_receive_processdata:

参数说明

函数功能

五、ecx_esidump:

参数说明

函数功能

总结



前言

上次学习总结了ec_config.c文件里的函数接口,了解了SOEM在工作前的初始化工作,结合之前针对ec_base.c文件的学习,对SOEM主站的工作框架有了一定的认知,本次就针对ec_main.c文件进行学习。

一、ecx_init:



int ecx_init(ecx_contextt *context, const char *ifname)
{
    //初始化EtherCAT邮箱内存池
   ecx_initmbxpool(context);
    //绑定硬件网卡,并初始化网卡的EtherCAT通信参数
   return ecx_setupnic(&context->port, ifname, FALSE);
}
//context->port:网卡端口结构体,用于存储网卡的硬件句柄、MAC地址、通信状态等信息
//ifname:传入的网卡标识,代表要绑定哪一个网卡
//FALSE:表示“不使用双网卡冗余模式”

参数说明

context:上下文结构体,存储各项信息

ifname:要绑定的网卡标识,通常是网卡驱动的逻辑名称“eth0”

函数功能

该接口是SOEM主站初始化的核心入口,负责完成主站上下文初始化与网卡硬件绑定,为后续EtherCAT通信做准备。

二、ecx_statecheck:



// 入参:context-主站上下文;slave-从站编号;reqstate-目标状态;timeout-超时时间(ms)
// 返回值:从站最终的实际状态(成功则=reqstate,失败则=当前状态/0)
uint16 ecx_statecheck(ecx_contextt *context, uint16 slave, uint16 reqstate, int timeout)
{
   // 局部变量定义
   uint16 configadr, state, rval;  // configadr-从站配置地址;state-从站当前状态;rval-原始状态值
   ec_alstatust slstat;            // AL状态结构体(存储AL状态+状态码)
   osal_timert timer;              // 超时定时器(SOEM封装的跨平台定时器)
 
   // 第一步:合法性校验——如果指定的从站编号超过总从站数,直接返回0(无效)
   if (slave > context->slavecount)
   {
      return 0;
   }
 
   // 第二步:启动超时定时器(计时timeout毫秒)
   osal_timer_start(&timer, timeout);
 
   // 第三步:获取目标从站的配置地址(EtherCAT从站的唯一标识地址)
   configadr = context->slavelist[slave].configadr;
 
   // 第四步:循环检查从站状态,直到达到目标状态或超时
   do
   {
      // 分支1:slave<1 表示检查"所有从站"(广播检查)
      if (slave < 1)
      {
         rval = 0;
         // 广播读取所有从站的AL状态寄存器(ECT_REG_ALSTAT)
         // BRD=Broadcast Read,0=广播地址,读取2字节到rval
         ecx_BRD(&context->port, 0, ECT_REG_ALSTAT, sizeof(rval), &rval, EC_TIMEOUTRET);
         // 字节序转换(网络序→主机序,适配不同CPU)
         rval = etohs(rval);
      }
      // 分支2:slave≥1 表示检查"指定单个从站"
      else
      {
         // 初始化状态结构体
         slstat.alstatus = 0;
         slstat.alstatuscode = 0;
         // 精准读取单个从站的AL状态寄存器
         // FPRD=Fast Process Data Read,按配置地址读取从站的AL状态(包含状态+状态码)
         ecx_FPRD(&context->port, configadr, ECT_REG_ALSTAT, sizeof(slstat), &slstat, EC_TIMEOUTRET);
         // 转换字节序,得到从站的AL状态
         rval = etohs(slstat.alstatus);
         // 保存从站的AL状态码(用于后续故障排查,如状态切换失败的原因)
         context->slavelist[slave].ALstatuscode = etohs(slstat.alstatuscode);
      }
 
      // 关键:提取从站的实际状态——AL状态寄存器低4位是状态值(0x000f掩码过滤)
      state = rval & 0x000f; 
 
      // 如果当前状态≠目标状态,短暂延时1ms(避免高频轮询占用CPU)
      if (state != reqstate)
      {
         osal_usleep(1000);  // 延时1000微秒=1毫秒
      }
 
   // 循环条件:1. 当前状态≠目标状态;2. 定时器未超时 → 继续循环检查
   } while ((state != reqstate) && (osal_timer_is_expired(&timer) == FALSE));
 
   // 第五步:将最终读取到的状态保存到主站上下文(方便后续查询)
   context->slavelist[slave].state = rval;
 
   // 第六步:返回从站最终的实际状态
   return state;
}

参数说明

cintext:上下文结构体

slave:从站编号

reqstate:目标状态

timeout:超时时间

函数功能

该接口核心功能是在指定超时时间内,持续检查指定从站AL状态机,直到从站进入开发者要求的目标状态

三、ecx_send_processdata_group / ecx_send_processdata:



// 入参:context-主站上下文;group-从站组编号
// 返回值:WKC(工作计数器),0=发送失败,≥1=发送成功(具体值=成功响应的从站数)
int ecx_send_processdata_group(ecx_contextt *context, uint8 group)
{
   // 局部变量定义
   uint32 LogAdr;                // 逻辑地址(PDO数据在从站的内存地址)
   uint16 w1, w2;                // 逻辑地址拆分的高低16位
   int length;                   // 要发送的PDO总长度
   uint16 sublength;             // 分段传输时单段的长度
   uint8 idx;                    // 发送缓冲区索引(STM32以太网发送buf的索引)
   int wkc;                      // 工作计数器(返回值)
   uint8 *data;                  // 指向输出PDO数据的指针
   boolean first = FALSE;        // 标记是否是第一个DC同步帧
   uint16 currentsegment = 0;    // 当前分段索引
   uint32 iomapinputoffset;      // 重叠IO映射时的输入数据偏移
   uint16 DCO;                   // DC同步相关标识
 
   // ========== 第一步:初始化核心变量 ==========
   wkc = 0;  // 初始化WKC为0(默认失败)
   // 如果该组从站支持DC同步,标记first为TRUE(第一个帧要带DC同步指令)
   if (context->grouplist[group].hasdc)
   {
      first = TRUE;
   }
   // 清空该组的邮箱状态(避免旧状态干扰)
   ecx_clearmbxstatus(context, group);
 
   // ========== 第二步:计算PDO数据长度(区分重叠/非重叠IO映射) ==========
   /* For overlapping IO map use the biggest */
   if (context->overlappedMode == TRUE)
   {
      // 重叠IO映射:输入/输出共享内存,取输出/输入长度的最大值作为帧长度
      length = (context->grouplist[group].Obytes > context->grouplist[group].Ibytes) 
               ? context->grouplist[group].Obytes 
               : context->grouplist[group].Ibytes;
      // 加上邮箱状态长度(非周期数据)
      length += context->grouplist[group].mbxstatuslength;
      // 记录输入数据偏移(因为输出/输入共享内存,输入数据要存在偏移位置)
      iomapinputoffset = context->grouplist[group].Obytes;
   }
   else
   {
      // 非重叠IO映射:总长度=输出长度+输入长度+邮箱状态长度
      length = context->grouplist[group].Obytes +
               context->grouplist[group].Ibytes +
               context->grouplist[group].mbxstatuslength;
      iomapinputoffset = 0;  // 无偏移
   }
 
   // ========== 第三步:获取PDO逻辑起始地址 ==========
   LogAdr = context->grouplist[group].logstartaddr;
 
   // ========== 第四步:核心发送逻辑(长度>0才发送) ==========
   if (length)
   {
      wkc = 1;  // 长度有效,先置WKC为1(表示开始发送)
 
      // ========== 分支1:LRW被阻塞(只能分开读/写) ==========
      if (context->grouplist[group].blockLRW)
      {
         // 子分支1.1:如果有输入数据,先执行LRD(读)操作(虽然是发送函数,但先读再写)
         if (context->grouplist[group].Ibytes)
         {
            currentsegment = context->grouplist[group].Isegment;  // 输入分段起始索引
            data = context->grouplist[group].inputs;              // 指向输入数据缓冲区
            length = context->grouplist[group].Ibytes;             // 输入数据长度
            LogAdr += context->grouplist[group].Obytes;            // 调整逻辑地址到输入区域
 
            // 分段传输循环:直到所有分段发完或长度为0
            do
            {
               // 计算当前分段的长度
               if (currentsegment == context->grouplist[group].Isegment)
               {
                  sublength = (uint16)(context->grouplist[group].IOsegment[currentsegment++] - context->grouplist[group].Ioffset);
               }
               else
               {
                  sublength = (uint16)context->grouplist[group].IOsegment[currentsegment++];
               }
 
               // 1. 获取发送缓冲区索引(STM32的以太网发送buf是数组,idx是当前要用的下标)
               idx = ecx_getindex(&context->port);
               // 2. 拆分逻辑地址为高低16位(适配EtherCAT帧格式)
               w1 = LO_WORD(LogAdr);
               w2 = HI_WORD(LogAdr);
               DCO = 0;
               // 3. 组装LRD指令的EtherCAT数据帧
               ecx_setupdatagram(&context->port, &(context->port.txbuf[idx]), EC_CMD_LRD, idx, w1, w2, sublength, data);
               
               // 4. 如果是第一个帧且支持DC,添加DC同步指令(FRMW=写从站寄存器)
               if (first)
               {
                  DCO = ecx_adddatagram(&context->port, &(context->port.txbuf[idx]), EC_CMD_FRMW, idx, FALSE,
                                        context->slavelist[context->grouplist[group].DCnext].configadr,
                                        ECT_REG_DCSYSTIME, sizeof(int64), &context->DCtime);
                  first = FALSE;  // 标记已发送DC同步帧
               }
 
               // 5. 发送帧(STM32底层调用以太网驱动发送txbuf[idx]中的数据)
               ecx_outframe_red(&context->port, idx);
               // 6. 记录发送信息(方便后续接收时匹配)
               ecx_pushindex(context, idx, data, sublength, DCO);
               
               // 7. 更新长度、地址、数据指针,准备下一分段
               length -= sublength;
               LogAdr += sublength;
               data += sublength;
            } while (length && (currentsegment < context->grouplist[group].nsegments));
         }
 
         // 子分支1.2:如果有输出数据,执行LWR(写)操作(核心:发送输出PDO)
         if (context->grouplist[group].Obytes)
         {
            data = context->grouplist[group].outputs;  // 指向输出PDO数据(你要发给从站的控制指令)
            length = context->grouplist[group].Obytes; // 输出数据长度
            LogAdr = context->grouplist[group].logstartaddr; // 重置逻辑地址到输出区域
            currentsegment = 0;  // 重置分段索引
 
            // 分段传输循环
            do
            {
               // 计算当前分段长度
               sublength = (uint16)context->grouplist[group].IOsegment[currentsegment++];
               if ((length - sublength) < 0)
               {
                  sublength = (uint16)length;  // 最后一段可能不足,取剩余长度
               }
 
               // 1. 获取发送缓冲区索引
               idx = ecx_getindex(&context->port);
               // 2. 拆分逻辑地址
               w1 = LO_WORD(LogAdr);
               w2 = HI_WORD(LogAdr);
               DCO = 0;
               // 3. 组装LWR指令的EtherCAT数据帧(核心:把输出数据打包成帧)
               ecx_setupdatagram(&context->port, &(context->port.txbuf[idx]), EC_CMD_LWR, idx, w1, w2, sublength, data);
               
               // 4. DC同步(同上文)
               if (first)
               {
                  DCO = ecx_adddatagram(&context->port, &(context->port.txbuf[idx]), EC_CMD_FRMW, idx, FALSE,
                                        context->slavelist[context->grouplist[group].DCnext].configadr,
                                        ECT_REG_DCSYSTIME, sizeof(int64), &context->DCtime);
                  first = FALSE;
               }
 
               // 5. 发送帧(STM32以太网驱动发送数据)
               ecx_outframe_red(&context->port, idx);
               // 6. 记录发送信息
               ecx_pushindex(context, idx, data, sublength, DCO);
               
               // 7. 更新参数
               length -= sublength;
               LogAdr += sublength;
               data += sublength;
            } while (length && (currentsegment < context->grouplist[group].nsegments));
         }
      }
 
      // ========== 分支2:LRW可用(同时读写,效率更高) ==========
      else
      {
         // 确定数据指针(优先输出,无输出则用输入)
         if (context->grouplist[group].Obytes)
         {
            data = context->grouplist[group].outputs;  // 指向输出PDO(控制指令)
         }
         else
         {
            data = context->grouplist[group].inputs;
            iomapinputoffset = 0;  // 无输出时,输入偏移置0
         }
 
         // 分段传输循环(核心:组装LRW帧,同时发输出+收输入)
         do
         {
            sublength = (uint16)context->grouplist[group].IOsegment[currentsegment++];
            // 获取发送缓冲区索引
            idx = ecx_getindex(&context->port);
            // 拆分逻辑地址
            w1 = LO_WORD(LogAdr);
            w2 = HI_WORD(LogAdr);
            DCO = 0;
            // 组装LRW指令的EtherCAT数据帧(最核心:同时读写)
            ecx_setupdatagram(&context->port, &(context->port.txbuf[idx]), EC_CMD_LRW, idx, w1, w2, sublength, data);
            
            // DC同步
            if (first)
            {
               DCO = ecx_adddatagram(&context->port, &(context->port.txbuf[idx]), EC_CMD_FRMW, idx, FALSE,
                                     context->slavelist[context->grouplist[group].DCnext].configadr,
                                     ECT_REG_DCSYSTIME, sizeof(int64), &context->DCtime);
               first = FALSE;
            }
 
            // 发送帧
            ecx_outframe_red(&context->port, idx);
            // 记录发送信息(带偏移,适配重叠IO)
            ecx_pushindex(context, idx, (data + iomapinputoffset), sublength, DCO);
            
            // 更新参数
            length -= sublength;
            LogAdr += sublength;
            data += sublength;
         } while (length && (currentsegment < context->grouplist[group].nsegments));
      }
   }
 
   // ========== 第五步:返回WKC ==========
   return wkc;
}
 
 
 
int ecx_send_processdata(ecx_contextt *context)
{
   return ecx_send_processdata_group(context, 0);
}
 

参数说明

context:上下文结构体

group:从站组组号

函数功能

该接口核心功能主要是实现EtherCAT主站发送过程数据(PDO)到指定从站组,通过EtherCAT协议封装成数据帧,发送到总线的从站,同时支持DC分布式时钟同步,分段传输,重叠IO映射等高级特性。ecx_send_processdata函数是对此函数的调用封装。

四、ecx_receive_processdata_group / ecx_receive_processdata:



// 入参:context-主站上下文;group-从站组编号(实际未直接使用,兼容接口);timeout-接收超时时间(ms)
// 返回值:WKC(成功=有效从站数,EC_NOFRAME=-1=超时/无帧,0=解析失败)
int ecx_receive_processdata_group(ecx_contextt *context, uint8 group, int timeout)
{
   // 局部变量定义
   uint8 idx;                    // 接收缓冲区索引(对应发送时的txbuf索引)
   int pos;                      // 索引栈的位置(遍历栈用)
   int wkc = 0, wkc2;            // wkc=最终返回的WKC;wkc2=临时WKC/组编号占位
   uint16 le_wkc = 0;            // 网络字节序的WKC(需要字节序转换)
   int valid_wkc = 0;            // 标记是否解析到有效WKC(0=无效,1=有效)
   int64 le_DCtime;              // 网络字节序的DC时间(需要字节序转换)
   ec_idxstackT *idxstack;       // 指向索引栈(记录发送帧的信息)
   ec_bufT *rxbuf;               // 指向接收缓冲区(STM32以太网rxbuf)
 
   // ========== 第一步:占位处理(避免编译器警告) ==========
   /* just to prevent compiler warning for unused group */
   wkc2 = group;  // group参数未直接使用(因为索引栈已关联组),仅占位
 
   // ========== 第二步:初始化核心指针 ==========
   idxstack = &context->idxstack;  // 绑定索引栈(发送时push的帧信息)
   rxbuf = context->port.rxbuf;    // 绑定STM32的以太网接收缓冲区
 
   // ========== 第三步:从索引栈取出第一个发送帧的位置 ==========
   /* get first index */
   pos = ecx_pullindex(context);  // 从栈顶取出第一个帧的位置(pos≥0表示有帧)
 
   // ========== 第四步:核心循环:遍历所有发送的帧,接收并解析响应 ==========
   /* read the same number of frames as send */
   while (pos >= 0)  // 只要栈里还有未处理的帧,就继续循环
   {
      // 1. 获取当前帧的缓冲区索引(对应发送时的idx)
      idx = idxstack->idx[pos];
 
      // 2. 等待接收响应帧(核心:STM32阻塞等待,直到收到帧或超时)
      // 返回值:wkc2>EC_NOFRAME(-1)表示收到帧;否则超时/无帧
      wkc2 = ecx_waitinframe(&context->port, idx, timeout);
 
      // 3. 检查是否收到有效帧
      if (wkc2 > EC_NOFRAME)
      {
         // ========== 分支1:响应帧是LRD(读)或LRW(读写)→ 解析输入PDO + WKC ==========
         if ((rxbuf[idx][EC_CMDOFFSET] == EC_CMD_LRD) || (rxbuf[idx][EC_CMDOFFSET] == EC_CMD_LRW))
         {
            // 子分支1.1:包含DC同步数据(dcoffset>0)
            if (idxstack->dcoffset[pos] > 0)
            {
               // a. 拷贝输入PDO数据到主站缓冲区(核心:把从站数据存到你能访问的内存)
               // rxbuf[idx][EC_HEADERSIZE]:跳过帧头,取实际数据;length[pos]:数据长度
               memcpy(idxstack->data[pos], &(rxbuf[idx][EC_HEADERSIZE]), idxstack->length[pos]);
               
               // b. 解析WKC(网络字节序→主机序)
               memcpy(&le_wkc, &(rxbuf[idx][EC_HEADERSIZE + idxstack->length[pos]]), EC_WKCSIZE);
               wkc = etohs(le_wkc);  // 字节序转换(适配STM32的小端序)
               
               // c. 解析DC时间(同步主从站时钟)
               memcpy(&le_DCtime, &(rxbuf[idx][idxstack->dcoffset[pos]]), sizeof(le_DCtime));
               context->DCtime = etohll(le_DCtime);  // 64位字节序转换
            }
            // 子分支1.2:无DC同步数据 → 仅拷贝输入PDO + 累加WKC
            else
            {
               /* copy input data back to process data buffer */
               // 核心:把从站返回的输入PDO数据拷贝到主站缓冲区(比如传感器数据)
               memcpy(idxstack->data[pos], &(rxbuf[idx][EC_HEADERSIZE]), idxstack->length[pos]);
               wkc += wkc2;  // 累加临时WKC
            }
            valid_wkc = 1;  // 标记解析到有效WKC
         }
         // ========== 分支2:响应帧是LWR(写)→ 仅解析WKC(无输入数据) ==========
         else if (rxbuf[idx][EC_CMDOFFSET] == EC_CMD_LWR)
         {
            // 子分支2.1:包含DC同步数据
            if (idxstack->dcoffset[pos] > 0)
            {
               // a. 解析WKC(LWR的WKC需要×2,对齐LRW的计数规则)
               memcpy(&le_wkc, &(rxbuf[idx][EC_HEADERSIZE + idxstack->length[pos]]), EC_WKCSIZE);
               /* output WKC counts 2 times when using LRW, emulate the same for LWR */
               wkc = etohs(le_wkc) * 2;
               
               // b. 解析DC时间
               memcpy(&le_DCtime, &(rxbuf[idx][idxstack->dcoffset[pos]]), sizeof(le_DCtime));
               context->DCtime = etohll(le_DCtime);
            }
            // 子分支2.2:无DC同步数据 → 仅累加WKC(×2)
            else
            {
               /* output WKC counts 2 times when using LRW, emulate the same for LWR */
               wkc += wkc2 * 2;
            }
            valid_wkc = 1;  // 标记解析到有效WKC
         }
      }
 
      // ========== 第五步:释放接收缓冲区 ==========
      /* release buffer */
      ecx_setbufstat(&context->port, idx, EC_BUF_EMPTY);  // 标记缓冲区为空,可复用
 
      // ========== 第六步:取出索引栈的下一个帧位置 ==========
      /* get next index */
      pos = ecx_pullindex(context);
   }
 
   // ========== 第七步:清空索引栈(释放资源) ==========
   ecx_clearindex(context);
 
   // ========== 第八步:返回最终结果 ==========
   /* if no frames has arrived */
   if (valid_wkc == 0)  // 未解析到有效WKC(超时/无帧)
   {
      return EC_NOFRAME;  // 返回-1,表示无有效帧
   }
   return wkc;  // 返回有效WKC(成功=从站数,失败=小于从站数)
}
 
 
int ecx_receive_processdata(ecx_contextt *context, int timeout)
{
   return ecx_receive_processdata_group(context, 0, timeout);
}

参数说明

函数功能

该接口核心功能主要负责主站接受指定从站组的过程数据(PDO),解析数据并更新WKC工作计数器。大致流程为在指定超时时间范围内,接收并解析对应从站组的EtherCAT响应帧,将从站返回的输入PDO数据写入到主站内存缓冲区,同时计算并返回WKC,来判断通信是否成功。

五、ecx_esidump:



// 入参:context-主站上下文;slave-从站编号;esibuf-主站缓冲区(存储读取的ESI数据)
// 返回值:无(ESI数据直接写入esibuf)
void ecx_esidump(ecx_contextt *context, uint16 slave, uint8 *esibuf)
{
   // 局部变量定义
   uint16 configadr, address, incr;  // configadr-从站配置地址;address-EEPROM读取地址;incr-读取步长
   uint64 *p64;                      // 64位数据指针(适配8字节读取)
   uint16 *p16;                      // 16位数据指针(适配2字节读取,esibuf的操作指针)
   uint64 edat;                      // 存储从EEPROM读取的64位数据
   uint8 eectl = context->slavelist[slave].eep_pdi;  // 保存原始EEPROM控制权状态
 
   // ========== 第一步:切换EEPROM控制权到主站 ==========
   ecx_eeprom2master(context, slave); /* set eeprom control to master */
   // 作用:告诉从站“现在由主站控制EEPROM”,否则主站无法读取ESI文件
 
   // ========== 第二步:初始化核心参数 ==========
   configadr = context->slavelist[slave].configadr;  // 获取从站的配置地址(唯一标识)
   address = ECT_SII_START;                          // 设置EEPROM读取起始地址(0x0000,ESI文件起始位置)
   p16 = (uint16 *)esibuf;                           // 将esibuf转为16位指针(方便按2/4步长操作)
 
   // 第三步:确定读取步长(incr)——根据从站EEPROM支持的读取粒度
   if (context->slavelist[slave].eep_8byte)
   {
      incr = 4;  // 8字节读取(64位):每次读8字节,指针步长=4(因为p16是16位,4×2=8字节)
   }
   else
   {
      incr = 2;  // 2字节读取(16位):每次读2字节,指针步长=2(2×2=4字节?实际是每次读2字节,步长2是兼容逻辑)
   }
 
   // ========== 第四步:核心循环:逐段读取ESI文件到缓冲区 ==========
   do
   {
      // 1. 从从站EEPROM读取64位数据(核心:实际读取ESI内容)
      // ecx_readeepromFP:Fast Process读取EEPROM,configadr=从站地址,address=EEPROM地址,超时时间EC_TIMEOUTEEP
      edat = ecx_readeepromFP(context, configadr, address, EC_TIMEOUTEEP);
 
      // 2. 将读取的64位数据写入主站缓冲区(esibuf)
      p64 = (uint64 *)p16;  // 临时转为64位指针
      *p64 = edat;          // 把读取的ESI数据写入缓冲区

      // 3. 更新指针和地址,准备下一次读取
      p16 += incr;          // 缓冲区指针后移(按步长)
      address += incr;      // EEPROM读取地址后移(按步长)
 
   // 循环条件:
   // 1. address ≤ (EC_MAXEEPBUF >> 1):读取地址不超过缓冲区最大地址(避免溢出)
   // 2. edat != 0xffffffff:读取的数据不是全1(ESI文件结束标记是全1)
   } while ((address <= (EC_MAXEEPBUF >> 1)) && ((uint32)edat != 0xffffffff));

   // ========== 第五步:恢复EEPROM控制权(如果原始是PDI控制) ==========
   if (eectl)
   {
      ecx_eeprom2pdi(context, slave); /* if eeprom control was previously pdi then restore */
      // 作用:读取完成后恢复从站EEPROM的原始控制权,避免影响从站正常工作
   }
}

参数说明

context:上下文结构体

slave:从站编号

esibuf:主站缓冲区(存储读取的ESI数据)

函数功能

该接口核心功能主要实现将指定从站的ESI文件(存储在从站的EEPROM中)逐段读取到主站的内存缓冲区(esibuf),读取完成后恢复从站EEPROM控制权,为后续解析从站配置(如PDO映射)提供原始数据。

总结

本次主要是对SOEM中ec_main.c文件里的主要接口进行了学习总结,包括主站对从站的初始化,以及针对从站的PDO数据通信以及ESI文件的读取,这些在后续主站开发中都会详细的进行配置,也是为后续开发打下基础。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
沐的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容