TwinCAT3 - 实现自己的Tc2_SerialCom
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,总结
国内一些厂家也做了自己的串口通信模块,函数库设计上也是类似的思路;但是实际使用中接线方式,自动识别,函数接口等方面总是差点意思。也有一些厂家使用串口服务器来替代串口通信模块,由于官方授权的存在,成本上似乎还有降低。希望国产制造业能越做越好吧。
TwinCAT3 - 实现自己的Tc2_SerialCom的更多相关文章
- 倍福TwinCAT3上位机与PLC通信测试(ADS通信) 包含C#和C++代码
倍福TwinCAT3上位机与PLC通信测试(ADS通信) 包含C#和C++代码 本次测试需要环境: VS2013,TwinCAT3(本人版本TC31-Full-Setup.3.1.4018.16) 代 ...
- TwinCAT3提示找不到TcPch.h错误解决
我使用git对TwinCAT3的工程进行版本控制,但是别的电脑clone的仓库会提示找不到TcPch.h的错误,无法编译. 明明文件就在那里,就是不让编译... 解决办法更奇葩,只需要把工程文件压缩, ...
- 倍福TwinCAT(贝福Beckhoff)基础教程5.1 TwinCAT-3 读写注册表
读写注册表和读写文件一样,里面涉及的输入类型比较复杂,需要参考官方范例 sSubKey是指注册表的路径 sValName是指注册表要写入的名值对的名称 eValType是一个枚举类型(而且不是什么常规 ...
- TwinCAT3的c++和标准c++(c++11)特性区别
1.vector不能使用花括号初始化 2.不支持cmath,需要使用TcMath.h
- 卸载TwinCat3之后vs未能正确加载包错误解决
如上图所示错误. 使用vs开发人员命令提示,输入以下代码.会清除所有用户设置,然后就没有错误提示了. devenv /resetuserdata
- twincat3新建cpp提示"在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include "stdafx.h"
自己之前在windows下面写过一些c++的函数,想在倍福工控机上直接使用,发现添加了.cpp和.h文件后无法完成编译,会提示 在查找预编译头时遇到意外的文件结尾.是否忘记了向源中添加“#includ ...
- 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-点击激活配置进入到运行模式直接死机或蓝屏怎么办
下载我提供的TCRtime.sys文件,替换掉TwinCAT/Driver目录下的原有文件(原有文件要小一点,这个是159KB的) 如果你同时也安装了TwinCAT3,请不要替换这个,他是398KB的 ...
- 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-如何在程序中添加注释
在TwinCAT2中,(*中间输入注释*),也可以用这种方法批量注释,在TwinCAT3中,使用//即可 更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: http://i.youk ...
- 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-报错0X4655,18005错误怎么办
首先确认驱动器没有报错(如果驱动器报错,请先解决绝对值编码器的清除多圈数据问题) 报错一般上使能就会报错,没法测试运转,而且不管是用贝福自带的NC功能还是自己写的都会一样的效果 请删除在贝福的Et ...
- 倍福TwinCAT(贝福Beckhoff)应用教程12.2 TwinCAT控制松下伺服 NC初步
在前面我们已经学会了使用贝福自带的调试软件完成试运行,接下来是使用TWINCAT PLC实现这个功能,右击PLC添加一个PLC项目 在VISUs上右击添加一个HMI人机界面 目前PLC程序和人 ...
随机推荐
- 韦东山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 等待 ...
- mac mysql 设置默认编码格式utf-8
导读 博主百度一番,发现更改mysql默认编码格式,归结以下几个步骤. 详细步骤 切换当前目录 cd / cd private/etc 新建my.cnf文件 在当前目录下:private/etc su ...
- C# 日期帮助类
using System; using System.Data; namespace Erp.Ship.Tool { [Serializable] public enum DateInterval { ...
- Java-Filter:过滤器请求拦截
1.概念 web中的过滤器:当访问服务器资源时,过滤器可以将请求拦截下来,完成一些特殊的功能 过滤器的作用: 一般用于完成通用的操作,如:登录验证,统一编码处理,敏感字符过滤 2.快速入门 1.步骤 ...
- oeasy教您玩转vim - 16 - # 行内贴靠
行头行尾 回忆上节课内容 跳跃 向前跳跃是 f 向后跳跃是 F 继续 保持方向是 ; 改变方向是 , 可以加上 [count] 来加速 还有什么好玩的吗? 动手 #这次还是用无配置的方式启动 vi - ...
- oeasy教您玩转vim - 45 - # 按行编辑
按行编辑 回忆上节课内容 上次我们主要就是综合运用 很好玩的,更快速的解决问题 进行计算 ctrl+a,将具体的数字加1 ctrl+x,将具体的数字减1 5ctrl+a,将具体的数字加5 一次命令 ...
- 渐变边框文字效果?CSS 轻松拿捏!
今天,有个群友问了我这么一个问题,如果不想切图,是否有办法实现带渐变边框的字体效果?如下所示: 本文,就将尝试一下,在 CSS 中,我们可以如何尽可能的实现这种渐变边框字体效果. 元素叠加 首先,比较 ...
- Django Template层之Template概述
Django Template层之Template概述 by:授客 QQ:1033553122 实践环境 Python版本:python-3.4.0.amd64 下载地址:https://www.py ...
- LeetCode513. 找树左下角的值
题目链接:https://leetcode.cn/problems/find-bottom-left-tree-value/description/ 题目叙述: 给定一个二叉树的 根节点 root,请 ...
- php环境,性能优化
根据宝塔的推荐进行参数修改 我的是8G内存,修改成4G内存 下面是备份:修改前的 ; Start a new pool named 'www'.; the variable $pool can be ...