通过把SerialPort进行封装,以多线程和缓存的方式处理串口的发送和接收动作。
一、现象
不管如何设置ReceivedBytesThreshold的值,DataReceived接收到的数据都是比较混乱,不是一个完整的应答数据。
二、原因
1、上位机下发的命令比较密集,以200ms周期发送实时状态轮询命令。
2、在状态实时轮询命令中间有操作命令插入。
2、不同的命令,接收的应答格式也不同。
三、分析
不同的命令有不同的应答数据,但是不同的应答数据中都具有唯一的结束符,可以根据结束符来作为多个应答数据的分割标志。因此可以把应答数据进行缓存,然后另起一个线程对缓存的应答数据进行分析处理。
因此系统具有:
1、命令队列用来插入操作命令,空闲时处理状态实时轮询命令。
2、命令发送线程,以200ms周期性的发送队列中的命令。
3、应答集合,用来缓存DataReceived接收数据。
4、应答处理线程,对应答集合中的数据进行集中处理。
using System;
using System.Collections.Generic;
using System.Threading;
using System.IO.Ports;
using System.Text.RegularExpressions;
namespace TestDemoC {
class Program {
static void Main(string[] args) {
Open232();
while (true) {
var r = send(("AA 00 02 25 26 03 BB").ToBytes(), 1000);
if (r != 0) { Log("Recv " + "err r." + r); continue; }
Log("Recv " + rs.Result.Remove().ToHexStr());
}
Console.ReadLine();
}
private static void Log(string msg) {
Console.Write($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff} [Info] { msg}{System.Environment.NewLine}");
}
#region RS232
private static RS232 rs = new RS232();
private static long send_expire = 0;
private static int Open232() {
var r = rs.Open("com1", 9600);
if (r != 0) { return -1; }
rs.MaxLength = 1024;
return 0;
}
private static int send(byte[] buf, int ti = 3000) {
var r = rs.Send(buf);
send_expire = DateTime.Now.Ticks + ti * 10000;
var findhead = -1;
var len = 5; //Recv 返回指令最短长度(注:考虑无数据的情况)
RETRY:
for (; DateTime.Now.Ticks < send_expire; Thread.Sleep(20)) if (rs.Length >= len) break;
if (rs.Length < len) {
rs.Clear(rs.Length);
return 404;
}
//起始符
if (rs.Result[0] != 0x5A || rs.Result[1] != 0x3C) {
findhead = rs.Result.Find(new byte[] { buf[0], buf[1] });
rs.Clear(findhead == -1 ? rs.Length : findhead);
goto RETRY;
}
if (rs.Length <= 0) return 404;
len = len + rs.Result[2];
for (; DateTime.Now.Ticks < send_expire; Thread.Sleep(20)) if (rs.Length >= len) break;
//长度err
if (rs.Length < len) {
return 2;
}
//结束符
if (rs.Result[len - 1] != 0xDB) {
return 3;
}
return 0;
}
#endregion
}
public static class Expand {
public static bool IsEmpty(this string s) { return string.IsNullOrWhiteSpace(s); }
public static byte[] Remove(this byte[] b, byte cut = 0x00) {
var list = new List<byte>();
list.AddRange(b);
for (var i = list.Count - 1; i >= 0; i--) {
if (list[i] == cut) { list.RemoveAt(i); } else { break; }
}
var lastbyte = new byte[list.Count];
for (var i = 0; i < list.Count; i++) {
lastbyte[i] = list[i];
}
return lastbyte;
}
public static string ToHexStr(this byte[] b) { return BitConverter.ToString(b, 0, b.Length).Replace("-", " "); }
public static byte[] ToBytes(this string s) {
s = Replace(s, @"[^\dA-Fa-f]+", "");
if (s.Length % 2 > 0) s = "0" + s;
var max = s.Length / 2;
var buf = new byte[max];
for (var i = 0; i < max; i++) buf[i] = Convert.ToByte(s.Substring(i * 2, 2), 16);
return buf;
}
public static string Replace(this string s, string p, string r) { return Regex.Replace(s, p, r); }
public static string ToStr(this object o) { return string.Concat(o); }
}
public class RS232 : IDisposable {
#region base
private SerialPort p;
public bool SendBeforeClear { get; set; } = true;
public byte[] ResultHeader { get; set; }
public string msg { get; private set; }
/// <summary>
/// 接收缓冲区
/// </summary>
public byte[] Result { get; private set; }
/// <summary>
/// 接收缓冲区字节Max
/// </summary>
public int MaxLength {
get { return max; }
set {
Result = new byte[value];
max = value;
}
}
public int Length => i;
private int i = 0;
private int max = 255;
private bool islife = false;
#endregion
#region RS232
public RS232() { }
public RS232(string port, int baud)
: this() {
if (port.ToUpper().StartsWith("COM")) Open(port, baud);
}
#endregion
#region Open
/// <summary>
/// Open
/// </summary>
/// <param name="port">Port Name</param>
/// <param name="baud">Baud Rate</param>
/// <param name="dbit">Data Bits(def 8)</param>
/// <param name="sbit">Stop Bits 0 None, 1 One(def), 2 Two, 3 OnePointFive</param>
/// <param name="parity">-1 None(def), 0 Space, 1 Odd, 2 Even, 3 Mark</param>
/// <returns></returns>
public int Open(string port, int baud, int dbit = 8, int sbit = 1, int parity = -1) {
if (port.ToLower().StartsWith("com")) return open232(port, baud, dbit, sbit, parity);
return 0;
}
private int open232(string port, int baud, int dbit = 8, int sbit = 1, int parity = -1) {
int r = 0;
msg = "";
if (null != p) {
if (p.IsOpen) p.Close();
p.Dispose();
}
p = null;
p = new SerialPort();
p.PortName = port;
p.BaudRate = baud;
p.DataBits = 8;
p.Handshake = Handshake.None;
p.RtsEnable = true;启用请求发送信号
p.DtrEnable = true;启用控制终端就续信号
switch (sbit) {
case 0: p.StopBits = StopBits.None; break;
case 1: p.StopBits = StopBits.One; break;
case 2: p.StopBits = StopBits.Two; break;
default: p.StopBits = StopBits.OnePointFive; break;
}
switch (parity) {
case -1: p.Parity = Parity.None; break;
case 0: p.Parity = Parity.Space; break;
case 1: p.Parity = Parity.Odd; break;
case 2: p.Parity = Parity.Even; break;
default: p.Parity = Parity.Mark; break;
}
try {
p.Open();
} catch (Exception e) {
r = -500;
msg = e.Message;
}
if (r < 0) {
if (null != p) {
if (p.IsOpen) p.Close();
p.Dispose();
}
p = null;
return r;
}
islife = true;
p.DataReceived += receive;
Result = new byte[max];
i = 0;
return r;
}
#endregion
#region Send
public int Send(byte[] buf) {
if (p != null) return Send232(buf);
throw new InvalidOperationException("can't send");
}
private int Send232(byte[] buf) {
int r = 1;
if (!islife) return 0;
if (SendBeforeClear) {
p.DiscardInBuffer(); p.DiscardInBuffer(); p.DiscardInBuffer();
p.DiscardOutBuffer(); p.DiscardOutBuffer(); p.DiscardOutBuffer();
i = 0;
Result.Clear();
}
try {
p.Write(buf, 0, buf.Length);
} catch (Exception e) {
r = 2;
msg = e.Message;
}
return r;
}
#endregion
#region receive
private static object lck_result = new object();
private void receive(object sender, SerialDataReceivedEventArgs e) {
Monitor.Enter(lck_result);
if (!islife) return;
int len = 0;
try {
len = p.BytesToRead;
} catch (Exception ex) {
//Log("[BytesToRead] " + ex.Message);
islife = false;
}
if (!islife) return;
var buf = new byte[len];
try {
p.Read(buf, 0, len);
} catch (Exception ex) {
//Log("[Read] " + ex.Message);
}
if (max >= (i + len)) {
Result.SetData(buf, i);
i += len;
} else {
Result.SetData(buf, i, max - i);
i = max;
}
Monitor.Exit(lck_result);
}
#endregion
#region Dispose
public int Close() {
int r = 0;
msg = "";
try {
if (p?.IsOpen == true) p.Close();
} catch (Exception e) {
r = -500;
msg = e.Message;
}
islife = false;
return r;
}
public void Dispose() {
if (null != p) p.DataReceived -= receive;
Close();
try {
p?.Dispose();
} catch { }
p = null;
}
#endregion
#region LRC、CRC16
public void LRC(ref byte[] buf, int pos = 0, int len = -1, int vfypos = -1) {
int n = 0;
if (len < 0) len = buf.Length;
for (int i = pos; i < len; i++) n ^= buf[i];
if (vfypos < 0) buf[buf.Length + vfypos] = (byte)n;
else buf[vfypos] = (byte)n;
}
public int CRC16_POLYNOMIAL = 0x8408;
public int CRC16_DEFAULT = 0x0000;
public int? CRC16_CHECK = null;
public int CRC16_FOOTER { get { return CRC16_CHECK ?? 0; } set { CRC16_CHECK = value; } }
public bool CRC16(ref byte[] buf, int vfypos, int pos = 0, int len = -1, bool vfyreve = false) {
if (buf.Length < pos) return false;
if (len > 0 && buf.Length < (pos + len)) return false;
int crc = crc16(buf, pos, len);
if (vfyreve) {
buf[vfypos + 1] = (byte)(crc & 0xFF);
buf[vfypos] = (byte)(crc >> 8);
} else {
buf[vfypos] = (byte)(crc & 0xFF);
buf[vfypos + 1] = (byte)(crc >> 8);
}
return true;
}
public int CRC16(byte[] buf, int pos = 0, int count = -1) {
if (buf.Length < pos) return 0;
if (count > 0 && buf.Length < (pos + count)) return 0;
return crc16(buf, pos, count);
}
private int crc16(byte[] buf, int pos, int len) {
int crc = CRC16_DEFAULT; // PRESET_VALUE
int i = 0, j = 0;
if (len <= 0) len = buf.Length - pos;
byte b = 0;
for (i = pos; i < len + pos; i++) {
crc ^= buf[i];
for (j = 0; j < 8; j++) {
b = (byte)(crc & 0x01);
crc >>= 1;
if (b == 0x01) crc ^= CRC16_POLYNOMIAL;
}
}
crc = ~crc;
if (CRC16_CHECK.HasValue) crc ^= CRC16_CHECK.Value;
return crc;
}
public bool CRC16AD(ref byte[] buf, int vfypos, int pos = 0, int len = -1, bool vfyreve = false) {
if (buf.Length < pos) return false;
if (len > 0 && buf.Length < (pos + len)) return false;
int crc = crc16_andea(buf, pos, len);
if (vfyreve) {
buf[vfypos + 1] = (byte)(crc & 0xFF);
buf[vfypos] = (byte)(crc >> 8);
} else {
buf[vfypos] = (byte)(crc & 0xFF);
buf[vfypos + 1] = (byte)(crc >> 8);
}
return true;
}
private int crc16_andea(byte[] buf, int pos, int len) {
int crc = CRC16_DEFAULT; // PRESET_VALUE
int i = 0, j = 0;
if (len <= 0) len = buf.Length - pos;
byte b = 0;
for (i = pos; i < len + pos; i++) {
crc ^= buf[i];
crc = crc & CRC16_DEFAULT;
for (j = 0; j < 8; j++) {
b = (byte)(crc & 1);
crc >>= 1;
if (b == 0x01) {
crc ^= CRC16_POLYNOMIAL;
crc = crc & CRC16_DEFAULT;
}
}
}
return crc;
}
public bool CRC16Verify(byte[] buf, byte[] vfy, int pos = 0, int len = -1) {
if (buf.Length < pos) return false;
if (len > 0 && buf.Length < (pos + len)) return false;
if (vfy.Length < 2) return false;
int crc = crc16(buf, pos, len);
if (vfy[0] != (byte)(crc & 0xFF) || vfy[1] != (byte)(crc >> 8)) return false;
return true;
}
#endregion
public string[] Prots() {
var arr = SerialPort.GetPortNames();//获取当前计算机的串行端口名称数组。
Array.Sort(arr);
return arr;
}
public void Clear(int len) {
if (len <= 0) return;
if (len > Length) len = Length;
lock (lck_result) {
Result = Result.Remove(0, len);
i -= len;
}
}
}
public static class Rs232Exand {
/// <summary>
/// 组包
/// </summary>
/// <param name="me"></param>
/// <param name="buf"></param>
/// <param name="idx"></param>
/// <param name="len"></param>
/// <returns></returns>
public static byte[] SetData(this byte[] me, byte[] buf, int idx = 0, int len = -1) {
if (len < 0) len = buf.Length;
if ((idx + len) > me.Length) len -= ((idx + len) - me.Length);
Array.Copy(buf, 0, me, idx, len);
return me;
}
public static byte[] GetData(this byte[] me, int idx = 0, int len = -1) {
if (len < 0) len = me.Length - idx;
if ((idx + len) > me.Length) len -= ((idx + len) - me.Length);
var buf = new byte[len];
Array.Copy(me, idx, buf, 0, len);
return buf;
}
public static byte[] Clear(this byte[] buf) {
for (int i = 0; i < buf.Length; i++) buf[i] = 0;
return buf;
}
public static byte[] Remove(this byte[] data, int start, int len) {
byte[] result = new byte[data.Length];
if (start == 0) Array.Copy(data, len, result, 0, data.Length - len);
if (start > 0) {
Array.Copy(data, 0, result, 0, start);
Array.Copy(data, start + len, result, start, data.Length - (start + len));
}
return result;
}
public static int Find(this byte[] data, byte b, int start = 0, int len = -1) {
int i = 0;
int ok_start = -1;
if (len > 0) len = Math.Min(data.Length, len);
else len = data.Length;
for (i = start; i < len; i++) {
if (data[i] == b) {
ok_start = i;
break;
}
}
return ok_start;
}
public static int Find(this byte[] data, byte[] buf, int start = 0, int len = -1) {
int i = 0;
int buf_idx = 0;
int ok_start = -1;
if (len > 0) len = Math.Min(data.Length, len);
else len = data.Length;
for (i = start; i < len; i++) {
if (data[i] == buf[buf_idx]) {
if (ok_start == -1) ok_start = i;
buf_idx++;
if (buf_idx >= buf.Length) break;
} else {
buf_idx = 0;
ok_start = -1;
}
}
if (buf_idx < buf.Length) ok_start = -1;
return ok_start;
}
}
}