首页 > 其他分享 >TwinCAT3 - 实现自己的Tc2_SerialCom

TwinCAT3 - 实现自己的Tc2_SerialCom

时间:2023-06-20 16:45:04浏览次数:35  
标签:END GVL ComBuffer pComOut Tc2 TwinCAT3 VAR 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,总结

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

作者:tossorrow
出处:TwinCAT3 - 实现自己的Tc2_SerialCom
转载:欢迎转载,请保留此段声明,请在文章中给出原文链接;

标签:END,GVL,ComBuffer,pComOut,Tc2,TwinCAT3,VAR,SerialCom
From: https://www.cnblogs.com/tossorrow/p/17493540.html

相关文章

  • VCU整车控制器 ,量产模型搭配底层软件 ,某知名电动汽车 量产VCU模型搭配英飞凌tc234底
    VCU整车控制器,量产模型搭配底层软件,某知名电动汽车量产VCU模型搭配英飞凌tc234底层驱动软件,可完成编译烧写,运行。服务一:应用层模型,服务二:信号矩阵协议,信号接口定义表服务三:底层驱动源代码,接口层源码;可以供,全套,有兴趣的汽车工程师们可以看看,2022最好的投资是啥,投资自己,多多学......
  • ASEMI代理LTC2954CTS8-2#TRMPBF原装ADI车规级芯片
    编辑:llASEMI代理LTC2954CTS8-2#TRMPBF原装ADI车规级芯片型号:LTC2954CTS8-2#TRMPBF品牌:ADI/亚德诺封装:TSOT-23-8批号:2023+引脚数量:23工作温度:-40°C~85°C安装类型:表面贴装型LTC2954CTS8-2#TRMPBF汽车芯片LTC2954CTS8-2#TRMPBF特性可调电源开/关定时器低电源电流:6μA宽工作电压范围......
  • ASEMI代理LTC2954CTS8-2#TRMPBF原装ADI车规级芯片
    编辑:llASEMI代理LTC2954CTS8-2#TRMPBF原装ADI车规级芯片型号:LTC2954CTS8-2#TRMPBF品牌:ADI/亚德诺封装:TSOT-23-8批号:2023+引脚数量:23工作温度:-40°C~85°C安装类型:表面贴装型LTC2954CTS8-2#TRMPBF汽车芯片LTC2954CTS8-2#TRMPBF特性可调电源开/关定时器低电源电流:6μA......
  • ASEMI代理亚德诺LTC2954CTS8-2#TRMPBF车规级芯片
    编辑-ZLTC2954CTS8-2#TRMPBF参数描述:型号:LTC2954CTS8-2#TRMPBF电源电压范围:2.7-26.4VVIN供电电流:6-12µAVIN欠电压锁定:2.2-2.5VVIN欠压锁定滞后:50-700mVONT上拉电流:–2.4to–3.6µAONT下拉电流:2.4-3.6µA内部开启解除抖动时间:26-41ms额外的可调节开启时间:9-13.5m......
  • 在TwinCat3上同时对两个从站发送数字量,验证EtherCat同步性
    提问: TwinCat3上面接了两个从站,都在正常工作中。我想做的是同时对两个从站发出数字量信息。比方说,我的从站1上面有LED0-LED7,都是由主站TwinCat控制亮灭。从站2也有LED0-L......
  • LTC2440串行SPI通讯时序
    LTC2440简介我们使用4-wireSPI接口按照时序图上的描述,SDO是在SCLK的下降沿更新数据,那么FPGA接收端就应该在上升沿采集数据。实际测试发现SDO数据相对于SCLK延迟了6......
  • 1579_AURIX_TC275_MTU中的ECC机理以及各种寄存器实现
    全部学习汇总:​​GreyZhang/g_TC275:happyhackingforTC275!(github.com)​​这一夜的信息全是寄存器地址信息,在了解功能的时候都是非关键信息。后续的内容整理中,这部......
  • 1577_AURIX_TC275_MTU中检测控制相关寄存器
    全部学习汇总:​​GreyZhang/g_TC275:happyhackingforTC275!(github.com)​​开篇介绍的功能室之前看过很多次的一个握手的功能。快速行以及快速列模式的测试中,这个行......
  • 1583_AURIX_TC275_SMU的控制以及FSP
    全部学习汇总:​​GreyZhang/g_TC275:happyhackingforTC275!(github.com)​​SMU的软件控制接口主要是实现了一些控制命令,用于控制SMU的状态机以及FSP。具体的内容在上......
  • 1584_AURIX_TC275_SMU的调试以及部分寄存器
    全部学习汇总:​​GreyZhang/g_TC275:happyhackingforTC275!(github.com)​​前面学习的过程中,突然间减速了不少。但是为了保证学习的推进,还是得有每天的稳定输出。我......