说明:本文主要研究的是使用C# WinForm开发的串口调试软件(其中包含Modbus协议相关操作)。Modbus相关协议可以查阅百度文库等,可参考: 《http://wenku.baidu.com/link?url=J-QZeQVLfvfZh7_lh8Qf0MdwANZuVjEoTqox6zJYrSnKyfgES2RTb_bjC5ZTn8-xgsuUAyiELRYVA3-3FBkBGywWhQ9YGoavJOzwB0IxTyK 》。

  (1)先测试串口设置,发送和接收数据。

  (2)发送modbus的命令帧数据和使用DataReceived接收缓冲区的数据。

一、简单的串口调试工具

  下图为串口调试工具的界面,主要包括串口基本设置,功能操作,状态框以及发送接收框。由于这里只是简单的初始化数据,所以当需要发送数据的时候需要点击“串口检测”,来测试当前可用的串口,然后输入需要发送的数据,最后点击“发送数据”(由于测试需要,让发送什么数据就返回什么数据,这里的底层硬件做了短接处理,使用短接貌P30-P31,具体操作可以自行百度)

  基于Modbus的C#串口调试开发-冯金伟博客园

  

  1.1 发送数据操作

    (1)点击 串口检测

    (2)输入发送数据

    (3)点击 发送数据

    下面开始时具体代码:

    #1 软件打开时候初始化操作:Form1_Load(),主要初始化操作串口设置的下拉列表。

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

 1 private void Form1_Load(object sender, EventArgs e)
 2         {
 3             
 4             //设置窗口大小固定
 5             this.MaximumSize = this.Size;
 6             this.MinimumSize = this.Size;
 7 
 8             //1、设置串口下拉列表
 9             for ( int i = 0; i < 10; i++ ) 
10             {
11                 cbxCOMPort.Items.Add("COM" + (i + 1).ToString());
12             }
13             cbxCOMPort.SelectedIndex = 2;//默认选项
14 
15 
16             //2、设置常用波特率
17             int bt = 300;
18             for (int i = 0; i < 8; i++)
19             {
20                 cbxBaudRate.Items.Add(bt.ToString());
21                 bt *= 2;
22             }
23 
24             cbxBaudRate.Items.Add("38400");
25             cbxBaudRate.Items.Add("43000");
26             cbxBaudRate.Items.Add("56000"); 
27             cbxBaudRate.Items.Add("57600");
28             cbxBaudRate.Items.Add("115200");
29             cbxBaudRate.SelectedIndex = 5;
30 
31 
32             //3、列出停止位
33             cbxStopBits.Items.Add("0");
34             cbxStopBits.Items.Add("1");
35             cbxStopBits.Items.Add("1.5");
36             cbxStopBits.Items.Add("2");
37             cbxStopBits.SelectedIndex = 1;
38 
39             //4、设置奇偶检验
40             cbxParity.Items.Add("");
41             cbxParity.Items.Add("奇校验");
42             cbxParity.Items.Add("偶校验");
43             cbxParity.SelectedIndex = 0;
44 
45             //5、设置数据位
46             cbxDataBits.Items.Add("8");
47             cbxDataBits.Items.Add("7");
48             cbxDataBits.Items.Add("6");
49             cbxDataBits.Items.Add("5");
50             cbxDataBits.SelectedIndex = 0;
51 
52 
53         }

private void Form1_Load(object sender, EventArgs e)

    

    #2 检查串口基本设置的参数:CheckPortSetting(),

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

 1 /// <summary>
 2         /// 【检测端口设置】
 3         /// </summary>
 4         /// <returns></returns>
 5         private bool CheckPortSetting()
 6         {
 7             //检测端口设置
 8             if (cbxCOMPort.Text.Trim() == "" || cbxBaudRate.Text.Trim() == "" || cbxStopBits.Text.Trim() == "" || cbxParity.Text.Trim() == "" || cbxDataBits.Text.Trim() == "")
 9                 return false;
10             return true;
11         }

private bool CheckPortSetting()

    #3 设置串口属性,创建SerialPort对象  

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

 1 /// <summary>
 2         /// 【设置串口属性】
 3         /// </summary>
 4         private void SetPortProperty()
 5         {
 6             //1、设置串口的属性
 7             sp = new SerialPort();
 8 
 9             sp.ReceivedBytesThreshold = 1;//获取DataReceived事件发生前内部缓存区字节数
10             sp.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);//设置委托
11 
12             sp.PortName = cbxCOMPort.Text.Trim();
13 
14             //2、设置波特率
15             sp.BaudRate =  Convert.ToInt32( cbxBaudRate.Text.Trim());
16 
17             //3、设置停止位
18             float f = Convert.ToSingle( cbxStopBits.Text.Trim());
19 
20             if (f == 0) 
21             {
22                 sp.StopBits = StopBits.None;//表示不使用停止位
23             }
24             else if (f == 1.5)
25             {
26                 sp.StopBits = StopBits.OnePointFive;//使用1.5个停止位
27             }
28             else if (f == 2)
29             {
30                 sp.StopBits = StopBits.Two;//表示使用两个停止位
31             }
32             else
33             {
34                 sp.StopBits = StopBits.One;//默认使用一个停止位
35             }
36 
37             //4、设置数据位
38             sp.DataBits = Convert.ToInt16(cbxDataBits.Text.Trim());
39 
40             //5、设置奇偶校验位
41             string s = cbxParity.Text.Trim();
42             if (s.CompareTo("") == 0) 
43             {
44                 sp.Parity = Parity.None;//不发生奇偶校验检查
45             }
46             else if (s.CompareTo("奇校验") == 0)
47             {
48                 sp.Parity = Parity.Odd;//设置奇校验
49             }
50             else if (s.CompareTo("偶校验") == 0)
51             {
52                 sp.Parity = Parity.Even;//设置偶检验
53             }
54             else
55             {
56                 sp.Parity = Parity.None;
57             }
58 
59             //6、设置超时读取时间
60             sp.ReadTimeout = -1;
61 
62             //7、打开串口
63             try
64             {
65                 sp.Open();
66                 isOpen = true;
67             }
68             catch(Exception)
69             {
70                 lblStatus.Text = "打开串口错误!";
71             }
72 
73         }

private void SetPortProperty()

     

    #4 “发送数据”按钮点击事件:btnSend_Click(), 在发送数据需要进行,#2,#3验证,然后开始通过串口对象写入数据

 1     /// <summary>
 2         /// 【发送数据】
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void btnSend_Click(object sender, EventArgs e)
 7         {
 8             //发送串口数据
 9 
10             //1、检查串口设置
11             if (!CheckPortSetting()) 
12             {
13                 MessageBox.Show("串口未设置!", "错误提示");
14                 return;
15             }
16 
17             //2、检查发送数据是否为空
18             if(tbxSendData.Text.Trim() == ""){
19                  MessageBox.Show("发送数据不能为空");
20                  return;
21             }
22 
23             //3、设置
24             if (!isSetProperty) 
25             {
26                 SetPortProperty();
27                 isSetProperty = true;
28             }
29 
30             //4、写串口数据
31             if (isOpen)
32             {
33                 //写出口数据
34                 try
35                 {
36                     sp.Write(tbxSendData.Text);
37                     tbxStatus.Text = "发送成功!";
38                     
39 
40                     tbxRecvData.Text += sp.ReadLine();//读取发送的数据                    
41 
42                 }
43                 catch
44                 {
45                     tbxStatus.Text = "发送数据错误";
46                 }
47             }
48             else
49             {
50                 MessageBox.Show("串口未打开", "错误提示");
51             }
52 
53             
54         }                        

  1.2 接受数据操作

    接收数据和发送数据有点类似   

 1 /// <summary>
 2         /// 【读取数据】
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void btnRecv_Click(object sender, EventArgs e)
 7         {
 8             if(isOpen) 
 9             { 
10                 try  
11                 {
12                     //读取串口数据
13   
14                     tbxRecvData.Text += sp.ReadLine()+"
"15                 }  
16                 catch(Exception) 
17                 {  
18                     lblStatus.Text = "读取串口时发生错误!"19                     return20                 } 
21             } 
22             else 
23             {  
24               MessageBox.Show("串口未打开!""错误提示"); 
25               return26                 
27             } 
28         }

  最后附上该窗体的后台代码:Form1.cs 

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.IO.Ports;
  7 using System.Linq;
  8 using System.Text;
  9 using System.Threading.Tasks;
 10 using System.Windows.Forms;
 11 
 12 
 13 namespace 串口调试
 14 {
 15     public partial class Form1 : Form
 16     {
 17         SerialPort sp = null;
 18 
 19         bool isOpen = false;//是否打开
 20 
 21         bool isSetProperty = false;//是否通过串口设置
 22 
 23 
 24         public Form1()
 25         {
 26             InitializeComponent();
 27         }
 28 
 29         private void Form1_Load(object sender, EventArgs e)
 30         {
 31             
 32             //设置窗口大小固定
 33             this.MaximumSize = this.Size;
 34             this.MinimumSize = this.Size;
 35 
 36             //1、设置串口下拉列表
 37             for ( int i = 0; i < 10; i++ ) 
 38             {
 39                 cbxCOMPort.Items.Add("COM" + (i + 1).ToString());
 40             }
 41             cbxCOMPort.SelectedIndex = 2;//默认选项
 42 
 43 
 44             //2、设置常用波特率
 45             int bt = 300;
 46             for (int i = 0; i < 8; i++)
 47             {
 48                 cbxBaudRate.Items.Add(bt.ToString());
 49                 bt *= 2;
 50             }
 51 
 52             cbxBaudRate.Items.Add("38400");
 53             cbxBaudRate.Items.Add("43000");
 54             cbxBaudRate.Items.Add("56000"); 
 55             cbxBaudRate.Items.Add("57600");
 56             cbxBaudRate.Items.Add("115200");
 57             cbxBaudRate.SelectedIndex = 5;
 58 
 59 
 60             //3、列出停止位
 61             cbxStopBits.Items.Add("0");
 62             cbxStopBits.Items.Add("1");
 63             cbxStopBits.Items.Add("1.5");
 64             cbxStopBits.Items.Add("2");
 65             cbxStopBits.SelectedIndex = 1;
 66 
 67             //4、设置奇偶检验
 68             cbxParity.Items.Add("");
 69             cbxParity.Items.Add("奇校验");
 70             cbxParity.Items.Add("偶校验");
 71             cbxParity.SelectedIndex = 0;
 72 
 73             //5、设置数据位
 74             cbxDataBits.Items.Add("8");
 75             cbxDataBits.Items.Add("7");
 76             cbxDataBits.Items.Add("6");
 77             cbxDataBits.Items.Add("5");
 78             cbxDataBits.SelectedIndex = 0;
 79 
 80 
 81         }
 82 
 83 
 84         /// <summary>
 85         /// 【串口检测按钮】
 86         /// </summary>
 87         /// <param name="sender"></param>
 88         /// <param name="e"></param>
 89         private void btnCheckCOM_Click(object sender, EventArgs e)
 90         {
 91             //1、检测哪些端口可用
 92             cbxCOMPort.Items.Clear();
 93             cbxCOMPort.Text = "";
 94             
 95             lblStatus.Text = "执行中...";
 96             string str = "";
 97             for (int i = 0; i < 10; i++)
 98             {
 99                 try
100                 {
101             ////把所有可能的串口都测试一遍,打开关闭操作,只有可用的串口才可会放到下拉列表中
102                     SerialPort sp = new SerialPort("COM" + (i + 1).ToString());
103                     sp.Open();
104                     sp.Close();
105                     cbxCOMPort.Items.Add("COM" + (i + 1).ToString());
106                 }
107                 catch
108                 {
109                     str += "COM" + (i + 1).ToString() + "";
110                     continue;
111                 }
112             }
113         
114         //如果当前下拉列表有可用的串口,则默认选择第一个
115             if(cbxCOMPort.Items.Count > 0)
116                 cbxCOMPort.SelectedIndex = 0;
117             lblStatus.Text = "完成";
118             tbxStatus.Text = str;
119         }
120 
121 
122         /// <summary>
123         /// 【检测端口设置】
124         /// </summary>
125         /// <returns></returns>
126         private bool CheckPortSetting()
127         {
128             //检测端口设置
129             if (cbxCOMPort.Text.Trim() == "" || cbxBaudRate.Text.Trim() == "" || cbxStopBits.Text.Trim() == "" || cbxParity.Text.Trim() == "" || cbxDataBits.Text.Trim() == "")
130                 return false;
131             return true;
132         }
133 
134         /// <summary>
135         /// 【检测发送数据是否为空】
136         /// </summary>
137         /// <returns></returns>
138         private bool CheckSendData()
139         {
140             if (tbxSendData.Text.Trim() == "")
141                 return false;
142             return true;
143         }
144 
145 
146         /// <summary>
147         /// 【设置串口属性】
148         /// </summary>
149         private void SetPortProperty()
150         {
151             //1、设置串口的属性
152             sp = new SerialPort();
153 
154             sp.PortName = cbxCOMPort.Text.Trim();
155 
156             //2、设置波特率
157             sp.BaudRate =  Convert.ToInt32( cbxBaudRate.Text.Trim());
158 
159             //3、设置停止位
160             float f = Convert.ToSingle( cbxStopBits.Text.Trim());
161 
162             if (f == 0) 
163             {
164                 sp.StopBits = StopBits.None;//表示不使用停止位
165             }
166             else if (f == 1.5)
167             {
168                 sp.StopBits = StopBits.OnePointFive;//使用1.5个停止位
169             }
170             else if (f == 2)
171             {
172                 sp.StopBits = StopBits.Two;//表示使用两个停止位
173             }
174             else
175             {
176                 sp.StopBits = StopBits.One;//默认使用一个停止位
177             }
178 
179             //4、设置数据位
180             sp.DataBits = Convert.ToInt16(cbxDataBits.Text.Trim());
181 
182             //5、设置奇偶校验位
183             string s = cbxParity.Text.Trim();
184             if (s.CompareTo("") == 0) 
185             {
186                 sp.Parity = Parity.None;//不发生奇偶校验检查
187             }
188             else if (s.CompareTo("奇校验") == 0)
189             {
190                 sp.Parity = Parity.Odd;//设置奇校验
191             }
192             else if (s.CompareTo("偶校验") == 0)
193             {
194                 sp.Parity = Parity.Even;//设置偶检验
195             }
196             else
197             {
198                 sp.Parity = Parity.None;
199             }
200 
201             //6、设置超时读取时间
202             sp.ReadTimeout = -1;
203 
204             //7、打开串口
205             try
206             {
207                 sp.Open();
208                 isOpen = true;
209             }
210             catch(Exception)
211             {
212                 lblStatus.Text = "打开串口错误!";
213             }
214 
215         }
216 
217        
218 
219         /// <summary>
220         /// 【发送数据】
221         /// </summary>
222         /// <param name="sender"></param>
223         /// <param name="e"></param>
224         private void btnSend_Click(object sender, EventArgs e)
225         {
226             //发送串口数据
227 
228             //1、检查串口设置
229             if (!CheckPortSetting()) 
230             {
231                 MessageBox.Show("串口未设置!", "错误提示");
232                 return;
233             }
234 
235             2、检查发送数据是否为空
236             if (!CheckSendData())
237             {
238                 MessageBox.Show("请输入要发送的数据!", "错误提示");
239                 return;
240             }
241 
242             //3、设置
243             if (!isSetProperty) 
244             {
245                 SetPortProperty();
246                 isSetProperty = true;
247             }
248 
249             //4、写串口数据
250             if (isOpen)
251             {
252                 //写出口数据
253                 try
254                 {
255                     sp.Write(tbxSendData.Text);
256                     tbxStatus.Text = "发送成功!";
257 
258                     tbxRecvData.Text += sp.ReadLine()+"
";
259                 }
260                 catch
261                 {
262                     tbxStatus.Text = "发送数据错误";
263                 }
264             }
265             else
266             {
267                 MessageBox.Show("串口未打开", "错误提示");
268             }
269 
270             
271         }
272 
273         /// <summary>
274         /// 【读取数据】
275         /// </summary>
276         /// <param name="sender"></param>
277         /// <param name="e"></param>
278         private void btnRecv_Click(object sender, EventArgs e)
279         {
280             if(isOpen) 
281             { 
282                 try  
283                 {
284                     //读取串口数据
285   
286                     tbxRecvData.Text += sp.ReadLine()+"
"287                 }  
288                 catch(Exception) 
289                 {  
290                     lblStatus.Text = "读取串口时发生错误!"291                     return292                 } 
293             } 
294             else 
295             {  
296               MessageBox.Show("串口未打开!""错误提示"); 
297               return298                 
299             } 
300         }
301  
302     }
303 }

Form1.cs

二、基于Modbus协议的数据发送和接收

   这里主要是在前面的基础上,把发送和接收的数据进行格式化(符合Modbus的数据帧格式),如下左图所示,右图为加入Modbus协议的窗体,主要添加了命令帧的输入框组:

   基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

  

  2.1 获取字节的的高位和低位:WORD_LO()、WORD_HI()   

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

 1 /// <summary>
 2         /// 【获取低位字节】
 3         /// </summary>
 4         /// <param name="crcCLo"></param>
 5         /// <returns></returns>
 6         public static byte WORD_LO(ushort crcCLo)
 7         {
 8             crcCLo = (ushort)(crcCLo & 0X00FF);
 9             return (byte)crcCLo;
10         }
11 
12         /// <summary>
13         /// 【获取高位字节】
14         /// </summary>
15         /// <param name="crcHI"></param>
16         /// <returns></returns>
17         public static byte WORD_HI(ushort crcHI)
18         {
19             crcHI = (ushort)(crcHI >> 8 & 0X00FF);
20             return (byte)crcHI;
21         }

WORD_LO() WORD_HI()

  

  2.2 CRC高位表和低位表 

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

 1  #region CRC高位表 byte[] _auchCRCHi
 2         private static readonly byte[] _auchCRCHi = new byte[]//crc高位表
 3         {
 4             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
 5             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
 6             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
 7             0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
 8             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
 9             0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 
10             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
11             0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
12             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
13             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
14             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
15             0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
16             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
17             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
18             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
19             0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
20             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
21             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
22             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
23             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
24             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
25             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
26             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
27             0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
28             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
29             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
30         };
31         #endregion
32 
33         #region CRC低位表 byte[] _auchCRCLo
34         private static readonly byte[] _auchCRCLo = new byte[]//crc低位表
35         {
36             0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 
37             0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 
38             0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 
39             0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 
40             0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 
41             0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 
42             0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 
43             0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 
44             0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 
45             0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 
46             0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 
47             0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 
48             0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 
49             0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 
50             0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 
51             0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 
52             0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 
53             0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 
54             0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 
55             0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 
56             0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 
57             0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 
58             0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 
59             0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 
60             0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 
61             0x43, 0x83, 0x41, 0x81, 0x80, 0x40
62         };
63         #endregion

CRC高低位表

  2.3 CRC校验方法:CRC16()

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

 1 /// <summary>
 2         /// 【CRC校验】
 3         /// </summary>
 4         /// <param name="buffer">命令帧合适前6字节</param>
 5         /// <param name="Sset">开始位</param>
 6         /// <param name="Eset">结束位</param>
 7         /// <returns>CRC校验码</returns>
 8         public static ushort CRC16(Byte[] buffer, int Sset, int Eset)
 9         {
10             byte crcHi = 0xff;  // 高位初始化
11 
12             byte crcLo = 0xff;  // 低位初始化
13 
14             for (int i = Sset; i <= Eset; i++)
15             {
16                 int crcIndex = crcHi ^ buffer[i]; //查找crc表值
17 
18                 crcHi = (byte)(crcLo ^ _auchCRCHi[crcIndex]);
19                 crcLo = _auchCRCLo[crcIndex];
20             }
21 
22             return (ushort)(crcHi << 8 | crcLo);
23         }

public static ushort CRC16(Byte[] buffer, int Sset, int Eset)

   

  2.4 获取数据帧,把需要发送的数据格式化成Modbus协议数据帧

   

 1 /// <summary>
 2         /// 【获取读数据命令,返回命令帧】
 3         /// </summary>
 4         /// <param name="mdaddr">地址码</param>
 5         /// <param name="R_CMD">功能码</param>
 6         /// <param name="min_reg">寄存器地址</param>
 7         /// <param name="data_len">寄存器个数</param>
 8         /// <param name="R_CMD_LEN">命令长度</param>
 9         /// <returns></returns>
10         public byte[] GetReadFrame(byte mdaddr, byte R_CMD, ushort min_reg, ushort data_len, int R_CMD_LEN)
11         {
12             //主机命令帧格式
13             //  字节    功能描述            例子
14             //
15             //  1         地址码             0x01
16             //  2         功能码             0x03
17             //  3         寄存器地址高       0x00
18             //  4         寄存器地址低       0x00
19             //  5         寄存器个数高       0x00
20             //  6         寄存器个数低       0x02
21             //  7         CRC检验码低        0xC4
22             //  8         CRC校验码高        0x0B
23 
24             ushort crc;
25             byte[] message = new byte[8];
26 
27             //设置模块号
28             message[0] = mdaddr;
29             //设置命令字
30             message[1] = R_CMD;
31 
32             //设置开始寄存器
33             message[2] = WORD_HI(min_reg);
34             message[3] = WORD_LO(min_reg);
35 
36             //设置数据长度
37             message[4] = WORD_HI(data_len);
38             message[5] = WORD_LO(data_len);
39 
40             //设置 CRC
41             crc = CRC16(message, 0, R_CMD_LEN - 3);
42 
43             message[6] = WORD_HI(crc);//CRC校验码高位
44             message[7] = WORD_LO(crc);//CRC校验码低位
45 
46 
47             return message;
48         }

   2.6 对于DataReceived的使用

    #1 设置委托和方法

       1 private delegate void myDelegate(byte[] readBuffer);     

1 /// <summary>
2         /// 【显示接收返回的数据】
3         /// </summary>
4         /// <param name="resbuffer"></param>
5         public void ShowRst(byte[] resbuffer) 
6         {
7             MyModbus modbus = new MyModbus();
8             tbxRecvData.Text += "Recv:" + modbus.SetText(resbuffer) + "
";
9         }

     #2 设置属性:ReceivedBytesThreshold = 1

1  //1、设置串口的属性
2             sp = new SerialPort();
3 
4             sp.ReceivedBytesThreshold = 1;//获取DataReceived事件发生前内部缓存区字节数
5             sp.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);//设置委托

     #3 点击“发送数据”按钮的事件如下:  

 1         /// <summary>
 2         /// 【发送数据】
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void btnSend_Click(object sender, EventArgs e)
 7         {
 8             //发送串口数据
 9 
10             //1、检查串口设置
11             if (!CheckPortSetting()) 
12             {
13                 MessageBox.Show("串口未设置!", "错误提示");
14                 return;
15             }
16 
17             //2、检查发送数据是否为空
18             //if (!CheckSendData())
19             //{
20             //    MessageBox.Show("请输入要发送的数据!", "错误提示");
21             //    return;
22             //}
23 
24             //3、设置
25             if (!isSetProperty) 
26             {
27                 SetPortProperty();
28                 isSetProperty = true;
29             }
30 
31             //4、写串口数据
32             if (isOpen)
33             {
34                 //写出口数据
35                 try
36                 {
37                     //sp.Write(tbxSendData.Text);
38                     tbxStatus.Text = "发送成功!";
39                     //tbxSendData.Text += tbxAddress.Text;
40                     
41                 
42                     byte address = Convert.ToByte( tbxAddress.Text.Trim(), 16);//地址码
43                     byte cmd = Convert.ToByte(tbxCmd.Text.Trim(),16);//命令帧
44                     byte regAddr = Convert.ToByte(tbxRegAddr.Text.Trim(), 16);//寄存器地址
45                     byte regNum = Convert.ToByte(tbxRegNum.Text.Trim(), 16);//寄存器数量
46 
47                     
48                     //Modbus相关处理对象
49                     MyModbus modbus = new MyModbus();
50                     byte[] text = modbus.GetReadFrame(address, cmd, regAddr, regNum, 8);
51 
52                     sp.Write(text, 0, 8);
53                     tbxRecvData.Text += "Send:" + BitConverter.ToString(text)+ "
";      
54 
55                 }
56                 catch
57                 {
58                     tbxStatus.Text = "发送数据错误";
59                 }
60             }
61             else
62             {
63                 MessageBox.Show("串口未打开", "错误提示");
64             }
65 
66             
67         }

    

  2.7 附加代码

    #1 这里的MyModbus主要为Modbus相关一些操作,包括把发送数据封装成Modbus数据帧等。   

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

  1 using System;
  2 using System.Collections.Generic;
  3 using System.IO.Ports;
  4 using System.Linq;
  5 using System.Text;
  6 using System.Threading.Tasks;
  7 
  8 namespace 串口调试
  9 {
 10     class MyModbus
 11     {
 12        
 13         #region CRC高位表 byte[] _auchCRCHi
 14         private static readonly byte[] _auchCRCHi = new byte[]//crc高位表
 15         {
 16             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
 17             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
 18             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
 19             0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
 20             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
 21             0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 
 22             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
 23             0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
 24             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
 25             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
 26             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
 27             0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
 28             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
 29             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
 30             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
 31             0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
 32             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
 33             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
 34             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
 35             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
 36             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
 37             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
 38             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
 39             0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
 40             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
 41             0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
 42         };
 43         #endregion
 44 
 45         #region CRC低位表 byte[] _auchCRCLo
 46         private static readonly byte[] _auchCRCLo = new byte[]//crc低位表
 47         {
 48             0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 
 49             0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 
 50             0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 
 51             0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 
 52             0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 
 53             0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 
 54             0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 
 55             0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 
 56             0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 
 57             0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 
 58             0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 
 59             0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 
 60             0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 
 61             0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 
 62             0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 
 63             0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 
 64             0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 
 65             0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 
 66             0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 
 67             0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 
 68             0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 
 69             0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 
 70             0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 
 71             0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 
 72             0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 
 73             0x43, 0x83, 0x41, 0x81, 0x80, 0x40
 74         };
 75         #endregion
 76 
 77         /// <summary>
 78         /// 【获取读数据命令,返回命令帧】
 79         /// </summary>
 80         /// <param name="mdaddr">地址码</param>
 81         /// <param name="R_CMD">功能码</param>
 82         /// <param name="min_reg">寄存器地址</param>
 83         /// <param name="data_len">寄存器个数</param>
 84         /// <param name="R_CMD_LEN">命令长度</param>
 85         /// <returns></returns>
 86         public byte[] GetReadFrame(byte mdaddr, byte R_CMD, ushort min_reg, ushort data_len, int R_CMD_LEN)
 87         {
 88             //主机命令帧格式
 89             //  字节    功能描述            例子
 90             //
 91             //  1         地址码             0x01
 92             //  2         功能码             0x03
 93             //  3         寄存器地址高       0x00
 94             //  4         寄存器地址低       0x00
 95             //  5         寄存器个数高       0x00
 96             //  6         寄存器个数低       0x02
 97             //  7         CRC检验码低        0xC4
 98             //  8         CRC校验码高        0x0B
 99 
100             ushort crc;
101             byte[] message = new byte[8];
102 
103             //设置模块号
104             message[0] = mdaddr;
105             //设置命令字
106             message[1] = R_CMD;
107 
108             //设置开始寄存器
109             message[2] = WORD_HI(min_reg);
110             message[3] = WORD_LO(min_reg);
111 
112             //设置数据长度
113             message[4] = WORD_HI(data_len);
114             message[5] = WORD_LO(data_len);
115 
116             //设置 CRC
117             crc = CRC16(message, 0, R_CMD_LEN - 3);
118 
119             message[6] = WORD_HI(crc);//CRC校验码高位
120             message[7] = WORD_LO(crc);//CRC校验码低位
121 
122 
123             return message;
124         }
125 
126         /// <summary>
127         /// 【格式化输出,校验读取的数据】
128         /// </summary>
129         /// <param name="readBuffer"></param>
130         /// <returns></returns>
131         public string SetText(byte[] readBuffer)
132         {
133             //将byte 转换成string 用于显示
134             //string readstr = string.Empty;
135             if (readBuffer != null)
136             {
137                 ushort crc = CRC16(readBuffer, 0, readBuffer.Length - 3);
138                 if (readBuffer[readBuffer.Length - 2] == WORD_HI(crc) && readBuffer[readBuffer.Length - 1] == WORD_LO(crc))//crc校验
139                 { 
140                     return ToHexString(readBuffer);
141                 }
142                 else
143                 {
144                     return "CRC校验错误";
145                 }
146             }
147 
148             return "程序出错";
149         }
150 
151 
152         /// <summary>
153         /// 【CRC校验】
154         /// </summary>
155         /// <param name="buffer">命令帧合适前6字节</param>
156         /// <param name="Sset">开始位</param>
157         /// <param name="Eset">结束位</param>
158         /// <returns>CRC校验码</returns>
159         public static ushort CRC16(Byte[] buffer, int Sset, int Eset)
160         {
161             byte crcHi = 0xff;  // 高位初始化
162 
163             byte crcLo = 0xff;  // 低位初始化
164 
165             for (int i = Sset; i <= Eset; i++)
166             {
167                 int crcIndex = crcHi ^ buffer[i]; //查找crc表值
168 
169                 crcHi = (byte)(crcLo ^ _auchCRCHi[crcIndex]);
170                 crcLo = _auchCRCLo[crcIndex];
171             }
172 
173             return (ushort)(crcHi << 8 | crcLo);
174         }
175 
176         /// <summary>
177         /// 【获取大写字母】
178         /// </summary>
179         /// <param name="bytes"></param>
180         /// <returns></returns>
181         public static string ToHexString(byte[] bytes) // 0xae00cf => "AE00CF "
182         {
183             string hexString = string.Empty;
184 
185             if (bytes != null)
186             {
187 
188                 StringBuilder strB = new StringBuilder();
189 
190                 for (int i = 0; i < bytes.Length; i++)
191                 {
192 
193                     strB.Append(bytes[i].ToString("X2"));
194 
195                 }
196 
197                 hexString = strB.ToString();
198 
199             } return hexString;
200 
201         }
202 
203         //取Word变量的高位字节、低位字节
204         /// <summary>
205         /// 【获取低位字节】
206         /// </summary>
207         /// <param name="crcCLo"></param>
208         /// <returns></returns>
209         public static byte WORD_LO(ushort crcCLo)
210         {
211             crcCLo = (ushort)(crcCLo & 0X00FF);
212             return (byte)crcCLo;
213         }
214 
215         /// <summary>
216         /// 【获取高位字节】
217         /// </summary>
218         /// <param name="crcHI"></param>
219         /// <returns></returns>
220         public static byte WORD_HI(ushort crcHI)
221         {
222             crcHI = (ushort)(crcHI >> 8 & 0X00FF);
223             return (byte)crcHI;
224         }
225     }
226 }

MyModbus.cs

    

    #2 下面为Form1.cs的代码,主要包括窗体一些基本操作。   

基于Modbus的C#串口调试开发-冯金伟博客园基于Modbus的C#串口调试开发-冯金伟博客园

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.IO.Ports;
  7 using System.Linq;
  8 using System.Text;
  9 using System.Threading.Tasks;
 10 using System.Windows.Forms;
 11 
 12 
 13 namespace 串口调试
 14 {
 15     public partial class Form1 : Form
 16     {
 17         SerialPort sp = null;
 18 
 19         bool isOpen = false;//是否打开
 20 
 21         bool isSetProperty = false;
 22 
 23 
 24         //数据接收使用的代理
 25         private delegate void myDelegate(byte[] readBuffer);
 26 
 27         public Form1()
 28         {
 29             InitializeComponent();
 30         }
 31 
 32         private void Form1_Load(object sender, EventArgs e)
 33         {
 34             
 35             //设置窗口大小固定
 36             this.MaximumSize = this.Size;
 37             this.MinimumSize = this.Size;
 38 
 39             //1、设置串口下拉列表
 40             for ( int i = 0; i < 10; i++ ) 
 41             {
 42                 cbxCOMPort.Items.Add("COM" + (i + 1).ToString());
 43             }
 44             cbxCOMPort.SelectedIndex = 2;//默认选项
 45 
 46 
 47             //2、设置常用波特率
 48             int bt = 300;
 49             for (int i = 0; i < 8; i++)
 50             {
 51                 cbxBaudRate.Items.Add(bt.ToString());
 52                 bt *= 2;
 53             }
 54 
 55             cbxBaudRate.Items.Add("38400");
 56             cbxBaudRate.Items.Add("43000");
 57             cbxBaudRate.Items.Add("56000"); 
 58             cbxBaudRate.Items.Add("57600");
 59             cbxBaudRate.Items.Add("115200");
 60             cbxBaudRate.SelectedIndex = 5;
 61 
 62 
 63             //3、列出停止位
 64             cbxStopBits.Items.Add("0");
 65             cbxStopBits.Items.Add("1");
 66             cbxStopBits.Items.Add("1.5");
 67             cbxStopBits.Items.Add("2");
 68             cbxStopBits.SelectedIndex = 1;
 69 
 70             //4、设置奇偶检验
 71             cbxParity.Items.Add("");
 72             cbxParity.Items.Add("奇校验");
 73             cbxParity.Items.Add("偶校验");
 74             cbxParity.SelectedIndex = 0;
 75 
 76             //5、设置数据位
 77             cbxDataBits.Items.Add("8");
 78             cbxDataBits.Items.Add("7");
 79             cbxDataBits.Items.Add("6");
 80             cbxDataBits.Items.Add("5");
 81             cbxDataBits.SelectedIndex = 0;
 82 
 83 
 84         }
 85 
 86 
 87         /// <summary>
 88         /// 【串口检测按钮】
 89         /// </summary>
 90         /// <param name="sender"></param>
 91         /// <param name="e"></param>
 92         private void btnCheckCOM_Click(object sender, EventArgs e)
 93         {
 94             //1、检测哪些端口可用
 95             cbxCOMPort.Items.Clear();
 96             cbxCOMPort.Text = "";
 97             
 98             lblStatus.Text = "执行中...";
 99             string str = "";
100             for (int i = 0; i < 10; i++)
101             {
102                 try
103                 {
104                     SerialPort sp = new SerialPort("COM" + (i + 1).ToString());
105                     sp.Open();
106                     sp.Close();
107                     cbxCOMPort.Items.Add("COM" + (i + 1).ToString());
108                 }
109                 catch
110                 {
111                     str += "COM" + (i + 1).ToString() + "";
112                     continue;
113                 }
114             }
115 
116             if(cbxCOMPort.Items.Count > 0)
117                 cbxCOMPort.SelectedIndex = 0;
118             lblStatus.Text = "完成";
119             tbxStatus.Text = str;
120         }
121 
122 
123         public void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
124         {
125             myDelegate md = new myDelegate(ShowRst);
126             try
127             {
128                 if (sp.IsOpen)
129                 {
130                     int count = sp.BytesToRead;
131                     if (count > 0)
132                     {
133                         byte[] readBuffer = new byte[count];
134                         sp.Read(readBuffer, 0, count);//读取串口数据
135                         //     serialPort1.Write(readBuffer, 0, count);
136                         Invoke(md, readBuffer);
137                     }
138                 }
139             }
140             catch (Exception err)
141             {
142                 throw err;
143             }
144 
145         }
146 
147         /// <summary>
148         /// 【显示接收返回的数据】
149         /// </summary>
150         /// <param name="resbuffer"></param>
151         public void ShowRst(byte[] resbuffer) 
152         {
153             MyModbus modbus = new MyModbus();
154             tbxRecvData.Text += "Recv:" + modbus.SetText(resbuffer) + "
";
155             tbxRecvData.Text += "
";
156         }
157 
158         /// <summary>
159         /// 【检测端口设置】
160         /// </summary>
161         /// <returns></returns>
162         private bool CheckPortSetting()
163         {
164             //检测端口设置
165             if (cbxCOMPort.Text.Trim() == "" || cbxBaudRate.Text.Trim() == "" || cbxStopBits.Text.Trim() == "" || cbxParity.Text.Trim() == "" || cbxDataBits.Text.Trim() == "")
166                 return false;
167             return true;
168         }
169 
170         /// <summary>
171         /// 【检测发送数据是否为空】
172         /// </summary>
173         /// <returns></returns>
174         private bool CheckSendData()
175         {
176             if (tbxSendData.Text.Trim() == "")
177                 return false;
178             return true;
179         }
180 
181 
182         /// <summary>
183         /// 【设置串口属性】
184         /// </summary>
185         private void SetPortProperty()
186         {
187             //1、设置串口的属性
188             sp = new SerialPort();
189 
190             sp.ReceivedBytesThreshold = 1;//获取DataReceived事件发生前内部缓存区字节数
191             sp.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);//设置委托
192 
193             sp.PortName = cbxCOMPort.Text.Trim();
194 
195             //2、设置波特率
196             sp.BaudRate =  Convert.ToInt32( cbxBaudRate.Text.Trim());
197 
198             //3、设置停止位
199             float f = Convert.ToSingle( cbxStopBits.Text.Trim());
200 
201             if (f == 0) 
202             {
203                 sp.StopBits = StopBits.None;//表示不使用停止位
204             }
205             else if (f == 1.5)
206             {
207                 sp.StopBits = StopBits.OnePointFive;//使用1.5个停止位
208             }
209             else if (f == 2)
210             {
211                 sp.StopBits = StopBits.Two;//表示使用两个停止位
212             }
213             else
214             {
215                 sp.StopBits = StopBits.One;//默认使用一个停止位
216             }
217 
218             //4、设置数据位
219             sp.DataBits = Convert.ToInt16(cbxDataBits.Text.Trim());
220 
221             //5、设置奇偶校验位
222             string s = cbxParity.Text.Trim();
223             if (s.CompareTo("") == 0) 
224             {
225                 sp.Parity = Parity.None;//不发生奇偶校验检查
226             }
227             else if (s.CompareTo("奇校验") == 0)
228             {
229                 sp.Parity = Parity.Odd;//设置奇校验
230             }
231             else if (s.CompareTo("偶校验") == 0)
232             {
233                 sp.Parity = Parity.Even;//设置偶检验
234             }
235             else
236             {
237                 sp.Parity = Parity.None;
238             }
239 
240             //6、设置超时读取时间
241             sp.ReadTimeout = -1;
242 
243             //7、打开串口
244             try
245             {
246                 sp.Open();
247                 isOpen = true;
248             }
249             catch(Exception)
250             {
251                 lblStatus.Text = "打开串口错误!";
252             }
253 
254         }
255 
256        
257 
258         /// <summary>
259         /// 【发送数据】
260         /// </summary>
261         /// <param name="sender"></param>
262         /// <param name="e"></param>
263         private void btnSend_Click(object sender, EventArgs e)
264         {
265             //发送串口数据
266 
267             //1、检查串口设置
268             if (!CheckPortSetting()) 
269             {
270                 MessageBox.Show("串口未设置!", "错误提示");
271                 return;
272             }
273 
274             //2、检查发送数据是否为空
275             //if (!CheckSendData())
276             //{
277             //    MessageBox.Show("请输入要发送的数据!", "错误提示");
278             //    return;
279             //}
280 
281             //3、设置
282             if (!isSetProperty) 
283             {
284                 SetPortProperty();
285                 isSetProperty = true;
286             }
287 
288             //4、写串口数据
289             if (isOpen)
290             {
291                 //写出口数据
292                 try
293                 {
294                     //sp.Write(tbxSendData.Text);
295                     tbxStatus.Text = "发送成功!";
296                     //tbxSendData.Text += tbxAddress.Text;
297                     
298                 
299                     byte address = Convert.ToByte( tbxAddress.Text.Trim(), 16);//地址码
300                     byte cmd = Convert.ToByte(tbxCmd.Text.Trim(),16);//命令帧
301                     byte regAddr = Convert.ToByte(tbxRegAddr.Text.Trim(), 16);//寄存器地址
302                     byte regNum = Convert.ToByte(tbxRegNum.Text.Trim(), 16);//寄存器数量
303 
304                     
305                     //Modbus相关处理对象
306                     MyModbus modbus = new MyModbus();
307                     byte[] text = modbus.GetReadFrame(address, cmd, regAddr, regNum, 8);
308 
309                     sp.Write(text, 0, 8);
310                     tbxRecvData.Text += "Send:" + BitConverter.ToString(text)+ "
";      
311 
312                 }
313                 catch
314                 {
315                     tbxStatus.Text = "发送数据错误";
316                 }
317             }
318             else
319             {
320                 MessageBox.Show("串口未打开", "错误提示");
321             }
322 
323             
324         }
325 
326         /// <summary>
327         /// 【读取数据】
328         /// </summary>
329         /// <param name="sender"></param>
330         /// <param name="e"></param>
331         private void btnRecv_Click(object sender, EventArgs e)
332         {
333             if(isOpen) 
334             { 
335                 try  
336                 {
337                     //读取串口数据
338   
339                     tbxRecvData.Text += sp.ReadLine()+"
"340                 }  
341                 catch(Exception) 
342                 {  
343                     lblStatus.Text = "读取串口时发生错误!"344                     return345                 } 
346             } 
347             else 
348             {  
349               MessageBox.Show("串口未打开!""错误提示"); 
350               return351                 
352             } 
353         }
354  
355     
356     }
357 }

Form1.cs

 三、最后说一句

  由于需要查询大量资料,有些代码是引用别人的,所以这里要感谢那些分享的人,秉着分享精神,希望可以帮助更多的人。

  如果发现有什么错误,或者建议,请留言,谢谢!