软件编程
位置:首页>> 软件编程>> C#编程>> C#串口连接的读取和发送详解

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

作者:Dwaynerbing  发布时间:2022-05-11 10:04:47 

标签: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();
   }
 }

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

总结

来源:https://www.cnblogs.com/dongweian/p/14232446.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com