《串口设备数据的接收和处理.ppt》由会员分享,可在线阅读,更多相关《串口设备数据的接收和处理.ppt(37页珍藏版)》请在三一办公上搜索。
1、工控程序设计,学习情景2.2 串口设备数据的接收和处理,学习情景2.2 串口设备数据的接收和处理,2.2.1 学习要点 1.知识点:SerialPort类实例的构造,接收串口数据的方法,接收超时异常处理,HSDZC电能综合测试仪的数据格式,数据帧片段的缓存方法 2.技能点:应用程序项目和SerialPort对象的创建,串口数据接收,接收数据的缓存和数据帧的提取2.2.2 任务描述 某些串口设备能够定时、主动地向上位机发送数据,处于上位机端的工业控制程序,需要获取串口收到的数据并进行分析和处理。利用.NET Framework 2.0及更高版本提供的SerialPort类可以比较方便地在C#应用
2、程序中实现串口通信功能。从编程的角度看,串口数据的接收就是利用SerialPort对象的Read或ReadByte方法将操作系统存放在串口输入缓冲区中的数据读入到一个字节数组中。该教学情景通过串口数据接收、接收数据的缓存、数据帧的提取、浮点数解码这几个实施步骤,达到使用SerialPort对象接收单个串口设备(下位机)数据的目的。,学习情景2.2 串口设备数据的接收和处理,2.2.3 相关知识 1 SerialPort类实例的构造 SerialPort类是从.NET Framework 2.0版本开始提供的一个串口控制类,该类包含在命名空间中,使用该类的实例可以打开串口,并发送数据。通过构造函
3、数可以产生SerialPort类的实例,SerialPort类的构造函数在定义时进行了重载,表中列出了这些重载构造函数的接口定义:,表2.2.1 SerialPort类的构造函数定义,表2.2.1 SerialPort类的构造函数定义,学习情景2.2 串口设备数据的接收和处理,学习情景2.2 串口设备数据的接收和处理,除了在构造SerialPort类实例的时候可以对端口名称、波特率、奇偶校验位、数据位和停止位进行设置外,构造好SerialPort类实例后,还可以通过PortName、BaudRate、Parity、DataBits、StopBits属性对这些参数进行修改,因此构造SerialP
4、ort类实例的程序书写形式比较灵活。2 串口的打开和关闭SerialPort类中与串口打开和关闭相关的成员如下:(1)Open方法Open方法打开新的串口连接,该方法不带参数,接口定义如下:public void Open()(2)Close方法Close方法关闭已经打开的串口连接,该方法不带参数,接口定义如下:public void Close(),学习情景2.2 串口设备数据的接收和处理,(3)IsOpen属性通过IsOpen属性可以获知串口当前是否为打开状态,返回一个bool类型的值,该属性只读,接口定义如下:public bool IsOpen get;串口不能重复进行打开和关闭操作,
5、在打开或关闭串口前,都要使用该属性检查串口是否已经处于打开或已经处于关闭状态,以免引发异常。3 接收串口数据的方法(1)ReadByte方法 SerialPort类的ReadByte方法可以从串口接收缓冲区中读取一个字节,该方 法的接口定义是:public int ReadByte();该方法从串口接收缓冲区中读取一个字节,要和ReadTimeout属性配合使用。当调用ReadByte方法时,如果接收缓冲区中没有数据,则程序被阻塞,直到缓冲区中有数据或到达ReadTimeout属性指定读取数据超时的毫秒数,程序才能继续运行。,学习情景2.2 串口设备数据的接收和处理,ReadByte方法返回一
6、个int类型的值,在实际应用时,要将返回值强制转换为byte类型。(2)Read方法Read方法从串口接收缓冲区中读取多个字节,该方法的接口定义是:public int Read(byte buffer,int offset,int count);该方法试图从接收缓冲区中读取count个字节,并写入字节数组buffer中,写入的起始位置是offset,执行后返回实际读取的字节数。例如,现在接收缓冲区中只有6个字节,但count参数值为10,则返回值为6。在实际应用时,通常先通过SerialPort对象的BytesToRead属性获得接收缓冲区中已有的字节数,并根据该属性值来设置count参数。
7、和ReadByte方法不同的是,调用Read方法时,不管接收缓冲区中有无数据,都不会对应用程序的当前线程造成阻塞。,学习情景2.2 串口设备数据的接收和处理,4 接收超时异常处理 当使用ReadByte方法从接收缓冲区中读取一个字节时,如果下位机发送过来的数据没有在ReadTimeout规定的时间内到达,则会引发TimeoutException,应用程序可以捕获该异常,并作相应处理。下面的测试程序说明了超时异常处理的方法:SerialPort sp;private void btnStart_Click(object sender,EventArgs e)byte firstByte;sp.R
8、eadExisting();/清空接收缓冲区,准备接收数据 sp.ReadTimeout=10000;/10秒内没有收到回复数据,将捕获到异常 try firstByte=(byte)sp.ReadByte();/同步读取(程序在此被阻塞)t1.Text=firstByte.ToString(X2);/显示在文本框t1中 catch(TimeoutException ex)/捕获到接收超时异常 t1.Text=ex.Message;/在t1中显示异常信息,学习情景2.2 串口设备数据的接收和处理,图2.2.1 接收超时异常测试 如上图所示,在点击“开始”按钮后,程序被阻塞,此时窗口对鼠标和键盘
9、的输入都不响应。为了有足够操作时间,SerialPort对象sp的ReadTimeout属性为10000毫秒(10秒)。在10秒时间内,测试者可以通过串口测试程序发送一个或多个字节过来,此时程序结束阻塞状态继续运行,并在文本框中显示接收到的第1个字节;如果10秒内没有数据到达,则引发TimeoutException。若上位机程序要一直等待,可以把ReadTimeout属性的值设置为-1,但在实际应用中一般不这样做。,学习情景2.2 串口设备数据的接收和处理,5 HSDZC电能综合测试仪的数据格式 HSDZC电能综合测试仪在钻机性能测试系统中用于输入功率、输出功率以及功率因素(参见引言介绍)。该
10、设备提供两种电脑测量方式,在仪表提示“测量方式”时,可以分别按“显示1”和“显示2”进入。在测量方式1的情况下测试仪每秒通过RS-232接口发送1组数据,长度46字节。信号格式:开始4字节均为FFH,后续每三个字节构成一个浮点数值,分别表示I1、U1、I2、U2、I3、U3,以及功率因数、视在功率、效率、输入功率、无功功率、负载率、输出功率、转速,共14个数据。在测量方式2的情况下测试仪也是每秒通过RS-232接口发送1组数据,长度30字节。信号格式:开始3字节均为12H、34H、56H,后续每三个字节构成一个浮点数值,分别表示I1、U1、P1、I2、U2、P2、I3、U3、P3,共9个数据。
11、上位机在和HSDZC电能综合测试仪进行通信时,不需要向设备写入数据,而是被动地读取设备发送过来的数据。下面的相关知识将进一步介绍对已接收到数据的存储和处理方法。,学习情景2.2 串口设备数据的接收和处理,构成浮点数值的3个字节含义是:第1字节(B1)为尾数低位,第2字节(B2)为尾数高位,第3字节(B3)为阶码指数。其中阶码指数各位的含义如表所示:表2.2.2 阶码指数字节中各位的含义,学习情景2.2 串口设备数据的接收和处理,尾数B的值在0到1之间,计算公式是:下面的HexToFloat函数描述了将3个字节转换为浮点数的算法:private float HexToFloat(byte B1,
12、byte B2,byte B3)float f,B,n;/B:尾数 n:阶码指数 B=(B2*256+B1)/65536F;if(B3,学习情景2.2 串口设备数据的接收和处理,6 数据帧片段的缓存方法(1)使用集合类存储接收数据 在通信过程中,数据帧表示由多个字节构成的,具有一定格式,表示完整语意的一组数据。例如,在和HSDZC电能综合测试仪进行通信时,如果测试仪工作在测量方式1,则一帧数据的长度为46个字节(包含起始标志和14个数值)。在系统实际运行中,下位机向上位机发送数据时,可能会将数据帧分为几个片段依次发送,或者在一次接收到的数据中包含了不止一帧数据。上位机必须提供一个字节的集合来对
13、接收到的数据进行缓存(暂存),在确认接收完毕一帧数据后,再进行进一步的分析和处理。.NET Framework 2.0以上版本提供了List类来实现集合元素管理。List类支持泛型,在存取byte类型的集合元素时无需进行“装箱”和“拆箱”操作,比使用ArrayList效率高。,学习情景2.2 串口设备数据的接收和处理,List类提供了丰富的方法来对集合中的元素进行操作,表列出了List类的常用方法,表中的示例建立在如下定义的基础上:byte b;byte ba;List li=new List();/创建List类的实例li,元素类型为byte 表2.2.3 List类的常用方法,学习情景2.
14、2 串口设备数据的接收和处理,学习情景2.2 串口设备数据的接收和处理,学习情景2.2 串口设备数据的接收和处理,(2)集合大小的设置 为了保证集合中任何时候能容纳一个完整的数据帧,集合中能容纳的字节数量应为数据帧长度的两倍。集合中的数据采取队列形式进行处理,新来的数据从队尾进,如果原有数据字节数加上新接收字节数超过集合的最大容量,则将队首的部份数据删除。图展示数据入队和出队的过程:L-队列总容量 N-原有字节数 M-新接收字节数(1)队列剩余空间能容纳新接收数据 L-队列总容量 N-原有字节数 M-新接收字节数(2)队列剩余空间能容纳新接收数据 图2.2.2 新接收数据的缓存,学习情景2.2
15、 串口设备数据的接收和处理,2.2.4 任务实施 1应用程序项目的创建 串口通信应用程序的开发在Visual Studio集成开发环境中进行,首先创建一个基于C#语言的“Windows应用程序”项目,名为“ReceiveData”,并存放在工作目录“F:SerialPort”下,如图所示:,学习情景2.2 串口设备数据的接收和处理,2 SerialPort对象的创建 创建SerialPort对象可以通过添加控件和编写代码两种方式,添加控件的方式相对简单,适合初学者练习;编写代码的方式具有较强的灵活性,可以把SerialPort对象对串口的操作细节隐藏起来,与数据编码、解码等功能一起封装为一个功
16、能完整的串口通信模块。(1)通过添加控件创建SerialPort对象在“工具箱”中展开“组件”选项卡,选择“SerialPort”组件(如图),用鼠标拖动到设计视图中的窗口上。由于SerialPort是不可见组件,所以不直接出现在窗体上,而是出现在窗口下方,如图所示。该操作的实质是在窗体设计器的源代码(文件名)中添加了如下代码:private serialPort1;this.serialPort1=new System.IO.Ports.SerialPort(ponents);实质上是定义并实例化了一个名为serialPort1的SerialPort对象。,学习情景2.2 串口设备数据的接收
17、和处理,(2)编写代码创建SerialPort对象 切换到代码视图,在Form1.cs中编写上述代码同样可以创建SerialPort对象。首先在最前面引入命名空间:using;然后定义SerialPort类的对象sp,作为Form1类的私有成员:private SerialPort sp;最后在Form1类的构造函数中创建对象:public Form1()InitializeComponent();sp=new SerialPort();,学习情景2.2 串口设备数据的接收和处理,2.2.4 选择SerialPort组件,学习情景2.2 串口设备数据的接收和处理,2.2.5 将SerialPo
18、rt组件加入窗体中当然,工程实践中,SerialPort对象不一定在窗体类中定义。如果项目代码量大,可以专门建立一个负责串口通信和调度的类,在里面定义一个或多个SerialPort对象作为该类的成员。,学习情景2.2 串口设备数据的接收和处理,3 串口数据的接收 在下面的程序中,当点击“开始接收”按钮时,程序调用SerialPort对象的ReadByte方法,阻塞当前线程,并开始接收下位机发送的数据。如果在10秒内接收到数据,则将接收到的数据以十六进制方式显示在文本框中,否则在文本框中显示“操作超时”异常提示。在编写程序之前,先在系统中安装虚拟串口对COM1和COM2,本程序连接COM1,串口
19、测试程序连接COM2。程序界面如下图:图2.2.6 串口数据的接收,学习情景2.2 串口设备数据的接收和处理,其中文本框名字为t1,按钮名字为btnStart,对应的后台代码如下:private SerialPort sp;private void Form1_Load(object sender,EventArgs e)sp=new SerialPort(COM1,9600,Parity.None,8,StopBits.One);sp.Open();private void Form1_FormClosing(object sender,FormClosingEventArgs e)if(s
20、p!=null/10秒内没有收到回复数据,将捕获到异常 try,学习情景2.2 串口设备数据的接收和处理,first=(byte)sp.ReadByte();/同步读取(程序在此被阻塞)tmp=new bytesp.BytesToRead+1;tmp0=first;sp.Read(tmp,1,tmp.Length-1);/读取剩余字节到tmp数组中/接收的数据以十六进制形式显示在文本框t1中 t1.Text=CCheck.BinaryToHexString(tmp);catch(TimeoutException ex)t1.Text=ex.Message;/在文本框t1中显示异常信息,学习情景
21、2.2 串口设备数据的接收和处理,/以下代码包含在CCheck类中:public class CCheck/字节数组转换为十六进制字符串 public static string BinaryToHexString(byte buff)StringBuilder tmp=new StringBuilder();for(int i=0;i buff.Length;i+)tmp.Append(buffi.ToString(X2)+);return tmp.ToString().Trim();,学习情景2.2 串口设备数据的接收和处理,该程序在点击“开始接收”按钮后会停止对用户输入的响应,这是因为调
22、用ReadByte方法造成当前线程被阻塞,直到下位机发送的数据到达,或接收超时才解除阻塞。实际操作时,先运行该程序,再打开串口测试工具,并连接COM2,在测试工具中输入一些数据,做好发送的准备工作。在程序点击“开始接收”按钮后,立即切换到串口测试工具,并点击“发送”按钮,就可以在程序中看到发送过来的数据。“发送”操作要在10秒内完成,否则会引起“接收超时”异常。4接收数据的缓存和数据帧的提取(1)创建数据接收队列 CRecvBuf是一个用于缓存接收数据的类,利用List类来实现。在创建该类的实例时,要指定参数maxLength,表示队列的总容量(即图中的参数L)。每当接收到一段数据,就调用Ad
23、d方法将收到的数据添加到队列中,Add方法中判断添加数据后字节数是否超过maxLength,并将超出部份从队首删除。,学习情景2.2 串口设备数据的接收和处理,class CRecvBuf public List li;public int maxLength;public CRecvBuf(int maxLength)li=new List();this.maxLength=maxLength;public void Add(byte b)if(b.Length maxLength)throw new Exception(接收到的数据量超出缓冲区长度);li.AddRange(b);if(l
24、i.Count maxLength)li.RemoveRange(0,li.Count-maxLength);,学习情景2.2 串口设备数据的接收和处理,(2)使用数据接收队列 当HSDZC电能综合测试仪工作在“测量方式2”的情况下,使用下面的程序可以接收数据,并使用前面设计的CRecvBuf类的实例缓存接收到的数据,再从中提取出一个完整数据帧。程序运行界面如图:2.2.7 利用队列缓存接收数据,学习情景2.2 串口设备数据的接收和处理,当按下“开始”按钮时,上位机准备接收数据,等待时间为10秒,此时通过串口测试程序按HSDZC电能综合测试仪工作在“测量方式2”时的数据格式,向上位机发送一个数
25、据片段。上位机采用3个步骤处理接收数据:第1步接收到下位机发送的数据;第2步将接收的数据添加到队列中;第3步从队列中根据起始标志和约定数据长度提取出一个完整的数据帧,后台代码如下:private SerialPort sp;private CRecvBuf recvBuf;private void btnStart_Click(object sender,EventArgs e)byte firstByte;byte tmp;sp.ReadTimeout=10000;/10秒内没有收到回复数据,将捕获到异常 try firstByte=(byte)sp.ReadByte();/读取第1个回复字
26、节(程序在此被阻塞),学习情景2.2 串口设备数据的接收和处理,tmp=new bytesp.BytesToRead+1;tmp0=firstByte;sp.Read(tmp,1,tmp.Length-1);/读取剩余字节到tmp数组中 t1.Text=CCheck.BinaryToHexString(tmp);/接收数据显示在文本框t1中 recvBuf.Add(tmp);/添加接收数据到队列中/显示队列中的全部数据 t2.Text=CCheck.BinaryToHexString(recvBuf.getAllData();/提取一个完整的数据帧 tmp=getBlock(recvBuf.l
27、i);if(tmp!=null)t3.Text=CCheck.BinaryToHexString(tmp);/显示在文本框t3中 else t3.Text=;,学习情景2.2 串口设备数据的接收和处理,catch(TimeoutException ex)t1.Text=ex.Message;/在t1文本框中显示异常信息 private void Form1_Load(object sender,EventArgs e)recvBuf=new CRecvBuf(60);/队列长度为数据帧长度的两倍 sp=new SerialPort(COM1,9600,Parity.None,8,StopBit
28、s.One);sp.Open();private void Form1_FormClosing(object sender,FormClosingEventArgs e),学习情景2.2 串口设备数据的接收和处理,if(sp!=null/已经找到,接收循环 if(p!=-1)/p为数据块的起始位置,学习情景2.2 串口设备数据的接收和处理,b=new byte30;li.CopyTo(p,b,0,30);/拷贝数据块到b数组 return b;/返回找到的完整数据帧 上面的代码已经获取到了一个完整的数据帧,根据“相关知识”中介绍的HSDZC电能综合测试仪的解码算法,可以把I1、U1、P1、I2
29、、U2、P2、I3、U3、P3这9个浮点数值计算出来,具体实现的代码作为练习,由读者自己完成。,学习情景2.2 串口设备数据的接收和处理,2.2.5 考核要点,学习情景2.2 串口设备数据的接收和处理,2.2.6 能力拓展 1.使用SerialPort类要引入什么命名空间,如何创建SerialPort对象?2.SerialPort类的Read和ReadByte方法有什么区别?3.如何捕获并处理串口数据接收超时异常?4.为什么要定义一个队列来缓存接收到的数据,该队列如何实现?5.如何在队列中查找一个最新的,且完整的数据帧?6.根据HSDZC电能综合测试仪的浮点数编码格式,在“任务实施”第4个步骤的例子中增加一段代码,将解码后的I1、U1、P1、I2、U2、P2、I3、U3、P3这9个浮点数值显示在文本框t3中。,