作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
TwinCAT 3 简介
倍福推出的基于 PC 的控制技术定义了自动化领域的全球标准。在软件方面,1996 年推出的 TwinCAT(The Windows Control and Automation Technology,基于 Windows 的控制和自动化技术)自动化套件是倍福控制系统的核心部分。通过与 TwinCAT 软件结合使用,基于开放性和高度可扩展性原则,构建优化协调的自动化解决方案。
TwinCAT 可将任何一个基于 PC 的系统转换为一个带多个 PLC、NC、CNC 和机器人实时操作系统的实时控制系统。可模块化扩展的硬件和软件组件便于随时修改和添加功能,在需要时,控制解决方案具备的开放性不仅允许集成第三方组件,还可以为现有设备和系统定制改造方案,这样既能确保灵活性,又能保障客户的投资安全。
TwinCAT 3 是 TwinCAT 2 进一步发展的产物,重新定义了我们对自动化技术的认知。TwinCAT 3 为工程技术开辟了一条新的道路,并通过添加很多功能对实时内核进行扩展。工程环境完全集成在微软的 Visual Studio 框架中,除了系统配置、运动控制、I/O 和 IEC61131 PLC 编程语言之外,还可以进行编程和调试。
TwinCAT CoE、PDO、SDO 简介
TwinCAT CoE(CANopen over EtherCAT)是一种用于在 EtherCAT 网络上进行 CANopen 通信的协议。它允许通过 EtherCAT 总线与 CANopen 设备进行通信,并支持读取和写入 CANopen 对象字典中的数据。
PDO(Process Data Object)是 CANopen 协议中的一种过程数据对象,是 CANopen 中最主要的数据传输方式。由于 PDO 的传输不需要应答,且 PDO 的长度可以小于 8 个字节,因此传输速度快。
SDO(Service Data Object)是 CANopen 协议中的一种服务数据对象,通过对象索引和子索引与对象字典建立联系, 通过 SDO 可以读取对象字典中的对象内容, 或者在允许的情况下修改对象数据。所有的 SDO 报文数据段都必须是 8 个字节。
EtherCAT 简介
EtherCAT(以太网控制自动化技术)是一个开放架构,以以太网为基础的现场总线系统,其名称的 CAT 为控制自动化技术(Control Automation Technology)字首的缩写。EtherCAT 是确定性的工业以太网,最早是由德国的 Beckhoff 公司研发。自动化对通讯一般会要求较短的资料更新时间(或称为周期时间)、资料同步时的通讯抖动量低,而且硬件的成本要低,EtherCAT 开发的目的就是让以太网可以运用在自动化应用中。
EtherCAT 的主站与从站通信方式,理解起来就是从主站发出一个火车,这个火车上拉着可以确定目标从站地址的信息、数据以及 EtherCAT 报文的其他部分。当经过一个从站的时候,从站管理器(ESC)就会将 EtherCAT 报文中和自己这一站有关的数据提取出来,同时将从站需要发给主站的数据插入上去,然后这个火车就会继续开往下个从站,直到最后一个从站。当发现到达最后一个从站后,这个火车就会顺着原路返回到主站,将从站的数据带给主站。
EtherCAT 从站对数据帧的读取、解析、过程数据的提取与插入完全由硬件来实现,这使得数据帧的处理不受 CPU 的性能、软件的实现方式等影响,时间延迟极小、实时性很高。而软件只需要把从站管理器从报文中提取出来的数据(RxPDO)进行处理,然后将 TxPDO 传给从站管理器。对于 SDO 也是一样,对下发的 SDO 数据进行处理,然后对上送的 SDO 数据进行读取、传递。
TwinCAT PLC 读写 SDO
- 导入库:TC2_EtherCAT
- 获取主站的 EtherCAT 地址
- 获取从站的 EtherCAT 地址
- 在 CoE - Online 中确定 Index 和 SubIndex
- 示例代码
PROGRAM MAIN
VAR
sNetId : T_AmsNetId := '169.254.235.251.6.1';
fbSdoRead : FB_EcCoESdoRead;
bExecute_r : BOOL := TRUE;
nSlaveAddr_r : UINT := 1001;
nIndex_r : WORD := 16#1011;
nSubIndex_r : BYTE := 0;
vendorId_r : UDINT;
bError_r : BOOL;
nErrId_r : UDINT;
fbSdoWrite : FB_EcCoESdoWrite;
bExecute_w : BOOL := TRUE;
nSlaveAddr_w : UINT := 1001;
nIndex_w : WORD := 16#6071;
nSubIndex_w : BYTE := 0;
vendorId_w : INT := 33; (*注意数据类型*)
bError_w : BOOL;
nErrId_w : UDINT;
END_VAR
fbSdoRead(
sNetId:= sNetId,
nSlaveAddr:= nSlaveAddr_r,
nSubIndex:= nSubIndex_r,
nIndex:= nIndex_r,
pDstBuf:= ADR(vendorId_r),
cbBufLen:= SIZEOF(vendorId_r),
bExecute:= bExecute_r,
tTimeout:= ,
bBusy=> ,
bError=> bError_r,
nErrId=> nErrId_r,
cbRead=> );
fbSdoWrite(
sNetId:= sNetId,
nSlaveAddr:= nSlaveAddr_w,
nSubIndex:= nSubIndex_w,
nIndex:= nIndex_w,
pSrcBuf:= ADR(vendorId_w),
cbBufLen:= SIZEOF(vendorId_w),
bExecute:= bExecute_w,
tTimeout:= ,
bBusy=> ,
bError=> bError_w,
nErrId=> nErrId_w);
TwinCAT C++ 读写 SDO
- 参考倍福的官方示例程序
- 重写 AdsReadCon 和 AdsWriteCon
读写操作都是使用的请求和响应的形式来实现,需要配合使用。
// CAds
void SubmitAdsCoEReadReq(AmsAddr amsAddr, DWORD index, DWORD subIndex, ULONG length);
virtual void AdsReadCon(AmsAddr& rAddr, ULONG invokeId, ULONG result, ULONG cbLength, PVOID pData);
void SubmitAdsCoEWriteReq(AmsAddr amsAddr, DWORD index, DWORD subIndex, ULONG length, PVOID pData);
virtual void AdsWriteCon(AmsAddr& rAddr, ULONG invokeId, ULONG result);
- 主要代码
HRESULT CTcSdoAccessModul::CycleUpdate(ITcTask* ipTask, ITcUnknown* ipCaller, ULONG_PTR context)
{
HRESULT hr = S_OK;
// handle pending ADS indications and confirmations
CheckOrders();
// TODO: Submit your ADS requests
ULONGLONG cnt = 0;
if (SUCCEEDED(ipTask->GetCycleCounter(&cnt)))
{
if (cnt % 100 == 0)
{
// Cyclic call only for explanation
// CoE can be call during SAFEOP
m_vector.push_back(&m_Outputs.DataList[0]);
SubmitAdsCoEReadReq(m_Inputs.AmsAddress, 0x2006, 0, 2);
m_vector.push_back(&m_Outputs.DataList[1]);
SubmitAdsCoEReadReq(m_Inputs.AmsAddress, 0x2100, 0, 1);
m_vector.push_back(&m_Outputs.DataList[2]);
SubmitAdsCoEReadReq(m_Inputs.AmsAddress, 0x2101, 0, 1);
m_vector.push_back(&m_Outputs.DataList[3]);
SubmitAdsCoEReadReq(m_Inputs.AmsAddress, 0x2200, 0, 1);
m_Inputs.Value = 11;
m_Inputs.Status = 22;
m_Inputs.Data = 33;
SubmitAdsCoEWriteReq(m_Inputs.AmsAddress, 0x6068, 0, 2, &m_Inputs.Value);
SubmitAdsCoEWriteReq(m_Inputs.AmsAddress, 0x6071, 0, 2, &m_Inputs.Status);
SubmitAdsCoEWriteReq(m_Inputs.AmsAddress, 0x607A, 0, 4, &m_Inputs.Data);
}
}
return hr;
}
void CTcSdoAccessModul::SubmitAdsCoEReadReq(AmsAddr amsAddr, DWORD index, DWORD subIndex, ULONG length)
{
// index = HIWORD(iOffs), subIndex = LOBYTE(LOWORD(iOffs))
DWORD nOffset = (index << 16) | subIndex;
AdsReadReq(amsAddr, invokeIdReadVendorIdByCoE, ADSIGRP_CANOPEN_SDO, nOffset, length);
}
void CTcSdoAccessModul::AdsReadCon(AmsAddr& rAddr, ULONG invokeId, ULONG nResult, ULONG cbLength, PVOID pData)
{
if (invokeId == invokeIdReadVendorIdByCoE)
{
if (nResult != ADSERR_NOERR)
{
// "read failed with error=0x%x(%s)", nResult, AdsGetErrorText(nResult)
}
else
{
if (m_vector.size() > 0) {
PVOID varAddr = m_vector[0];
m_vector.erase(m_vector.begin());
PULONG temp = static_cast<PULONG>(varAddr);
*temp = *static_cast<PULONG>(pData);
}
}
}
else
{
__super::AdsReadWriteCon(rAddr, invokeId, nResult, cbLength, pData);
}
}
void CTcSdoAccessModul::SubmitAdsCoEWriteReq(AmsAddr amsAddr, DWORD index, DWORD subIndex, ULONG length, PVOID pData)
{
// index = HIWORD(iOffs), subIndex = LOBYTE(LOWORD(iOffs))
DWORD nOffset = (index << 16) | subIndex;
AdsWriteReq(amsAddr, invokeIdWriteBaudRateByCoE, ADSIGRP_CANOPEN_SDO, nOffset, length, pData);
}
void CTcSdoAccessModul::AdsWriteCon(AmsAddr& rAddr, ULONG invokeId, ULONG nResult)
{
if (invokeId == invokeIdWriteBaudRateByCoE)
{
if (nResult != ADSERR_NOERR)
{
// "write failed with error=0x%x(%s)", nResult, AdsGetErrorText(nResult)
}
}
}
- 效果展示