• 串口数据处理


    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;
            }
        }
    }
    
  • 相关阅读:
    重磅!KubeEdge单集群突破10万边缘节点|云原生边缘计算峰会前瞻
    大型物联网平台如何来保障亿级设备安全连接上云?
    跟我学Python图像处理丨5种图像阈值化处理及算法对比
    什么是算子下盘?
    一文带你了解J.U.C的FutureTask、Fork/Join框架和BlockingQueue
    如何使用Superset可无缝对接MRS进行自助分析
    谁说Redis不能存大key
    云图说|每个成功的业务系统都离不开APIG的保驾护航
    手把手教你实战开发黑白棋实时对战游戏
    当运行npm install 命令的时候带上ignorescripts,会发生什么?
  • 原文地址:https://www.cnblogs.com/lonelyofsoul/p/Serial.html
Copyright © 2020-2023  润新知