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; } } }