如何在TwinCAT(C++)中读取Emergency(EMCY)消息?
解决方案:用C++读取TwinCAT EtherCAT从站EMCY邮箱消息
核心思路
EtherCAT的EMCY消息属于CoE(CANopen over EtherCAT)邮箱对象,无法通过PDO映射,必须通过ADS接口直接访问从站的邮箱区域,或利用TwinCAT的事件通知机制实现触发式获取,而非周期性轮询。
方法一:使用TwinCAT ADS C++ API周期性读取
前置准备
- 项目中引入TwinCAT ADS的头文件与库文件:
- 头文件路径:
C:\TwinCAT\AdsApi\C++\Include - 库文件:
TcAds.lib(32位)或TcAdsX64.lib(64位)
- 头文件路径:
- 运行程序的PC需安装TwinCAT Runtime,本地访问默认无需额外权限
代码示例
#include <iostream> #include <iomanip> #include "TcAdsDef.h" #include "TcAdsAPI.h" int main() { long nErr; AmsAddr amsAddr; // 打开ADS端口 nErr = AdsPortOpen(); if (nErr != 0) { std::cerr << "ADS端口打开失败,错误码: " << nErr << std::endl; return 1; } // 获取本地AMS地址 nErr = AdsGetLocalAddress(&amsAddr); if (nErr != 0) { std::cerr << "获取本地AMS地址失败,错误码: " << nErr << std::endl; AdsPortClose(); return 1; } // 配置目标从站的AMS参数:替换为你的从站NetId和端口 amsAddr.port = 0x0001; // 规则:0x0001 + 从站在总线中的位置(第2个从站为0x0002) strcpy(amsAddr.netId, "192.168.0.1.1.1"); // 读取EMCY消息:CoE紧急对象索引组0x1001,子索引0x0000(组合值为0x10010000) unsigned char emcyBuffer[16] = {0}; unsigned long bytesRead; nErr = AdsSyncReadReq(&amsAddr, ADSIGRP_COE_READ, 0x10010000, sizeof(emcyBuffer), emcyBuffer, &bytesRead); if (nErr != 0) { std::cerr << "读取EMCY消息失败,错误码: " << nErr << std::endl; AdsPortClose(); return 1; } // 解析EMCY数据(示例:打印错误码与附加信息) unsigned short errorCode = (emcyBuffer[1] << 8) | emcyBuffer[0]; std::cout << "EMCY错误码: 0x" << std::hex << std::setw(4) << std::setfill('0') << errorCode << std::endl; std::cout << "附加数据: "; for (int i = 2; i < bytesRead; i++) { std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)emcyBuffer[i] << " "; } std::cout << std::dec << std::endl; AdsPortClose(); return 0; }
关键说明
ADSIGRP_COE_READ是CoE读取的固定索引组,0x10010000为紧急对象索引(0x1001)与子索引(0x0000)的组合值(索引<<16 | 子索引)- 从站的AMS端口需按总线位置计算,第一个从站为0x0001,依次递增
- 数据解析需参考从站的CoE手册,不同厂商的EMCY消息格式可能存在差异
方法二:触发式获取(ADS事件通知)
若无需周期性轮询,可配置TwinCAT在EMCY消息到来时触发ADS通知,实现异步获取:
代码示例
#include <iostream> #include <thread> #include <iomanip> #include "TcAdsDef.h" #include "TcAdsAPI.h" // ADS通知回调函数 void EmcyNotificationCallback(const AmsAddr* pAddr, const AdsNotificationHeader* pNotification, uint32_t hUser) { unsigned char* emcyData = (unsigned char*)pNotification->data; unsigned short errorCode = (emcyData[1] << 8) | emcyData[0]; std::cout << "[触发通知] EMCY错误码: 0x" << std::hex << std::setw(4) << std::setfill('0') << errorCode << std::endl; } int main() { long nErr; AmsAddr amsAddr; uint32_t hNotification; AdsPortOpen(); AdsGetLocalAddress(&amsAddr); amsAddr.port = 0x0001; // 替换为目标从站端口 strcpy(amsAddr.netId, "192.168.0.1.1.1"); // 替换为目标从站NetId // 注册EMCY对象的变化通知 nErr = AdsSyncAddDeviceNotificationReq(&amsAddr, ADSIGRP_COE_READ, 0x10010000, sizeof(unsigned char)*16, ADSTRANS_SERVERCYCLE, 0, nullptr, EmcyNotificationCallback, &hNotification); if (nErr != 0) { std::cerr << "注册通知失败,错误码: " << nErr << std::endl; AdsPortClose(); return 1; } std::cout << "等待EMCY通知...(按Ctrl+C退出)" << std::endl; while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); } // 取消通知(实际中需在退出前调用) AdsSyncDelDeviceNotificationReq(&amsAddr, hNotification); AdsPortClose(); return 0; }
常见问题排查
- ADS连接失败:检查TwinCAT Runtime是否运行,从站NetId与端口是否正确,本地防火墙是否放行ADS默认端口(48898)
- 读取返回错误码0x700(ADSERR_DEVICE_NOT_FOUND):确认从站已在线,端口号计算规则为
0x0001 + 从站在EtherCAT总线中的位置 - 数据为空或格式错误:核对从站CoE手册中EMCY对象的索引/子索引,确保缓冲区大小匹配从站的消息长度
- pyads读取失败:确认使用了正确的CoE索引组/子索引,从站AMS端口配置正确,且程序以管理员权限运行
内容的提问来源于stack exchange,提问作者Fabia__n
相关产品推荐
相关产品推荐

