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验证 客户端程序)-LMLPHP

05-11 16:23
查看更多