Trouble with sending and receiving data at the same time

Karlsson 1 Reputation point
2022-09-20T21:22:01.633+00:00

Hello!

I have a small program written in C# with windows forms to send data bytes using COM ports. I am testing the program on one computer, on two interconnected USB modules supporting serial communication, so that one transmits, the other receives and vice versa. This program allows to see sending data bytes and receiving bytes on two RichTextBoxes of the same application, but... Not as well as I expected.

243193-image.png

The problem is in "Test" card. On the left side I see sending hex bytes. I assumed I wanted to be able to send 1000 random characters one by one (this is done by the for loop) and see the other com port receive those 1000 characters. These characters are seen as received on the right side of the window, however, only after the entire loop has run, i.e. when the entire 1000 characters packet have been sent. However, I dream about the situation that single characters, however, would be sent immediately as soon as they would be in the buffer - not when the entire loop was finally executed. So I mean sending and receiving particular bytes at the same time.
Currently, this option is only for me if I simply run the .exe file twice (in two windows) and make it to send and receive data in this way. I feel that I need a proper usage of multithreading in this situation, but at the moment I cannot find the way to deal with repetitive sending other than this simple for loop. Is there an option to fix that? Any help will be appreciate.

using System;  
using System.Collections.Generic;  
using System.ComponentModel;  
using System.Data;  
using System.Drawing;  
using System.Linq;  
using System.Text;  
using System.Windows.Forms;  
  
using System.IO.Ports;  
  
namespace USB_tester  
{  
  
 public partial class USB_tester : Form  
 {  
        System.IO.Ports.SerialPort port1;  
        System.IO.Ports.SerialPort port2;  
        delegate void Delegat1();  
        delegate void Delegat2();  

        Delegat1 moj_del1;  
        Delegat2 moj_del2;  
  
 public USB_tester()  
 {  
            InitializeComponent();  
            port1 = new SerialPort();  
            port2 = new SerialPort();  
            port1.ReadTimeout = 500;  
            port1.WriteTimeout = 500;  
 port2.ReadTimeout = 500;  
            port2.WriteTimeout = 500;  
            Settings.Enter += new EventHandler(Ustawienia_Enter);  
            port1.DataReceived += new SerialDataReceivedEventHandler(DataRecievedHandler1);  
            port2.DataReceived += new SerialDataReceivedEventHandler(DataRecievedHandler2);  
            moj_del1 = new Delegat1(ShowReceived1);  
            moj_del2 = new Delegat2(ShowReceived2);  
 }  
 private void DataRecievedHandler1(object sender, SerialDataReceivedEventArgs e)  
        {  
  
            rtbTerminal1.Invoke(moj_del1);  
        }  
  
 private void DataRecievedHandler2(object sender, SerialDataReceivedEventArgs e)  
        {  
  
 rtbTerminal2.Invoke(moj_del2);  
        }  
  
        private void ShowReceived1()  
        {  
         label2.Text = port1.BytesToRead.ToString();  
          
         for (int k = port1.BytesToRead; k > 0; k--)  
         {  
          
         ColoredTerminal(rtbTerminal1, port1.ReadByte().ToString("X") + " ", System.Drawing.Color.Blue);  
         }  
        }  
 private void ShowReceived2()  
        {  
         label2.Text = port2.BytesToRead.ToString();  
          
         for (int l = port2.BytesToRead; l > 0; l--)  
         {  
          
         ColoredTerminal(rtbTerminal2, port2.ReadByte().ToString("X") + " ", System.Drawing.Color.Blue);  
         }  
        }  
 private void ColoredTerminal(System.Windows.Forms.RichTextBox RichTextBox, string Text, System.Drawing.Color Color)  
        {  
            var StartIndex = RichTextBox.TextLength;  
            RichTextBox.AppendText(Text);  
            var EndIndex = RichTextBox.TextLength;  
            RichTextBox.Select(StartIndex, EndIndex - StartIndex);  
            RichTextBox.SelectionColor = Color;  
            RichTextBox.ScrollToCaret();  
        }  
  
        void Ustawienia_Enter(object sender, EventArgs e)  
        {  
            this.cbName1.Items.Clear();  
            this.cbName2.Items.Clear();  
            this.cbParity.Items.Clear();  
            this.cbStop.Items.Clear();  
            foreach (String s in SerialPort.GetPortNames()) this.cbName1.Items.Add(s);  
            foreach (String s in SerialPort.GetPortNames()) this.cbName2.Items.Add(s);  
            foreach (String s in Enum.GetNames(typeof(Parity))) this.cbParity.Items.Add(s);  
            foreach (String s in Enum.GetNames(typeof(StopBits))) this.cbStop.Items.Add(s);  
  
            cbName1.Text = port1.PortName.ToString();  
            cbName2.Text = port2.PortName.ToString();  
            cbBaud.Text = port1.BaudRate.ToString();  
            cbData.Text = port1.DataBits.ToString();  
            cbParity.Text = port1.Parity.ToString();  
            cbStop.Text = port1.StopBits.ToString();  
        }  
        void ButSendClick(object sender, EventArgs e)  
 {  
 if (port1.IsOpen)   
            {   
             Random rnd = new Random();  
             for (int i = 0; i <= 1000; i++)  
             {  
             int num = rnd.Next(0,255);  
     string hexString = num.ToString("X2");  
                ColoredTerminal(rtbTerminal1, hexString + " ", System.Drawing.Color.Black);   
                Byte[] tosend = BitConverter.GetBytes(num);   
                port1.Write(tosend, 0, 1);   
             }  
            }   
            else System.Windows.Forms.MessageBox.Show("Make connection first");   
        }  
 void TabPage1Click(object sender, EventArgs e)  
 {  
  
 }  
 void Label1Click(object sender, EventArgs e)  
 {  
  
 }  
 void Label3Click(object sender, EventArgs e)  
 {  
  
 }  
 void Label4Click(object sender, EventArgs e)  
 {  
  
 }  
 void ButDefault(object sender, EventArgs e)  
 {  
  
 }  
 void CbNameSelectedIndexChanged(object sender, EventArgs e)  
 {  
  
 }  
 void CbBaudSelectedIndexChanged(object sender, EventArgs e)  
 {  
  
 }  
 void RtbTerminalTextChanged(object sender, EventArgs e)  
 {  
  
 }  
 void ButRefreshClick(object sender, EventArgs e)  
 {  
  
 }  
 void ButDefaultClick(object sender, EventArgs e)  
 {  
     this.cbName1.Text = "COM3";  
            this.cbName2.Text = "COM4";  
            this.cbBaud.Text = "9600";  
            this.cbData.Text = "8";  
            this.cbParity.Text = "None";  
            this.cbStop.Text = "One";  
 }  
 void ButCancelClick(object sender, EventArgs e)  
 {  
         cbName1.Text = port1.PortName.ToString();   
         cbName2.Text = port2.PortName.ToString();   
            cbBaud.Text = port1.BaudRate.ToString();   
            cbData.Text = port1.DataBits.ToString();   
            cbParity.Text = port1.Parity.ToString();   
            cbStop.Text = port1.StopBits.ToString();  
 }  
 void PbStatus1Click(object sender, EventArgs e)  
 {  
            if (port1.IsOpen)   
            {   
                pbStatus1.BackColor = System.Drawing.Color.Red;   
                port1.Close();   
                labStatus1.Text = "No connection";   
                ColoredTerminal(rtbTerminal1, "\nConnection end - " + port1.PortName + "\n", System.Drawing.Color.Orange);   
            }   
            else   
            {   
                try   
                {   
                    port1.PortName = this.cbName1.Text;  
                    port1.BaudRate = Int32.Parse(this.cbBaud.Text);   
                    port1.DataBits = Int32.Parse(this.cbData.Text);   
                    port1.Parity = (Parity)Enum.Parse(typeof(Parity), this.cbParity.Text);   
                    port1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), this.cbStop.Text);   
                    port1.Open();   
                    pbStatus1.BackColor = System.Drawing.Color.Green;   
                    labStatus1.Text = "Active connection (port:" + port1.PortName.ToString() + ", speed: " + port1.BaudRate.ToString() + ", data bits: " +   
                    port1.DataBits.ToString() + "\n stop bits: " + port1.StopBits.ToString() + ", parity: " + port1.Parity.ToString() + ")";   
                    ColoredTerminal(rtbTerminal1, "Connection started - " + port1.PortName + "\n", System.Drawing.Color.Orange);   
                }   
                catch(Exception exc)   
                {   
                    MessageBox.Show("Connection error:\n" + exc.Message);   
                }   
            }   
 }  
 void PbStatus2Click(object sender, EventArgs e)  
 {  
            if (port2.IsOpen)   
            {   
                pbStatus2.BackColor = System.Drawing.Color.Red;   
                port2.Close();   
                labStatus2.Text = "No connection";   
                ColoredTerminal(rtbTerminal2, "\nConnection end - " + port2.PortName + "\n", System.Drawing.Color.Orange);   
            }   
            else   
            {   
                try   
                {   
                    port2.PortName = this.cbName2.Text;   
                    port1.BaudRate = Int32.Parse(this.cbBaud.Text);   
                    port1.DataBits = Int32.Parse(this.cbData.Text);   
                    port1.Parity = (Parity)Enum.Parse(typeof(Parity), this.cbParity.Text);   
                    port1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), this.cbStop.Text);   
                    port2.Open();   
                    pbStatus2.BackColor = System.Drawing.Color.Green;   
                    labStatus2.Text = "Active connection (port:" + port2.PortName.ToString() + ", speed: " + port2.BaudRate.ToString() + ", data bits: " +   
                    port2.DataBits.ToString() + "\n stop bits: " + port2.StopBits.ToString() + ", parity: " + port2.Parity.ToString() + ")";   
                    ColoredTerminal(rtbTerminal2, "Connection started - " + port2.PortName + "\n", System.Drawing.Color.Orange);   
                }   
                catch(Exception exc)   
                {   
                    MessageBox.Show("Connection error:\n" + exc.Message);   
                }   
            }   
              
 }  
 void LabStatusClick(object sender, EventArgs e)  
 {  
  
 }  
 void NumericSendValueChanged(object sender, EventArgs e)  
 {  
  
 }  
 void Label7Click(object sender, EventArgs e)  
 {  
  
 }  
 }  
}  
Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,811 questions
Windows 10
Windows 10
A Microsoft operating system that runs on personal computers and tablets.
10,417 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,097 questions
{count} votes

2 answers

Sort by: Most helpful
  1. AgaveJoe 25,866 Reputation points
    2022-09-21T12:59:30.853+00:00

    In my opinion your design is not realistic. When are you ever going to have an application that talks to itself through a serial port?

    I would create one application that handles a specific serial port. Then just open the application twice.

    Follow the coding pattern in the Serial Port reference documentation.

    SerialPort Class

    1 person found this answer helpful.

  2. AgaveJoe 25,866 Reputation points
    2022-09-23T15:11:50.193+00:00

    I've already browsed articles about SerialPort. I admit that the examples are so diverse that I am still looking for a common part to put everything together in my head.

    I assume you are working with a virtual COM port. More than likely your observations are not what's actually happening. Your COM port device has a driver and that driver talks to the device over the USB port. Several buffers are involved as well as a virtual UART.

    It might seem like the bytes are only transmitted when the loop ends but really the bytes are send asynchronously when the device driver decides to flush the buffer. On the receive side the same thing happens but in the opposite direction. Bytes are being received by the hardware and buffered. At some point there is an interrupt that tells the system there are bytes in the receive buffer. At that point your high level code gets notified there's data in the buffer. Also, at that moment there can be more bytes in the buffer than reported to your application. It depends on how busy the COM port is that moment.

    In your test code the loop runs very fast. It seems like the bytes are magically held until the loop finishes due to the buffering and the asynchronous nature of hardware. Try putting a one second pause in the loop to see what happens.

    Every system I've ever worked on does not send arbitrary bytes over the COM port. Usually there is a very specific data protocol (packet) that might involve a start byte, data length, check sums, etc. The send side is always easy but the receive side needs to frame the data according to the protocol. Usually this is where problems show up due to how an asynchronous device works. The buffer could have 10 packets or a half packet. You just don't know. Of course this assumes full-duplex communication. Your test sends arbitrary bytes which might be okay if your building a proxy or data logger.

    In my opinion the best way to think of the serial port communication is your application has no idea what's in the buffer at any given time. There could be one byte or a thousand bytes.

    0 comments No comments