串口数据处理

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Threading;
using Util.Log;

namespace SerialLibrary
{
    public class SerialLib
    {
        #region Custom
        private bool running; //当前运行状态
        private SerialPort sp; //串口控件
        private long received_count, send_count; //发送总字节,接收总字节
        private byte[] Received; //所有接收数据的缓存
        private int received_offset; //当前缓冲存在空闲的位置
        private int cmd_offset; //已解出的命令的位置,下一个可用的开始位置
        const int BUF_SIZE = 512;
        const byte STX = 0x02; //STX
        const byte ETX = 0x03; //ETX
        private Dictionary<byte, WaitEvent> waitList;
        #endregion
        public SerialLib()
        {
            send_count = 0;
            received_count = 0;
            received_offset = 0;
            cmd_offset = 0;
            waitList = new Dictionary<byte, WaitEvent>();

            running = false;
            sp = new SerialPort();
            sp.ReadBufferSize = 512;
            sp.WriteBufferSize = 512;
            sp.BaudRate = 9600;
            sp.StopBits = StopBits.One;
            sp.DataBits = 8;
            sp.Parity = Parity.None;
            Received = new byte[BUF_SIZE];
            sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
        }
        ///
        /// 命令等待回应
        ///
        class WaitEvent : IDisposable
        {
            public ManualResetEvent Event;
            public byte[] recv;
            public WaitEvent()
            {
                recv = null;
                Event = new ManualResetEvent(false);
            }
            public void Dispose()
            {
                recv = null;
                Event.Dispose();
                Event = null;
            }
        }
        /// 接收串口数据,取出有效命令,发到线程池待处理
        /// 整个方法加锁,防止多次进入接收影响全局变量的值。必须完成一次后才能再次进入接收
        [MethodImpl(MethodImplOptions.Synchronized)]
        private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (!running) return;
            SerialPort sp = (SerialPort)sender;
            int n = sp.BytesToRead;
            if (received_offset + n > BUF_SIZE) //数据包超大,放不下,只接收能放下的字节数
            {
                pack_received_buffer(); //压缩
                if (received_offset + n > BUF_SIZE) //压缩后仍不能接收的,减少接收空间
                {
                    n = BUF_SIZE - received_offset;
                }
            }
            received_count += n; //接收的总字节数
            byte[] buf = new byte[n];
            sp.Read(buf, 0, n);
            Array.Copy(buf, 0, Received, received_offset, n);
            received_offset += n; //移入后偏移
            Debug.WriteLine("接收位置-{0},命令位置-{1},接收总字节-{2},发送总字节-{3}", received_offset, cmd_offset, received_count, send_count);
            Debug.WriteLine("接收数据:" + dump_buffer(buf));


            //按协议中长度找协议末尾
            int datalen = get_int(Received, cmd_offset + 1, 2);
            int end_offset = cmd_offset + 3 + datalen; //结束符的位置
            end_offset = end_offset < received_offset ? end_offset : cmd_offset + 1; //防止超过当前已读字节,读到垃圾字节
            if (Received[end_offset] != ETX) return; //接收数据不完整,等待下次接收
            while (Received[end_offset] != ETX && end_offset < received_offset) end_offset++;
            if (end_offset == received_offset)
            {
                //如果找了超过256字节仍无有效内容,直接清空全部。单包数据应该不超过128
                if (end_offset > cmd_offset + 256)
                {
                    received_offset = 0;
                    cmd_offset = 0;
                }
                return; //没有找到结束符
            }

            end_offset++; //后移一字节,包插进 LRC校验值
            byte[] cmd_buf = new byte[end_offset - cmd_offset + 1];
            Array.Copy(Received, cmd_offset, cmd_buf, 0, cmd_buf.Length); //复制出一条完整的命令
            ThreadPool.QueueUserWorkItem(receive_serial_data, cmd_buf);
            cmd_offset = end_offset + 1; //跳过LRC,到下一条

            if (received_offset > BUF_SIZE * 0.85) //缓存位置超过85%。实际不太可能,除非受干扰全乱码
            {
                pack_received_buffer();
            }

        }
        private void receive_serial_data(object data)
        {
            byte[] cmd_buf = (byte[])data;
            Debug.WriteLine("处理数据:" + dump_buffer(cmd_buf));
            Log.Info("处理数据:" + dump_buffer(cmd_buf));
            byte lrc = cmd_buf[cmd_buf.Length - 1]; //取最后一字节,lrc
            if (lrc != calc_lrc(cmd_buf, 3, cmd_buf.Length - 2))
            {
                Debug.WriteLine("数据LRC校验失败,不处理");
            }

            byte cmdtype = cmd_buf[3];
            lock (waitList)
            {
                if (waitList.ContainsKey(cmdtype))
                {
                    int dlength = get_int(cmd_buf, 1, 2);
                    WaitEvent we = waitList[cmdtype];
                    if (dlength > 0)
                    {
                        we.recv = new byte[dlength + 5];
                        Array.Copy(cmd_buf, 0, we.recv, 0, dlength + 5); //如果有数据返回,包括命令类型一起返回
                    }
                    we.Event.Set();
                }
            }
        }
        ///
        /// 启动串口
        ///
        ///通卡模块所在的串口号
        ///
        public int Start(int port)
        {
            if (running) return 0; //已经打开过的,直接返回成功
            sp.PortName = string.Format("COM{0}", port);
            try
            {
                sp.Open();
                sp.DiscardInBuffer();
                sp.DiscardOutBuffer();
                running = true;
                Log.Info("通卡模块串口COM" + port.ToString() + "启动成功");
                return 0;
            }
            catch (Exception e)
            {
                Log.Error("启动串口失败: ", e);
                return 1;
            }
        }
        ///
        /// 关闭串口
        ///
        public void Stop()
        {
            if (running)
            {
                try
                {
                    if (sp.IsOpen) sp.Close();
                    Log.Info("通卡模块串口关闭成功");
                }
                catch (Exception e)
                {
                    Log.Error("串口关闭异常: ", e);
                }
                running = false;
            }
        }
        ///
        /// 发送串口命令
        ///
        ///命令类型
        ///发送数据
        ///接收数据。可能为 null
        ///0为处理成功, 3为无响应,其他为失败
        public int process(byte cmdType, byte[] data, out byte[] recvdata, int Wait)
        {
            recvdata = null;
            cmd_offset = 0;
            int dlength = data.Length;
            byte[] cmd = new byte[dlength + 6]; //1头长+2长度(bcd)+1命令长度(bcd)+4长度终端编号(bcd)+4长度表文流水号(bcd)+N长度报文+1尾长+1校验长
            int datalen = data.Length + 1;
            bcdstr_to_byte_array(datalen.ToString(), ref cmd, 1, 2);//设置数据长度两个字节
            cmd[0] = STX;
            cmdType = int2bcd(cmdType);
            cmd[3] = cmdType;
            Array.Copy(data, 0, cmd, 4, dlength);
            cmd[4 + dlength] = ETX;
            cmd[5 + dlength] = calc_lrc(cmd, 3, cmd.Length - 2);
            send_count += cmd.Length;
            Debug.WriteLine("发送命令:" + dump_buffer(cmd));
            Log.Info("发送命令:" + dump_buffer(cmd));
            try
            {
                sp.Write(cmd, 0, cmd.Length);
                if (Wait == 0) return 0; //不需要接收回复
                //等待回复,超时 Wait 秒
                WaitEvent we = new WaitEvent();
                lock (waitList)
                {
                    waitList[cmdType] = we;
                }
                DateTime dt = DateTime.Now;
                dt = dt.AddSeconds(Wait);
                while (!we.Event.WaitOne(50) && DateTime.Now < dt)
                {
                    Thread.Sleep(50);
                }
                if (!we.Event.WaitOne(1))
                {
                    lock (waitList)
                    {
                        waitList.Remove(cmdType);
                    }
                    Debug.WriteLine("命令超时,没收到回应");
                    return 3; //未收到响应
                }
                if (we.recv != null)
                {
                    recvdata = new byte[we.recv.Length];
                    Array.Copy(we.recv, recvdata, we.recv.Length);
                    Log.Info("接受命令:" + dump_buffer(recvdata));
                }
                lock (waitList)
                {
                    waitList.Remove(cmdType);
                }
                we.Dispose();
                we = null;
                return 0;
            }
            catch (Exception e)
            {
                Log.Error("串口处理异常: ", e);
                return 1;
            }
        }
        ///
        /// 整数转为bcd码, 12(dec) --> 0x12
        ///
        ///
        ///
        private byte int2bcd(int x)
        {
            x = (byte)x;
            return (byte)((x / 10 * 16) + x % 10);
        }
        ///
        /// bcd转为整数, 0x12 --> 12(dec)
        ///
        ///
        ///
        private int bcd2int(byte x)
        {
            return (x / 16 * 10) + (x % 16);
        }
        ///
        /// 将bcd字符串转换成字节数据,填充到指定位置的指定长度
        ///
        ///
        ///
        ///
        ///
        private void bcdstr_to_byte_array(string bcdstr, ref byte[] array, int Start, int Length)
        {
            int i = bcdstr.Length;
            int k = 0;
            byte x;
            string st;
            while (i > 0)
            {
                if (i > 1) st = bcdstr.Substring(i - 2, 2);
                else st = bcdstr.Substring(0, 1); //不是双字符对齐时,会剩下一个字符要转换
                if (!byte.TryParse(st, out x)) x = 0;
                array[Start + Length - k - 1] = int2bcd(x);
                k++;
                i -= 2;
            }
            while (Start + Length - k - 1 >= Start)
            {
                array[Start + Length - k - 1] = 0;
                k++;
            }
        }
        ///
        /// 压缩接收缓冲,使有效数据前移,空间后方空间
        ///
        private void pack_received_buffer()
        {
            //后续缓存空间不够,向前移动,覆盖前面已用过的空间
            byte[] swap = new byte[BUF_SIZE];
            Array.Copy(Received, cmd_offset, swap, 0, received_offset - cmd_offset);
            Array.Copy(swap, 0, Received, 0, received_offset - cmd_offset);
            received_offset = received_offset - cmd_offset;
            cmd_offset = 0;
        }
        private string dump_buffer(byte[] buffer)
        {
            StringBuilder sp = new StringBuilder(1024);
            int n = 0;
            foreach (byte x in buffer)
            {
                sp.AppendFormat("{0:X2}", x);
                n++;
                if (n > 512) break; //缓存有限,只处理最长512字节
            }
            return sp.ToString();
        }
        private int get_int(byte[] buffer, int Start, int Length)
        {
            int n = 0;
            StringBuilder strBuilder = new StringBuilder();
            for (int i = Start; i < Start + Length && i < buffer.Length; i++)
            {
                strBuilder.AppendFormat("{0:X2}",buffer[i]);
            }
            n = Convert.ToInt32(strBuilder.ToString());
            return n;
        }
        ///
        /// 计算LRC校验, xor异或
        ///
        ///
        ///
        ///
        /// 
        private byte calc_lrc(byte[] data, int Start, int End)
        {
            byte lrc = 0;
            int dlength = data.Length;
            for (int i = Start; i < End && i < dlength; i++)
            {
                lrc ^= data[i];
            }
            return lrc;
        }
    }
}
posted @ 2014-07-22 10:17  liyx0618  阅读(1975)  评论(0编辑  收藏  举报