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程序和人 ...
随机推荐
- ZYNQ:Linux添加I2C-RTC驱动
硬件情况 使用的是DS1338这款RTC时钟芯片,I2C总线对应到PS端的I2C1. 配置 内核 添加有关的驱动: 因为DS1338用的驱动与DS13307相似,一找发现是同一个配置. CONFIG_ ...
- AM62x GPMC并口如何实现“小数据-低时延,大数据-高带宽”—ARM+FPGA低成本通信方案
GPMC并口简介 GPMC(General Purpose Memory Controller)是TI处理器特有的通用存储器控制器接口,支持8/16bit数据位宽,支持128MB访问空间,最高时钟速率 ...
- 牛客小白月赛97 A-D题解
AAAAAAAAAAAAAAAAAAAAA -----------------------------题解------------------------------------------- 统计数 ...
- Mac VMware Fusion 11.5 虚拟机带密钥
虚拟机 链接: https://pan.baidu.com/s/19V20p5ZV-1U5lFNHrotLgw 密码: rdun CentOS 6.5 链接: https://pan.baidu.co ...
- Kafka消费端抛出异常Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group的解决方案
总结/朱季谦 在一次测试Kafka通过consumer.subscribe()指定偏移量Offset消费过程中,因为设置参数不当,出现了一个异常提示-- [2024-01-04 16:06:32.55 ...
- 记录 中**信 ruoyi项目 部署全流程
零 本地环境改为线上环境 包括 1 后端的数据库连接地址 2 后端的文件存储本地地址 3 后端的文件存储ip地址 4 前端baseUrl 一 后端项目打包 双击package 二 mstsc 进入服务 ...
- 洛谷P1464
搜索优化后是记忆化搜索,再优化就是dp了 #include <iostream> #include <utility> using namespace std; typedef ...
- oeasy教您玩转vim - 58 - # 块可视化
块可视化编辑 回忆上节课内容 上次我们了解到行可视模式 行可视模式 V 也可配合各种motion o切换首尾 选区的开头和结尾是mark标记 开头是 '< 结尾是 '> 可以在选区内进 ...
- [rCore学习笔记 06]运行Lib-OS
QEMU运行第一章代码 切换分支 git checkout ch1 detail git checkout ch1 命令是用来切换到名为 ch1 的分支或者恢复工作目录中的文件到 ch1 提交的状态 ...
- npm和yarn 命令比较
命令比较 npm init | yarn init:创建一个新包 npm run | yarn run:运行 package.json 中定义的脚本 npm test | yarn test:测试一个 ...