前几天做毕业设计,其中要用到串口和下位机进行通信,于是自己捣鼓了一个简单的串口通信程序。
在做通信之前要先弄一个SerialPort组件出来,当然也可以通过程序来创建。本次设计中采用的是拖的winform中提供的组件,效果是一样的。不过要使程序正确的运行起来,电脑上要安装一个pl232的驱动,安装完成后,还必须要插入一根串口线才行,或者是USB转串口的线也可以。
SerialPort有很多的属性和方法,但是在我做的这个简单的串口通信中用到的属性和方法很少,可以讲VS调出来看看有哪些属性和方法,有很多的属性已经帮我们设置好了,利用默认的设置就行
1、设置COM口
串口通信是需要利用计算机的COM接口,而很多的接口是被其它程序使用的,故首先要获得计算机当前我们程序中能够使用的COM接口,首先要添加一个引用:Microsoft.VisualBasic
<span style="font-size:18px;"> private string LoadSerial () { Microsoft.VisualBasic.Devices.Computer pc; pc = new Microsoft.VisualBasic.Devices.Computer(); //判断能够使用的COM口数量 if (pc.Ports.SerialPortNames.Count > 0) { //当有COM口可以使用的时候,将第一个COM口赋值给属性PortName //也可以直接:serial.PortName = "COM2"; 指明了要使用COM2接口 serial.PortName = pc.Ports.SerialPortNames[0]; serial.BaudRate = 115200; //设置通信的波特率,默认的是9600,所以也可以不用设置 serial.StopBits = System.IO.Ports.StopBits.One; //停止位的位数,一般的串口通信都有停止位 serial.DateBit = 8; //数据位数,每一帧有效数据的位数,默认的为8,这个根据需要设置 } else { return "系统没有可用串口,请检查和下位机连线是否正常"; } return "OK"; }</span>
我设置的属性也基本上都是这些,其他的什么读缓存大小写缓存大小以及什么读取超时和写超时等等都使用默认的就OK,主要还是COM端口的设置
2、发送数据
串口通信,对方是下位机,故在通信中采用的是ASCII码,所以要先将数据转换成二进制然后进行发送,由于系统会自动的添加什么启示结束位等,我们也就不需要关系,直接将要发送的数据丢给他就行,当然也可以手动的进行判断。发送数据前需要先打开串口。
<span style="font-size:18px;"> try { if (!serial.IsOpen) //判断串口是否打开,会返回一个布尔值 { serial.Open(); //如果串口没有打开就打开串口 } byte[] by = ASCIIEncoding.ASCII.GetBytes("b"); //将要发送的数据转换成二进制 serial.Write(by, 0, by.Length); //发送 } catch (Exception ex) { return "请求结束出错:" + ex.Message; }</span>
因为要打开串口,串口是硬件资源,打开的时候很可能会失败抛异常,故用一个try...catch...语句包起来。发送数据的方法还有一个WriteLine,但个人觉得用Wrte更好。其实严格的讲,在发送之前应该先判断是否能够进行发送,这可以通过属性来完成,在这里就没有判断了,直接发送。
3、创建一个接收数据的后台线程
本次用串口来进行通信主要是用来接收数据,就收数据中没有采用系统提供的事件机制,而是创建了一个后台的线程来进行不断的监视读取缓存中有没有数据。先创建后台线程:
<span style="font-size:18px;"> private string LoadThread() { try { Thread thread = new Thread(ReceiveDate); //传递的是接收数据的方法,这个方法中用来接收数据 thread.Name = "serialThread"; thread.IsBackground = true;//设置线程为后台线程,前台关闭,此线程也就关闭 thread.Start(); } catch (Exception ex) { return "出错了!" + ex.Message; } return "OK"; }</span>
4、数据的接收和处理
设计中事先约定好的通信协议就是:总共七位数据,第一位是ABCDE五个字符中任意一个,第二位是数字,第三位是小数点,第4~6位是数字,最后一位是字符V。
由于ASCII中得到的空格和换行符是用" "和“ ”来表示的,利用string.trim()方法是不能够将这个给移除的,所以每次都只是读取一位数据,当然这样肯定会影响通信的效率。读取一位数据后先判断是不是空格和换行,是就将数据丢弃,一般空格和换行都是出现在上一组数据发送完成后才有的,没有考虑数据出错后变为" "和" "的问题,这也算是一个漏洞。七位数据接收完成后,通过正则表达式来进行验证,验证通过后将数据存入到事先准备好的字典中,字典的关键字是七位数据中的第一位子字符串,值是一个字符串集合,用来将小数点去掉后四位数字组成的字符串存储下来。
代码如下:
<span style="font-size:18px;"> //声明一个字典,存放数据 Dictionary<string, List<string>> receiveDate = null; private void ReceiveDate () { string date = ""; //保存接收的数据 //实例化这个字典 receiveDate = new Dictionary<string, List<string>>(); string datePath = "";//用来存放,当前是那一路的信号 int index = 0; //七位数据,用来标识接收的是第几位数据 DateTime time1=DateTime.Now; while (true) { try { if (serial.IsOpen) //如果串口打开就进行数据的读取 { if (serial.BytesToRead > 0) //先判断读缓存中有没有数据 { byte[] byteDate = new byte[1]; //设置每一次读取的字节数为1 serial.Read(byteDate, 0, byteDate.Length); string strDate = ASCIIEncoding.ASCII.GetString(byteDate); if (strDate == " " || strDate == " ") { index = 0; date = ""; continue; } else { index++; //接收的是多少位数 date += strDate; } if (index == 7) //七位数据接收完成 { //利用正则表达式来进行匹配和数据的提取 Regex reg = new Regex(@"^([ABCDE]{1})(d{1}).(d{3})V$"); Match match = reg.Match(date, 0, date.Length); if (match.Success) { datePath = match.Groups[1].Value; string numStr = match.Groups[2].Value + match.Groups[3].Value; int num = -1; //将接收到的数据进行一个转换,转换成功后保存 if (int.TryParse(numStr, out num)) { //经过正则表达式验证后,datePath一定是ABCDE中的一个数据,先判断是否包含这个关键字,如果不包含就添加 if (!receiveDate.ContainsKey(datePath)) { receiveDate[datePath] = new List<int>();//添加关键字就需要声明一个集合赋值给它 } receiveDate[datePath].Add(num);//将数据存储起来 time = DateTime.Now; //记录正确接收数据的时间 } } //清空,进入下一轮的数据接收 date = ""; datePath = ""; index = 0; } } else { <span style="white-space: pre;"> </span>//这个else中做的事情是如果长时间接收不到数据就添加0,代码就不附加了 } } } catch (Exception ex) { } } }</span>
到此,一个简单的串口通信程序就搞定了。