摘要:本文主要向大家介绍了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();
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)+ "\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频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号