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

1,前言

开发环境:在 Visual Studio 2017,.NET Core 2.x

串口通讯用于设备之间,传递数据,物联网设备中广泛使用串口方式连接通讯,物联网通讯协议 :Modbus 协议 ASCII、RTU、TCP模式是应用层的协议,与通讯方式无关。

笔者现在实现的是 串口通信,实现后,可以在上层加上 Modbus 协议,笔者的另一篇文章即是在串口上实现 Modbus 协议,计算中心向物联网设备发送消息,要求设备响应,传送设备信息、检测状态等。

本文是 串口通讯 的实现。

2,安装虚拟串口软件

由于开发在 Windows,也为了调试方便,使用需要安装虚拟串口软件:Virtual Serial Port Driver

安装完成后

添加串口

请添加 4-6 个串口,COM1,COM2,COM3,COM4 ... ...

关机重启

好了,为了使串口生效,请关机重启(不一定要关机,不过为了避免出现问题,还是关机重启比较好)。

开机后,打开 设备管理器 ,查看 设备 - 端口(COM 和 LPT),出现如下图所示,说明正常

原理

因为是虚拟串口,有些问题需要注意一下

A B(或者说服务端、客户端)不能使用同一个串口,你在设备管理器查看串口时(上面也有图),是不是看到

COM1 -> COM2

COM2 -> COM1

因为这是一个虚拟串口,所以只能是单方向的,所以 A、B 需要分别使用两个串口进行通讯,而虚拟串口把 COM1 - COM2 连接起来了。我们不需要关心这个,这里只是说明一下。

3,新建项目,加入flyfire.CustomSerialPort

新建一个 .NET Core 控制台项目

名字可以随便起,笔者用了SerialPortTest ,那我们都用这个吧

添加flyfire.CustomSerialPort

在项目中 添加 Nuget,搜索flyfire.CustomSerialPort ,然后安装

把类库需要的 Linux 依赖库添加到项目中,关于原因、添加方法,可以看笔者的另一篇文章https://www.cnblogs.com/whuanle/p/10499498.html#4

4,flyfire.CustomSerialPort 说明

CustomSerialPort 类,所有功能都集中在这里面了,笔者将详细说明此类下字段、方法等的使用

protected SerialPortStream sp;

支持通讯串口通讯的类

public CustomSerialPort(string portName, int baudRate = 115200,
 Parity parity = Parity.None, int databits = 8, StopBits stopBits = StopBits.One);

用于初始化一个串口,使用此串口进行通讯

  • portName  串口名称
  • baudRate  比特率,是指每秒传送的比特(bit)数,默认115200bps,不清楚 -> 百度
  • parity     表示奇偶性校验方式,枚举,None:没有校验为,Odd:奇校验,Even:偶检验,Space:总为0,Mark:总为1
  • databits  设置数据位,这里表示 8位
  • stopBits  停止位,One,One5,Twe方便表示1、1.5、2个停止位

因为串口设备通讯是在 OSI 七层的传输层,所以对这些都有相应的规定。TCP/IP 相对于 串口 来说,不必要关注这些。

        public int ReceiveTimeout { get; set; }  //接收超时时间
        public bool ReceiveTimeoutEnable { get; set; }  
        public bool RtsEnable { get; set; }    //不详
        public bool DtrEnable { get; set; }    //不详
        public bool IsOpen { get; }        //检测是否在使用
        public StopBits StopBits { get; set; }  //枚举,上面说明的
        public int DataBits { get; set; }    //上面说明了
        public Parity Parity { get; set; }    //枚举,上面说明了
        public int BaudRate { get; set; }
        public int BufSize { get; set; }
        public string PortName { get; set; }    //使用的串口名
     public event CustomSerialPortReceivedEventHandle ReceivedEvent;    //一个事件,可以把接收到消息后需要触发的时间绑定到此事件

        public static string ByteToHexStr(byte[] bytes);              //把比特流转为字符串
        public static string[] GetPortNames();
        public void Close();                              //关闭串口
        public void Dispose();
        public bool Open();                                //释放串口
        public void Write(string text);                        //以字符串的形式写入串口
        public void Write(byte[] buffer);                       //以字节流的方式写入串口(推荐)
        public void WriteLine(string text);                      //写入字符串,应该是与Modbus ASCII有关,Ascii方式需要在数据后面加上换行符表示已经结束传送
        protected void ReceiveTimeoutCheckFunc();
        protected void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e);  //后台线程处理,表示收到串口消息后,触发那些事件

以上,就是对 flyfire.CustomSerialPort 的说明,下面笔者说明怎么使用。

5,开始使用flyfire.CustomSerialPort

新建一个类SerialSerice.cs

新建一个类SerialSerice.cs ,设计此类用于提供串口通讯服务。

在SerialSerice.cs 引入

using flyfire.IO.Ports;
using RJCP.IO.Ports;
using System.Threading;

编写以下代码(你可能觉得有些奇怪,原因后面说),先不管这些东西,也不要管为什么这样写

namespace SerialPortTest
{
    /// <summary>
    ///  用于封装需要的串口通讯
    /// </summary>
    public class SerialSerice
    {
        /// <summary>
        /// 获取计算机的所有串口
        /// </summary>
        public void GetSerial()
        {
        //CustomSerialPort.GetPortNames() 静态方法,获取计算机的所有串口名称
        //因为已经继承,也可以使用 string[] vs = 串口通讯.GetPortNames();
            string[] vs = CustomSerialPort.GetPortNames();
            Console.WriteLine("你电脑的串口列表:");
            foreach (var i in vs)
            {
                Console.WriteLine(i);
            }
        }
    }

    public class 串口通讯 : CustomSerialPort
    {
        public 串口通讯(string portName, int baudRate = 115200, Parity parity = Parity.None, int databits = 8, StopBits stopBits = StopBits.One)
            :base(portName, baudRate, parity, databits, stopBits)
        {

        }
    }
}

开始在Program.cs 中使用

     static void Main(string[] args)
        {
            SerialSerice serialSerice = new SerialSerice();
            serialSerice.GetSerial();
            Console.ReadKey();
        }

运行试试

6,实现把数据写入串口

上面已经获取到串口,要把数据写入一个串口,就要初始化串口类,实现使用串口、向串口写入不同类型、不同进制的数据

为了简单一些,我们使用默认配置。

把代码 Copy 到你的项目,笔者已经详细列举出步骤

namespace SerialPortTest
{
    /// <summary>
    ///  用于封装需要的串口通讯
    /// </summary>
    public class SerialSerice
    {
        //实现串口通讯的对象
        串口通讯 串口;
        /// <summary>
        /// 获取计算机的所有串口 步骤 1
        /// </summary>
        public void GetSerial()
        {
            string[] vs = 串口通讯.GetPortNames();
            Console.WriteLine("你电脑的串口列表(输入名称此端口,注意大小写):");

            foreach (var i in vs)
            {
                Console.WriteLine(i);
            }
        }
        //初始化串口 步骤 2
        public void 初始化(string portname)
        {
            串口 = new 串口通讯(portname);
        串口.Open();
        }
        //向串口写入数据 步骤 3
        public void 写入(string str)
        {
            //方式 1
            串口.Write(str);
            Console.WriteLine("已经向串口输入:" + str);
        Thread.Sleep(500);
            //方式 2、3
            byte[] b_字符 = Encoding.Default.GetBytes(str);
            byte[] b_16进制 = new byte[b_字符.Length];

            //转16进制再发送
            Console.WriteLine("发送的16进制数据:");
            for (int i = 0; i < b_字符.Length; i++)
            {
                b_16进制[i] = Convert.ToByte(b_字符[i].ToString(), 16);
                Console.Write(b_16进制[i] + " ");
            }
        Console.WriteLine();
            //方式 2、3 写入串口
            串口.Write(b_字符);
        Thread.Sleep(500);
            串口.Write(b_16进制);
        Thread.Sleep(500);
        }
    }

    public class 串口通讯 : CustomSerialPort
    {
        public 串口通讯(string portName, int baudRate = 115200, Parity parity = Parity.None, int databits = 8, StopBits stopBits = StopBits.One)
            : base(portName, baudRate, parity, databits, stopBits)
        {

        }
    }
}

服务已经配置好,接下来就是使用写好的服务了。

class Program
    {
        static void Main(string[] args)
        {
            // 初始化串口通讯服务
            SerialSerice 串口功能 = new SerialSerice();

            //显示串口列表、并让用户选择串口
            串口功能.GetSerial();
            string portname= Console.ReadLine();

            //步骤 2
            串口功能.初始化(portname);

            Console.WriteLine("输入你想发送给客户端的内容,退出请输入 exit");
            //因为示例了三种写入方法,第三种方法需要转换,非数字会报错
            //实际上你可以发送如何类型的数据,就看你怎么写步骤 3 的方法
            Console.WriteLine("只能输入数字!8进制、10进制、16进制均可,请勿输入字符串");
            while (true)
            {
                string str = Console.ReadLine();
                if (str == "exit")
                    break;

                //步骤 3
                串口功能.写入(str);
            }

            Console.ReadKey();
        }

示例:

关于进制转换这些,可以找一些文章看,串口通讯对 byte、int16、int32、string 等类型间的转换要求比较高。

7,实现监听串口消息、多设备进行通讯

在开始前,看一下图:

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

上面是 flyfire.CustomSerialPort 的 属性、字段和方法,Sp_DataReceived() 这个方法是实现后台监控数据,并触发预设事件的方法,开辟新线程不断循环接收数据。不过这里的实现并不那么好。

框架作者的博客https://www.cnblogs.com/flyfire-cn/p/10434171.html

通过上面可以发现,这个监控方法是 protected 的,所以需要使用一个类继承,才能使用此方法。

另外,事件委托为

public delegate void CustomSerialPortReceivedEventHandle(object sender, byte[] bytes)

基于以上,来做一个可以后台接收数据并在控制台输出的代码。

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using flyfire.IO.Ports;
using RJCP.IO.Ports;

namespace SerialPortTest
{
    /// <summary>
    ///  用于封装需要的串口通讯
    /// </summary>
    public class SerialSerice
    {
        //实现串口通讯的对象
        串口通讯 串口;
        /// <summary>
        /// 获取计算机的所有串口 步骤 1
        /// </summary>
        public void GetSerial()
        {
            string[] vs = 串口通讯.GetPortNames();
            Console.WriteLine("你电脑的串口列表(输入名称此端口,注意大小写):");

            foreach (var i in vs)
            {
                Console.WriteLine(i);
            }
        }
        //初始化串口 步骤 2
        public void 初始化(string portname)
        {
            串口 = new 串口通讯(portname);
            串口.Open();
        }
        //向串口写入数据 步骤 3
        public void 写入(string str)
        {
            //方式 1
            串口.Write(str);
            Console.WriteLine("已经向串口输入:" + str);
            Thread.Sleep(500);
            //方式 2、3
            byte[] b_字符 = Encoding.Default.GetBytes(str);
            byte[] b_16进制 = new byte[b_字符.Length];

            //转16进制再发送
            Console.WriteLine("发送的16进制数据:");
            for (int i = 0; i < b_字符.Length; i++)
            {
                b_16进制[i] = Convert.ToByte(b_字符[i].ToString(), 16);
                Console.Write(b_16进制[i] + " ");
            }
            Console.WriteLine();
            //方式 2、3 写入串口
            串口.Write(b_字符);
            Thread.Sleep(500);
            串口.Write(b_16进制);
            Thread.Sleep(500);
        }
        public void 开启后台监听()
        {
            //收到消息时要触发的事件
            串口.ReceivedEvent += 被触发的事件_1;

            串口.开始后台监控();

        }
        public static void 被触发的事件_1(object sender, byte[] bytes)
        {
            Console.WriteLine("收到数据");
            foreach (var i in bytes)
            {
                Console.Write(i + " ");
            }
            Console.WriteLine("");
        }

    }

    public class 串口通讯 : CustomSerialPort
    {
        public 串口通讯(string portName, int baudRate = 115200, Parity parity = Parity.None, int databits = 8, StopBits stopBits = StopBits.One)
            : base(portName, baudRate, parity, databits, stopBits)
        {

        }
        //无意义,只是因为父类的 Sp_DataReceived() 不是 public
        public void 开始后台监控()
        {

            Sp_DataReceived(new object(), new SerialDataReceivedEventArgs(SerialData.Eof));
        }
    }
}
using System;

namespace SerialPortTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化串口通讯服务
            SerialSerice 串口功能 = new SerialSerice();

            //显示串口列表、并让用户选择串口
            串口功能.GetSerial();
            string portname= Console.ReadLine();

            //步骤 2
            串口功能.初始化(portname);
            串口功能.开启后台监听();
            Console.WriteLine("输入你想发送给客户端的内容,退出请输入 exit");
            //因为示例了三种写入方法,第三种方法需要转换,非数字会报错
            //实际上你可以发送如何类型的数据,就看你怎么写步骤 3 的方法
            Console.WriteLine("只能输入数字!8进制、10进制、16进制均可,请勿输入字符串");
            while (true)
            {
                string str = Console.ReadLine();
                if (str == "exit")
                    break;

                //步骤 3
                串口功能.写入(str);
            }

            Console.ReadKey();
        }
    }
}

为了实现串口通讯,我们把这个项目复制到别的目录,另外打开运行。即同一份代码变成两份,运行时就有两个控制台了。

注:你会发现,输入一条消息,会收到几条信息。那是因为笔者在写入方法那部分,给出了三个写入方式,删除2个即可。

为了便于理解,笔者使用了中文对方法进行命名。

串口通讯已经已经实现了,如何实现 Modbus 协议,跟设备(单片机、开发板之类的小设备)进行约定通讯呢~笔者的另一篇文章~

项目源码已经上传到http://pan.whuanle.cn/?dir=uploads/dotnet-core-串口

8,Modbus 协议的实现例子

由于时间和篇幅问题,这里简单说一下 Modbus 和实现的示例。

Modbus 是一种通信协议,有 ASCII、RTU、TCP等实现方式,广泛应用于物联网设备、工业控制、自动化场景等。

协议的实现,由一台主机、多个从机组成,我们把它想象成智能家居吧,一台电脑是主机,空调、电视机、冰箱等是从机。那么多设备,它们只能向主机发送数据,不能直接通讯,每台设备都有其地址。

传输的数据流格式如下

(以上两张图来自互联网)

然后,我实现了Modbus协议,对要发送的消息进行检验、封装、打包成帧、接收、处理发送。

分为服务器、客户端。每个客户端都有一个地址,下面示范,

我在服务器使用了 02 04 00 01 25 26,

代表:客户端地址02,功能码:04(代表要设备要干嘛),要读取设备的温湿度数据:00 01(00 02,00 03代表读取其他数据),后面 25 26 有其他功能作用,不过笔者手里没有真实的设备,所以没对其进行实现,理解就行。

服务端向客户端(02)发送数据,功能是读取寄存器(04),然后是读取温度数据还是湿度数据(00 01 代表两个都读取),25 26( 转为10进制为 9510 ) 可以定义为 要客户端发返回 9510 条记录。

返回的2 4 0 1 25 26 BB 4B,后面两个是 CRC 检验,由于数据传输可能发送丢失或出错,使用后面两位由于检验数据是否正确接收。

上面是在控制台输入 16 进制的数,下面是 直接 输入 10 进制的数。

到此这篇关于.NET Core使用flyfire.CustomSerialPort实现Windows/Linux跨平台串口通讯的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

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

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

  • .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跨平台串口通讯使用SerialPortStream基础类库问题解决

    说明 由于.net core 2.x 中,已经找不到 serialport 库,使用需要使用第三方框架,可以直接在 Nuget 中搜索 SerialPortStream 开源地址https://github.com/jcurl/SerialPortStream 为了方便使用,有人封装把它了起来,在 Nuget 中搜索 flyfire.CustomSerialPort 这是一个增强的自定义串口类,实现协议无关的数据帧完整接收功能,支持跨平台使用,使用 SerialPortStream 基础类库.

  • Asp.net Core 初探(发布和部署Linux)

    前言 俗话说三天不学习,赶不上刘少奇.Asp.net Core更新这么长时间一直观望,周末帝都小雨,宅在家看了下Core Web App,顺便搭建了个HelloWorld环境来尝尝鲜,第一次看到.Net Web运行在Linux上还是有点小激动(只可惜微软走这一步路走的太晚,要不然屌丝们也不会每每遇见Java VS .Net就想辩论个你死我活). 开发环境和部署环境 Windows 10.VS2015 Update3.安装.Net Core SDK.DotNetCore.1.0.1-VS2015T

  • Python3监控windows,linux系统的CPU、硬盘、内存使用率和各个端口的开启情况详细代码实例

    由于项目的需要,需要做一个简单监控服务器的CPU利用率.CPU负载.硬盘使用率.内存利用率和服务器的各个端口的开启情况的程序,并把结果通知到监控平台,如果出现异常,监控平台打电话或者发短信通知给具体的运维人员 python版本要求:python3.0 以上 安装 python 的 psutil 包 和 requests 包 pip install psutil pip install requests Linux系统下运行效果 Windows系统下运行效果 代码实例核心程序 # 获取端口信息 @

  • .NET Core使用Topshelf方式创建Windows服务的全过程记录

    前言 Topshelf是一个.NET Standard库,它消除了在.NET Framework和.NET Core中创建Windows服务的那些麻烦. 安装 Install-Package Topshelf 代码 using System; using System.Collections.Generic; using System.Text; using Topshelf; namespace ConsoleApp2222 { public class LoggingService : Se

  • 最新DataGrip2020.2.x破解版激活码的步骤详解(支持Mac/Windows/Linux)

    DataGrip是数据库管理工具,操作数据库非常方便!本教程提供了DataGrip2020激活码.DataGrip2020破解版和DataGrip2020安装包,可以完美激活和破解所有2020版本(2020.2/2020 . 2 . 1/2020 . 2 . 2/2020 . 1)的DataGrip,支持包括Windows Mac Linux在内的所有操作系统. 声明:禁止将Datagrip破解教程.附带的破解包.激活码等文档用于非法或商业目的.如果有法律纠纷,跟我没关系.有能力的话可以支持正版

  • goland2020.2.x永久激活码破解详细教程亲测可用(Windows Linux Mac)

    上篇文章给大家分享了goland2019.1版本的激活码和激活教程,感兴趣的朋友点击此处了解详情! goland已经更新到2020.2.3,很多小伙伴都在用2019版,该升级了! 本文针对goland 2020.2.3安装提供教程.破解版.激活码.注册补丁,可以永久破解2020所有版本,支持Mac Windows Linux操作系统! 声明:GoLand2020安装破解版教程及相关附属文件只能用于学习和交流目的,不得用于非法或商业目的.请在下载后24小时内删除相关文件.如果有任何法律纠纷,这与本

  • Windows+Linux系统下Go语言环境安装配置过程

    Go 是一个开源的编程语言,它能让构造简单.可靠且高效的软件变得容易. Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本.现在Go的开发已经是完全开放的,并且拥有一个活跃的社区. 1. Windows安装配置 1️⃣ 下载SDK SDK 的全称是Software Development Kit

  • Android 和 windows C/C++/QT通讯时字节存储

    ava:采用大端字节序存储数据[低地址存放数据的高位,高地址存放数据的低位,数据高位存放在数组的前面] windows(intel平台):采用小端字节序存储数据[低地址存放数据的低位,高地址存放数据的高位,数据的高位存放在数组的后面](windows接收java发送过来的short,int需要调用ntohs和ntohl来转换到小数端) [数据高位]:0x1234的高位为 0x12 [数据低位]:0x1234的低位为 0x34 如: int ihex = 0x12345678; short she

随机推荐