• C#串口通信程序实现无感知签到与答题


    最近公司项目上线,之前利用串口通讯实现校牌的无感知签到程序, 项目上线以后刚刚好有时间把之前的出现的问题做下记录,废话不多,直接到主题

    串口介绍:

    串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。(至于再详细,自己百度)

    正文:

    最近在公司让用C#写一个串口通讯程序,下面我将这次遇到的问题和解决方法奉献出来,希望对工作中需要的朋友有所帮助!

    我们来看具体的实现步骤。

    公司要求实现以下几个功能:

    1.)启动程序打开串口通信,接受嵌入式校牌发送过来的16进制形式的数据指令执行业务操作,业务操作完做出回应。

    2.)根据需要设置串口通信的必要参数。

    3.)通过校牌指令执行相关业务,拉取数据通过访问java的http接口获取数据,并将数据进行处理转换为16进制形式下发给校牌

    4.)配置相关接口地址

    5.)校牌答题与教室端互动通过本地UPD传递给教室端,

    看着好像挺复杂,其实都是纸老虎,一戳就破,前提是你敢去戳。我尽量讲的详细一些,争取说到每个知识点。

    C#代码实现:采用SerialPort

    实例化一个SerialPort

    1. private SerialPort ComDevice = new SerialPort();
    

    我自己写了个串口的类就直接上代码

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Configuration;
      4 using System.IO.Ports;
      5 using System.Linq;
      6 using System.Text;
      7 using System.Threading;
      8 using System.Threading.Tasks;
      9 
     10 namespace ZPZSerialPort.ComSerialPort
     11 {
     12     public sealed class ComDeviceManager
     13     {
     14         private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();//NLog日志记录串口信息
     15         private static ComDeviceManager _comManager;
     16         private static readonly object _instAsync = new object();
     17         public SerialPort ComDeviceComputerChip { get; private set; }
     18 
     19         public Action<Byte[]> ActionComputerChip { get; set; }
     20 
     21         /// <summary>
     22         /// 此处配置根据实际串口进行配置,也可以配置为可变的参数
     23         /// </summary>
     24         private ComDeviceManager()
     25         {
     26             ComDeviceComputerChip = new SerialPort();//实例化一个SerialPort
     27             ComDeviceComputerChip.PortName = ConfigurationManager.AppSettings["protnamexyt"];//端口号此处端口号不固定此处配置为可变参数
     28             ComDeviceComputerChip.BaudRate = 115200;// 串行波特率指定为115200
     29             ComDeviceComputerChip.Parity = (Parity)Convert.ToInt32("0");//
     30             ComDeviceComputerChip.DataBits = Convert.ToInt32("8");
     31             ComDeviceComputerChip.StopBits = (StopBits)Convert.ToInt32("1");
     32             ComDeviceComputerChip.DataReceived += ComDevice1_DataReceived;
     33 
     34         }
     35         /// <summary>
     36         /// 接受端口数据事件
     37         /// </summary>
     38         /// <param name="sender"></param>
     39         /// <param name="e"></param>
     40         private void ComDevice1_DataReceived(object sender, SerialDataReceivedEventArgs e)
     41         {
     42             byte[] buffers = new byte[ComDeviceComputerChip.BytesToRead];
     43             ComDeviceComputerChip.Read(buffers, 0, buffers.Length);
     44             ActionComputerChip?.Invoke(buffers);
     45         }
     46         /// <summary>
     47         /// 当前设备
     48         /// </summary>
     49         public static ComDeviceManager CurrentDevice
     50         {
     51             get
     52             {
     53                 if (_comManager == null)
     54                 {
     55                     lock (_instAsync)
     56                     {
     57                         if (_comManager == null)
     58                         {
     59                             return _comManager = new ComDeviceManager();
     60                         }
     61                     }
     62                 }
     63 
     64                 return _comManager;
     65             }
     66         }
     67         /// <summary>
     68         /// 打开端口
     69         /// </summary>
     70         /// <returns></returns>
     71         public bool OpenDevice()
     72         {
     73             try
     74             {
     75                 if (!ComDeviceComputerChip.IsOpen)
     76                 {
     77                     ComDeviceComputerChip.Open();
     78                 }
     79                 return true;
     80             }
     81             catch (Exception ex)
     82             {
     83                 logger.Error("打开设备错误:"+ex);
     84             }
     85 
     86             return false;
     87         }
     88         /// <summary>
     89         /// 发送数据
     90         /// </summary>
     91         /// <param name="data"></param>
     92         /// <returns></returns>
     93         public bool SendDzxp(byte[] data)
     94         {
     95             try
     96             {
     97                 if (ComDeviceComputerChip.IsOpen)
     98                 {
     99                     Thread.Sleep(10);// 延迟发送必须做延迟发送不然发送给校牌接受不到,这个问题浪费了一上午事件才发送在发送得时候需要做延迟
    100                     ComDeviceComputerChip.Write(data, 0, data.Length);//发送数据给串口端口
    101                     Thread.Sleep(10);// 延迟发送
    102                     return true;
    103                 }
    104             }
    105             catch (Exception ex)
    106             {
    107                 logger.Error(ex);
    108             }
    109 
    110             return false;
    111         }
    112 
    113 
    114     }
    115 }
     

    设备操作类已经编写完毕,接着就是我们收到指令主动执行操作:操作的步骤如下几点

    1.)同步时间

    收到同步时间指令获取当前系统时间转换为16进制字节,进行CRC校验之后带上,发送给基站,发送的格式为

    引导码+发送码+卡号+响应成功码+长度+内容(当前时间)+校验码

    2.)同步课程

    收到同步课程指令先通过接口拉取数据,把拉取到json数据解析,上课的开始时间,频点,日期,星期 数据进行解析为16进制字节数组

    引导码+发送码+卡号+响应成功码+长度+内容(一天课程上课时间)+校验码

    拉取到的课程与校牌成功以后 把卡号,频点,同步成功最后课程的时间 提交给接口保存

    3.)签到

    收到签到指令 进行回复

    引导码+发送码+卡号+响应成功码+长度+内容(校牌发送的签到指令)+校验码

    把校牌卡号与课程ID 提交给接口保存

    一 通讯层格式

    请求/控制数据帧

    引导码

    数据传输方向

    设备IC卡号

    命令码

    数据包长度

    数据内容

    校验码

    CRC16

    FA FA

    D0/D1

    4 bytes

    0x00~0xFF

    0x00~0x3F

    0~N

    CRC_L

    CRC_H

    • 引导码:2 bytes0xFA 0xFA
    • 数据传输方向:1 byte0xD0为电子校牌上传数据给服务器,0xD1为服务器下发数据到电子校牌;
    • 设备IC卡号:4 byte,对应内嵌电子校牌的IC卡号;
    • 命令码:1 byte,取值范围为0x00 – 0xFF
    • 数据包长度:1 byte0x00 – 0x3F
    • 数据内容:传输的数据信息,长度大小与数据包长度一致;
    • 校验码:2 bytes,低字节在前,高字节在后,采用CRC16校验方式,校验数据包括从数据传输方向到数据内容;

    响应数据帧

    引导码

    数据传输方向

    设备IC卡号

    命令码

    响应标志码

    数据包长度

    数据内容

    校验码

    CRC16

    FA FA

    D0/D1

    4 bytes

    0x00~0xFF

    0x80/0x81

    0x00~0x3F

    0~N

    CRC_L

    CRC_H

    • 引导码:2 bytes0xFA 0xFA
    • 数据传输方向:1 byte0xD0为终端设备上传数据给服务器,0xD1为服务器下发数据到终端设备;
    • 设备IC卡号:4 byte,对应内嵌电子校牌的IC卡号;
    • 命令码:1 byte,取值范围为0x00 – 0xFF
    • 响应标志码:1 byte0x80-----接收正确;0x81----接收有误;

        数据有误码:0x01-----数据格式有误

                        0x02-----校验码错误

                        0x03-----题型有误

    • 数据包长度:1 byte0x00 – 0x3F
    • 数据内容:传输的数据信息,长度大小与数据包长度一致;
    • 校验码:2 bytes,低字节在前,高字节在后,采用CRC16校验方式,校验数据包括从数据传输方向到数据内容;

     

    二 详细命令解析

    (以设备IC卡号为0xA0 0xA1 0xA2 0xA3为例)

    1. 电子校牌连接基站服务器 0x00

      命令码: 0x00

      数据内容:年/月/日/星期/时/分/秒 7 bytes

      举例:

      Send: FA FA D0 A0 A1 A2 A3 00 00 CRC16

      Recv: FA FA D1 A0 A1 A2 A3 00 80 07 YY MM DD WW hh mm ss CRC16 // 连接成功

    2. 电子校牌请求服务器同步课程表 0x01

      命令码: 0x01

      数据内容:ID号:A0 A1 A2 A3

      FF FF FF FF 表示对所有电子校牌统一下发

      N=2n+1:课程表(时间、频点) 星期几+(时间(小时/分钟)+频点)* n(课节数,最大10)

      Weekday:星期一 ~ 星期六(1~6), 星期日: 0

                   时间(H/M):((H-6)<< 4) | (M/5) 分钟为5的倍数

      举例:

      Send: FA FA D0 A0 A1 A2 A3 01 00 CRC16 // 校牌请求下发课程表

      Recv: FA FA D1 A0 A1 A2 A3 01 80 N weekday 1...2n CRC16 // 服务器下发课程表

      Send: FA FA D0 A0 A1 A2 A3 01 80 01 weekday CRC16 //校牌回复设置课程表成功

    3. 电子校牌完成签到功能 0x02

      命令码: 0x02

      数据内容: 年/月/日/时/分/秒 6 bytes

      举例:

      Send: FA FA D0 A0 A1 A2 A3 02 06 YY MM DD hh mm ss CRC16

      Recv: FA FA D1 A0 A1 A2 A3 02 80 06 YY MM DD hh mm ss CRC16 // 签到成功

      处理相关业务逻辑使用工厂模式

       
       1 using System;
       2 using System.Collections.Generic;
       3 using System.Linq;
       4 using System.Text;
       5 using System.Threading.Tasks;
       6 
       7 namespace ZPZSerialPort.Factory
       8 {
       9     public interface  ICommunication
      10     {
      11         bool Send(object data);
      12     }
      13     /// <summary>
      14     /// 同步时间
      15     /// </summary>
      16     public class SyncTime : ICommunication//
      17     {
      18         public bool Send(object data)
      19         {
      20             Console.WriteLine("同步时间接受的数据");
      21             return true;
      22         }
      23     }
      24     /// <summary>
      25     /// 同步课程
      26     /// </summary>
      27     public class SyncCourse : ICommunication
      28     {
      29         public bool Send(object data)
      30         {
      31             Console.WriteLine("同步课程接受的数据");
      32             return true;
      33         }
      34     }
      35     /// <summary>
      36     /// 签到
      37     /// </summary>
      38     public class Sign : ICommunication
      39     {
      40         public bool Send(object data)
      41         {
      42             Console.WriteLine("同步课程接受的数据");
      43             return true;
      44         }
      45 
      46     }
      47     /// <summary>
      48     /// 答题
      49     /// </summary>
      50     public class Answer : ICommunication
      51     {
      52         public bool Send(object data)
      53         {
      54             Console.WriteLine("答题接受的数据");
      55             return true;
      56         }
      57     }
      58 
      59 
      60 }
       1 using System;
       2 using System.Collections.Generic;
       3 using System.Linq;
       4 using System.Text;
       5 using System.Threading.Tasks;
       6 
       7 namespace ZPZSerialPort.Factory
       8 {
       9     /// <summary>
      10     /// 通讯工厂
      11     /// </summary>
      12     public class CommunicationFactory
      13     {
      14         public ICommunication CreateCommunicationFactory(string style)
      15         {
      16             switch (style)
      17             {
      18                 case "SyncTime"://同步时间
      19                     return new SyncTime();
      20                 case "SyncCourse"://同步课程
      21                     return new SyncCourse();
      22                 case "Sign"://签到
      23                     return new Sign();
      24                 case "Answer"://答题
      25                     return new Answer();
      26             }
      27             return null;
      28         }
      29     }
      30 }
       

      处理接受得数据实体

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 
      7 namespace ZPZSerialPort.COM_USB
      8 {
      9     /// <summary>
     10     /// 响应数据帧
     11     /// </summary>
     12     public class USBComReceiveEntity
     13     {
     14         //引导码 2 bytes,0xFA 0xFA
     15         public string header { get; set; }
     16 
     17         //数据传输方向  1 byte,0xD0为电子校牌上传数据给服务器,0xD1为服务器下发数据到电子校牌
     18         public string direction { get; set; }
     19 
     20         //设备IC卡号 4 byte,对应内嵌电子校牌的IC卡号
     21         public string icCard { get; set; }
     22 
     23         //命令码 1 byte,取值范围为0x00 – 0xFF
     24         public string code { get; set; }
     25 
     26         //响应标志码:1 byte,0x80-----接收正确;0x81----接收有误
     27         public string response { get; set; }
     28 
     29         //数据包长度 1 byte,0x00 – 0x3F
     30         public string length { get; set; }
     31 
     32         //数据内容 传输的数据信息,长度大小与数据包长度一致
     33         public string content { get; set; }
     34 
     35         //校验码CRC16 2 bytes,低字节在前,高字节在后,采用CRC16校验方式,校验数据包括从数据传输方向到数据内容
     36         public string check { get; set; }
     37 
     38         /// <summary>
     39         /// set 实体
     40         /// </summary>
     41         /// <param name="str"></param>
     42         /// <returns></returns>
     43         public static USBComReceiveEntity SetReceiveEntity(string str)
     44         {
     45             if (str == null || str.Length == 0) return null;
     46             USBComReceiveEntity entity = new USBComReceiveEntity();
     47             str = str.Replace(" ", "");
     48             if (str.Length >= 4) entity.header = str.Substring(0, 4);
     49             if (str.Length >= 6) entity.direction = str.Substring(4, 2);
     50             if (str.Length >= 14) entity.icCard = str.Substring(6, 8);
     51             if (str.Length >= 16) entity.code = str.Substring(14, 2);
     52             if (str.Length >= 18) entity.response = str.Substring(16, 2);
     53             if (str.Length >= 20) entity.length = str.Substring(18, 2);
     54             int count = 0;
     55             if (entity.length != null && entity.length.Length > 0) count = int.Parse(entity.length) * 2;
     56             if (count > 0 && str.Length >= 20 + count) entity.content = str.Substring(20, count);
     57             if (str.Length >= count + 20 + 4) entity.check = str.Substring(20 + count, 4);
     58             return entity;
     59         }
     60 
     61         /// <summary>
     62         /// 校验码CRC16
     63         /// </summary>
     64         /// <param name="sendEntity"></param>
     65         /// <returns></returns>
     66         public static string getCheckString(USBComReceiveEntity sendEntity)
     67         {
     68             string str = "";
     69             if (sendEntity.direction == null || sendEntity.direction.Length == 0) str = str + USBComUtil.Com_Send;
     70             else str = str + sendEntity.direction;
     71             if (sendEntity.icCard == null || sendEntity.icCard.Length == 0) str = str + "";
     72             else str = str + sendEntity.icCard;
     73             if (sendEntity.code == null || sendEntity.code.Length == 0) str = str + "";
     74             else str = str + sendEntity.code;
     75             if (sendEntity.response == null || sendEntity.response.Length == 0) str = str + "";
     76             else str = str + sendEntity.response;
     77             if (sendEntity.length == null || sendEntity.length.Length == 0) str = str + "";
     78             else str = str + sendEntity.length;
     79             if (sendEntity.content == null || sendEntity.content.Length == 0) str = str + "";
     80             else str = str + sendEntity.content;
     81             return CRCUtil.ToModbusCRC16(str);
     82         }
     83 
     84         /// <summary>
     85         /// 返回实体字符串
     86         /// </summary>
     87         /// <param name="sendEntity"></param>
     88         /// <returns></returns>
     89         public static string getEntityToString(USBComReceiveEntity sendEntity)
     90         {
     91             string str = "";
     92             if (sendEntity.header == null || sendEntity.header.Length == 0) str = USBComUtil.Com_Header;
     93             else str = sendEntity.header;
     94             if (sendEntity.direction == null || sendEntity.direction.Length == 0) str = str + USBComUtil.Com_Send;
     95             else str = str + sendEntity.direction;
     96             if (sendEntity.icCard == null || sendEntity.icCard.Length == 0) str = str + "";
     97             else str = str + sendEntity.icCard;
     98             if (sendEntity.code == null || sendEntity.code.Length == 0) str = str + "";
     99             else str = str + sendEntity.code;
    100             if (sendEntity.response == null || sendEntity.response.Length == 0) str = str + "";
    101             else str = str + sendEntity.response;
    102             if (sendEntity.length == null || sendEntity.length.Length == 0) str = str + "";
    103             else str = str + sendEntity.length;
    104             if (sendEntity.content == null || sendEntity.content.Length == 0) str = str + "";
    105             else str = str + sendEntity.content;
    106             if (sendEntity.check == null || sendEntity.check.Length == 0) str = str + "";
    107             else str = str + sendEntity.check;
    108             return str;
    109         }
    110     }
    111 }

    CRC16校验 算法类

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 
      7 namespace ZPZSerialPort.COM_USB
      8 {
      9     public class CRCUtil
     10     {
     11         #region  CRC16
     12         public static byte[] CRC16(byte[] data)
     13         {
     14             int len = data.Length;
     15             if (len > 0)
     16             {
     17                 ushort crc = 0xFFFF;
     18 
     19                 for (int i = 0; i < len; i++)
     20                 {
     21                     crc = (ushort)(crc ^ (data[i]));
     22                     for (int j = 0; j < 8; j++)
     23                     {
     24                         crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
     25                     }
     26                 }
     27                 byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
     28                 byte lo = (byte)(crc & 0x00FF);         //低位置
     29 
     30                 return new byte[] { lo, hi };
     31             }
     32             return new byte[] { 0, 0 };
     33         }
     34         #endregion
     35 
     36         #region  ToCRC16
     37         public static string ToCRC16(string content)
     38         {
     39             return ToCRC16(content, Encoding.UTF8);
     40         }
     41 
     42         public static string ToCRC16(string content, bool isReverse)
     43         {
     44             return ToCRC16(content, Encoding.UTF8, isReverse);
     45         }
     46 
     47         public static string ToCRC16(string content, Encoding encoding)
     48         {
     49             return ByteToString(CRC16(encoding.GetBytes(content)), true);
     50         }
     51 
     52         public static string ToCRC16(string content, Encoding encoding, bool isReverse)
     53         {
     54             return ByteToString(CRC16(encoding.GetBytes(content)), isReverse);
     55         }
     56 
     57         public static string ToCRC16(byte[] data)
     58         {
     59             return ByteToString(CRC16(data), true);
     60         }
     61 
     62         public static string ToCRC16(byte[] data, bool isReverse)
     63         {
     64             return ByteToString(CRC16(data), isReverse);
     65         }
     66         #endregion
     67 
     68         #region  ToModbusCRC16
     69         public static string ToModbusCRC16(string s)
     70         {
     71             return ToModbusCRC16(s, true);
     72         }
     73 
     74         public static string ToModbusCRC16(string s, bool isReverse)
     75         {
     76             return ByteToString(CRC16(StringToHexByte(s)), isReverse);
     77         }
     78 
     79         public static string ToModbusCRC16(byte[] data)
     80         {
     81             return ToModbusCRC16(data, true);
     82         }
     83 
     84         public static string ToModbusCRC16(byte[] data, bool isReverse)
     85         {
     86             return ByteToString(CRC16(data), isReverse);
     87         }
     88         #endregion
     89 
     90         #region  ByteToString
     91         public static string ByteToString(byte[] arr, bool isReverse)
     92         {
     93             try
     94             {
     95                 byte hi = arr[0], lo = arr[1];
     96                 return Convert.ToString(isReverse ? hi + lo * 0x100 : hi * 0x100 + lo, 16).ToUpper().PadLeft(4, '0');
     97             }
     98             catch (Exception ex) { throw (ex); }
     99         }
    100 
    101         public static string ByteToString(byte[] arr)
    102         {
    103             try
    104             {
    105                 return ByteToString(arr, true);
    106             }
    107             catch (Exception ex) { throw (ex); }
    108         }
    109         #endregion
    110 
    111         #region  StringToHexString
    112         public static string StringToHexString(string str)
    113         {
    114             StringBuilder s = new StringBuilder();
    115             foreach (short c in str.ToCharArray())
    116             {
    117                 s.Append(c.ToString("X4"));
    118             }
    119             return s.ToString();
    120         }
    121         #endregion
    122 
    123         #region  StringToHexByte
    124         private static string ConvertChinese(string str)
    125         {
    126             StringBuilder s = new StringBuilder();
    127             foreach (short c in str.ToCharArray())
    128             {
    129                 if (c <= 0 || c >= 127)
    130                 {
    131                     s.Append(c.ToString("X4"));
    132                 }
    133                 else
    134                 {
    135                     s.Append((char)c);
    136                 }
    137             }
    138             return s.ToString();
    139         }
    140 
    141         private static string FilterChinese(string str)
    142         {
    143             StringBuilder s = new StringBuilder();
    144             foreach (short c in str.ToCharArray())
    145             {
    146                 if (c > 0 && c < 127)
    147                 {
    148                     s.Append((char)c);
    149                 }
    150             }
    151             return s.ToString();
    152         }
    153 
    154         /// <summary>
    155         /// 字符串转16进制字符数组
    156         /// </summary>
    157         /// <param name="hex"></param>
    158         /// <returns></returns>
    159         public static byte[] StringToHexByte(string str)
    160         {
    161             return StringToHexByte(str, false);
    162         }
    163 
    164         /// <summary>
    165         /// 字符串转16进制字符数组
    166         /// </summary>
    167         /// <param name="str"></param>
    168         /// <param name="isFilterChinese">是否过滤掉中文字符</param>
    169         /// <returns></returns>
    170         public static byte[] StringToHexByte(string str, bool isFilterChinese)
    171         {
    172             string hex = isFilterChinese ? FilterChinese(str) : ConvertChinese(str);
    173 
    174             //清除所有空格
    175             hex = hex.Replace(" ", "");
    176             //若字符个数为奇数,补一个0
    177             hex += hex.Length % 2 != 0 ? "0" : "";
    178 
    179             byte[] result = new byte[hex.Length / 2];
    180             for (int i = 0, c = result.Length; i < c; i++)
    181             {
    182                 result[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
    183             }
    184             return result;
    185         }
    186         #endregion
    187     }
    188 }

    具体得业务代码就不贴出来了,由于是公司产品项目,大家都明白我也不多说。

    代码下载:ZPZSerialPort.rar

    不足之处,还望见谅!

  • 相关阅读:
    CSS弹性盒布局(display:flex)
    CSS中的display属性(none,block,inline,inline-block,inherit)
    【Python全栈-JavaScript】JavaScript的window.onload()与jQuery 的ready()的区别
    【数据可视化-Echarts】Echart基础
    【Python全栈】HTML <!--...--> 注释 、CSS/JS //注释 和 /*.....*/ 注释
    Python内置函数
    【Django】Model操作进阶
    【Django】Form组件-2
    【Django】分页
    【Django】模板语言
  • 原文地址:https://www.cnblogs.com/zengzhanping/p/10072037.html
Copyright © 2020-2023  润新知