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

2022-07-18,,,,

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

        摘要

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

        引言

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

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

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

        基础类库的选择

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

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

        相关编译配置说明在https://github.com/jcurl/serialportstream已有介绍,也可参考本人的拙作

        类库的实现

        创建跨平台类库

        为了支持跨平台,我们使用visual studio 2017创建一个基于.net standard的类库。

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

        .net core是一个托管框架,针对构建控制台、云、asp.net core和uwp应用程序进行了优化。

        每一种托管实现(如.net core、.net framework或xamarin)都必须遵循.net standard实现基类库(bcl)。

        关于net standard和跨平台的详细说明在此:

        笔者也不再啰嗦呵。

        实现机制/条件

        通常串口通讯中,发送数据后,会有一段时间用于等待接收方应答,如此一来,两次数据发送之间,必然会有一定的时间间隔。如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()的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。

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

        下载本文的Word格式文档,以方便收藏与打印。