1,前言

在TwinCAT3中,典型的串口通信,硬件需要模块EL6022(类似的模块有EL6001、EL6002、EL6021),函数库需要Tc2_SerialCom,使用此函数库需要购买官方的TF6340授权。

如果实现了自己的Tc2_SerialCom,则不再需要购买官方授权。

2,原生Tc2_SerialCom简单使用

先简单介绍下原生函数库的使用,对此不熟悉的建议先看完倍福官方文档。

新建TwinCAT3项目,添加Tc2_SerialCom引用,在项目Device添加EL6022硬件。

在全局变量中定义两个硬件链接结构体,并链接到EL6022,再定义数据收发缓存(其实你想定义在哪都行,只要能访问到)。

GVL_IO
VAR_GLOBAL
ComIn AT%I*: EL6inData22B;
ComOut AT%Q*: EL6OutData22B;
ComSendBuf: ComBuffer; //数据发送缓存
ComReceiveBuf: ComBuffer; //数据接收缓存
END_VAR

然后新建一个Task,周期设置为2ms,命名为FastTask;新建一个PROGRAM挂载在FastTask下,用来跑硬件与缓存之间的数据交换。

PROGRAM PRG_SerialLine
VAR
serialLine: SerialLineControl; //硬件与缓存之间的数据交换
END_VAR //代码部分
serialLine(Mode:= SERIALLINEMODE_EL6_22B, pComIn:= ADR(GVL_IO.ComIn), pComOut:= ADR(GVL_IO.ComOut),
SizeComIn:= SIZEOF(GVL_IO.ComIn), TxBuffer:= GVL_IO.ComSendBuf, RxBuffer:= GVL_IO.ComReceiveBuf);

接下来就在PROGRAM MAIN中进行数据的收发了,MAIN所在的Task一般周期在20ms左右。

VAR
bSend: BOOL;
SendS: SendString; //发字符串
StringToSend: STRING; bReceive: BOOL;
ReceiveS: ReceiveString; //收字符串
StringToReceive: STRING;
END_VAR //代码部分
IF bSend THEN
SendS(SendString:= StringToSend, TXbuffer:= GVL_IO.ComSendBuf);
bSend:= FALSE;
END_IF
IF bReceive THEN
ReceiveS(Prefix:= 'A', Suffix:= 'B', TimeOut:= T#200MS, ReceivedString:= StringToReceive, RXbuffer:= GVL_IO.ComReceiveBuf);
bReceive:= FALSE;
END_IF

SendS功能块将用户想发送的字符传给缓存GVL_IO.ComSendBuf,serialLine功能块将缓存传给GVL_IO.ComOut,硬件就将数据发送出去了

3,实现自己的Tc2_SerialCom

根据上面的简单使用,可知Tc2_SerialCom必需的结构体和功能块有:

  • 结构体:EL6inData22B,EL6outData22B,ComBuffer
  • 功能块:SerialLineControl,SendString,ReceiveString

下面依葫芦画瓢,逐个进行实现

3.1,EL6inData22B,EL6outData22B

先定义一个常数,表示EL6022收发区的大小

VAR_GLOBAL CONSTANT
HardwareLength: USINT:= 22;
END_VAR

然后直接照抄官方的定义就好了

TYPE EL6inData22B :
STRUCT
Status: WORD;
DataIn: ARRAY[0..GVL_Constant.HardwareLength - 1] OF BYTE;
END_STRUCT
END_TYPE TYPE EL6outData22B :
STRUCT
Ctrl: WORD;
DataOut: ARRAY[0..GVL_Constant.HardwareLength - 1] OF BYTE;
END_STRUCT
END_TYPE

其中状态字Status和控制字Ctrl是操作EL6022的硬件的关键,查阅官方文档可知

  • Status:

    • bit0:Transmit Done
    • bit1:Receive Request
    • bit2:Init Accepted
    • bit3:SndBuffer Full
    • bit8-bit15:Input Length
  • Ctrl:
    • bit0:Transmit Request
    • bit1:Receive Accecpted
    • bit2:Init Request
    • bit3:Send Continues
    • bit8-bit15:Output Length

DataIn和DataOut对应EL6022的接收区和发送区

3.2,ComBuffer

官方的ComBuffer用到了RingBuffer,我没太看明白,其实大部分情况下用不到RingBuffer,除非数据量很大,收发间隔很短。所以直接使用一个普通的Buffer得了

TYPE ComBuffer :
STRUCT
Buffer: ARRAY [0..300] OF BYTE;
Count: UDINT;
END_STRUCT
END_TYPE

其中Buffer就是供用户代码使用的缓存,Count表示缓存中有效数据长度

3.3,SerialLineControl

此功能块是整个函数库的核心

FUNCTION_BLOCK SerialLineControl
VAR_INPUT
pComIn: POINTER TO EL6inData22B;
pComOut: POINTER TO EL6outData22B;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
TxBuffer: ComBuffer;
RxBuffer: ComBuffer;
END_VAR
VAR
pInputLength: POINTER TO USINT;
pOutputLength: POINTER TO USINT; A: INT;
TimerWait: TON;
StateCom: INT;
CurrentInCycle: INT;
CurrentOutCycle: INT; bInited: BOOL;
END_VAR //代码部分
//硬件发送区和接收区的长度指针
pInputLength:= pComIn + 1;
pOutputLength:= pComOut + 1; //初始化硬件
IF NOT bInited THEN
pComOut^.Ctrl:= 4; //Init Request
IF pComIn^.Status = 4 THEN //Init Accepted
pComOut^.Ctrl:= 0;
bInited:= TRUE;
END_IF
RETURN;
END_IF CASE StateCom OF
0:
IF TxBuffer.Count >= 1 THEN
StateCom:= 100;
ELSIF pComIn^.Status.1 <> pComOut^.Ctrl.1 THEN //ReceiveRequest
StateCom:= 200;
END_IF //发送
100:
CurrentOutCycle:= 0;
pComOut^.Ctrl.3:= TRUE;
StateCom:= 110;
110:
pOutputLength^:= MIN(GVL_Constant.HardwareLength, UDINT_TO_USINT(TxBuffer.Count - CurrentOutCycle * GVL_Constant.HardwareLength));
FOR A:= 0 TO pOutputLength^ - 1 DO
pComOut^.DataOut[A]:= TxBuffer.Buffer[MIN(300, CurrentOutCycle * GVL_Constant.HardwareLength + A)];
END_FOR
pComOut^.Ctrl.0:= NOT pComOut^.Ctrl.0;
CurrentOutCycle:= CurrentOutCycle + 1;
StateCom:= 120;
120:
IF pComIn^.Status.0 = pComOut^.Ctrl.0 THEN
IF CurrentOutCycle >= TxBuffer.Count / INT_TO_REAL(GVL_Constant.HardwareLength) THEN
CurrentOutCycle:= 0;
StateCom:= 130;
ELSE
StateCom:= 110;
END_IF
END_IF
130:
IF pComIn^.Status.0 = pComOut^.Ctrl.0 THEN
pOutputLength^:= 0;
TxBuffer.Count:= 0;
StateCom:= 0;
END_IF //接收
200:
CurrentInCycle:= 0;
MEMSET(ADR(RxBuffer), 0, SIZEOF(RxBuffer));
StateCom:= 210;
210:
FOR A:= 0 TO pInputLength^ - 1 DO
IF CurrentInCycle * GVL_Constant.HardwareLength + A <= 300 THEN
RxBuffer.Buffer[CurrentInCycle * GVL_Constant.HardwareLength + A]:= pComIn^.DataIn[A];
RxBuffer.Count:= RxBuffer.Count + 1;
END_IF
END_FOR
CurrentInCycle:= CurrentInCycle + 1;
StateCom:= 220;
220:
pComOut^.Ctrl.1:= pComIn^.Status.1;
StateCom:= 230;
230:
IF pInputLength^ >= 1 AND pComIn^.Status.1 <> pComOut^.Ctrl.1 THEN //ReceiveRequest
TimerWait(IN:= FALSE);
StateCom:= 210;
ELSE
TimerWait(IN:= TRUE, PT:= T#20MS); //这个时间不能太短,如果用RingBuffer就不需要这个了
IF TimerWait.Q THEN
TimerWait(IN:= FALSE);
CurrentInCycle:= 0;
StateCom:= 0;
END_IF
END_IF
END_CASE

逻辑不算复杂,发送就是将ComBuffer的数据往EL6OutData22B搬,接收就是将EL6inData22B的数据往ComBuffer搬,每次最多搬GVL_Constant.HardwareLength个字节,搬不完就硬件把这些字节处理完继续搬

3.4,SendString,ReceiveString

这俩就很简单了,实现用户代码和缓存之间的交互

FUNCTION_BLOCK SendString
VAR_INPUT
SendString: STRING;
END_VAR
VAR_IN_OUT
TXbuffer: ComBuffer;
END_VAR //代码部分
TXbuffer.Count:= INT_TO_UINT(LEN(SendString));
MEMCPY(ADR(TXbuffer.Buffer), ADR(SendString), TXbuffer.Count);
FUNCTION_BLOCK ReceiveString
VAR_INPUT
Prefix: STRING;
Suffix: STRING;
TimeOut: TIME;
END_VAR
VAR_OUTPUT
bTimeOut: BOOL;
StringReceived: BOOL;
Error: BOOL;
END_VAR
VAR_IN_OUT
ReceivedString: STRING;
RXbuffer: ComBuffer;
END_VAR
VAR
Timer: TON;
PrefixIndex, SuffixIndex: INT;
P: POINTER TO STRING(255);
P1: POINTER TO STRING(255);
END_VAR //代码部分
Timer(IN:= TRUE, PT:= TimeOut);
bTimeOut:= FALSE;
IF Timer.Q THEN
Timer(IN:= FALSE);
bTimeOut:= TRUE;
RxBuffer.Count:= 0;
END_IF PrefixIndex:= 0;
SuffixIndex:= 0;
P:= ADR(RXbuffer.Buffer);
PrefixIndex:= FIND(P^, Prefix);
P1:= P+PrefixIndex;
SuffixIndex:= PrefixIndex+FIND(P1^, Suffix);
StringReceived:= UDINT_TO_INT(RxBuffer.Count) >= SuffixIndex AND SuffixIndex > PrefixIndex > 0; IF StringReceived THEN
Timer(IN:= FALSE);
MEMSET(ADR(ReceivedString), 0, SIZEOF(ReceivedString));
MEMCPY(destAddr:= ADR(ReceivedString), srcAddr:= P1-1, SuffixIndex - PrefixIndex + 1);
RxBuffer.Count:= 0;
END_IF

ReceiveString本身很简单,加了根据Prefix和Suffix截取数据才多了几行代码

4,自己的Tc2_SerialCom简单使用

先引用自己写的Tc2_SerialCom,PlcTask的PROGRAM MAIN

PROGRAM MAIN
VAR
ComIn AT%I*: EL6inData22B;
ComOut AT%Q*: EL6outData22B;
TxBuffer: ComBuffer;
RxBuffer: ComBuffer; sendString: SendString;
receiveString: ReceiveString;
StringToSend: STRING;
StringToReceive: STRING;
END_VAR //代码部分
IF LEN(StringToSend) > 0 THEN
sendString(SendString:= StringToSend, TXbuffer:= TxBuffer);
StringToSend:= '';
END_IF receiveString(Prefix:= 'A', Suffix:= 'B', TimeOut:= T#200MS, ReceivedString:= StringToReceive, RXbuffer:= RxBuffer);
IF receiveString.bTimeOut THEN
StringToReceive:= '';
ELSIF receiveString.StringReceived THEN
//解析数据
END_IF

FastTask的PROGRAM

PROGRAM PRG_SerialLine
VAR
serialLine: SerialLineControl;
END_VAR //代码部分
serialLine(pComIn:= ADR(MAIN.ComIn), pComOut:= ADR(MAIN.ComOut), TxBuffer:= MAIN.TxBuffer, RxBuffer:= MAIN.RxBuffer);

使用起来和原生的几乎一毛一样。

5,总结

国内一些厂家也做了自己的串口通信模块,函数库设计上也是类似的思路;但是实际使用中接线方式,自动识别,函数接口等方面总是差点意思。也有一些厂家使用串口服务器来替代串口通信模块,由于官方授权的存在,成本上似乎还有降低。希望国产制造业能越做越好吧。

作者:tossorrow

出处:TwinCAT3 - 实现自己的Tc2_SerialCom

转载:欢迎转载,请保留此段声明,请在文章中给出原文链接;

TwinCAT3 - 实现自己的Tc2_SerialCom的更多相关文章

  1. 倍福TwinCAT3上位机与PLC通信测试(ADS通信) 包含C#和C++代码

    倍福TwinCAT3上位机与PLC通信测试(ADS通信) 包含C#和C++代码 本次测试需要环境: VS2013,TwinCAT3(本人版本TC31-Full-Setup.3.1.4018.16) 代 ...

  2. TwinCAT3提示找不到TcPch.h错误解决

    我使用git对TwinCAT3的工程进行版本控制,但是别的电脑clone的仓库会提示找不到TcPch.h的错误,无法编译. 明明文件就在那里,就是不让编译... 解决办法更奇葩,只需要把工程文件压缩, ...

  3. 倍福TwinCAT(贝福Beckhoff)基础教程5.1 TwinCAT-3 读写注册表

    读写注册表和读写文件一样,里面涉及的输入类型比较复杂,需要参考官方范例 sSubKey是指注册表的路径 sValName是指注册表要写入的名值对的名称 eValType是一个枚举类型(而且不是什么常规 ...

  4. TwinCAT3的c++和标准c++(c++11)特性区别

    1.vector不能使用花括号初始化 2.不支持cmath,需要使用TcMath.h

  5. 卸载TwinCat3之后vs未能正确加载包错误解决

    如上图所示错误. 使用vs开发人员命令提示,输入以下代码.会清除所有用户设置,然后就没有错误提示了. devenv /resetuserdata

  6. twincat3新建cpp提示"在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include "stdafx.h"

    自己之前在windows下面写过一些c++的函数,想在倍福工控机上直接使用,发现添加了.cpp和.h文件后无法完成编译,会提示 在查找预编译头时遇到意外的文件结尾.是否忘记了向源中添加“#includ ...

  7. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-点击激活配置进入到运行模式直接死机或蓝屏怎么办

    下载我提供的TCRtime.sys文件,替换掉TwinCAT/Driver目录下的原有文件(原有文件要小一点,这个是159KB的) 如果你同时也安装了TwinCAT3,请不要替换这个,他是398KB的 ...

  8. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-如何在程序中添加注释

    在TwinCAT2中,(*中间输入注释*),也可以用这种方法批量注释,在TwinCAT3中,使用//即可     更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: http://i.youk ...

  9. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-报错0X4655,18005错误怎么办

    首先确认驱动器没有报错(如果驱动器报错,请先解决绝对值编码器的清除多圈数据问题) 报错一般上使能就会报错,没法测试运转,而且不管是用贝福自带的NC功能还是自己写的都会一样的效果   请删除在贝福的Et ...

  10. 倍福TwinCAT(贝福Beckhoff)应用教程12.2 TwinCAT控制松下伺服 NC初步

    在前面我们已经学会了使用贝福自带的调试软件完成试运行,接下来是使用TWINCAT PLC实现这个功能,右击PLC添加一个PLC项目   在VISUs上右击添加一个HMI人机界面   目前PLC程序和人 ...

随机推荐

  1. 瑞芯微RK3568J如何“调节主频”,实现功耗降低?一文教会您!

    RK3568J主频模式说明 为降低RK3568J功耗,提高运行系统健壮性,在产品现场对RK3568J实现主频调节则显得尤为重要. 图 1 RK3568J官方数据手册主频模式描述 normal模式 根据 ...

  2. React Context 的使用

    使用React开发应用程序时,如果多个组件需要共享数据,可以把数据放到父组件中,通过属性向下传递给子组件.但当组件嵌套较深时,两个最底层的组件要想共享数据,那就霜要把数据放到最顶层的组件中,然后再一层 ...

  3. 【Redis】BigKey问题

    面试题 海量数据里查询某一固定前缀的key 生产上如何限制 keys * / flushdb / flushall 等危险命令以防止误删误用? MEMORY USAGE 命令用过吗? BigKey问题 ...

  4. Spring 获取Bean ApplicationContextAware的使用

    创建类继承ApplicationContextAware package net.ybclass.online_ybclass.utils; import org.springframework.be ...

  5. 在IDEA中找不到Mapper报错

    前言 相信大多数互联网公司的持久层框架都是使用 Mybatis 框架,而大家在 Service 层引入自己编写的 Mapper 接口时应该会遇到下面的情况: 我们可以看到,上面的红色警告在提示我们,找 ...

  6. Three光源Target位置改变光照方向不变的问题及解决方法

    0x00 楔子 在 Three.js 中,光源的目标(target)是一种用于指定光源方向的重要元素.在聚光灯中和定向光(DirectionalLight)中都有用到. 有时我们可能会遇到光源目标位置 ...

  7. [oeasy]python0066_控制序列_光标位置设置_ESC_逃逸字符_CSI

    光标位置 回忆上次内容 上次讲了 三引号的输出 三引号中 回车和引号 都会 被原样输出 \ 还是需要从 \\转义 黑暗森林 快被摸排清了 还有哪个 转义序列 没 研究过吗? \e是 干什么的? 回忆转 ...

  8. oeasy教您玩转vim - 25 - 更多颜色

    ​ 更多颜色 回忆上节课内容 我们上次深入了配色方案 定义了自己的配色方案 oeasy 建立了自己的配色 oeasy 在状态栏应用了自己的配色 ​ 明确能用的颜色 先胡乱地尝试一下修改颜色代码 hi ...

  9. 题解:B3646 数列前缀和 3

    分析 板子题,线段树维护矩阵区间积,除了难写没什么思维难度. 所以直接放代码吧. Code #include<bits/stdc++.h> #define int long long us ...

  10. Arch Linux install i3-wm

    Arch Linux install i3-wm 简介 i3-wm 是一种动态的平铺式窗口管理器,它的设计目标是提供一个快速.简洁.可定制的桌面环境,适合开发者和高级用户使用.它有以下几个特点: 它使 ...