基于Modbus的C#串口调试开发
说明:本文主要研究的是使用C# WinForm开发的串口调试软件(其中包含Modbus协议相关操作)。Modbus相关协议可以查阅百度文库等,可参考: 《http://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 }
#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 }
#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 }
#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 }
二、基于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 }
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
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 }
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 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 }
#2 下面为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 //数据接收使用的代理 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) + "\r\n"; 155 tbxRecvData.Text += "\r\n"; 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)+ "\r\n"; 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()+"\r\n"; 340 } 341 catch(Exception) 342 { 343 lblStatus.Text = "读取串口时发生错误!"; 344 return; 345 } 346 } 347 else 348 { 349 MessageBox.Show("串口未打开!", "错误提示"); 350 return; 351 352 } 353 } 354 355 356 } 357 }
三、最后说一句
由于需要查询大量资料,有些代码是引用别人的,所以这里要感谢那些分享的人,秉着分享精神,希望可以帮助更多的人。
如果发现有什么错误,或者建议,请留言,谢谢!