c串口操作系列(3).doc_第1页
c串口操作系列(3).doc_第2页
c串口操作系列(3).doc_第3页
c串口操作系列(3).doc_第4页
c串口操作系列(3).doc_第5页
免费预览已结束,剩余10页可下载查看

下载本文档

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

文档简介

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

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

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

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

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

6、ing System.Data;usingSystem.Drawing;using System.Linq;usingSystem.Text;using System.Windows.Forms;usingSystem.IO.Ports;usingSystem.Text.RegularExpressions;namespaceSerialportSamplepublic partial classSerialportSampleForm : FormprivateSerialPort comm = new SerialPort();privateStringBuilder builder =

7、new StringBuilder();/避免在事件处理方法中反复的创建,定义到外面。private longreceived_count = 0;/ 接收计数private longsend_count = 0;/发送计数private boolListening = false;/是否没有执行完invoke 相关操作private bool Closing = false;/ 是否正在关闭串口,执行Application.DoEvents,并阻止再次 invokeprivate List<byte> buffer = newList<byte>(4096);/默认分

8、配 1页内存,并始终限制不允许超过private byte binary_data_1 = newbyte9;/AA 44 05 01 02 03 04 05 EApublicSerialportSampleForm()InitializeComponent();/窗体初始化private void Form1_Load(objectsender, EventArgs e)/初始化下拉串口名称列表框string ports =SerialPort.GetPortNames();Array.Sort(ports);comboPortName.Items.AddRange(ports);combo

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

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

11、ed = 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校验数据,确认数据正确/异或校验,逐个字节异或得到校验码byte checksum = 0;for (int i =0; i < len + 3; i+)/len+3表示校验之前的位置checksum =bufferi;if (c

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

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

15、ToString(X2) + +binary_data_16.ToString(X2) + +binary_data_17.ToString(X2);/更新界面this.Invoke(EventHandler)(delegate txData.Text =data; );/如果需要别的协议,只要扩展这个data_n_catched就可以了。往往我们协议多的情况下,还会包含数据编号,给来的数据进行/编号,协议优化后就是:头+编号 +长度 +数据 +校验/</协议解析>/构造器的内容builder.Clear();/因为要访问清除字符串ui 资源,所以需要使用invoke 方式同步 u

16、i。this.Invoke(EventHandler)(delegate/判断是否是显示为16 禁止if (checkBoxHexView.Checked)串/依次的拼接出16 进制字符foreach (byte b in buf)builder.Append(b.ToString(X2) + );else/直接按ASCII规则转换成字符串builder.Append(Encoding.ASCII.GetString(buf);/追加的形式添加到文本框末端,并滚动到最后。this.txGet.AppendText(builder.ToString();/修改接收计数labelGetCount.

17、Text = Get: +received_count.ToString(););闭串口了。finallyListening = false;/我用完了, ui可以关privatevoid buttonOpenClose_Click(object sender, EventArgs e)/根据当前串口对象,来判断操作if (comm.IsOpen)Closing = true;while (Listening) Application.DoEvents();/打开时点击,则关闭串口comm.Close();else/关闭时点击,则设置好端口,波特率后打开comm.PortName =combo

18、PortName.Text;comm.BaudRate = int.Parse(comboBaudrate.Text);trycomm.Open();catch(Exception ex)/捕获到异常信息,创建一个新的comm 对象,之前的不能用了。comm = new SerialPort();/现实异常信息给客户。MessageBox.Show(ex.Message);/设置按钮的状态buttonOpenClose.Text = comm.IsOpen ? Close : Open;buttonSend.Enabled = comm.IsOpen;/动态的修改获取文本框是否支持自动换行。p

19、rivate voidcheckBoxNewlineGet_CheckedChanged(object sender,EventArgs e) txGet.WordWrap = checkBoxNewlineGet.Checked; private void buttonSend_Click(object sender, EventArgs e)/定义一个变量,记录发送了几个字节int n = 0;/16 进制发送if(checkBoxHexSend.Checked)/我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数MatchCollection mc = Regex.

20、Matches(txSend.Text,(?i)/da-f2); List<byte> buf = new List<byte>();/ 填充到这个临时列表中/依次添加到列表中foreach (Match min mc)buf.Add(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 =txSend.Text.Length;send_count += n;/累加发送字节数labelSendCount.Text = Send: + send_count.ToString();/更新界面private void

温馨提示

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

评论

0/150

提交评论