之前针对EtherCAT主站系统的学习了基础配置,主从站初始化以及通信接口的学习,但对于驱动伺服电机还不够,本次就针对子协议层进行学习,今天主要总结COE协议层的学习。COE是基于 CANopen 协议的对象字典(OD)、SDO/PDO 通信、紧急报文(Emergency)、心跳报文等,是 EtherCAT 最基础的应用层协议,几乎所有的伺服从站都支持COE,是控制伺服从站的核心。
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 *context | SOEM 上下文结构体,包含主站通信句柄、从站列表、邮箱缓冲区等核心信息 |
| ec_SDOt *SDOp | SDO报文结构体指针,封装SDO通信的核心字段(索引、子索引、命令、数据等) |
| ec_mbxbuft *MbxIn/MbxOut | 邮箱缓冲区指针,Mbxout为主站发送的报文,MbxIn为从站响应的报文 |
| uint8 cnt/toggle | cnt是邮箱会话计数(防报文重复),toggle是分段传输的切换位(防丢包) |
| boolean NotLast | 分段传输标记,
TRUE表示还有后续分段,
FALSE表示最后一段 |
| int wkc | 通信结果返回值(Work Counter),>0 成功,0 失败 |
该接口实现SOEM主站读取从站对象字典(OD)参数的核心函数,支持两种读写方式:快速传输(Expedited)适用于小于4字节短数据,一次报文传输完成,分段传输(Segmented)适用于大于4字节的数据,通过多段报文分块传输,最终将读取到的数据存储到用户指定的缓冲区p。函数大致流程可分为初始化--发送SDO请求--接收从站响应--解析响应数据(快速/分段)--异常处理--资源释放。
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)写入参数,同样支持快速传输/分段传输两种模式整体流程可分为初始化--判断传输迷失--封装并发送请求--接收从站响应--分段传输循环--异常处理-资源释放
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;
}
| 参数 | 作用 |
| RxPDOnumber | RxPDO 编号(如 0x1600、0x1601),对应从站对象字典中预配置的 RxPDO 映射表 |
| maxdata | 单帧可传输的最大 PDO 数据长度(= 从站邮箱大小 - 8 字节协议头) |
| framedatasize | 实际要发送的 PDO 数据长度(不超过
maxdata) |
| ECT_COES_RXPDO | COE 协议中 “RxPDO 传输” 的服务码(固定值),标记报文类型为 RxPDO |
| MBX_HDR_SET_CNT | 邮箱会话计数(防报文重复,和 SDO 函数逻辑一致) |
该接口的核心作用是主站通过EtherCAT邮箱,将实时控制数据(如电机速度给定、IO输出信号)发送到指定从站的指定RxPDO通道,大致流程为“初始化--计算传输长度--封装PDO报文--发送报文--返回结果”。
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_SDOservicet | SDO拓展服务结构体(复用邮箱缓冲区),用于封装“读取OD列表”的请求/响应 |
| ECT_GET_ODLIST_REQ | “读取 OD 列表请求” 操作码(主站发送) |
| ECT_GET_ODLIST_RES | “读取 OD 列表响应” 操作码(从站返回) |
| Fragments | 分段标记:>0 表示还有后续分段,=0 表示最后一段 |
| offset | 数据偏移量:第一段响应需跳过 6 字节协议头,后续分段偏移量为 0 |
| EC_MAXODLIST | OD 列表的最大索引数(SOEM 宏定义,默认 2048),防止缓冲区溢出 |
| First | 分段标记:
TRUE表示处理第一段响应,
FALSE表示处理后续分段 |
函数功能
该接口实现主站向从站发送“读取对象字典列表”请求,接受并解析从站返回的所有支持的对象字典索引,存储到ec_ODlistt结构体中,大致流程为“初始化--封装请求--发送请求--循环接收分段响应--解析索引--释放资源”。
今天主要学习了COE层的主要函数,涵盖了SDO(非周期性读写)、PDO(实施周期数据传输)、OD列表读取(从站适配),后续可以针对从站进行数据通信,控制伺服电机的运动。
¥17.83
试用于小度AI智能音箱机器人钢化膜儿童小杜早教机S18纳米高清屏幕防爆防摔全屏防蓝光护眼非玻璃保护贴膜
¥12.90
小度音箱AI智能视频机器人钢化膜NV6101小度在家1C保护贴膜防辐射护眼小度在家7英寸NV5001高清屏幕防爆玻璃
¥13.90
适用于小度音箱AI智能视频机器人1S钢化膜保护贴膜1C小度在家7英寸NV5001高清屏幕NV6001防爆NV6101百度助手
¥16.80
试用于Lofeeke罗菲克智能机器人钢化膜早教学习机全覆盖贴膜高清防爆防刮玻璃7英寸屏幕保护膜
¥15.87
试用于名校堂P8机器人学习机钢化膜7寸全屏覆盖高清纳米防蓝光护眼防爆防摔非玻璃保护贴膜
¥5.50
prona台湾宝丽尼龙PU双层油漆涂料喷漆管机器人往复机防爆油漆管