C#串口连接的读取和发送详解

一、串口连接的打开与关闭

串口,即COM口,在.NET中使用 SerialPort 类进行操作。串口开启与关闭,是涉及慢速硬件的IO操作,频繁打开或关闭会影响整体处理速度,甚至导致打开或关闭串口失败。非特殊情况,串口一次性打开后,在退出程序时关闭串口即可。在打开串口前,可以设置一些常用的参数。常用的参数如下:

(1)串口的接受/发送超时时间:ReadTimeout/WriteTimeout。

(2)  串口的接受/发送缓存区大小:ReadBufferSize/WriteBufferSize。

具体代码如下:

// Open Com
   _serialPort = new SerialPort(com, baud);
   if (_serialPort.IsOpen) _serialPort.Close();

   // Set the read / write timeouts
   _serialPort.ReadTimeout = 500;
   _serialPort.WriteTimeout = 500;

   // Set read / write buffer Size,the default of value is 1MB
   _serialPort.ReadBufferSize = 1024 * 1024;
   _serialPort.WriteBufferSize = 1024 * 1024;

   _serialPort.Open();

   // Discard Buffer
   _serialPort.DiscardInBuffer();
   _serialPort.DiscardOutBuffer();

需要注意的是超出缓冲区的部分会被直接丢弃。因此,如果需要使用串口传送大文件,那接收方和发送方都需要将各自的缓冲区域设置的足够大,以便能够一次性存储下大文件的二进制数组。若条件限制,缓冲区域不能设置过大,那就需要在发送大文件的时候按照发送缓冲区大小分包去发送,接收方按顺序把该数组组合起来形成接受文件的二进制数组。

二、串口发送

SerialPort 类发送支持二进制发送与文本发送,需要注意的是文本发送时,需要知道转换的规则,一般常用的是ASCII、UTF7、UTF-8、UNICODE、UTF32。具体代码如下:

#region Send
    /// <summary>
    /// 发送消息(byte数组)
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="offset"></param>
    /// <param name="count"></param>
    public void Send(byte[] buffer, int offset, int count)
    {
      lock (_mux)
      {
        _serialPort.Write(buffer, offset, count);
        _sendCount += (count - offset);
      }
    }

    /// <summary>
    /// 发送消息(字符串)
    /// </summary>
    /// <param name="encoding">字符串编码方式,具体方式见<see cref="Encoding"/></param>
    /// <param name="message"></param>
    public void Send(Encoding encoding , string message)
    {
      lock (_mux)
      {
        var buffer = encoding.GetBytes(message);
        _serialPort.Write(buffer, 0, buffer.Length);
        _sendCount += buffer.Length;
      }
    }
    #endregion

三、串口接受

串口接受需要注意,消息接受与消息处理要代码分离。不能把流程处理的代码放入信息接受处,因为消息处理或多或少会有耗时,这会造成当发送方发送过快时,接受方的接受缓冲区会缓存多条消息。我们可以把接受到的消息放入队列中,然后在外部线程中,尝试去拿出该条消息进行消费。采用 “生产-消费”模式。具体代码如下:

#region Receive
    private void PushMessage()
    {
      _serialPort.DataReceived += (sender, e) =>
      {
        lock (_mux)
        {
          if (_serialPort.IsOpen == false) return;
          int length = _serialPort.BytesToRead;
          byte[] buffer = new byte[length];
          _serialPort.Read(buffer, 0, length);
          _receiveCount += length;
          _messageQueue.Enqueue(buffer);
          _messageWaitHandle.Set();
        }
      };
    }

    /// <summary>
    /// 获取串口接受到的内容
    /// </summary>
    /// <param name="millisecondsToTimeout">取消息的超时时间</param>
    /// <returns>返回byte数组</returns>
    public byte[] TryMessage(int millisecondsToTimeout = -1)
    {
      if (_messageQueue.TryDequeue(out var message))
      {
        return message;
      }

      if (_messageWaitHandle.WaitOne(millisecondsToTimeout))
      {
        if (_messageQueue.TryDequeue(out message))
        {
          return message;
        }
      }
      return default;
    }
    #endregion

四、完整代码与测试结果

串口工具类的完整代码如下:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SerialportDemo
{
  public class SSerialPort
  {
    private SerialPort _serialPort;
    private readonly ConcurrentQueue<byte[]> _messageQueue;
    private readonly EventWaitHandle _messageWaitHandle;
    private int _receiveCount, _sendCount;
    private readonly object _mux;
    public int ReceiveCount
    {
      get => _receiveCount;
    }
    public  int SendCount
    {
      get => _sendCount;
    }
    public SSerialPort(string com, int baud )
    {
      // initialized
      _mux=new object();
      _receiveCount = 0;
      _sendCount = 0;
      _messageQueue = new ConcurrentQueue<byte[]>();
      _messageWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);

      // Open Com
      OpenCom(com.ToUpper(),baud);

      // Receive byte
      PushMessage();
    }

    private void OpenCom(string com, int baud)
    {
      // Open Com
      _serialPort = new SerialPort(com, baud);
      if (_serialPort.IsOpen) _serialPort.Close();

      // Set the read / write timeouts
      _serialPort.ReadTimeout = 500;
      _serialPort.WriteTimeout = 500;

      // Set read / write buffer Size,the default of value is 1MB
      _serialPort.ReadBufferSize = 1024 * 1024;
      _serialPort.WriteBufferSize = 1024 * 1024;

      _serialPort.Open();

      // Discard Buffer
      _serialPort.DiscardInBuffer();
      _serialPort.DiscardOutBuffer();
    }

    #region Static
    /// <summary>
    /// 获取当前计算机的串行端口名的数组
    /// </summary>
    /// <returns></returns>
    public static string[] GetPortNames()
    {
      return SerialPort.GetPortNames();
    }
    #endregion

    #region Receive
    private void PushMessage()
    {
      _serialPort.DataReceived += (sender, e) =>
      {
        lock (_mux)
        {
          if (_serialPort.IsOpen == false) return;
          int length = _serialPort.BytesToRead;
          byte[] buffer = new byte[length];
          _serialPort.Read(buffer, 0, length);
          _receiveCount += length;
          _messageQueue.Enqueue(buffer);
          _messageWaitHandle.Set();
        }
      };
    }

    /// <summary>
    /// 获取串口接受到的内容
    /// </summary>
    /// <param name="millisecondsToTimeout">取消息的超时时间</param>
    /// <returns>返回byte数组</returns>
    public byte[] TryMessage(int millisecondsToTimeout = -1)
    {
      if (_messageQueue.TryDequeue(out var message))
      {
        return message;
      }

      if (_messageWaitHandle.WaitOne(millisecondsToTimeout))
      {
        if (_messageQueue.TryDequeue(out message))
        {
          return message;
        }
      }
      return default;
    }
    #endregion

    #region Send
    /// <summary>
    /// 发送消息(byte数组)
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="offset"></param>
    /// <param name="count"></param>
    public void Send(byte[] buffer, int offset, int count)
    {
      lock (_mux)
      {
        _serialPort.Write(buffer, offset, count);
        _sendCount += (count - offset);
      }
    }

    /// <summary>
    /// 发送消息(字符串)
    /// </summary>
    /// <param name="encoding">字符串编码方式,具体方式见<see cref="Encoding"/></param>
    /// <param name="message"></param>
    public void Send(Encoding encoding , string message)
    {
      lock (_mux)
      {
        var buffer = encoding.GetBytes(message);
        _serialPort.Write(buffer, 0, buffer.Length);
        _sendCount += buffer.Length;
      }
    }
    #endregion

    /// <summary>
    /// 清空接受/发送总数统计
    /// </summary>
    public void ClearCount()
    {
      lock (_mux)
      {
        _sendCount = 0;
        _receiveCount = 0;
      }
    }

    /// <summary>
    /// 关闭串口
    /// </summary>
    public void Close()
    {
      _serialPort.Close();
    }
  }
}

测试代码如下:

class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine($"该计算机可使用的串口列表:{string.Join(",", SSerialPort.GetPortNames())}");

      Console.Write("请输入需要打开的串口:");
      string port = Console.ReadLine();
      SSerialPort com = new SSerialPort(port, 57600);
      Console.WriteLine($"串口 {port} 打开成功...");

      Console.Write("请输入需要打开的串口发送的消息:");
      string text = Console.ReadLine();

      while (true)
      {
        com.Send(Encoding.Default, text);
        Console.WriteLine($"总共发送 {com.SendCount}");
        var message = com.TryMessage();
        if (message != null)
        {
          Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss fff")} {Encoding.Default.GetString(message)}");

          //// TEST:从添加延时可以测试到,接受消息和处理消息必须分不同线程处理。因为对于消息的处理或多或少都需要耗时,这样容易造成消息处理不及时。而添加到队列后,我们可以随时取出处理
          //System.Threading.Thread.Sleep(100*1);
        }
        Console.WriteLine($"总共接受 {com.ReceiveCount}");
      }

      Console.ReadKey();
    }
  }

使用串口工具测试如下,对于串口的接受如丝般顺滑。当我们在消息中增加测试延时后,就会发现当串口工具继续快速发送一段时间后关闭发送,发现使用队列后,依然没有丢失一条来自发送方的消息。

总结

到此这篇关于C#串口连接的读取和发送的文章就介绍到这了,更多相关C#串口连接读取和发送内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#串口通信模块使用方法示例

    C#串口模块的使用.使用VS .net框架下WinForm程序应用开发. C#开发的串口通信小工具. 相比于QT添加的串口类,WinForm是通过组件的形式将串口加入到程序中.. 在创建完windows窗体之后,添加组件类,就可以将串口加入到其中. 然后就需要写数据成员,初始化和方法,实现串口的读写功能. 串口类数据成员 SerialPort SComm; // 使用构造函数取串口控件 TextBox MsgRc; // 接收数据成员 //构造函数初始化 public SerialComm(Se

  • C#串口编程实例代码

    由于工作需要,第一次接触串口编程.所以不得不在网上查阅各种编程实例.最后结合自己的理解与实践,最终有了如下代码. 本代码只经过了简单的软件测试,与简单的硬件测试. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Threading; using System.IO.Port

  • C#实现把指定数据写入串口

    public static bool WriteToSerialPort(byte[]byteArr) { SerialPort Com = newSerialPort(); try { Com.ReadTimeout = 5000; Com.WriteTimeout = 5000; Com.PortName = "Com1"; Com.BaudRate = 9600; Com.StopBits = StopBits.One; Com.Parity = Parity.None; Com

  • C# 串口接收数据中serialPort.close()死锁的实例

    最近在做一个有关高铁模拟仓显示系统的客户端程序,在这个程序中要运用串口serialPort传输数据,因为每次接收数据结束后要更新UI界面,所以就用到了的Invoke,将更新UI的程序代码封装到一个方法中,然后通过Incoke调用,程序跑起来没有任何问题,但是当你执行serialPort.close()是程序就会发生死锁,整个程序卡在那里动都动不了. 上网查了很多资料,有各种这样的说法,有的说定义一个接收数据的标志,如果在执行关闭程序是进行判断,如果数据接收完了就关闭串口,没有的话继续执行,但是经

  • C#获取串口列表实现实时监控串口

    常用的两种方法 方法一: using Microsoft.Win32; RegistryKey keyCom = Registry.LocalMachine.OpenSubKey("Hardware//DeviceMap//SerialComm"); if (keyCom != null) { string[] sSubKeys = keyCom.GetValueNames(); foreach (string sName in sSubKeys) { string sValue =

  • C#串口通信程序实例详解

    创建C#串口通信程序之命名空间 System.IO.Ports命名空间中最重用的是SerialPort 类. 创建C#串口通信程序之创建SerialPort 对象 通过创建SerialPort 对象,我们可以在程序中控制串口通信的全过程. 我们将要用到的SerialPort 类的方法: ReadLine():从输入缓冲区读一新行的值,如果没有,会返回NULLWriteLine(string):写入输出缓冲Open():打开一个新的串口连接Close():关闭 复制代码 代码如下: SerialP

  • C#串口通讯概念及简单的实现方法

    前言 最近在研究串口通讯,其中有几个比较重要的概念,RS-232这种适配于上位机和PC端进行连接,RS-232只限于PC串口和设备间点对点的通信.它很简单的就可以进行连接,由于串口通讯是异步的,也就是说你可以同时向两端或者更多进行数据发送,它们之间的传输数据类型是byte,串口通信最重要的参数是波特率.数据位.停止位和奇偶校验.对于两个进行通信的端口,这些参数必须匹配. 听大佬说的几个关于串口通讯的术语,啥?啥,这是啥? 就让我这个"小白"给你说说:第一个波特率,这个东西在不同领域都有

  • C# 实现简易的串口监视上位机功能附源码下载

    实现上位机和下位机之间的通信,通常使用的是串口通信,接下来实现一个通过上位机和串口调试助手来完成串口通信测试. 首先创建一个WInfrom窗体应用工程文件,创建过程可参考  https://www.jb51.net/article/150973.htm 在创建好的工程下面,通过工具箱中已有的控件完成界面的搭建,如下图所示,为了方便初学者容易看懂程序,下图将控件的命名一并标注出来: 直接进入正题,将完整的工程代码黏贴出来: using System; using System.Collection

  • C#串口通信实现方法

    本文实例讲述了C#串口通信实现方法.分享给大家供大家参考.具体方法如下: 通过COM1发送数据,COM2接收数据.当COM2接收完本次发送的数据后,向COM1发送信息通知COM1本次数据已发完,COM1接到通知后,再发下一段数据.这样可以确保每次发送的数据都可以被正确接收. 代码如下: 复制代码 代码如下: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data;

  • C#串口连接的读取和发送详解

    一.串口连接的打开与关闭 串口,即COM口,在.NET中使用 SerialPort 类进行操作.串口开启与关闭,是涉及慢速硬件的IO操作,频繁打开或关闭会影响整体处理速度,甚至导致打开或关闭串口失败.非特殊情况,串口一次性打开后,在退出程序时关闭串口即可.在打开串口前,可以设置一些常用的参数.常用的参数如下: (1)串口的接受/发送超时时间:ReadTimeout/WriteTimeout. (2)  串口的接受/发送缓存区大小:ReadBufferSize/WriteBufferSize. 具

  • 对python3 Serial 串口助手的接收读取数据方法详解

    其实网上已经有许多python语言书写的串口,但大部分都是python2写的,没有找到一个合适的python编写的串口助手,只能自己来写一个串口助手,由于我只需要串口能够接收读取数据就可以了,故而这个串口助手只实现了数据的接收读取. 创建串口助手首先需要创建一个类,重构类的实现过程如下: #coding=gb18030 import threading import time import serial class ComThread: def __init__(self, Port='COM3

  • C++利用MySQL API连接和操作数据库实例详解

    1.C++连接和操作MySQL的方式 系列文章: MySQL 设计和命令行模式下建立详解 C++利用MySQL API连接和操作数据库实例详解 在Windows平台,我们可以使用ADO.ODBC或者MySQL API进行连接和操作.ADO (ActiveX Data Objects,ActiveX数据对象)是Microsoft提出的一个用于存取数据源的COM组件.它提供了程序语言和统一数据访问方式OLE DB的一个中间层,也就是Microsoft提出的应用程序接口(API)用以实现访问关系或非关

  • Spring中属性文件properties的读取与使用详解

    Spring中属性文件properties的读取与使用详解 实际项目中,通常将一些可配置的定制信息放到属性文件中(如数据库连接信息,邮件发送配置信息等),便于统一配置管理.例中将需配置的属性信息放在属性文件/WEB-INF/configInfo.properties中. 其中部分配置信息(邮件发送相关): #邮件发送的相关配置 email.host = smtp.163.com email.port = xxx email.username = xxx email.password = xxx

  • python通过socket实现多个连接并实现ssh功能详解

    一.前言 上一篇中我们已经知道了客户端通过socket来连接服务端,进行了一次数据传输,那如何实现客户端多次发生数据?而服务端接受多个客户端呢? 二.发送中文信息 在python3中,socket只能发送bytes类型的数据,bytes类型只能表示0-225的ASCII码的值,并不能表示中文,所以当我们需要发送中文时,需要使用到编码和解码. 客户端: import socket # 客户端 # 声明协议类型,同时生成socket对象 client = socket.socket() # clie

  • python连接mongodb集群方法详解

    简单的测试用例 #!/usr/bin/python # -*- coding: UTF-8 -*- import time from pymongo import MongoClient # 连接单机 # single mongo # c = MongoClient(host="192.168.89.151", port=27017) # 连接集群 c = MongoClient('mongodb://192.168.89.151,192.168.89.152,192.168.89.1

  • Java使用FileInputStream流读取文件示例详解

    一.File流概念 JAVA中针对文件的读写操作设置了一系列的流,其中主要有FileInputStream,FileOutputStream,FileReader,FileWriter四种最为常用的流 二.FileInputStream 1)FileInputStream概念  FileInputStream流被称为文件字节输入流,意思指对文件数据以字节的形式进行读取操作如读取图片视频等 2)构造方法 2.1)通过打开与File类对象代表的实际文件的链接来创建FileInputStream流对象

  • go学习笔记读取consul配置文件详解

    目录 新建yaml文件 读取远程配置 新建yaml文件 在上文我们的 go学习笔记:使用 consul 做服务发现和配置共享 这里我们单独来用viper实现读取consul的配置, 我习惯与用yaml格式, 所以 首先 新建yaml文件 store: book: - author: john price: 10 - author: ken price: 12 bicycle: color: red price: 19.95 读取远程配置 可以直接调用viper.AddRemoteProvider

  • java通过ssh连接服务器执行shell命令详解及实例

    java通过ssh连接服务器执行shell命令详解 java通过ssh连接服务器执行shell命令:JSch 是SSH2的一个纯Java实现.它允许你连接到一个sshd 服务器,使用端口转发,X11转发,文件传输等等.你可以将它的功能集成到你自己的 程序中.同时该项目也提供一个J2ME版本用来在手机上直连SSHD服务器. SSH是Secure Shell的缩写,一种建立在应用层和传输层基础上的安全协议.SSH在连接和传送过程中会加密所有数据,可以用来在不同系统或者服务器之间进行安全连接.SSH提

  • Java连接操作Oracle数据库代码详解

    废话不多说了,直接给大家贴关键代码了,具体代码如下所示: package com.sp.test; import java.sql.*; import java.util.*; public class Text_lianxi extends Thread { public void run() { try { yunxing(); Thread.sleep(10000); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.pr

随机推荐