.Net Core应用增强型跨平台串口类库CustomSerialPort()详解

目录
  • 摘要
  • 引言
  • 基础类库的选择
  • 类库的实现
    • 创建跨平台类库
    • 实现机制/条件
    • .net core跨平台实现
    • 主要代码
  • 创建.net core控制台程序
  • 类库地址
  • 跨平台测试
    • Windows测试输出界面
    • ubuntu测试输出界面
  • 源码地址

摘要

在使用SerialPort进行串口协议解析过程中,经常遇到接收单帧协议数据串口接收事件多次触发,协议解析麻烦的问题。针对此情况,基于开源跨平台串口类库SerialPortStrem进行了进一步封装,实现了一种接收超时响应事件机制,简化串口通讯的使用。

引言

最近,写了一篇博文《.net core跨平台应用研究-串口篇》得到了一些园友的好评,文中介绍了在跨平台应用研究过程中,在dotnet core下使用SerialPort类库在linux下不能支持的踩坑经历及解决办法。

因网上关于SerialPort类库使用的相关文章较多,在该文中,对串口类库的使用,一笔带过。但在实际使用,使用过SerialPort类库的同学,可能遇到过在数据接收时,由于数据接收事件的触发具有不确定性,很多时候,一帧通讯协议数据,会多次触发,造成程序处理协议数据较为麻烦的问题。

为简化串口通讯类库的使用,笔者结合自己的相关经验,封装了一个自定义增强型跨平台串口类库,以解决一帧协议数据,多次触发的问题。

基础类库的选择

由于考虑的是跨平台应用,SerialPort类库并不支持linux系统(在前一篇文章中已介绍过踩坑经历),笔者选用了SerialPortStream类库进行封装。

该类库支持windows系统和Linux系统,但在Linux系统下运行,需要额外编译目标平台支持库并进行相关环境配置。

相关编译配置说明在https://github.com/jcurl/SerialPortStream已有介绍,也可参考本人的拙作《.net core跨平台应用研究-串口篇》

类库的实现

创建跨平台类库

为了支持跨平台,我们使用Visual Studio 2017创建一个基于.NET Standard的类库。

NET Standard是一项API规范,每一个特定的版本,都定义了必须实现的基类库。

.NET Core是一个托管框架,针对构建控制台、云、ASP.NET Core和UWP应用程序进行了优化。

每一种托管实现(如.NET Core、.NET Framework或Xamarin)都必须遵循.NET Standard实现基类库(BCL)。

关于NET Standard和跨平台的详细说明在此:

//www.jb51.net/article/234699.htm

笔者也不再啰嗦呵。

实现机制/条件

通常串口通讯中,发送数据后,会有一段时间用于等待接收方应答,如此一来,两次数据发送之间,必然会有一定的时间间隔。如ModbusRTU协议就规定,两次数据报文发送之间,需要等待超过发送4个字节以上的间隔时间。

笔者在单片机以及实时性较高的嵌入式系统中,为处理串口接收与协议的无关性,通常采用数据帧接收超时来处理数据帧的接收。根据串口通讯的速率计算出两次通讯之间所需要超时间隔,取两倍超时间隔时间作为超时参数,每接收到一个字节,将数据放入缓冲区并进行计时,当最后一个字节的接收时间超过超时时间,返回接收数据并清空缓存,一次完整接收完成(DMA接收方式不在此讨论)。

.net core跨平台实现

在自定义的串口类中,订阅基础串口类数据接收事件,在接收事件每次触发后,读出当前可用的缓冲数据到自定义缓冲区,同时,标记最后接收时间Tick为当前系统Tick。判断是否开启了接收超时处理线程,如未开启,则开启一个接收超时处理线程。

接收超时处理线程中,以一个较小的时间间隔进行判断,如果最后接收时间与当前时间之间的间隔小于设置值(默认128ms),休眠一段时间(默认16ms)后循环检查。如间隔时间大于设定值,触发外部接收订阅事件,传出接收到的数据,退出超时处理线程。

此处应有流程图。呵呵,懒得画了,大家自行脑补吧。 ^_^

在windows系统或linux系统中,因系统的多任务处理的特性,系统实时性较差,通常50ms以下时间间隔的定时任务,较大程度会出现不可靠的情况(任务执行时间都有可能超过调用间隔时间)。

因此,默认超时时间间隔设置为128ms。也可根据实际使用情况调整,但最小间隔不宜低于64ms。

注:此处为个人经验和理解,如不认同,请直接忽视。

主要代码

串口接收事件代码:

         protected void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
         {
             int canReadBytesLen = 0;
             if (ReceiveTimeoutEnable)
             {
                 while (sp.BytesToRead > 0)
                 {
                     canReadBytesLen = sp.BytesToRead;
                     if (receiveDatalen + canReadBytesLen > BufSize)
                     {
                         receiveDatalen = 0;
                         throw new Exception("Serial port receives buffer overflow!");
                     }
                     var receiveLen = sp.Read(recviceBuffer, receiveDatalen, canReadBytesLen);
                     if (receiveLen != canReadBytesLen)
                     {
                         receiveDatalen = 0;
                         throw new Exception("Serial port receives exception!");
                     }
                     //Array.Copy(recviceBuffer, 0, receivedBytes, receiveDatalen, receiveLen);
                     receiveDatalen += receiveLen;
                     lastReceiveTick = Environment.TickCount;
                     if (!TimeoutCheckThreadIsWork)
                     {
                         TimeoutCheckThreadIsWork = true;
                         Thread thread = new Thread(ReceiveTimeoutCheckFunc)
                         {
                             Name = "ComReceiveTimeoutCheckThread"
                         };
                         thread.Start();
                     }
                 }
             }
             else
             {
                 if (ReceivedEvent != null)
                 {
                     // 获取字节长度
                     int bytesNum = sp.BytesToRead;
                     if (bytesNum == 0)
                         return;
                     // 创建字节数组
                     byte[] resultBuffer = new byte[bytesNum];

                     int i = 0;
                     while (i < bytesNum)
                     {
                         // 读取数据到缓冲区
                         int j = sp.Read(recviceBuffer, i, bytesNum - i);
                         i += j;
                     }
                     Array.Copy(recviceBuffer, 0, resultBuffer, 0, i);
                     ReceivedEvent(this, resultBuffer);
                     //System.Diagnostics.Debug.WriteLine("len " + i.ToString() + " " + ByteToHexStr(resultBuffer));
                 }
                 //Array.Clear (receivedBytes,0,receivedBytes.Length );
                 receiveDatalen = 0;
             }
         }

接收超时处理线程代码:

         /// <summary>
         /// 超时返回数据处理线程方法
         /// </summary>
         protected void ReceiveTimeoutCheckFunc()
         {
             while (TimeoutCheckThreadIsWork)
             {
                 if (Environment.TickCount - lastReceiveTick > ReceiveTimeout)
                 {
                     if (ReceivedEvent != null)
                     {
                         byte[] returnBytes = new byte[receiveDatalen];
                         Array.Copy(recviceBuffer, 0, returnBytes, 0, receiveDatalen);
                         ReceivedEvent(this, returnBytes);
                     }
                     //Array.Clear (receivedBytes,0,receivedBytes.Length );
                     receiveDatalen = 0;
                     TimeoutCheckThreadIsWork = false;
                 }
                 else
                     Thread.Sleep(16);
             }
         }

创建.net core控制台程序

为验证我们的类库是否能够正常工作,我们创建一个使用类库的.net core控制台程序。

为啥选择dotnet core,原因很简单,跨平台。本程序分别需在windows和linux系统下进行运行测试。

  • 显示系统信息(系统标识、程序标识等)
  • 列举系统可用串口资源
  • 选择串口
  • 打开串口/关闭串口
  • 串口测试(打开/发送/关闭)
         static void Main(string[] args)
         {
             SetLibPath();
             ShowWelcome();

             GetPortNames();
             ShowPortNames();

             if (serailports.Length == 0)
             {
                 Console.WriteLine($"Press any key to exit");
                 Console.ReadKey();

                 return;
             }
 #if RunIsService
             RunService();
 #endif

             bool quit = false;
             while (!quit)
             {
                 Console.WriteLine("\r\nPlease Input command Key\r\n");
                 Console.WriteLine("p:Show SerialPort List");
                 Console.WriteLine($"t:Test Uart:\"{selectedComPort}\"");
                 Console.WriteLine($"o:Open Uart:\"{selectedComPort}\"");
                 Console.WriteLine($"c:Close Uart:\"{selectedComPort}\"");
                 Console.WriteLine("n:select next serial port");
                 Console.WriteLine("q:exit app");
                 Console.WriteLine();
                 var key = Console.ReadKey().KeyChar;
                 Console.WriteLine();

                 switch (key)
                 {
                     case (Char)27:
                     case 'q':
                     case 'Q':
                         quit = true;
                         break;
                     case 's':
                         ShowWelcome();
                         break;
                     case 'p':
                         ShowPortNames();
                         break;
                     case 'n':
                         SelectSerialPort();
                         break;
                     case 't':
                         TestUart(selectedComPort);
                         break;
                     case 'w':
                         TestWinUart(selectedComPort);
                         break;
                     case 'o':
                         OpenUart(selectedComPort);
                         break;
                     case 'c':
                         CloseUart();
                         break;
                 }
             }
         }

笔者使用类库是直接引用类库项目,大家需要使用的话,可在解决方案资源管理器中,项目的依赖项上点击右键

在NuGet包管理器中,搜索SerialPort或flyfire即可找到并安装本类库。

类库地址

类库地址:https://www.nuget.org/packages/flyfire.CustomSerialPort

跨平台测试

Windows测试输出界面

ubuntu测试输出界面

源码地址

类库源码与例程地址:https://github.com/flyfire-cn/flyfire.CustomSerialPort

有需要的同学,请自行获取。

到此这篇关于.Net Core应用增强型跨平台串口类库CustomSerialPort()的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • .Net Core跨平台应用开发串口篇HelloArm

    目录 引言 硬件环境 硬件资源特性 支持的系统平台 系统平台 .NET Core支持的Linux版本 系统平台选择 连接嵌入式Linux系统 物理连接 串口连接 网络连接 .NET Core跨平台验证 验证程序设计 显示系统平台信息 串口资源列举 串口测试 跨平台发布 远程部署 Linux环境运行.Net Core程序 第三方串口类库 Linux串口类库编译 环境变量配置 Linux串口测试 配置程序开机运行 结束语 引言 为了验证采用dotnet core技术开发的物联网设备数据采集接入服务应

  • .NET Core使用flyfire.CustomSerialPort实现Windows/Linux跨平台串口通讯

    1,前言 开发环境:在 Visual Studio 2017,.NET Core 2.x 串口通讯用于设备之间,传递数据,物联网设备中广泛使用串口方式连接通讯,物联网通讯协议 :Modbus 协议 ASCII.RTU.TCP模式是应用层的协议,与通讯方式无关. 笔者现在实现的是 串口通信,实现后,可以在上层加上 Modbus 协议,笔者的另一篇文章即是在串口上实现 Modbus 协议,计算中心向物联网设备发送消息,要求设备响应,传送设备信息.检测状态等. 本文是 串口通讯 的实现. 2,安装虚拟

  • .Net Core应用增强型跨平台串口类库CustomSerialPort()详解

    目录 摘要 引言 基础类库的选择 类库的实现 创建跨平台类库 实现机制/条件 .net core跨平台实现 主要代码 创建.net core控制台程序 类库地址 跨平台测试 Windows测试输出界面 ubuntu测试输出界面 源码地址 摘要 在使用SerialPort进行串口协议解析过程中,经常遇到接收单帧协议数据串口接收事件多次触发,协议解析麻烦的问题.针对此情况,基于开源跨平台串口类库SerialPortStrem进行了进一步封装,实现了一种接收超时响应事件机制,简化串口通讯的使用. 引言

  • .NET Core中Object Pool的多种用法详解

    前言 复用,是一个重要的话题,也是我们日常开发中经常遇到的,不可避免的问题. 举个最为简单,大家最为熟悉的例子,数据库连接池,就是复用数据库连接. 那么复用的意义在那里呢? 简单来说就是减少不必要的资源损耗. 除了数据库连接,可能在不同的情景或需求下,还会有很多其他对象需要进行复用,这个时候就会有所谓的 Object Pool(对象池). 小伙伴们应该也自己实现过类似的功能,或用ConcurrentBag,或用ConcurrentQueue,或用其他方案. 这也里分享一个在微软文档中的实现 Ho

  • .NET Core利用 AsyncLocal 实现共享变量的代码详解

    目录 简介 AsyncLocal 解读 总结 简介 我们如果需要整个程序共享一个变量,我们仅需将该变量放在某个静态类的静态变量上即可(不满足我们的需求,静态变量上,整个程序都是固定值).我们在Web 应用程序中,每个Web 请求服务器都为其分配了一个独立线程,如何实现用户,租户等信息隔离在这些独立线程中.这就是今天要说的线程本地存储.针对线程本地存储 .NET 给我们提供了两个类 ThreadLocal 和 AsyncLocal.我们可以通过查看以下例子清晰的看到两者的区别: [TestClas

  • 使用JavaScript开发跨平台的桌面应用详解

    任何可以使用JavaScript来编写的应用,最终会由JavaScript编写.--Atwood定律 Atwood's Law是Jeff Atwood在2007年提出的:"any application that can be written in JavaScript, will eventually be written in JavaScript.".据说,这只是当时开的一个玩笑.不过,这个玩笑似乎渐渐变成了现实.从各种华丽的网页框架,到功能强大的库,到了现在的机器学习,服务器开

  • ASP.NET Core扩展库之实体映射使用详解

    在分层设计模式中,各层之间的数据通常通过数据传输对象(DTO)来进行数据的传递,而大多数情况下,各层数据的定义结构大同小异,如何在这些定义结构中相互转换,之前我们通过使用AutoMapper库,但AutoMapper功能庞大,使用较为复杂,而在很多场景下,可能我们只需要一些基础的对象映射功能,那么此时你可以选择扩展库中的轻量级AutoMapper实现. 实体映射包含以下核心功能: 在使用之前无需手动定义类型之间的映射关系 采用动态编译.缓存转换委托,提升性能. 支持通过特性定义属性映射关系 支持

  • C#中的串口通信SerialPort详解

    今天这篇文章带大家学习下C#中的串口通讯.在日常的开发工作中,如果工作内容是CS方向的同学应该很容易接触到串口通讯方面的业务需求.那么也就很容易想到C#中SerialPort类,它就是专门来处理串口通讯相关的. 了解什么是串口通讯 串口通讯是指外设和计算机间通过数据信号线,地线,控制线等,按位进行传输数据的一种通讯方式. 这种通讯方式使用的数据线少,在远距离通信中可以节约成本,但其传输速度比并行通讯慢. 串口是计算机上一种非常通用的设备通讯协议.大多数计算机包含两个基于RS-232的串口. 串口

  • .Net Core 3.1 Web API基础知识详解(收藏)

    目录 一.前言 二.Swagger调试Web API 三.配置文件 四.文件上传 五.统一WebApi数据返回格式 六.模型验证 七.日志使用 八.依赖注入 九.缓存 十.异常处理 十一.应用安全与JWT认证 十二.跨域 一.前言 随着近几年前后端分离.微服务等模式的兴起,.Net Core也似有如火如荼之势 ,自16年发布第一个版本到19年底的3.1 LTS版本,以及将发布的.NET 5,.NET Core一路更迭,在部署和开发工具上也都支持了跨平台应用.一直对.Net Core有所关注,但未

  • Android与JS之间跨平台异步调用实例详解

    Android与JS之间跨平台异步调用 为什么突然要搞这个问题呢? 在开发浏览器的时候遇到这个狗血的问题,花了将近1天的时间才想到这个解决方案,Android与JavaScirpt互调. 因为接口是抓取的别人的,所以出现了JS跨域问题,Android闪亮登场搞定了. GIF动画演示   WebView相关设置 WebSettings mWebSettings = getSettings(); mWebSettings.setDefaultTextEncodingName("UTF-8"

  • ASP.NET Core程序发布到Linux生产环境详解

    在这篇文章里我们将介绍如何在 Ubuntu 14.04 Server上部署ASP.NET Core应用程序.我们将把ASP.NET Core应用程序放到一个反向代理服务器的后面,由代理服务器把请求转交给我们的Kestrel服务器.除此之外,我们还将保证我们的web应用程序作为一个守护进程来进行启动.我们需要配置一个进程管理工具来帮助我们在程序崩溃时恢复程序,以保证高可用性. 章节: 准备 复制你的应用程序 配置一个反向代理服务器 监控我们的应用程序 启动我们的应用程序 观察日志 使我们的应用程序

  • 利用Node.js编写跨平台的spawn语句详解

    前言 Node.js 是跨平台的,也就是说它能运行在 Windows.OSX 和 Linux 平台上.很多 Node.js 开发者都是在 OSX 上做开发的,然后再将代码部署到 Linux 服务器上.由于 OSX 和 Linux 都是基于 Unix 的,因此两者共性很多.Windows 也是 Node.js 官方支持的平台,只要你通过正确的方式写代码,就能在各个平台上毫无压力的跑起来. Node.js 的子进程 (child_process) 模块下有一 spawn 函数,可以用于调用系统上的命

随机推荐