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. 韦东山freeRTOS系列教程之【第八章】事件组(event group)

    目录 系列教程总目录 概述 8.1 事件组概念与操作 8.1.1 事件组的概念 8.1.2 事件组的操作 8.2 事件组函数 8.2.1 创建 8.2.2 删除 8.2.3 设置事件 8.2.4 等待 ...

  2. mac mysql 设置默认编码格式utf-8

    导读 博主百度一番,发现更改mysql默认编码格式,归结以下几个步骤. 详细步骤 切换当前目录 cd / cd private/etc 新建my.cnf文件 在当前目录下:private/etc su ...

  3. C# 日期帮助类

    using System; using System.Data; namespace Erp.Ship.Tool { [Serializable] public enum DateInterval { ...

  4. Java-Filter:过滤器请求拦截

    1.概念 web中的过滤器:当访问服务器资源时,过滤器可以将请求拦截下来,完成一些特殊的功能 过滤器的作用: 一般用于完成通用的操作,如:登录验证,统一编码处理,敏感字符过滤 2.快速入门 1.步骤 ...

  5. oeasy教您玩转vim - 16 - # 行内贴靠

    行头行尾 回忆上节课内容 跳跃 向前跳跃是 f 向后跳跃是 F 继续 保持方向是 ; 改变方向是 , 可以加上 [count] 来加速 还有什么好玩的吗? 动手 #这次还是用无配置的方式启动 vi - ...

  6. oeasy教您玩转vim - 45 - # 按行编辑

    ​ 按行编辑 回忆上节课内容 上次我们主要就是综合运用 很好玩的,更快速的解决问题 进行计算 ctrl+a,将具体的数字加1 ctrl+x,将具体的数字减1 5ctrl+a,将具体的数字加5 一次命令 ...

  7. 渐变边框文字效果?CSS 轻松拿捏!

    今天,有个群友问了我这么一个问题,如果不想切图,是否有办法实现带渐变边框的字体效果?如下所示: 本文,就将尝试一下,在 CSS 中,我们可以如何尽可能的实现这种渐变边框字体效果. 元素叠加 首先,比较 ...

  8. Django Template层之Template概述

    Django Template层之Template概述 by:授客 QQ:1033553122 实践环境 Python版本:python-3.4.0.amd64 下载地址:https://www.py ...

  9. LeetCode513. 找树左下角的值

    题目链接:https://leetcode.cn/problems/find-bottom-left-tree-value/description/ 题目叙述: 给定一个二叉树的 根节点 root,请 ...

  10. php环境,性能优化

    根据宝塔的推荐进行参数修改 我的是8G内存,修改成4G内存 下面是备份:修改前的 ; Start a new pool named 'www'.; the variable $pool can be ...