基於Modbus Rtu

一、開始準備:

1、VisualStudio2015或以上版本;

2、Modbus測試軟件ModbusSlave;

3、虛擬串口工具VSPD;

二、Modbus 協議介紹:

控制器能設置為兩種傳輸模式(ASCII或RTU)中的任何一種在標準的Modbus網絡通信。用戶選擇想要的模式,包括串口通信參數(波特率、校驗方式等),在配置每個控制器的時候,在一個Modbus網絡上的所有設備都必須選擇相同的傳輸模式和串口參數。

1、RTU發送通信報文格式如下:

地址 功能代碼 數據數量 數據1 ... 數據n CRC低字節 CRC高字節

2、Ascii發送通信報文格式如下:

: 地址 功能代碼 數據數量 數據1 ... 數據n LRC高字節 LRC低字節 回車 換行

3、Modbus支持的功能碼:

  • 0x01 讀取線圈的操作,
  • 0x02 讀取離散的操作,
  • 0x03 讀取寄存器的值,
  • 0x05 寫一個線圈操作,
  • 0x06 寫一個寄存器值,
  • 0x0F 批量寫線圈操作,
  • 0x10 批量寫寄存器值,

4、Modbus Rtu接收報文格式:

起始位 設備地址 功能代碼 數據 CRC校驗 結束符

5、4、Modbus Ascii接收報文格式:

起始位 設備地址 功能代碼 數據 LRC校驗 結束符

三、上位機軟件編寫:

1、軟件界面添加如下圖控件:

基於Modbus Rtu/Ascii上位機編寫

2、代碼實現:

<code>using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Modbus通訊協議
{
public partial class Form1 : Form
{
SerialPort sp = new SerialPort();
string str_Received = "";
string words2 = "";
byte[] byte_Received = new byte[0];//接收數據的字節數組
int num_Byte = 0;///循環接收數據的自加1
bool receive_Flag = true;
Socket socket_Client;
public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
cmb_Com.SelectedIndex = 2;
CheckForIllegalCrossThreadCalls = false;
}
public bool OpenSerialPort()
{
if (!sp.IsOpen)
{
sp.PortName = cmb_Com.Text;
sp.Open();
return true;
}
else

{
sp.Close();
return false;
}
}
/// <summary>
/// LRC校驗
/// /<summary>
/// <param>
/// <param>
/// <param>
/// <returns>
public static byte[] Lrc(byte[] buffer, int start = 0, int len = 0)
{
if (buffer == null || buffer.Length == 0) return null;
if (start < 0) return null;
if (len == 0) len = buffer.Length - start;
int length = start + len;
if (length > buffer.Length) return null;
byte lrc = 0;// Initial value
for (int i = start; i < len; i++)
{
lrc += buffer[i];
}
lrc = (byte)((lrc ^ 0xFF) + 1);
return new byte[] { lrc };
}
/// <summary>
/// CRC校驗
/// /<summary>
/// <returns>
public string CRC16_Modbus(string text)
{
//1.預置CRC寄存器為0xFFFF
UInt16 CRC = 0xFFFF;
//2.分割文本框輸入的16進制待校驗數據
string[] arr = text.Split(' ');
//3.將字符串數組轉換為byte數組
byte[] Barr = new byte[arr.Length];
for (int i = 0; i < arr.Length; i++)
{
Barr[i] = Convert.ToByte(arr[i], 16);
}

for (int j = 0; j < Barr.Length; j++)
{
CRC ^= Barr[j];

for (int i = 0; i < 8; i++)
{
if ((CRC & 1) == 0)
{
CRC >>= 1;
}
else
{
CRC >>= 1;
CRC ^= 0xA001;
}
}
}
UInt16 Res = 0xFFFF;
Res &= CRC;
Res <<= 8;
CRC >>= 8;
Res |= CRC;
return Convert.ToString(Res, 16).ToUpper().PadLeft(4, '0');
}

private void btn_Send_Click(object sender, EventArgs e)
{
if(sp.IsOpen)
{
txtBox_Recive.Text = "";
str_Received = "";
int m1 = 0;
if (rb_Rtu.Checked)
{
string crc = CRC16_Modbus(txtBox_Send.Text);
string CRCH = crc.Substring(0, 2);/////取CRC檢驗碼高字節
string CRCL = crc.Substring(2, 2);/////取CRC檢驗碼低字節
int num1 = txtBox_Send.Text.Replace(" ", "").Length;
byte[] byte_Send = new byte[num1 / 2 + 2];
string[] str = new string[num1 / 2];
for (int i = 0; i < num1; i = i + 2)
{
str[m1] = (txtBox_Send.Text.Replace(" ", "")).Substring(i, 2);//除去空格再放進str數組
byte_Send[m1] = Convert.ToByte(str[m1], 16);
m1++;
}
byte_Send[byte_Send.Length - 2] = Convert.ToByte(CRCH, 16);
byte_Send[byte_Send.Length - 1] = Convert.ToByte(CRCL, 16);
try
{
sp.DiscardOutBuffer();
sp.Write(byte_Send, 0, byte_Send.Length);

}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
else
{
int Mylenth = txtBox_Send.Text.Replace(" ", "").Length;
byte[] byte_Send = new byte[Mylenth + 5];
string[] arr = txtBox_Send.Text.Split(' ');
string[] str = new string[Mylenth];
char[] chr = new char[Mylenth];
//3.將字符串數組轉換為byte數組
byte[] byte_Send_tmp = new byte[arr.Length];
for (int i = 0; i < arr.Length; i++)
{
byte_Send_tmp[i] = Convert.ToByte(arr[i], 16);
}
byte[] byte_Check = Lrc(byte_Send_tmp);
string Check = Convert.ToString(byte_Check[0], 16).ToUpper();
for (int i = 0; i < Mylenth; i++)
{
str[i] = (txtBox_Send.Text.Replace(" ", "")).Substring(i, 1);//除去空格再放進str數組
chr[i] = Convert.ToChar(str[i]);
short ich = (short)chr[i]; ////轉換成ASCII碼
byte_Send[i + 1] = Convert.ToByte(ich.ToString());
}
byte_Send[0] = Convert.ToByte(58);/////最低各字節數組放ASCII碼自動以十進制加分號到接收那邊會轉換成16進制
byte_Send[Mylenth + 1] = Convert.ToByte(Check[0]);///檢驗碼高位
byte_Send[Mylenth + 2] = Convert.ToByte(Check[1]);///檢驗碼低位
byte_Send[Mylenth + 3] = Convert.ToByte(13);/////回車
byte_Send[Mylenth + 4] = Convert.ToByte(10);////換行
sp.DiscardOutBuffer();
sp.Write(byte_Send, 0, byte_Send.Length);/////////現在可以發送ASCII數據了
}
}
else
{
MessageBox.Show("串口未打開!");
}
}


private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] byte_Received = new byte[2048];//定義一個接收的數組寧可大不可小
while (sp.BytesToRead > 0) //如果接收緩衝區還有數據就一直接收直到沒有數據
{
int a = sp.ReadByte();//讀取當前接收緩衝區的字節數數量
byte_Received[num_Byte] = Convert.ToByte(a);/////讀取過來到字節數組中
string ss = Convert.ToString(byte_Received[num_Byte], 16).ToUpper();///轉換成16進制並且以大寫形式給到SS中
str_Received = str_Received + ss.PadLeft(2, '0') + " "; ///每個字節保持兩個字符
num_Byte++;/////接著接收下一個字節
}
//str_Received = sp.ReadLine();
}
private void timer1_Tick(object sender, EventArgs e)
{
txtBox_Recive.Text = str_Received;
num_Byte = 0;
}

private void btn_Conn_Com_Click(object sender, EventArgs e)
{
bool flag_Port = OpenSerialPort();
if (flag_Port)
{
toolStripLabel1.Text = "Port is Open";
toolStrip1.ForeColor = Color.Green;
btn_Conn_Com.Text = "關閉串口";
sp.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);
}
else
{
sp.Close();
toolStripLabel1.Text = "Port is Close";
toolStrip1.ForeColor = Color.Red;
btn_Conn_Com.Text = "打開串口";
}
timer1.Interval = 200;
timer1.Enabled = true;
}

}
}/<code>

2、串口測試軟件Modbus Slave設置如下:


基於Modbus Rtu/Ascii上位機編寫

基於Modbus Rtu/Ascii上位機編寫

3、開始測試:

基於Modbus Rtu/Ascii上位機編寫

基於Modbus Rtu/Ascii上位機編寫


分享到:


相關文章: