EtherCAT 从站热插拔
热插拔描述了在不关闭 EtherCAT 主站的情况下更换、添加或移除 EtherCAT 从站的功能。为了支持热插拔EtherCAT从站,KRTSEtherCAT主站提供了一些功能。最值得注意的是函数 KS_installEcatHandler,其事件为 ‘KS_TOPOLOGY_CHANGE’。
安装了带有“KS_TOPOLOGY_CHANGE”的事件回调后,上下文使用类型为 EcatTopologyUserContext,并且字段 reason 具有以下值之一:
-
KS_ECAT_TOPOLOGY_MASTER_CONNECTED - 连接到拓扑的主站
-
KS_ECAT_TOPOLOGY_MASTER_DISCONNECTED - 主设备与拓扑断开连接
-
KS_ECAT_TOPOLOGY_SLAVE_COUNT_CHANGED - 从站在线数量更改
-
KS_ECAT_TOPOLOGY_SLAVE_ONLINE - 先前创建的从站在线
-
KS_ECAT_TOPOLOGY_SLAVE_OFFLINE - 之前创建的从站离线
通过这些事件并调用 KS_enumEcatSlaves,可以监控连接到 EtherCAT 主站的从站数量和类型。热插拔 EtherCAT 从站有许多实际应用,包括检测硬件故障和/或任何类型机器的热插拔部件。对机器的热插拔部件做出反应的可能方法可能是:
假设您期望有人不时地拔下和插入一个或多个从站。然后,您将使用 ‘KS_ECAT_TOPOLOGY_SLAVE_ONLINE’ 和 ‘KS_ECAT_TOPOLOGY_SLAVE_OFFLINE’ 常量来监视这些创建的从站。
如果从服务器拓扑中拔下,您将收到一个原因为“KS_ECAT_TOPOLOGY_SLAVE_OFFLINE”的回调,该从站的句柄将在 EcatTopologyUserContext 结构中提供。调用 KS_queryEcatSlaveState 会告诉你,这个从站现在不在线。
如果该从站再次插入拓扑,您将收到一个原因为“KS_ECAT_TOPOLOGY_SLAVE_ONLINE”的回调。此时从站仍然通电,但该从站的进程数据交换已停止,因此它可能下降到状态 SAFEOP。但是,由于主站仍然拥有有关此从服务器的所有信息,因此您只需再次将该从服务器置于OP中,即可进行正常操作。
如果你期望有人关闭和打开你的从站,这将是相同的过程。唯一的区别是,从服务器将处于 INIT 状态,因为它当然是断电的。但是,主站仍然拥有有关该从站的所有信息,并且可以将其放回 OP 以进行正常操作。
结论:
EtherCAT拓扑变化可以通过不同的事件进行监控。无需停止主站,也无需重新配置从站即可恢复正常运行。更重要的是,没有必要停止过程数据交换,可能其他一些从站正在依赖它。请注意,KS_readEcatDataSet 将给出值“KSERROR_NO_RESPONSE”的错误,因为一个从站没有响应数据集。
示例
我们假设您已经知道如何通过PDO控制伺服伺服的程序,如果不清楚,您可以查看安装目录下 smp/EtherCATDataExchange等示例代码。
下面展示热拔插的关键代码:
- 安装拓扑回调
// 网络拓扑结构发送改变后操作(从站热拔插)
error = KS_createKernelCallBack(&topology_call_back_, kernel_handle_, "TopologyCallBack", nullptr,
KSF_DIRECT_EXEC | KSF_SAVE_FPU, 0);
if (error != KS_OK)
{
OutPutError("KS_createKernelCallBack DataSetCallBack Failed", error);
return error;
}
error = KS_installEcatHandler(app_shared_data_->master_handle, KS_TOPOLOGY_CHANGE, topology_call_back_, 0);
if (error != KS_OK)
{
OutPutError("KS_installEcatHandler KS_TOPOLOGY_CHANGE Failed", error);
return error;
}
- 拓扑结构发生改变后,在回调中系统做相应调整,断网和断电的操作不同,这里仅展示断网情况
// EtherCAT拓扑变化回调函数
extern "C" __declspec(dllexport) KSError __stdcall TopologyCallBack(void *pArgs, void *pContext)
{
// 应当在正转运行处理,不运行不处理
if (kernel_data_ptr_!= nullptr && !kernel_data_ptr_->is_run_status)
{
return KS_OK;
}
KS_printK("+++++++++++ TopologyCallBack +++++++++++++ \n");
const auto user_context = (EcatTopologyUserContext*)pContext;
KS_printK("--------------------------- hSlave: %d \n", user_context->hSlave);
KS_printK("--------------------------- ctxType: 0x%X \n", user_context->ctxType);
KS_printK("--------------------------- reason: 0x%X \n", user_context->reason);
KS_printK("--------------------------- slavesOnline: 0x%X \n", user_context->slavesOnline);
KS_printK("--------------------------- slavesCreatedAndOnline: 0x%X \n", user_context->slavesCreatedAndOnline);
// 根据上下文信息,处理从站状态变化
if (user_context->ctxType == KS_ECAT_TOPOLOGY_CHANGE)
{
for (int index = 0; index < AXIS_COUNT; index++)
{
KSEcatSlaveState slave_state;
slave_state.structSize = sizeof(KSEcatSlaveState); // 不要忘记初始化结构大小
KSError error = KS_enumEcatSlaves(kernel_data_ptr_->master_handle, index, &slave_state, 0);
if (error == KS_OK)
{
//打印从站信息
KS_printK("Slave %d Vendor: 0x%X\n",index, slave_state.vendor);
KS_printK("Slave %d Product: 0x%X\n",index, slave_state.product);
KS_printK("Slave %d State: 0x%X\n",index, slave_state.slaveState);
}
else
{
KS_printK("KS_enumEcatSlaves exec failed");
continue;
}
}
// 从站上线
if (user_context->reason == KS_ECAT_TOPOLOGY_SLAVE_ONLINE)
{
KS_printK("--------------------------- ONLINE hSlave: %d \n", user_context->hSlave);
// 查询从站状态
KSEcatSlaveState slave_stater;
slave_stater.structSize = sizeof(KSEcatSlaveState); // 特别注意设置结构体大小
if (KSError error = KS_queryEcatSlaveState(user_context->hSlave, &slave_stater, 0); error == KS_OK)
{
KS_printK("--------------------------- Slave Stater: 0x%X \n",slave_stater.slaveState);
// 断电会自动切换状态到 KS_ECAT_STATE_INIT
if (slave_stater.slaveState == KS_ECAT_STATE_INIT)
{
KS_printK("--------------------------- Slave KS_ECAT_STATE_INIT \n");
error = KS_changeEcatState(user_context->hSlave, KS_ECAT_STATE_SAFEOP, 0);
if (error != KS_OK)
{
KS_printK("--------------------------- Slave KS_changeEcatState KS_ECAT_STATE_SAFEOP Error");
}
}
// 断网自动切换状态到 KS_ECAT_STATE_SAFEOP
if (slave_stater.slaveState == KS_ECAT_STATE_SAFEOP)
{
KS_printK("--------------------------- Slave KS_ECAT_STATE_SAFEOP \n");
error = KS_changeEcatState(user_context->hSlave, KS_ECAT_STATE_OP, 0);
if (error != KS_OK)
{
KS_printK("--------------------------- Slave KS_changeEcatState KS_ECAT_STATE_SAFEOP Error");
}
}
}
KSEcatMasterState master_stater;
master_stater.structSize = sizeof(KSEcatMasterState); // 特别注意设置结构体大小
if (KSError error = KS_queryEcatMasterState(user_context->hMaster, &master_stater, 0); error == KS_OK)
{
KS_printK("--------------------------- Master Stater: 0x%X \n",master_stater.masterState);
// 断电会自动切换状态到 KS_ECAT_STATE_INIT
if (master_stater.masterState == KS_ECAT_STATE_INIT)
{
KS_printK("--------------------------- Master KS_ECAT_STATE_INIT \n");
error = KS_changeEcatState(user_context->hMaster, KS_ECAT_STATE_SAFEOP, 0);
if (error != KS_OK)
{
KS_printK("--------------------------- Master KS_changeEcatState KS_ECAT_STATE_SAFEOP Error");
}
}
// 断网自动切换状态到 KS_ECAT_STATE_SAFEOP
if (master_stater.masterState == KS_ECAT_STATE_SAFEOP)
{
KS_printK("--------------------------- Master KS_ECAT_STATE_SAFEOP \n");
error = KS_changeEcatState(user_context->hMaster, KS_ECAT_STATE_OP, 0);
if (error != KS_OK)
{
KS_printK("--------------------------- Master KS_changeEcatState KS_ECAT_STATE_SAFEOP Error");
}
}
}
KSError error = KS_changeEcatState(kernel_data_ptr_->dataset_handle, KS_ECAT_STATE_OP, 0);
if (error != KS_OK)
{
KS_printK("--------------------------- Master KS_changeEcatState KS_ECAT_STATE_SAFEOP Error");
}
}
else if (user_context->reason == KS_ECAT_TOPOLOGY_SLAVE_OFFLINE) // 从站下线
{
KS_printK("OFFLINE hSlave: %d \n", user_context->hSlave);
}
}
return KS_OK;
}
3.忽略掉线的未响应
// 数据集回调,更新完EtherCAT通信数据包后,触发
extern "C" __declspec(dllexport) KSError __stdcall DataSetCallBack(void* pArgs, void* /*pContext*/)
{
KSError error = KS_readEcatDataSet(g_shared_data->data_set_handle, 0);
if (error != KS_OK)
{
if (KSERROR_CODE(error) == KSERROR_NO_RESPONSE) // 有些从属设备需要更多时间才能完全进入 SAFEOP 并回答 DataSet
{
return KS_OK;
}
return error; // 不可以返回 failed,否则数据集回调停止
}
// 执行相关操作
// ...
return KS_OK;
}
PS: 注意一些资源的释放!