串口通信849220001_第1页
串口通信849220001_第2页
串口通信849220001_第3页
串口通信849220001_第4页
串口通信849220001_第5页
已阅读5页,还剩7页未读, 继续免费阅读

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、C#串口操作系列(3)-协议篇,二进制协议数据解析«我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关, 这就需要关心我们的通讯协议如何缓存,分析,以及通知界面。我们先说一下通讯协议。 通讯协议就是通讯双方共同遵循的一套规则,定义协议的原则是尽可能的简单以提高传输率,尽可能的具有安全性保证数据传输完整正确。基于这2点规则,我们一个通讯协议应该是这样的:头+数据长度+数据正文+校验例如:AA 44 05 01 02 03 04 05 EA这里我假设的一条数据,协议如下:数据头: AA 44数据长度:05数据正文:01 02 03 04 05校验:EA一般数据的校

2、验,都会采用常用的方式,CRC16,CRC32,Xor。有的数据安全要求高的,不允许丢包的,可能还要加入重发机制或是加入数据恢复算法, 在校验后根据前面数据添加恢复字节流以恢复数据。我这里采用的是简单的异或校验,包含数据头的所有字节,依次异或得到的。协议很简单,我也认为分析协议是很简单的事情,下面我们就如何分析协议来实际的结合c#看一"下。er再等等,在我们实际开始编码之前,还有一个规则需要了解,我们有了通讯协议,如何结合串口的协议来分析,需要关心什么呢?哦。一般就是4个问题:缓存收到的所有数据,找到一条完整数据,分析数据,界面通知。如果分的更详细一点, 缓存收到的所有数据, 我们想

3、到最高效的办法就是顺序表,也就是数组,但数组的操作比较复杂,当你使用完一条数据后,用过的需要移除;新数据如果过 多的时候,缓存过大需要清理; 数据搬移等等,很有可能一个不小心就会丢数据导致软件出 些莫名其妙的小问题。个人建议,使用 List<byte> ,内部是数组方式实现,每次数据不足够 的时候会扩容1倍,数据的增删改都已经做的很完善了。不会出现什么小问题。找到一条完整数据,如何找到完整数据呢?就我们例子的这个协议,首先在缓存的数据中找AA 44 ,当我们找到后,探测后面的字节,发现是05,然后看缓存剩下的数据是否足够, 不足够就不用判断,减少时间消耗,如果剩余数据 >=6

4、个(包含1个字节的校验),我们就算 一个校验,看和最后的校验是否一致。分析数据:鉴于网络的开放性,我无法确定读者对c#的了解程度,介绍一下,常用的方式就是BitConvert.ToInt32这一系列的方法,把连续的字节(和变量长度一样)读取并转换为对应的变量。C+下使用 memcpy ,或直接类型转换后进行值拷贝,vb6下使用CopyMemory 这个 api。校验:前面说过了。完整性判断的时候需要和校验对比, 大多系统都不太严格,不支持 重发,所以数据错误就直接丢弃。 导致数据错误的原因很多, 比如电磁干扰导致数据不完整 或错误、硬件驱动效率不够导致数据丢失、 我们的软件缓存出错等。 这些软

5、件因素数据系统 错误,需要修改,但是电磁干扰么,有这个可能的。虽然很少。其实我知道,就算是我,看别人的博客也是,喜欢看图片,看代码,文字性的东西,一 看就头大。那我接下来贴出基于上一篇文章的改进版本,支持协议分析(协议不能配置,可配置的协议不是我们讨论的范畴。可以看看有DFA(确定性有限状态机)我们修改一下界面,以便能显示收到后分析的数据代码如下:view plaincopy to clipboardprint?using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;

6、using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.IO.Ports;using System.Text.RegularExpressions;namespace SerialportSamplepublic partial class SerialportSampleForm : Form privateSerialPort comm =new SerialPort();privateStringBuilder builder =new StringB

7、uilder();/避免在事件处理方法中反复的创建,定义到外面。privatelongreceived_count =:0;/接收计数privatelongsend_count = 0;/发送计数privateboolListening =false ; /是否没有执行完invoke相关操作privateboolClosing = false;/是否正在关闭串口,执行Application.DoEvents,并阻止再次invokeprivateList<byte > buffer =new List< byte >(4096); /默认分配1页内存,并始终限制不允许超过

8、privatebytebinary_data_1 =new byte 9; /AA 44 05 01 02 03 04 05 EApublicSerialportSampleForm。InitializeComponent();/窗体初始化private void Form1_Load( object sender, EventArgs e)/初始化下拉串口名称列表框string ports = SerialPort.GetPortNames();Array.Sort(ports);comboPortName.Items.AddRange(ports);comboPortName.Select

9、edIndex= comboPortName.Items.Count > 0 ? 0 : -1;comboBaudrate.SelectedIndexcomboBaudrate.Items.IndexOf( "19200");/初始化SerialPort 对象comm.NewLine ="rn"comm.RtsEnable =true ; /根据实际情况吧。/添加事件注册comm.DataReceived += comm_DataReceived;)void comm_DataReceived( object sender, SerialDataR

10、eceivedEventArgs e)if (Closing) return ; /如果正在关闭,忽略操作,直接返回,尽快的完成串口 监听线程的一次循环tryListening= true ; /设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。int n = comm.BytesToRead; /先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致byte buf = new byte n; /声明一个临时数组存储当前来的串口数据 received_count += n;/ 增力接收计数comm.Read(buf, 0, n);/ 读取缓冲数据/<协议解析>

11、;bool data_1_catched = false ; /缓存记录数据是否捕获到/1.缓存数据buffer.AddRange(buf);/2.完整性判断while (buffer.Count >= 4)/至少要包含头(2字节)+长度(1字节)+校验(1字节)/请不要担心使用 >=,因为 >=已经和 >,<,=一样,是独立操作符,并不是 解析成 >和=2个符号/2.1查找数据头if (buffer0 = 0xAA && buffer1 = 0x44)/2.2探测缓存数据是否有一条数据的字节,如果不够,就不用费劲的做其他验证了/前面已经限定

12、了剩余长度>=4 ,那我们这里一定能访问到buffer2这个长度int len = buffer2; / 数据长度/数据完整判断第一步,长度是否足够/len 是数据段长度,4个字节是while行注释的3部分长度if (buffer.Count < len + 4)break ; /数据不够的时候什么都不做/这里确保数据长度足够,数据头标志找到,我们开始计算校验/2.3校验数据,确认数据正确/异或校验,逐个字节异或得到校验码/len+3 表示校验之前的位byte checksum = 0;for ( int i = 0; i < len + 3; i+)( checksum A

13、= bufferi; )if (checksum != bufferlen + 3)/如果数据校验失败,丢弃这一包数据(buffer.RemoveRange(0, len + 4);/从缓存中删除错误数据continue ; /继续下一次循环 )/至此,已经被找到了一条完整数据。我们将数据直接分析,或是缓 存起来一起分析/我们这里采用的办法是缓存一次,好处就是如果你某种原因,数据 堆积在缓存buffer 中/已经很多了,那你需要循环的找到最后一组,只分析最新数据,过 往数据你已经处理不及时/ 了,就不要浪费更多时间了,这也是考虑到系统负载能够降低。buffer.CopyTo(0, binary

14、_data_1,0, len + 4);/ 复制一条完整数据到具体的数据缓存data_1_catched =true ;buffer.RemoveRange(0, len + 4);/ 正确分析一条数据,从缓存中移除数据。)else(/这里是很重要的,如果数据开始不是头,则删除数据buffer.RemoveAt(0);)/分析数据if (data_1_catched)(/我们的数据都是定好格式的,所以当我们找到分析出的数据1,就知道固定位置一定是这些数据,我们只要显示就可以了binary_data_14.ToString(string data = binary_data_13.ToStrin

15、g(binary_data_15.ToString("X2")"X2" )+"binary_data_16.ToString( "X2" ) + " " +binary_data_17.ToString("X2");/更新界面this .Invoke(EventHandler)(delegate txData.Textdata; );/如果需要别的协议,只要扩展这个 data_n_catched就可以了。往往我们协议多的情况下,还会包含数据编号,给来的数据进行/编号,协议优化后就是:头

16、+编号+长度+数据+校验/</协议解析>/builder.Clear();/清除字符串构造器的内容/因为要访问ui资源,所以需要使用invoke方式同步ui。this .Invoke(EventHandler)( delegate/判断是否是显示为16禁止if (checkBoxHexView.Checked)/依次的拼接出16进制字符串foreach ( byte b in buf)builder.Append(b.ToString("X2" ) + "");else/直接按ASCII规则转换成字符串builder.Append(Encod

17、ing.ASCII.GetString(buf);/追加的形式添加到文本框末端,并滚动到最后。this .txGet.AppendText(builder.ToString();修改接收计数labelGetCount.Text ="Get:" + received_count.ToString(););finallyListening =false ; / 我用完了,ui 可以关闭串口了。private void buttonOpenClose_Click( object sender, EventArgs e)/根据当前串口对象,来判断操作if (comm.IsOpen)

18、trueClosing = while (Listening) Application.DoEvents();/打开时点击,则关闭串口comm.Close(); else/关闭时点击,则设置好端口,波特率后打开comm.PortName = comboPortName.Text;comm.BaudRate =int .Parse(comboBaudrate.Text);try comm.Open(); catch (Exception ex) /捕获到异常信息,创建一个新的comm对象,之前的不能用了。comm =new SerialPort();/现实异常信息给客户。MessageBox.S

19、how(ex.Message); /设置按钮的状态 buttonOpenClose.Text = comm.IsOpen ?"Close" : "Open"buttonSend.Enabled = comm.IsOpen; /动态的修改获取文本框是否支持自动换行。 private void checkBoxNewlineGet_CheckedChanged( object sender, EventArgs e) txGet.WordWrap = checkBoxNewlineGet.Checked; private void buttonSend_Cl

20、ick( object sender, EventArgs e) /定义一个变量,记录发送了几个字节 int n = 0;/16进制发送 if (checkBoxHexSend.Checked) /我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六 进制数MatchCollectionmc = Regex.Matches(txSend.Text,(?i)da-f2");List<byte > buf = new List< byte >(); / 填充到这个临时列表中/依次添加到列表中foreach (Match m in mc)buf.Add

21、(byte .Parse(m.Value,System.Globalization.NumberStyles.HexNumber); /转换列表为数组后发送comm.Write(buf.ToArray(), 0, buf.Count);/记录发送的字节数n = buf.Count; else /ascii 编码直接发送(/包含换行符if (checkBoxNewlineSend.Checked) (comm.WriteLine(txSend.Text);n = txSend.Text.Length + 2; else /不包含换行符(comm.Write(txSend.Text);n = tx

22、Send.Text.Length;send_count += n;/累加发送字节数labelSendCount.Text ="Send:" + send_count.ToString(); / 更新界面private void buttonReset_Click( object sender, EventArgs e) (/复位接受和发送的字节数计数器并更新界面。send_count = received_count = 0;labelGetCount.Text ="Get:0"labelSendCount.Text ="Send:0"

23、;data标签显示出至此,你只要按这个协议格式发送数据到软件打开的串口。就能在数据的 你的数据内容,我们现在是直接显示为:01 02 03 04 05也就是数据段内容。运行截图:发送模拟数据的界面,使用通用工具SSCOMM32.exe我们在回顾一下,一般二进制格式数据就是这样分析,分析数据长度是否足够, 找到数据头,数据长度,校验,然后分析。wince不支持,也可以分析方式很多。结合各自实际情况操作,可以使用序列化方式,但是 用BitConvert方式将连续的字节读取为某个类型的变量。希望看到这里,能给你带来帮助,欢迎大家和我讨论,希望经验丰富的朋友不吝赐教。上一篇中,有朋友说用 BeginI

24、nvoke可以避免死锁问题,我暂时没有线,没有测试成功,改天测 试后再公布结果。DataReceived事件中,最高效的做法是指缓存数据,然后异步的去分析数据。但是,这样较 复杂,在效率要求不是很高的情况下(大多数情况),可以在DataReceived事件中缓存数据后,立刻进行数据完整性检查,有效性检查,分析数据,以及更新界面。但这么做有一个隐 患,底层串口操作的效率依赖于数据分析和界面更新,任何一个环节频繁耗时过长,都会造成数据的堆积。文章只假设都不拖时间的情况。谢谢观赏,通讯协议分析系列,未完待续C#串口操作系列(4)-协议篇,文本协议数据解析发表时间:2010-8-20阅读数:107网友

25、评论:0 条上一篇已经介绍了协议的组成,一个协议,一般具有:协议头+长度+数据+校验,文本格式可以直观的定义回车换行是协议的结尾,所以我们可以省略数据长度,增加协议尾。即:协议头+数据+校验+数据尾。文本方式的数据比较容易分析。如果数据缓存,可以考虑用StringBuilder。或是不缓存也可以。文本格式数据大多有换行结尾。稍微修改即可。例如分析常见的NMEA 0183格式的卫星坐标数据 GGA。$GPGGA,121252.000,3937.3032,N,11611.6046,E,1,05,2.0,45.9,M,-5.7,M,0000*77$开始GPGGA命令字*结尾77校验对上一篇代码稍作修改就可以了。例子不贴了。文本格式比较简单,只是为了内容完整。贴来做参考。只有分析的地方简化很多。void comm_DataReceived(object sender, SerialDataReceivedEventArgs e) if (Closing) return;/如果正在关闭

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论