Modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议。通过此协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如PLC可编程控制器等。Modbus通讯物理接口可以选用串口(包括RS232和RS485),也可以选择以太网口。

1、十六进制字符串的CRC验证 

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace modbustest
{
public static class ByteHexHelper
{
private static char[] _buffedChars = null;
private static byte[] _buffedBytes = null; static ByteHexHelper()
{
_buffedChars = new char[(byte.MaxValue - byte.MinValue + ) * ];
int idx = ;
byte b = byte.MinValue;
while (true)
{
string hexs = b.ToString("X2");
_buffedChars[idx++] = hexs[];
_buffedChars[idx++] = hexs[]; if (b == byte.MaxValue) break;
++b;
} _buffedBytes = new byte[0x67];
idx = _buffedBytes.Length;
while (--idx >= )
{
if ((0x30 <= idx) && (idx <= 0x39))
{
_buffedBytes[idx] = (byte)(idx - 0x30);
}
else
{
if ((0x61 <= idx) && (idx <= 0x66))
{
_buffedBytes[idx] = (byte)((idx - 0x61) + );
continue;
}
if ((0x41 <= idx) && (idx <= ))
{
_buffedBytes[idx] = (byte)((idx - 0x41) + );
}
}
}
} /// <summary>
/// 字节数组转16进制字符串
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string ByteToHex(byte[] bytes)
{
if (bytes == null)
{
return null;
} char[] result = new char[bytes.Length * ];
for (int i = ; i < bytes.Length; ++i)
{
int startIndex = (bytes[i] - byte.MinValue) * ;
result[i * ] = _buffedChars[startIndex];
result[i * + ] = _buffedChars[startIndex + ];
} return new string(result);
} /// <summary>
/// 16进制字符串转字节数组
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static byte[] HexToByte(string str)
{
str = str.Replace(" ","");
if (str == null || (str.Length & ) == )
{
return null;
} byte[] result = new byte[str.Length / ];
int charIndex = ;
int byteIndex = ;
int length = result.Length;
while (--length >= )
{
int first = ;
int second = ;
try
{
first = _buffedBytes[str[charIndex++]];
second = _buffedBytes[str[charIndex++]];
}
catch
{
return null;
}
result[byteIndex++] = (byte)((first << ) + second);
}
return result;
}
}
}

2、字符串转十六进制|十六进制转字符串

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace modbustest
{
class CRC
{ public static string CRCCheck(string val)
{
val = val.TrimEnd(' ');
string[] spva = val.Split(' ');
byte[] bufData = new byte[spva.Length + ];
bufData = ToBytesCRC(val);
ushort CRC = 0xffff;
ushort POLYNOMIAL = 0xa001;
for (int i = ; i < bufData.Length - ; i++)
{
CRC ^= bufData[i];
for (int j = ; j < ; j++)
{
if ((CRC & 0x0001) != )
{
CRC >>= ;
CRC ^= POLYNOMIAL;
}
else
{
CRC >>= ;
}
}
} return ToHex(System.BitConverter.GetBytes(CRC));
}
/// <summary>
/// 例如把如下字符串转换成字节数组
/// AA AA AA AA 0A 00 68 00 06 03 04 54 21 28 22 E5 F3 16 BB BB BB BB 转换为字节数组
/// </summary>
/// <param name="hex">十六进制字符串</param>
/// <returns></returns>
public static byte[] ToBytesCRC(string hex)
{
string[] temp = hex.Split(' ');
byte[] b = new byte[temp.Length + ]; for (int i = ; i < temp.Length; i++)
{
b[i] = Convert.ToByte(temp[i], );
} return b;
}
/// <summary>
/// 将字节数据转换为十六进制字符串,中间用 “ ”分割 如:AA AA AA AA 0A 00 68 00 06 03 04 54 21 28 22 E5 F3 16 BB BB BB BB
/// </summary>
/// <param name="vars">要转换的字节数组</param>
/// <returns></returns>
public static String ToHex(byte[] vars)
{
return BitConverter.ToString(vars).Replace('-', ' ').Trim();
}
}
}

3、窗体主程序

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.Text.RegularExpressions; namespace modbustest
{
public partial class Form1 : Form
{ SerialPort sp = null;//申明一个串口类
bool isOpen = false;//打开串口标识位
bool isSetProperty = false;//属性设置标志位
bool isHex = false;//十六进制显示标志位 public Form1()
{
InitializeComponent();
}
//窗体初始化
private void Form1_Load(object sender, EventArgs e)
{
this.MaximumSize = this.Size;
this.MinimumSize = this.Size;
this.MaximizeBox = false;
//最大支持10个串口
for (int i = ; i < ; i++)
{
protBox.Items.Add("COM" + (i + ).ToString());
}
protBox.SelectedIndex = ; //初始化波特率
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add("");
BaudRate.Items.Add(""); BaudRate.SelectedIndex = ; //初始化停止位 stopBits.Items.Add("");
stopBits.Items.Add("");
stopBits.Items.Add("1.5");
stopBits.Items.Add("");
stopBits.SelectedIndex = ; //初始化数据位
dataBits.Items.Add("");
dataBits.Items.Add("");
dataBits.Items.Add("");
dataBits.Items.Add("");
dataBits.SelectedIndex = ; //初始化奇偶校验位
parity.Items.Add("无");
parity.Items.Add("Odd");
parity.Items.Add("Even");
parity.SelectedIndex = ; //默认显示hex
rbnHex.Checked = true;
} //检查哪些串口可用
private void checkCOM_Click(object sender, EventArgs e)
{
bool comExistence = false;//有可用的串口标志位
protBox.Items.Clear();//清除当前串口号中的所有串口名称
for (int i = ; i < ;i++ )
{
try{ SerialPort sp = new SerialPort("COM"+(i+).ToString());
sp.Open();
sp.Close();
protBox.Items.Add("COM" + (i + ).ToString());
comExistence = true; }
catch { continue;
}
}
if (comExistence)
{ protBox.SelectedIndex = ;
}
else { MessageBox.Show("没有找到可用串口","错误提示");
} }
//检查串口是否设置
private bool checkPortSetting()
{
if (protBox.Text.Trim() == "") return false; if (BaudRate.Text.Trim() == "") return false;
if (dataBits.Text.Trim() == "") return false;
if (parity.Text.Trim() == "") return false;
if (stopBits.Text.Trim() == "") return false; return true; } private bool checkSendData()
{
if (tbxSend.Text.Trim() == "") return false;
return true; } private void SetPortProperty()//设置串口的属性
{
sp=new SerialPort();
sp.PortName=protBox.Text.Trim();//设置串口名
sp.BaudRate=Convert.ToInt32(BaudRate.Text.Trim());//设置串口的波特率
float f=Convert.ToSingle(stopBits.Text.Trim());//设置停止位
if(f==){ sp.StopBits=StopBits.None;
}
else if(f==1.5){ sp.StopBits=StopBits.OnePointFive;
}
else if(f==){ sp.StopBits=StopBits.One;
}
else if(f==){ sp.StopBits=StopBits.Two;
}
else{
sp.StopBits=StopBits.One;
}
sp.DataBits=Convert.ToInt16(dataBits.Text.Trim());//设置数据位
string s=parity.Text.Trim();//设置奇偶校验位
if(s.CompareTo("None")==){
sp.Parity=Parity.None;
}
else if(s.CompareTo("Odd")==){ sp.Parity=Parity.Odd;
}
else if(s.CompareTo("Even")==){ sp.Parity=Parity.Even;
}
else{ sp.Parity=Parity.None;
}
sp.ReadTimeout=-;//设置超时读取时间
sp.RtsEnable=true;
//定义DataReceived事件,当串口收到数据后触发事件
sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
if(rbnHex.Checked){
isHex=true;
}else{
isHex=false;
}
}
private void btnSend_Click(object sender,EventArgs e)//发送串口数据
{
if(isOpen)//写串口数据
{
try{ sp.WriteLine(tbxSend.Text);
}
catch(Exception){ MessageBox.Show("发送数据时发生错误!","错误提示");
return;
}
}else{
MessageBox.Show("串口未打开!","错误提示");
return;
}
if(!checkSendData())//检测要发送的数据
{
MessageBox.Show("请输入要发送的数据!","错误提示");
return;
}
} //打开串口
private void openPort_Click(object sender, EventArgs e)
{
if(isOpen==false){
if(!checkPortSetting())//检测串口设置
{
MessageBox.Show("串口未设置!","错误提示");
return;
}
if(!isSetProperty)//串口未设置则设置串口
{
SetPortProperty();
isSetProperty=true;
}
try//打开串口
{
//sp = new SerialPort("COM1",19200,Parity.Even,8,StopBits.One);
sp.Open();
isOpen=true;
openPort.Text="关闭串口";//串口打开后则相关的串口设置按钮便不可再用 protBox.Enabled=false;
BaudRate.Enabled=false;
dataBits.Enabled=false;
parity.Enabled=false;
stopBits.Enabled=false;
rbnChar.Enabled=false;
rbnHex.Enabled=false;
}
catch(Exception){
//打开串口失败后,相应标志位取消
isSetProperty=false;
isOpen=false;
MessageBox.Show("串口无效或已被占用!","错误提示");
}
}else{
try//打开串口
{
sp.Close();
isOpen=false;
isSetProperty=false;
openPort.Text="打开串口";//关闭串口后,串口设置选项便可以继续使用
protBox.Enabled=true;
BaudRate.Enabled=true;
dataBits.Enabled=true;
parity.Enabled=true;
stopBits.Enabled=true;
rbnChar.Enabled=true;
rbnHex.Enabled=true;
}
catch(Exception){
lblStatus.Text="关闭串口时发生错误";
}
}
}
private void sp_DataReceived(object sender,SerialDataReceivedEventArgs e)
{
System.Threading.Thread.Sleep();//延时100ms等待接收完数据//this.Invoke就是跨线程访问ui的方法,也是本文的范例
this.Invoke((EventHandler)(delegate{ if(isHex==false){ tbxRec.Text+=sp.ReadLine();
}else{ Byte[]ReceivedData=new Byte[sp.BytesToRead+];//创建接收字节数组
sp.Read(ReceivedData,,ReceivedData.Length);//读取所接收到的数据
String RecvDataText=null; for( int i=;i<ReceivedData.Length-; i++){ RecvDataText+=(ReceivedData[i].ToString("X2")+" ");
}
tbxRec.Text+=RecvDataText;
}
sp.DiscardInBuffer();//丢弃接收缓冲区数据
}));
} private void button1_Click_1(object sender, EventArgs e)
{ if (isOpen)//写串口数据
{
try
{
byte[] send_data;
string str = this.tbxSend.Text;
string strc = str + " " + CRC.CRCCheck(this.tbxSend.Text);
//int iLen = 0;
send_data = ByteHexHelper.HexToByte(strc); // send_data = HexStringToByteArray(strc);
//iLen = send_data.GetLength(0);
//this.tbxRec = byteToHexStr(send_data);
// byte[] acc_data = new byte[] { 0x30, 0x31 };
//acc_data = send_data;
//String acc_str = System.Text.Encoding.Default.GetString(acc_data);
////ModBus comm = new ModBus();
this.tbxRec.Text = str + " "+ CRC.CRCCheck(str);
//String rec = str + " " + CRC.CRCCheck(acc_str);
//sp.WriteLine(strs);
sp.Write(send_data,,send_data.Length); //this.tbxRec.Text = sp.BaudRate + " " + sp.BytesToWrite + "kong"; }
catch (Exception)
{ MessageBox.Show("发送数据时发生错误!", "错误提示");
return;
}
}
else
{
MessageBox.Show("串口未打开!", "错误提示");
return;
}
if (!checkSendData())//检测要发送的数据
{
MessageBox.Show("请输入要发送的数据!", "错误提示");
return;
}
} private void button2_Click(object sender, EventArgs e)
{
byte[] acc_data;
String str2 = this.tbxRec.Text;
int iLen = ;
acc_data = System.Text.Encoding.ASCII.GetBytes(str2);
iLen = acc_data.GetLength(); byte[] send_data = new byte[] { 0x30, 0x31 };
send_data = acc_data;
String acc_str = System.Text.Encoding.ASCII.GetString(acc_data);
//ModBus comm = new ModBus(); this.tbxSend.Text = str2 + " " + CRC.CRCCheck(acc_str);
} private void button3_Click(object sender, EventArgs e)
{
tbxRec.Text = "";
tbxSend.Text = ""; } }
}

4、窗体

MODBUS协议相关代码(CRC验证 客户端程序)的更多相关文章

  1. 初识Modbus TCP/IP-------------C#编写Modbus TCP客户端程序(一)

    转自:http://blog.csdn.net/thebestleo/article/details/52269999 首先我要说明一下,本人新手一枚,本文仅为同样热爱学习的同学提供参考,有不 对的地 ...

  2. Socket编程之聊天程序 - 模拟Fins/ModBus协议通信过程

    设备控制软件编程涉及到的基本通信方式主要有TCP/IP与串口,用到的数据通信协议有Fins与ModBus. 更高级别的通信如.net中的Remoting与WCF在进行C/S架构软件开发时会采用. 本篇 ...

  3. c++下基于windows socket的单线程服务器客户端程序(基于TCP协议)

    今天自己编写了一个简单的c++服务器客户端程序,注释较详细,在此做个笔记. windows下socket编程的主要流程可概括如下:初始化ws2_32.dll动态库-->创建套接字-->绑定 ...

  4. c++下基于windows socket的服务器客户端程序(基于UDP协议)

    前天写了一个基于tcp协议的服务器客户端程序,今天写了一个基于UDP协议的,由于在上一篇使用TCP协议的服务器中注释已经较为详细,且许多api的调用是相同的,故不再另外注释. 使用UDP协议需要注意几 ...

  5. 初识Modbus TCP/IP-------------C#编写Modbus TCP客户端程序(二)

    由于感觉上一次写的篇幅过长,所以新开一贴,继续介绍Modbus TCP/IP的初步认识, 书接上回 3).03(0x03)功能码--------读保持寄存器 请求与响应格式 这是一个请求读寄存器108 ...

  6. Modbus协议 CRC 校验码

    CRC(循环冗余校验)在线计算 http://www.ip33.com/crc.html 里面的8005的多项式值,但网上看到的算法都是用A001来异或的 ---------------------- ...

  7. Gradle Android客户端程序打包(基于gradle 1.12版本验证通过)

    一.前言 android客户端开发进入尾声,负责SEO同事突然发给我一个涉及45个发布渠道的噩耗,之前只发布自有渠道的工作方式(手动修改参数打包)已经不满足需求,所以引入最近比较流行的gradle打包 ...

  8. 在Android程序中使用Modbus协议时报 java.net.SocketException: recvfrom failed: ECONNRESET解决办法

    最近在开发基本Modbus协议的Android端PLC控制程序,C#版程序没有任何问题,移到JAVA下出现各种问题,其中比较苦恼的是java.net.SocketException: recvfrom ...

  9. Gradle Android客户端程序打包(基于gradle 2.10版本验证通过)

    一.前言 目前正在准备从eclipse开发环境向AndroidStudio迁移,提前过去探探路,不出所料,原来gradle脚本果然报错,无法运行,想想索性把本地的gradle一起升级到最新版本,毕竟1 ...

随机推荐

  1. Netty之Reactor模式

    无论是C++还是Java编写的网络框架,大多数都是基于Reactor模式进行设计和开发,Reactor模式基于事件驱动,特别适合处理海量的I/O事件. 1. 单线程模型 Reactor单线程模型,指的 ...

  2. StringUtils详解

    public static void StringUtil(){ //null 和 ""操作~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //判断是否 ...

  3. [C++] CONST 2

    The C++ 'const' Declaration: Why & How The 'const' system is one of the really messy features of ...

  4. win7下cygwin命令行颜色和中文乱码解决

    在cygwin虚拟机中可以使用ls命令等Linux下的一些命令,如果在win下将环境变量path中添加x:\cygwin\bin(x:指的是cygwin所在的盘符),可以在cmd环境中使用这些命令,而 ...

  5. monkeyrunner小结

    上次说到已经配好了MonkeyRunner的运行环境,现在讲解怎么进行简单的MonkeyRunner测试.这个拖了很久才有时间和心情总结一下.真是计划赶不上变化啊. 就不说废话了.http://dev ...

  6. 更改文本的编码jsp.xml.java

    JSP改为UTF-8编码 更改xml workspace resource

  7. javascrip总结42:属性操作案例: 点击盒子,改变盒子的位置和背景颜色

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8& ...

  8. WordPaster2项目变化

    1.1.1. jsp 1.引入json2.min.js 2.控件名称改为WordPasterManager 3.文件保存逻辑更新,直接使用控件生成的文件名称 1.1.2. asp.net 1.引入js ...

  9. 使用 JAVA 中的动态代理实现数据库连接池

    数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈.我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共 ...

  10. Strict Weak Ordering

    Description A Strict Weak Ordering is a Binary Predicate that compares two objects, returning true i ...