Detailed explanation of reading and sending of C# serial port connection

Posted by eshban on Mon, 07 Feb 2022 22:27:42 +0100

Detailed explanation of reading and sending of C# serial port connection
1, Opening and closing of serial port connection
Serial port, i.e. COM port, is in NET uses the SerialPort class to operate. Serial port opening and closing are IO operations involving slow hardware. Frequent opening or closing will affect the overall processing speed and even lead to failure to open or close the serial port. In non special cases, after the serial port is opened at one time, close the serial port when exiting the program. Before opening the serial port, you can set some common parameters. Common parameters are as follows:

(1) Receive / send timeout of serial port: ReadTimeout/WriteTimeout.

(2) The size of the receive / send buffer of the serial port: ReadBufferSize/WriteBufferSize.

The specific codes are as follows:

// 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();

2, Serial port transmission
SerialPort class sending supports binary sending and text sending. It should be noted that when sending text, you need to know the conversion rules. Generally, ASCII, UTF7, UTF-8, UNICODE and UTF32 are commonly used. The specific codes are as follows
It should be noted that the part beyond the buffer will be discarded directly. Therefore, if you need to use the serial port to transmit large files, both the receiver and the sender need to set their respective buffer areas large enough to store the binary array of large files at one time. If the conditions are limited and the buffer area cannot be set too large, it is necessary to send large files by subcontracting according to the size of the sending buffer, and the receiver combines the arrays in order to form a binary array of receiving files.

#region Send
    /// <summary>
    ///Send message (byte array)
    /// </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>
    ///Send message (string)
    /// </summary>
    ///For the encoding method, see < paraname = "encoding" / > string
    /// <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

3, Serial port receiving
Attention should be paid to serial port reception. Message reception and message processing should be separated by code. The process processing code cannot be put into the information receiving place, because the message processing will be more or less time-consuming, which will cause the receiving buffer to cache multiple messages when the sender sends too fast. We can put the received message into the queue, and then try to take out the message for consumption in the external thread. Adopt the "production consumption" model. The specific codes are as follows:

#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>
    ///Get the content received by the serial port
    /// </summary>
    ///< param name = "millisecondstotimeout" > take the timeout of the message < / param >
    ///< returns > return byte array < / 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

4, Complete code and test results

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>
    ///Gets an array of serial port names for the current computer
    /// </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>
    ///Get the content received by the serial port
    /// </summary>
    ///< param name = "millisecondstotimeout" > take the timeout of the message < / param >
    ///< returns > return byte array < / 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>
    ///Send message (byte array)
    /// </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>
    ///Send message (string)
    /// </summary>
    ///< param name = "encoding" > string encoding method. See < 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>
    ///Clear the statistics of total accepted / sent
    /// </summary>
    public void ClearCount()
    {
      lock (_mux)
      {
        _sendCount = 0;
        _receiveCount = 0;
      }
    }
 
    /// <summary>
    ///Close the serial port
    /// </summary>
    public void Close()
    {
      _serialPort.Close();
    }
  }
}

The test code is as follows:

class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine($"List of serial ports available for this computer:{string.Join(",", SSerialPort.GetPortNames())}");
 
      Console.Write("Please enter the serial port to be opened:");
      string port = Console.ReadLine();
      SSerialPort com = new SSerialPort(port, 57600);
      Console.WriteLine($"Serial port {port} Open successfully...");
 
      Console.Write("Please enter the message sent by the serial port to be opened:");
      string text = Console.ReadLine();
 
      while (true)
      {
        com.Send(Encoding.Default, text);
        Console.WriteLine($"Total sent {com.SendCount}");
        var message = com.TryMessage();
        if (message != null)
        {
          Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss fff")} {Encoding.Default.GetString(message)}");
 
           TEST:From adding delay, we can see that receiving messages and processing messages must be processed in different threads. Because the processing of messages takes more or less time, it is easy to cause the message processing not in time. After being added to the queue, we can take it out for processing at any time
          //System.Threading.Thread.Sleep(100*1);
        }
        Console.WriteLine($"Total acceptance {com.ReceiveCount}");
      }
 
 
      Console.ReadKey();
    }
  }

Use the serial port tool to test as follows, and the acceptance of the serial port is as smooth as silk. After the test, we will find that there is still no message from the sender after the sending time of the serial port is closed.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO.Ports;
namespace PacketsTest
{
    
   class ComSR
  {
    private SerialPort serialPort;
    private bool status = false;//Accept the end status. true means the data is successfully received
    private byte[] reciveData = null;//The received data is stored here
    public bool Status
    {
      set { status = value; }
      get { return status; }
        
    }
    public byte[] RecivedData
    {
      get { return reciveData; }
    }
    public SerialPort _SerialPort
    {
      get { return serialPort; }
    }
  
    public ComSR(string portName,int baudRate,int dataBits,Parity parity,StopBits stopbits)
    {
      serialPort = new SerialPort();
      serialPort.PortName = portName;//Set port
      serialPort.BaudRate = baudRate;//set baud rate
      serialPort.DataBits = dataBits;//Set data length
      serialPort.Parity = parity;//Set parity protocol
      serialPort.StopBits = stopbits;//Stop bit
      serialPort.ReceivedBytesThreshold = 1;//Here is the trigger condition of read event, and 1 represents a byte
      serialPort.DtrEnable = true;//Enable data terminal ready signal
      serialPort.RtsEnable = true;//Request to send ready signal
      serialPort.DataReceived += new SerialDataReceivedEventHandler(Data_Recive);//Bind functions that handle read events
    }
  
    public void SendBuff(byte[] data)
    {
      try
      {
          
        serialPort.Write(data, 0, data.Length);//Here is writing      
      }
      catch (Exception e)
      {
        if (serialPort != null && serialPort.IsOpen)
        {
          status = false; //This status is the attribute that I judge the read-write status of the port in the upper class, which can be removed
          serialPort.Close();
        }
      }
    }
  
    void Data_Recive(object o, EventArgs e)//This is the read port and the event handler
    {
      try
      {
        Thread.Sleep(3);
        int length = serialPort.BytesToRead;
        reciveData = new byte[length];
        serialPort.Read(reciveData, 0, length);
        while (serialPort.BytesToRead != 0)//Because the data length is different from reading and writing, and the time is difficult to determine, this processing is done here
        {
          Thread.Sleep(1);
        }
        if (serialPort.IsOpen)
        {
          status = true;
          //serialPort.Close();// The port should not be closed here. The port should be closed on a higher-level program
        }
      }
      catch (Exception ex)
      {
        if (serialPort != null && serialPort.IsOpen)
        {
          status = false;
          serialPort.Close();
        }
      }
    }
  
  
  
    public void PortOpen()
    {
      if (!serialPort.IsOpen)
      {
        serialPort.Open();
      }
      else
      {
        serialPort.Close();
        serialPort.Open();
      }
    }
    /// <summary>
    ///Close port
    /// </summary>
    public void PortClose()
    {
      if (serialPort != null && serialPort.IsOpen)
      {
        serialPort.Close();
      }
    }
  }
}

Topics: C#