我现在有个读卡设备,设备是不停向计算机串口发送数据,但发送的数据不固定,因为有刷卡记录时就发刷卡记录,没有记录就会隔1ms左右发一次设备状态等消息。我用串口调试器可以取到数据,但现在主要是取到数据向解析格式化数据问题,如下:
02 00 0C 03 03 01 48 50 B3 A5 04 00 0B 12 03
为一个完整的包,规定以02开头,03结尾。
从第二个字节开始 00 0C为从00开始到12之前的数据长度,并且高位在前。
倒数第2个数 12 为校验位。现在的目的就是想接到一个完整的包后然后执行另一个程序(如解析各数据作用),问题就是每次传过来的字节数并不完整,如
02 00
0C 03 03 01
48 50 B3 A5 04 00 0B 12 03
我设置SerilPort.ReceivedBytesThreshold 事件也不行,因为我不知道这个包有多少个字节,只能等到第 2 和 3 字节(00 0C)后才知道这个包的长度,然后才接收,到倒数第2位时再计算一下前面校验数是否等于 12 ,这些条件都符合执行解析方法,否则丢弃此包。我本想声明一个类内变量byte[] bArray;
在serialPort_DataReceived事件中首先:
int bytes = serialPort.BytesToRead;这个方法中思维特别忙,请各位串口方面有过开发经验的朋友给予指点。本人开发C#应用软件方面比较多,特别对于byte,二进制数头疼。不管您能不能帮忙,给出建议,均非常感谢!

解决方案 »

  1.   

    楼主是不是用的serialport控件来实现串口通信的,如果是,请注意以下几点:
    如果串口数据比较频繁,建议使用多线程和队列来操作,这样不会造成串口数据丢失,我看了楼主从取到的数据,发觉很有可能是因为通信过快所以数据丢失了。serialport的datarecevie事件中将收到的数据及时写入到一个数组缓存中,然后另开一个线程专门处理该数组中数据内容,这样可以确保万无一失。
      

  2.   

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.IO.Ports;using System.Data.SqlClient;
    using System.Configuration;
    using System.IO;namespace UDMS200.showDig
    {
         
        public partial class setPara : Form
        {
                //存放输入数据的变量
            string inputData = string.Empty;
                     //this delegate enables asynchronous calls for setting
            //the text property on a TextBox control;
            delegate void SetTextCallback(string text);        public setPara()
            {
                InitializeComponent();
                
                //获取当前PC机上的所有串口  nice methods to browse all available ports;
                string[] ports = SerialPort.GetPortNames();
                //将获得的所有串口绑定到combobox控件  add all port names to the comboBox;
                foreach (string port in ports)
                {
                    ComboBox_ports.Items.Add(port);
                }
            }        private void setPara_Load(object sender, EventArgs e)
            {
                bind();
            }        //初始化页面
            protected void bind()
            {
             }
            //选择串口后,下拉列表关闭后触发的事件
            private void ComboBox_ports_SelectionChangeCommitted(object sender, EventArgs e)
            {
                if (SpCom.IsOpen) SpCom.Close();
                SpCom.PortName = ComboBox_ports.SelectedItem.ToString();
                label_motion.Text = "串口信息:" + SpCom.PortName + ":9600,8n1";
                //try to open the selected port;
                try
                {
                    SpCom.Open();
                }
                //give a message,if the port is not available;
                catch
                {
                    MessageBox.Show("Serial port" + SpCom.PortName + "cannot be opened!", "RS232 tester", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    ComboBox_ports.Text = "";
                    label_motion.Text = "Select serial port!";
                }
            }        //接收读入的数据
            private void SpCom_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                //System.Threading.Thread.Sleep(500);
                inputData = SpCom.ReadExisting();
                try
                {
                    if (inputData != string.Empty)
                    {
                        SetText(inputData);
                    }
                 }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }        }        //读出数据
            private void SetText(string text)
            {
                // InvokeRequired required compares the thread ID of the
                // calling thread to the thread ID of the creating thread.
                // If these threads are different, it returns true.            if (this.textBox_answer.InvokeRequired)
                {
                    SetTextCallback d = new SetTextCallback(SetText);
                    this.Invoke(d, new object[] { text });
                }
                else
                {
                    textBox_answer.Text += text;
                    if (textBox_answer.Text.Trim().EndsWith("03"))         //判断接收一串字符是否结束,
                    {
                        //这里可以放你想要做的字符串的解析操作
                    }
                    
                }
            }    }
    }
    你可以试一下,我也遇到过你的问题,就是这样解决的,是看的网上的一些资料,不知道是不是你想要的结果。好像说是C#的这个控件是每次只读8个字节还是8个字符的,还是什么的,所以>8个字符,好像就拆开读了,具体我也没太看细,你自己再找找网上的资源看看吧。
      

  3.   

    非常感谢,我之前这样做过,把取到的内容直接转成十六进制就如
    02 00 0C 03 03 01 48 50 B3 A5 04 00 0B 12 03 
    这样的数据直接存入文本框中,然后另启动一线程,每隔XX毫秒从文本框中读取数据一次数据,然后判断并解析,但我担心这样数据量大时内存也会占用比较多(因为全存在文本框中),并且每次得先转成十六进制,然后还要解析成Byte类型或二进制类型,所以转换过程也会造成时间上的浪费。
    我这个方法类似于二楼aminta_xx 兄弟的做法。其实一楼兄弟geaim 的做法挺好.但我如果先定义一个数据缓存(byte[]),然后将取到的数据取到数据缓存数组中,这样不断读取并存后,会使数组一直增大,这块有法解决吗?
      

  4.   

    一次只接受一个数据
    如果第一个是02 计数器置0;
    通过第二\三个数据得到数据包的长度,只提供一个思路.下面我的代码已aa aa 开头第三个为数据包长度.其中只有0x0d长度为0x16外,其余都是6private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                byte r_byte;
                while (serialPort1.BytesToRead != 0)
                {
                    r_byte = Convert.ToByte(serialPort1.ReadByte());               
                    switch (r_flag)
                    {
                        case 0:
                            if (r_byte == 0xaa)
                            {
                                r_flag += 1;
                                serial_input[0] = r_byte;
                            }
                            break;
                        case 1:
                            if (r_byte == 0xaa)
                            {
                                r_flag += 1;
                                serial_input[1] = r_byte;
                                r_count = 0;
                            }
                            else
                            {
                                r_flag = 0;
                                r_count = 0;
                            }
                            break;
                        case 2:
                            serial_input[r_count + 2] = r_byte;
                            r_count += 1;
                            if (serial_input[2] != 0xd0)
                            {
                                if (r_count == 4)
                                {
                                    r_flag = 0;
                                    r_count = 0;
                                    rs_cl();//调用接收处理函数
                                }
                            }
                            else
                            {
                                if (r_count == 0x14)
                                {
                                    r_flag = 0;
                                    r_count = 0;
                                    rs_cl();//调用接收处理函数
                                }                        }
                            break;
                    }
                }
            }
      

  5.   

    解决的办法很简单,定义一个arraylist 动态数组来接收从串口收到的数据,解析完一个就删除一个,这样可以确保串口数据的数组不会太大。我平常也是这么做的。
      

  6.   

    geaim 你好,我现在用您的方法处理,方法这样,看有没有问题或建议。我的QQ:250441732 ,E-Mail:[email protected]
    首先类内定义:ArrayList arrData = new ArrayList();Thread threadx;
    然后将所有串口的 DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
    然后private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                arrData.Add(serialPort.ReadByte());
            }
    在private void FrmMain_Load(object sender, EventArgs e)
            {
                //开始监听并读取记录
                    threadx = new Thread(new ThreadStart(ReadRecord));
                  threadx.Start();
            }
    //ReadRecord方法为线程实现方法,主要实现线程控制时钟,300毫秒执行一次事件
         private void ReadRecord()
            {
                tmReadSpan = new System.Timers.Timer();
                tmReadSpan.Interval = 300;
                tmReadSpan.Enabled = true;
                tmReadSpan.Elapsed += new System.Timers.ElapsedEventHandler(tmReadSpan_Elapsed);
                tmReadSpan.Start();
            }
    //时钟到后执行的事件方法
         void tmReadSpan_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
            {
                //实现数据解析并相应操作。
                  //删除解析后的数据从arrData
            }