Modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议。通过此协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如PLC可编程控制器等。Modbus通讯物理接口可以选用串口(包括RS232和RS485),也可以选择以太网口。
1、十六进制字符串的CRC验证
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace modbustest 7 { 8 public static class ByteHexHelper 9 { 10 private static char[] _buffedChars = null; 11 private static byte[] _buffedBytes = null; 12 13 static ByteHexHelper() 14 { 15 _buffedChars = new char[(byte.MaxValue - byte.MinValue + 1) * 2]; 16 int idx = 0; 17 byte b = byte.MinValue; 18 while (true) 19 { 20 string hexs = b.ToString("X2"); 21 _buffedChars[idx++] = hexs[0]; 22 _buffedChars[idx++] = hexs[1]; 23 24 if (b == byte.MaxValue) break; 25 ++b; 26 } 27 28 _buffedBytes = new byte[0x67]; 29 idx = _buffedBytes.Length; 30 while (--idx >= 0) 31 { 32 if ((0x30 <= idx) && (idx <= 0x39)) 33 { 34 _buffedBytes[idx] = (byte)(idx - 0x30); 35 } 36 else 37 { 38 if ((0x61 <= idx) && (idx <= 0x66)) 39 { 40 _buffedBytes[idx] = (byte)((idx - 0x61) + 10); 41 continue; 42 } 43 if ((0x41 <= idx) && (idx <= 70)) 44 { 45 _buffedBytes[idx] = (byte)((idx - 0x41) + 10); 46 } 47 } 48 } 49 } 50 51 /// <summary> 52 /// 字节数组转16进制字符串 53 /// </summary> 54 /// <param name="bytes"></param> 55 /// <returns></returns> 56 public static string ByteToHex(byte[] bytes) 57 { 58 if (bytes == null) 59 { 60 return null; 61 } 62 63 char[] result = new char[bytes.Length * 2]; 64 for (int i = 0; i < bytes.Length; ++i) 65 { 66 int startIndex = (bytes[i] - byte.MinValue) * 2; 67 result[i * 2] = _buffedChars[startIndex]; 68 result[i * 2 + 1] = _buffedChars[startIndex + 1]; 69 } 70 71 return new string(result); 72 } 73 74 /// <summary> 75 /// 16进制字符串转字节数组 76 /// </summary> 77 /// <param name="str"></param> 78 /// <returns></returns> 79 public static byte[] HexToByte(string str) 80 { 81 str = str.Replace(" ",""); 82 if (str == null || (str.Length & 1) == 1) 83 { 84 return null; 85 } 86 87 byte[] result = new byte[str.Length / 2]; 88 int charIndex = 0; 89 int byteIndex = 0; 90 int length = result.Length; 91 while (--length >= 0) 92 { 93 int first = 0; 94 int second = 0; 95 try 96 { 97 first = _buffedBytes[str[charIndex++]]; 98 second = _buffedBytes[str[charIndex++]]; 99 } 100 catch 101 { 102 return null; 103 } 104 result[byteIndex++] = (byte)((first << 4) + second); 105 } 106 return result; 107 } 108 } 109 }
2、字符串转十六进制|十六进制转字符串
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 namespace modbustest 6 { 7 class CRC 8 { 9 10 public static string CRCCheck(string val) 11 { 12 val = val.TrimEnd(‘ ‘); 13 string[] spva = val.Split(‘ ‘); 14 byte[] bufData = new byte[spva.Length + 2]; 15 bufData = ToBytesCRC(val); 16 ushort CRC = 0xffff; 17 ushort POLYNOMIAL = 0xa001; 18 for (int i = 0; i < bufData.Length - 2; i++) 19 { 20 CRC ^= bufData[i]; 21 for (int j = 0; j < 8; j++) 22 { 23 if ((CRC & 0x0001) != 0) 24 { 25 CRC >>= 1; 26 CRC ^= POLYNOMIAL; 27 } 28 else 29 { 30 CRC >>= 1; 31 } 32 } 33 } 34 35 return ToHex(System.BitConverter.GetBytes(CRC)); 36 } 37 /// <summary> 38 /// 例如把如下字符串转换成字节数组 39 /// AA AA AA AA 0A 00 68 00 06 03 04 54 21 28 22 E5 F3 16 BB BB BB BB 转换为字节数组 40 /// </summary> 41 /// <param name="hex">十六进制字符串</param> 42 /// <returns></returns> 43 public static byte[] ToBytesCRC(string hex) 44 { 45 string[] temp = hex.Split(‘ ‘); 46 byte[] b = new byte[temp.Length + 2]; 47 48 for (int i = 0; i < temp.Length; i++) 49 { 50 b[i] = Convert.ToByte(temp[i], 16); 51 } 52 53 return b; 54 } 55 /// <summary> 56 /// 将字节数据转换为十六进制字符串,中间用 “ ”分割 如:AA AA AA AA 0A 00 68 00 06 03 04 54 21 28 22 E5 F3 16 BB BB BB BB 57 /// </summary> 58 /// <param name="vars">要转换的字节数组</param> 59 /// <returns></returns> 60 public static String ToHex(byte[] vars) 61 { 62 return BitConverter.ToString(vars).Replace(‘-‘, ‘ ‘).Trim(); 63 } 64 } 65 }
3、窗体主程序
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using System.IO.Ports; 10 using System.Text.RegularExpressions; 11 12 namespace modbustest 13 { 14 public partial class Form1 : Form 15 { 16 17 SerialPort sp = null;//申明一个串口类 18 bool isOpen = false;//打开串口标识位 19 bool isSetProperty = false;//属性设置标志位 20 bool isHex = false;//十六进制显示标志位 21 22 public Form1() 23 { 24 InitializeComponent(); 25 } 26 //窗体初始化 27 private void Form1_Load(object sender, EventArgs e) 28 { 29 this.MaximumSize = this.Size; 30 this.MinimumSize = this.Size; 31 this.MaximizeBox = false; 32 //最大支持10个串口 33 for (int i = 0; i < 10; i++) 34 { 35 protBox.Items.Add("COM" + (i + 1).ToString()); 36 } 37 protBox.SelectedIndex = 0; 38 39 //初始化波特率 40 BaudRate.Items.Add("1200"); 41 BaudRate.Items.Add("2400"); 42 BaudRate.Items.Add("4800"); 43 BaudRate.Items.Add("9600"); 44 BaudRate.Items.Add("19200"); 45 BaudRate.Items.Add("38400"); 46 BaudRate.Items.Add("43000"); 47 BaudRate.Items.Add("56000"); 48 BaudRate.Items.Add("57600"); 49 BaudRate.Items.Add("115200"); 50 51 BaudRate.SelectedIndex = 5; 52 53 //初始化停止位 54 55 stopBits.Items.Add("0"); 56 stopBits.Items.Add("1"); 57 stopBits.Items.Add("1.5"); 58 stopBits.Items.Add("2"); 59 stopBits.SelectedIndex = 1; 60 61 //初始化数据位 62 dataBits.Items.Add("8"); 63 dataBits.Items.Add("7"); 64 dataBits.Items.Add("6"); 65 dataBits.Items.Add("5"); 66 dataBits.SelectedIndex = 0; 67 68 //初始化奇偶校验位 69 parity.Items.Add("无"); 70 parity.Items.Add("Odd"); 71 parity.Items.Add("Even"); 72 parity.SelectedIndex = 2; 73 74 //默认显示hex 75 rbnHex.Checked = true; 76 } 77 78 //检查哪些串口可用 79 private void checkCOM_Click(object sender, EventArgs e) 80 { 81 bool comExistence = false;//有可用的串口标志位 82 protBox.Items.Clear();//清除当前串口号中的所有串口名称 83 for (int i = 0; i < 10;i++ ) 84 { 85 try{ 86 87 SerialPort sp = new SerialPort("COM"+(i+1).ToString()); 88 sp.Open(); 89 sp.Close(); 90 protBox.Items.Add("COM" + (i + 1).ToString()); 91 comExistence = true; 92 93 } 94 catch { 95 96 continue; 97 } 98 } 99 if (comExistence) 100 { 101 102 protBox.SelectedIndex = 0; 103 } 104 else { 105 106 MessageBox.Show("没有找到可用串口","错误提示"); 107 } 108 109 } 110 //检查串口是否设置 111 private bool checkPortSetting() 112 { 113 if (protBox.Text.Trim() == "") return false; 114 115 if (BaudRate.Text.Trim() == "") return false; 116 if (dataBits.Text.Trim() == "") return false; 117 if (parity.Text.Trim() == "") return false; 118 if (stopBits.Text.Trim() == "") return false; 119 120 return true; 121 122 } 123 124 private bool checkSendData() 125 { 126 if (tbxSend.Text.Trim() == "") return false; 127 return true; 128 129 } 130 131 private void SetPortProperty()//设置串口的属性 132 { 133 sp=new SerialPort(); 134 sp.PortName=protBox.Text.Trim();//设置串口名 135 sp.BaudRate=Convert.ToInt32(BaudRate.Text.Trim());//设置串口的波特率 136 float f=Convert.ToSingle(stopBits.Text.Trim());//设置停止位 137 if(f==0){ 138 139 sp.StopBits=StopBits.None; 140 } 141 else if(f==1.5){ 142 143 sp.StopBits=StopBits.OnePointFive; 144 } 145 else if(f==1){ 146 147 sp.StopBits=StopBits.One; 148 } 149 else if(f==2){ 150 151 sp.StopBits=StopBits.Two; 152 } 153 else{ 154 sp.StopBits=StopBits.One; 155 } 156 sp.DataBits=Convert.ToInt16(dataBits.Text.Trim());//设置数据位 157 string s=parity.Text.Trim();//设置奇偶校验位 158 if(s.CompareTo("None")==0){ 159 sp.Parity=Parity.None; 160 } 161 else if(s.CompareTo("Odd")==0){ 162 163 sp.Parity=Parity.Odd; 164 } 165 else if(s.CompareTo("Even")==0){ 166 167 sp.Parity=Parity.Even; 168 } 169 else{ 170 171 sp.Parity=Parity.None; 172 } 173 sp.ReadTimeout=-1;//设置超时读取时间 174 sp.RtsEnable=true; 175 //定义DataReceived事件,当串口收到数据后触发事件 176 sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived); 177 if(rbnHex.Checked){ 178 isHex=true; 179 }else{ 180 isHex=false; 181 } 182 } 183 private void btnSend_Click(object sender,EventArgs e)//发送串口数据 184 { 185 if(isOpen)//写串口数据 186 { 187 try{ 188 189 sp.WriteLine(tbxSend.Text); 190 } 191 catch(Exception){ 192 193 MessageBox.Show("发送数据时发生错误!","错误提示"); 194 return; 195 } 196 }else{ 197 MessageBox.Show("串口未打开!","错误提示"); 198 return; 199 } 200 if(!checkSendData())//检测要发送的数据 201 { 202 MessageBox.Show("请输入要发送的数据!","错误提示"); 203 return; 204 } 205 } 206 207 //打开串口 208 private void openPort_Click(object sender, EventArgs e) 209 { 210 if(isOpen==false){ 211 if(!checkPortSetting())//检测串口设置 212 { 213 MessageBox.Show("串口未设置!","错误提示"); 214 return; 215 } 216 if(!isSetProperty)//串口未设置则设置串口 217 { 218 SetPortProperty(); 219 isSetProperty=true; 220 } 221 try//打开串口 222 { 223 //sp = new SerialPort("COM1",19200,Parity.Even,8,StopBits.One); 224 sp.Open(); 225 isOpen=true; 226 openPort.Text="关闭串口";//串口打开后则相关的串口设置按钮便不可再用 227 228 protBox.Enabled=false; 229 BaudRate.Enabled=false; 230 dataBits.Enabled=false; 231 parity.Enabled=false; 232 stopBits.Enabled=false; 233 rbnChar.Enabled=false; 234 rbnHex.Enabled=false; 235 } 236 catch(Exception){ 237 //打开串口失败后,相应标志位取消 238 isSetProperty=false; 239 isOpen=false; 240 MessageBox.Show("串口无效或已被占用!","错误提示"); 241 } 242 }else{ 243 try//打开串口 244 { 245 sp.Close(); 246 isOpen=false; 247 isSetProperty=false; 248 openPort.Text="打开串口";//关闭串口后,串口设置选项便可以继续使用 249 protBox.Enabled=true; 250 BaudRate.Enabled=true; 251 dataBits.Enabled=true; 252 parity.Enabled=true; 253 stopBits.Enabled=true; 254 rbnChar.Enabled=true; 255 rbnHex.Enabled=true; 256 } 257 catch(Exception){ 258 lblStatus.Text="关闭串口时发生错误"; 259 } 260 } 261 } 262 private void sp_DataReceived(object sender,SerialDataReceivedEventArgs e) 263 { 264 System.Threading.Thread.Sleep(100);//延时100ms等待接收完数据//this.Invoke就是跨线程访问ui的方法,也是本文的范例 265 this.Invoke((EventHandler)(delegate{ 266 267 if(isHex==false){ 268 269 tbxRec.Text+=sp.ReadLine(); 270 }else{ 271 272 Byte[]ReceivedData=new Byte[sp.BytesToRead+1];//创建接收字节数组 273 sp.Read(ReceivedData,0,ReceivedData.Length);//读取所接收到的数据 274 String RecvDataText=null; 275 276 for( int i=0;i<ReceivedData.Length-1; i++){ 277 278 RecvDataText+=(ReceivedData[i].ToString("X2")+" "); 279 } 280 tbxRec.Text+=RecvDataText; 281 } 282 sp.DiscardInBuffer();//丢弃接收缓冲区数据 283 })); 284 } 285 286 287 288 private void button1_Click_1(object sender, EventArgs e) 289 { 290 291 292 if (isOpen)//写串口数据 293 { 294 try 295 { 296 byte[] send_data; 297 string str = this.tbxSend.Text; 298 string strc = str + " " + CRC.CRCCheck(this.tbxSend.Text); 299 //int iLen = 0; 300 send_data = ByteHexHelper.HexToByte(strc); 301 302 // send_data = HexStringToByteArray(strc); 303 //iLen = send_data.GetLength(0); 304 //this.tbxRec = byteToHexStr(send_data); 305 // byte[] acc_data = new byte[] { 0x30, 0x31 }; 306 //acc_data = send_data; 307 //String acc_str = System.Text.Encoding.Default.GetString(acc_data); 308 ////ModBus comm = new ModBus(); 309 this.tbxRec.Text = str + " "+ CRC.CRCCheck(str); 310 //String rec = str + " " + CRC.CRCCheck(acc_str); 311 //sp.WriteLine(strs); 312 sp.Write(send_data,0,send_data.Length); 313 314 //this.tbxRec.Text = sp.BaudRate + " " + sp.BytesToWrite + "kong"; 315 316 317 } 318 catch (Exception) 319 { 320 321 MessageBox.Show("发送数据时发生错误!", "错误提示"); 322 return; 323 } 324 } 325 else 326 { 327 MessageBox.Show("串口未打开!", "错误提示"); 328 return; 329 } 330 if (!checkSendData())//检测要发送的数据 331 { 332 MessageBox.Show("请输入要发送的数据!", "错误提示"); 333 return; 334 } 335 } 336 337 private void button2_Click(object sender, EventArgs e) 338 { 339 byte[] acc_data; 340 String str2 = this.tbxRec.Text; 341 int iLen = 0; 342 acc_data = System.Text.Encoding.ASCII.GetBytes(str2); 343 iLen = acc_data.GetLength(0); 344 345 byte[] send_data = new byte[] { 0x30, 0x31 }; 346 send_data = acc_data; 347 String acc_str = System.Text.Encoding.ASCII.GetString(acc_data); 348 //ModBus comm = new ModBus(); 349 350 this.tbxSend.Text = str2 + " " + CRC.CRCCheck(acc_str); 351 } 352 353 private void button3_Click(object sender, EventArgs e) 354 { 355 tbxRec.Text = ""; 356 tbxSend.Text = ""; 357 358 } 359 360 361 362 363 364 } 365 }
4、窗体
时间: 2024-10-10 14:37:12