这篇文章主要介绍了C#实现上位机与欧姆龙PLC通讯(FINS)方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
先介绍下一些基本定义
串行通信:通过的是PLC上的串行口RS232/RS422/485口,上位机链接系统 Hostlink系统是对于FA系统一种及优化有经济的通信方式。
适用于一台上位机与一台或者多台的PLC进行数据通信。
通讯协议分两种
1:C-mode commands
只可以通过串口进行通讯
2:Fins commands
既可以通过串口通讯也可以通过各类网络通讯(适应性较强)
本文只介绍fins通讯
fins (factory in terfave network service) 通讯协议是欧姆龙公司开发的一款自动化控制网络指令/响应系统的通讯协议;运用FINS指令可实现各种网络间的无缝通信
FINS帧结构
- 发送命令结构 命令码2个字节
- 响应命令结构 命令码2个字节
- 命令码 01 01读数据
- 命令码 01 02写数据
- 结束码 00 00无错误
1、获取PLC节点地址
上位机和PLC建立TCP通讯之后,可以发送以下命令来获得PLC的节点地址;
帧结构见下表:
列表 | Content(十六进制) | Description(说明) |
---|---|---|
头 | 46 49 4E 53 | ASCII分别代表的是 F I N S |
长度 | 00 00 00 0C | 从Command之后的数据包的长度 |
错误代码 | 00 00 00 00 | 目前暂时没用,因为在服务器不需要检测内部错误 |
连接的节点地址 | 00000000~000000FE | 分别是0~254,当设置为0时表示自动获取客户端的Fins节点地址 |
2、命令码介绍
详细内容说明介绍见下表:
命令内容 | 命令代码(MR SR) | 说明 | 功能 |
---|---|---|---|
访问I/O存储区 | 01 01 | 读内存区 | 读连续I/O存储区字的内容 |
访问I/O存储区 | 01 02 | 写内存区 | 写连续I/O存储区字的内容 |
访问I/O存储区 | 01 03 | 填充内存区 | 将相同的数据写入指定范围的I/O存储器区 |
访问I/O存储区 | 01 04 | 多个存储区读取 | 读取指定的非连续I/O存储区字 |
访问I/O存储区 | 01 05 | 存储区传输 | 将连续存储I/O存储区字内容复制到另外的I/O存储区 |
访问参数区 | 02 01 | 读取参数区 | 读取连续参数区字内容 |
访问参数区 | 02 02 | 写入参数区 | 写入连续参数区字内容 |
访问参数区 | 02 03 | 填充参数区 | 将相同数据写入到指定范围参数区域字 |
改变操作模式 | 04 01 | 设置CPU操作模式为RUN | 将CPU单元的操作模式更改为RUN或MONITOR |
改变操作模式 | 04 02 | 设置CPU操作模式为STOP | 将CPU单元的操作模式更改为编程 |
改变操作模式 | 06 01 | 读取CPU单元状态 | 读取CPU单元状态 |
错误日志 | 21 01 | 错误清除 | 清除错误或错误信息 |
错误日志 | 21 02 | 读取错误日志 | 读取错误日志 |
错误日志 | 21 03 | 清除错误日志 | 清除错误日志指针 |
3、I / O存储器地址标识
区域 | 数据类型 | 存储区代码 | 存储区地址范围 | 存储地址 | 字节长度 |
---|---|---|---|---|---|
DM | Bit | 02 | D0000000到D3276715 | 000000到7FFF0F | 1 |
DM | Word | 82 | D00000到D32767 | 000000到7FFF00 | 2 |
鉴于我们在和PLC通讯时,一般只需要进行读取DM区寄存器操作,因为本文只介绍读取和写入DM区寄存器。其他的有需要的童鞋进行自己功能拓展。
举例:
读取DM区地址100,连续10个地址的数据
发送命令:010182006400000A
返回命令:010100000102030405060708090A
发送的命令进行说明:
- 1、01 01代表功能码,读连续I/O存储区字的内容
- 2、82代表进行字操作
- 3、00 64转成十进制就是100代表的是我们要读取的起始地址
- 4、00000A转成十进制就是10代表的是我们要读取得长度
响应的命令说明
- 1、01 01代表功能码,读连续I/O存储区字的内容
- 2、00 00代表结束码,当不是00 00的时候表明数据帧不对
- 3、01 02 03 04 05 06 07 08 09 0A分别代表要读取的十个寄存器的数据值
具体协议可以查看百度文库:欧姆龙fins通讯协议
综上,结合之前的博客,C#Socket客户端:C#Socket客户端我们可以整合得到一个欧姆龙fins的类,如下所示:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace OmrEntFINS
{
public class OmronFINS
{
/// <summary>
/// 客户端连接Socket
/// </summary>
private Socket clientSocket;
/// <summary>
/// 连接状态
/// </summary>
public Boolean connected = false;
/// <summary>
/// 发送数据
/// </summary>
private Byte[] SendMess;
/// <summary>
/// 连接点
/// </summary>
private IPEndPoint hostEndPoint;
/// <summary>
/// 连接信号量
/// </summary>
private static AutoResetEvent autoConnectEvent = new AutoResetEvent(false);
/// <summary>
/// 接受到数据时的委托
/// </summary>
/// <param name="info"></param>
public delegate void ReceiveMsgHandler(Byte[] info);
/// <summary>
/// 接收到数据时调用的事件
/// </summary>
public event ReceiveMsgHandler OnMsgReceived;
/// <summary>
/// 开始监听数据的委托
/// </summary>
public delegate void StartListenHandler();
/// <summary>
/// 开始监听数据的事件
/// </summary>
public event StartListenHandler StartListenThread;
/// <summary>
/// 发送信息完成的委托
/// </summary>
/// <param name="successorfalse"></param>
public delegate void SendCompleted(bool successorfalse);
/// <summary>
/// 发送信息完成的事件
/// </summary>
public event SendCompleted OnSended;
/// <summary>
/// 监听接收的SocketAsyncEventArgs
/// </summary>
private SocketAsyncEventArgs listenerSocketAsyncEventArgs;
int Plcport;
public OmronFINS(String hostName, Int32 port, Int32 PLCStaion)
{
Plcport = PLCStaion;
IPAddress[] addressList = Dns.GetHostAddresses(hostName);
this.hostEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port);
this.clientSocket = new Socket(this.hostEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
}
/// <summary>
/// 连接服务端
/// </summary>
private bool Connect()
{
using (SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs())
{
connectArgs.UserToken = this.clientSocket;
connectArgs.RemoteEndPoint = this.hostEndPoint;
connectArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnect);
clientSocket.ConnectAsync(connectArgs);
//等待连接结果
bool autores = autoConnectEvent.WaitOne(1000);
if (this.connected)
{
listenerSocketAsyncEventArgs = new SocketAsyncEventArgs();
byte[] receiveBuffer = new byte[1024];//设置接收buffer区大小
listenerSocketAsyncEventArgs.UserToken = clientSocket;
listenerSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
listenerSocketAsyncEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceive);
StartListenThread();
// SocketExtensions.SetKeepAlive(clientSocket, 3000, 1000);
return true;
}
else
return false;
}
}
/// <summary>
/// 开始监听线程的入口函数
/// </summary>
private void Listen()
{
(listenerSocketAsyncEventArgs.UserToken as Socket).ReceiveAsync(listenerSocketAsyncEventArgs);
}
public static List<SocketAsyncEventArgs> s_lst = new List<SocketAsyncEventArgs>();
/// <summary>
/// 发送信息
/// </summary>
/// <param name="message"></param>
private void Send(Byte[] message)
{
if (this.connected)
{
Byte[] sendBuffer = message;
SocketAsyncEventArgs senderSocketAsyncEventArgs = null;// new SocketAsyncEventArgs();
lock (s_lst)
{
if (s_lst.Count > 0)
{
senderSocketAsyncEventArgs = s_lst[s_lst.Count - 1];
s_lst.RemoveAt(s_lst.Count - 1);
}
}
if (senderSocketAsyncEventArgs == null)
{
senderSocketAsyncEventArgs = new SocketAsyncEventArgs();
senderSocketAsyncEventArgs.UserToken = this.clientSocket;
senderSocketAsyncEventArgs.RemoteEndPoint = this.hostEndPoint;
senderSocketAsyncEventArgs.Completed += (object sender, SocketAsyncEventArgs _e) =>
{
lock (s_lst)
{
s_lst.Add(senderSocketAsyncEventArgs);
}
};
}
senderSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);
clientSocket.SendAsync(senderSocketAsyncEventArgs);
}
else
{
this.connected = false;
}
SendMess = message;
}
/// <summary>
/// 断开连接
/// </summary>
private bool Disconnect()
{
bool returnDis = true;
try
{
this.clientSocket.Shutdown(SocketShutdown.Both);
this.clientSocket.Close();
//this.clientSocket.Dispose();
//clientSocket.Disconnect(true);
//clientSocket.Disconnect(false);
}
catch (Exception)
{
returnDis = false;
}
finally
{
}
this.connected = false;
return returnDis;
}
/// <summary>
/// 连接的完成方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnConnect(object sender, SocketAsyncEventArgs e)
{
this.connected = (e.SocketError == SocketError.Success);
autoConnectEvent.Set();
}
/// <summary>
/// 接收的完成方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnReceive(object sender, SocketAsyncEventArgs e)
{
if (e.BytesTransferred == 0)
{
//Console.WriteLine("Socket is closed", Socket.Handle);
if (clientSocket.Connected)
{
try
{
clientSocket.Shutdown(SocketShutdown.Both);
}
catch (Exception)
{
//client already closed
}
finally
{
if (clientSocket.Connected)
{
clientSocket.Close();
}
}
}
Byte[] rs = new Byte[0];
OnMsgReceived(rs);
this.connected = false;
}
else
{
byte[] buffer = new byte[e.BytesTransferred];
for (int i = 0; i < e.BytesTransferred; i++)
{
buffer[i] = e.Buffer[i];
}
this.OnMsgReceived(buffer);
Listen();
}
}
/// <summary>
/// 发送的完成方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnSend(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
OnSended(true);
}
else
{
OnSended(false);
this.ProcessError(e);
}
}
/// <summary>
/// 处理错误
/// </summary>
/// <param name="e"></param>
private void ProcessError(SocketAsyncEventArgs e)
{
Socket s = e.UserToken as Socket;
if (s.Connected)
{
try
{
s.Shutdown(SocketShutdown.Both);
}
catch (Exception)
{
//client already closed
}
finally
{
if (s.Connected)
{
s.Close();
}
}
this.connected = false;
}
//throw new SocketException((Int32)e.SocketError);
}
#region IDisposable Members
public void Dispose()
{
autoConnectEvent.Close();
if (this.clientSocket.Connected)
{
this.clientSocket.Close();
}
}
#endregion
#region 欧姆龙通讯协议
/// <summary>
/// 发送命令反馈
/// </summary>
/// <param name="successorfalse"></param>
void OmronFINS_OnSended(bool successorfalse)
{
if (!successorfalse)
{
}
else
{
}
}
Byte PCS;
Byte PLCS;
int SendOrRev;
int SendOrRev2;
Byte[] SendBack;
Byte[] RecvBack;
/// <summary>
/// 接受命令反馈
/// </summary>
/// <param name="info"></param>
void OmronFINS_OnMsgReceived(byte[] info)
{
if (SendmessHas)
{
if (info.Length >= 24)
{
if (int.Parse(info[23].ToString("X").Trim(), System.Globalization.NumberStyles.HexNumber) == Plcport)
{
PCS = info[19];
PLCS = info[23];
SendmessHas = false;
}
}
}
else
{
if (info.Length > 0)//PLC连接错误NO
{
if (SendOrRev2 == 1)//发送命令
{
SendBack = info;
SendOrRev2 = 0;
}
else if (SendOrRev == 2)//接受命令
{
RecvBack = info;
SendOrRev = 0;
}
}
else
{
if (SendOrRev2 == 1)//发送命令
{
SendBack = new Byte[1];
SendBack[0] = 0x00;
SendOrRev2 = 0;
}
else if (SendOrRev == 2)//接受命令
{
RecvBack = new Byte[1];
RecvBack[0] = 0x00;
SendOrRev = 0;
}
}
}
}
void OmronFINS_StartListenThread()
{
this.Listen();
}
bool SendmessHas = false;
/// <summary>
/// 打开连接
/// </summary>
/// <returns></returns>
public bool OpenLinkPLC()
{
bool Ret = false;
this.StartListenThread += new StartListenHandler(OmronFINS_StartListenThread);
this.OnMsgReceived += new ReceiveMsgHandler(OmronFINS_OnMsgReceived);
this.OnSended += new SendCompleted(OmronFINS_OnSended);
Ret = this.Connect();
if (Ret)
{
SendmessHas = true;
this.Send(GetPCAdress());
int addNs = 0;
while (SendmessHas && addNs < 500)
{
Thread.Sleep(2);
addNs++;
}
if (addNs < 500)
{
Ret = true;
}
else
{
Ret = false;
}
}
return Ret;
}
/// <summary>
/// 关闭连接
/// </summary>
/// <returns></returns>
public bool CloseLinkPLC()
{
return this.Disconnect();
}
/// <summary>
/// 写入值(Word)
/// </summary>
/// <param name="TypeInt">起始寄存器地址</param>
/// <param name="NumData">个数</param>
/// <param name="IntValue">值</param>
/// <returns>true,成功;false,失败</returns>
public bool WritePlcData(string TypeInt, int NumData, ref int[] IntValue)
{
bool sendRerun = false;
if (NumData > 0)
{
Byte[] numData = BitConverter.GetBytes(NumData);//Use numData[0] and numData[1]
Byte[] SendmessAge = new Byte[34 + NumData * 2];
if (GetType(TypeInt, 0) != 0x00 && Address != null && Address.Length > 1)
{
Byte[] getSendFunc = this.SetPLCvalue(PLCS, PCS, GetType(TypeInt, 0), Address[1], Address[0], 0x00, numData[1], numData[0], NumData);
for (int i = 0; i < 34 + NumData * 2; i++)
{
if (i < 34)
{
SendmessAge[i] = getSendFunc[i];
}
else
{
if ((i - 33) % 2 != 0)
{
Byte[] BuffInvalue = BitConverter.GetBytes(IntValue[(i - 33) / 2]);
SendmessAge[i] = BuffInvalue[1];
}
else
{
Byte[] BuffInvalue = BitConverter.GetBytes(IntValue[(i - 33) / 2 - 1]);
SendmessAge[i] = BuffInvalue[0];
}
//SendmessAge[i]
}
}
SendOrRev2 = 1;
this.Send(SendmessAge);
int Outtime = 0;
while (SendOrRev2 != 0 && this.connected && Outtime < 600)
{
Thread.Sleep(2);
Outtime++;
}
if (Outtime < 600 && SendBack != null)
{
try
{
//if (SendBack.Length > 1 && SendBack[26] == 0x01 && SendBack[27] == 0x02 && SendBack[28] == 0x00 && SendBack[29] == 0x00)
//{
if (SendBack.Length > 1 && SendBack[26] == 0x01 && SendBack[27] == 0x02 && SendBack[28] == 0x00)
{
sendRerun = true;
}
else
{
sendRerun = false;
}
}
catch (Exception)
{
}
}
else
{
SendOrRev2 = 0;
}
}
}
return sendRerun;
}
/// <summary>
/// 读取值(Word)
/// </summary>
/// <param name="TypeInt">起始寄存器地址</param>
/// <param name="NumData">个数</param>
/// <param name="IntValue">值</param>
/// <returns></returns>
public bool ReadPlcData(string TypeInt, int NumData, out int[] IntValue)
{
bool GetPlcRet = false;
IntValue = new int[1];
if (NumData > 0)
{
IntValue = new int[NumData];
Byte[] numData = BitConverter.GetBytes(NumData);//Use numData[0] and numData[1]
Byte[] RecivemessAge = new Byte[34];
if (GetType(TypeInt, 0) != 0x00 && Address != null && Address.Length > 1)
{
Byte[] getReciveFunc = this.GetPLCvalue(PLCS, PCS, GetType(TypeInt, 0), Address[1], Address[0], 0x00, numData[1], numData[0]);
this.Send(getReciveFunc);
SendOrRev = 2;
int Outtime = 0;
while (SendOrRev != 0 && this.connected && Outtime < 600)
{
Thread.Sleep(2);
Outtime++;
}
if (Outtime < 600 && RecvBack != null)
{
try
{
//if (RecvBack.Length > 1 && RecvBack[26] == 0x01 && RecvBack[27] == 0x01 && RecvBack[28] == 0x00 && RecvBack[29] == 0x00)
if (RecvBack.Length == 30 + NumData * 2 && RecvBack[26] == 0x01 && RecvBack[27] == 0x01 && RecvBack[28] == 0x00)
{
GetPlcRet = true;
for (int i = 0; i < NumData; i++)
{
IntValue[i] = int.Parse(RecvBack[30 + i * 2].ToString("X").PadLeft(2, '0') + RecvBack[30 + i * 2 + 1].ToString("X").PadLeft(2, '0'), System.Globalization.NumberStyles.HexNumber);
}
}
else
{
GetPlcRet = false;
}
}
catch (Exception)
{
}
}
else
{
SendOrRev = 0;
}
}
}
return GetPlcRet;
}
/// <summary>
/// 读取值(Word)
/// </summary>
/// <param name="TypeInt">起始寄存器地址</param>
/// <param name="NumData">个数</param>
/// <param name="IntValue">值</param>
/// <returns></returns>
public bool ReadPlcDataCIO(string TypeInt, int NumData, out int[] IntValue)
{
bool GetPlcRet = false;
IntValue = new int[1];
if (NumData > 0)
{
IntValue = new int[NumData];
Byte[] numData = BitConverter.GetBytes(NumData);//Use numData[0] and numData[1]
Byte[] RecivemessAge = new Byte[34];
if (GetType(TypeInt, 0) != 0x00 && Address != null && Address.Length > 1)
{
Byte[] getReciveFunc = this.GetPLCvalue(PLCS, PCS, GetType(TypeInt, 0), Address[1], Address[0], 0x00, numData[1], numData[0]);
this.Send(getReciveFunc);
SendOrRev = 2;
int Outtime = 0;
while (SendOrRev != 0 & this.connected & Outtime < 800)
{
Thread.Sleep(2);
Outtime++;
}
if (Outtime < 800 && RecvBack != null)
{
//try
//{
//if (RecvBack.Length > 1 && RecvBack[26] == 0x01 && RecvBack[27] == 0x01 && RecvBack[28] == 0x00 && RecvBack[29] == 0x00)
if (RecvBack.Length > 30 && RecvBack[26] == 0x01 && RecvBack[27] == 0x01 && RecvBack[28] == 0x00)
{
GetPlcRet = true;
for (int i = 0; i < NumData; i++)
{
IntValue[i] = int.Parse(RecvBack[30 + i * 2].ToString("X").PadLeft(2, '0') + RecvBack[30 + i * 2 + 1].ToString("X").PadLeft(2, '0'), System.Globalization.NumberStyles.HexNumber);
}
}
else
{
GetPlcRet = false;
}
/
本文标题为:C#实现上位机与欧姆龙PLC通讯(FINS)
基础教程推荐
- winform把Office转成PDF文件 2023-06-14
- 一个读写csv文件的C#类 2022-11-06
- C# List实现行转列的通用方案 2022-11-02
- C#类和结构详解 2023-05-30
- linux – 如何在Debian Jessie中安装dotnet core sdk 2023-09-26
- C#控制台实现飞行棋小游戏 2023-04-22
- unity实现动态排行榜 2023-04-27
- C# windows语音识别与朗读实例 2023-04-27
- C# 调用WebService的方法 2023-03-09
- ZooKeeper的安装及部署教程 2023-01-22