一、开始准备:
1、VisualStudio2015或以上版本;
2、Modbus测试软件ModbusSlave;
3、虚拟串口工具VSPD;
4、以上软件大家皆可百度自行搜索;
二、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、软件界面添加如下图控件:
2、代码实现:
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 name="buffer"></param>
/// <param name="start"></param>
/// <param name="len"></param>
/// <returns></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></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;
}
}
}
2、串口测试软件Modbus Slave设置如下:
3、开始测试:
本文暂时没有评论,来添加一个吧(●'◡'●)