C#编程之基于Modbus的C#串口调试开发
小标 2018-09-17 来源 : 阅读 1496 评论 0

摘要:本文主要向大家介绍了C#编程之基于Modbus的C#串口调试开发,通过具体的内容向大家展示,希望对大家学习C#编程有所帮助。

本文主要向大家介绍了C#编程之基于Modbus的C#串口调试开发,通过具体的内容向大家展示,希望对大家学习C#编程有所帮助。

说明:本文主要研究的是使用C# WinForm开发的串口调试软件(其中包含Modbus协议相关操作)。Modbus相关协议可以查阅百度文库等,可参考: 《//wenku.baidu.com/link?url=J-QZeQVLfvfZh7_lh8Qf0MdwANZuVjEoTqox6zJYrSnKyfgES2RTb_bjC5ZTn8-xgsuUAyiELRYVA3-3FBkBGywWhQ9YGoavJOzwB0IxTyK 》。
  (1)先测试串口设置,发送和接收数据。
  (2)发送modbus的命令帧数据和使用DataReceived接收缓冲区的数据。
 
一、简单的串口调试工具
  下图为串口调试工具的界面,主要包括串口基本设置,功能操作,状态框以及发送接收框。由于这里只是简单的初始化数据,所以当需要发送数据的时候需要点击“串口检测”,来测试当前可用的串口,然后输入需要发送的数据,最后点击“发送数据”(由于测试需要,让发送什么数据就返回什么数据,这里的底层硬件做了短接处理,使用短接貌P30-P31,具体操作可以自行百度)
  
  
  1.1 发送数据操作
    (1)点击 串口检测
    (2)输入发送数据
    (3)点击 发送数据
    下面开始时具体代码:
    #1 软件打开时候初始化操作:Form1_Load(),主要初始化操作串口设置的下拉列表。


 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(),


 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对象  


 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()+"\r\n"; 
15                 }  
16                 catch(Exception) 
17                 {  
18                     lblStatus.Text = "读取串口时发生错误!"; 
19                     return; 
20                 } 
21             } 
22             else 
23             {  
24               MessageBox.Show("串口未打开!", "错误提示"); 
25               return; 
26                 
27             } 
28         }

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


  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()+"\r\n";
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()+"\r\n"; 
287                 }  
288                 catch(Exception) 
289                 {  
290                     lblStatus.Text = "读取串口时发生错误!"; 
291                     return; 
292                 } 
293             } 
294             else 
295             {  
296               MessageBox.Show("串口未打开!", "错误提示"); 
297               return; 
298                 
299             } 
300         }
301  
302     }
303 }

Form1.cs
 
 
二、基于Modbus协议的数据发送和接收
   这里主要是在前面的基础上,把发送和接收的数据进行格式化(符合Modbus的数据帧格式),如下左图所示,右图为加入Modbus协议的窗体,主要添加了命令帧的输入框组:
   
 
  
  2.1 获取字节的的高位和低位:WORD_LO()、WORD_HI()   


 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高位表和低位表 


 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()


 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) + "\r\n";
9         }

 
     #2 设置属性:ReceivedBytesThreshold = 1

1  //1、设置串口的属性
2             sp = new SerialPort();

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)+ "\r\n";      
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数据帧等。   


  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            

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C#.NET频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程