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

论坛 期权论坛 脚本     
nimin   2021-1-7 05:40   2968   0

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

串口,即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 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1012780
帖子:202556
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP