Go Down

Topic: [SOLVED] C#/Serial write: does opening the port add garbage to the write buffer? (Read 2580 times) previous topic - next topic

Angelo Stavrow

I've a question regarding the use of the `SerialPort` class in C#.

I've got a working sketch on the Arduino that essentially watches the serial port for incoming data. If it reads `PING`, it writes `PONG`; otherwise, it writes `400`:

Code: [Select]

// Set the size of the serial-port read buffer.
const byte serialBufferLength = 255;

// Serial-port-related global variables.
char receivedChars[serialBufferLength];
boolean newData = false;

void setup() {
    Serial.begin(115200);   // Start the serial port.
}

void loop() {
    receiveWithEndMarker();
}

// Monitors the serial port for incoming data.
void receiveWithEndMarker()
{
    static byte index = 0;
    char endMarker = '\n';
    char receivedChar;

    while (Serial.available() > 0 && newData == false)
    {
        receivedChar = Serial.read();

        if (receivedChar != endMarker)
        {
            receivedChars[index] = receivedChar;
            index++;
            if (index >= serialBufferLength)
            {
                index = serialBufferLength -1;
            }
        }
        else
        {
            receivedChars[index] = '\0';  // Terminate the string.
            index = 0;
            newData = true;
        }
    }

    parseNewSerialData();
}

void parseNewSerialData()
{
    if (newData == true)
    {
        // Uncomment the line below for debugging.
        // Serial.println(receivedChars);

        // Convert the received character array into a String object to get
        // access to those fancy String-handling methods.
        String receivedString = String(receivedChars);

        // Immediately handle a client PING.
        if (receivedString == "PING")
        {
            Serial.println("PONG");
            newData = false;
            return;
        }
        else
        {
            Serial.println("400");
            newData = false;
            return;
        }
    }
}



This works with no problem using the Serial Monitor (watching for a `\n` newline character at 115200 baud).

I'm having some difficulty with the C# side of things, though. I have a basic form that includes a combobox (populated with available COM ports), a "Test Connection" button, and a status label.

Code: [Select]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LCLME_Calibration
{
    public partial class MainForm : Form
    {
        SerialPort comPort = new SerialPort();
        Boolean comPortIsSet = false;
        
        String rxString;

        public MainForm()
        {
            // That thing that all forms must do.
            InitializeComponent();

            // Enumerate COM ports in the selection comboBox.
            string[] availableComPorts = SerialPort.GetPortNames();
            comPortComboBox.DataSource = availableComPorts;

            // Set up the COM port.
            SetUpComPort();

            if (comPortIsSet)
            {
                comPortComboBox.SelectedIndex = comPortComboBox.FindString(comPort.PortName);
            }
        }

        private void comPortComboBox_SelectionChangeCommitted(object sender, EventArgs e)
        {
            // The COM port has been selected from the combobox.
            String comPortName = comPortComboBox.SelectedValue.ToString();
            if (comPort.IsOpen)
            {
                comPort.Close();
            }
            comPort.PortName = comPortName;
            comPort.Open();
            statusLabel.Text = @"Arduino port set to " + comPort.PortName;

            // Save the newly-selected COM port to User Defaults.
            Properties.Settings.Default.ComPort = comPortName;
            Properties.Settings.Default.Save();
            statusLabel.Text = statusLabel.Text + @"; saved as default.";

            comPortIsSet = true;
            PingArduino();
        }

        private void SetUpComPort()
        {
            // Set the basic COM port properties.
            comPort.NewLine = "\n";
            comPort.BaudRate = 115200;
            comPort.RtsEnable = true;
            //comPort.DtrEnable = true;
            
            // Set the data-received handler.
            comPort.DataReceived += serialPort1_DataReceived;

            // If there's a saved COM port name, fetch and set it.
            if (Properties.Settings.Default.ComPort.Length > 0)
            {
                comPort.PortName = Properties.Settings.Default.ComPort;
                statusLabel.Text = @"Arduino port set to " + comPort.PortName;
                comPort.Open();

                comPortIsSet = true;
            }
            else
            {
                statusLabel.Text = @"Please select Arduino COM port.";
                comPortIsSet = false;
            }

        }

        private void PingArduino()
        {
            String txString = @"PING";

            // The expectation is that after the COM port name is set, the port
            // is opened and stays open. But .NET's serial port implementation
            // is very shaky, so we verify this anyhow.
            if (!comPort.IsOpen)
            {
                comPort.Open();
            }

            // Send a "PING" command to the Arduino.
            try
            {
                comPort.WriteLine(txString);
                Debug.WriteLine(@"TX: " + txString);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                rxString = comPort.ReadLine();
                this.Invoke(new EventHandler(DisplayText));
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        private void DisplayText(object sender, EventArgs e)
        {
            Debug.WriteLine(@"RX: " + rxString);
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            comPort.Close();
        }

        private void MainForm_Shown(object sender, EventArgs e)
        {
            PingArduino();
        }

        private void testConnectionButton_Click(object sender, EventArgs e)
        {
            PingArduino();
        }
    }
}


As you can see, the debug console reports the TX and RX strings.

Now, when the program is first launched, or when a COM port is selected from the combobox, the Arduino returns a `400` when `PingArduino()` is called.

If I click the test-connection button, however, the Arduino returns `PONG` as expected.

If I have the Arduino echo what it receives from the PC, the "failing" ping it reports is actually `????PING`.

So of course, it parses this as a failure--which it is.

Normally, the port is opened when the program is launched, and closed when the program exits. So it seems that for some reason, the write buffer (?) gets prefixed with some random question-mark characters after the `comPort.Open()` call.

Is this because the Arduino resets after the port is opened? What would be the best way to guard against these random characters appearing in my serial communication?

Thanks in advance!

PaulS

Quote
Is this because the Arduino resets after the port is opened?
The Arduino is reset when the port is opened. Whether this causes the question mark output, or not, is not determinate.

What you could do is send a message, in setup(), and not have the C# app send data until it gets this message from the Arduino.
The art of getting good answers lies in asking good questions.

Angelo Stavrow

What you could do is send a message, in setup(), and not have the C# app send data until it gets this message from the Arduino.
That's a fantastic idea.

Unfortunately, the DataReceived handler doesn't fire when the Arduino sends the `READY` command, so it's never received by the WinForms application.  :-\

EDIT: I should say, the DataReceived handler rarely fires when the Arduino sends the `READY` command. It's very erratic in its behaviour.

Angelo Stavrow

Okay, updates.

Trying to solve this without broadcasting a Ready signal as @PaulS suggested (it's a good workaround suggestion, I just want to understand what's happening here).

Setting `comPort.DtrEnable` to `true` seems to fix the random ? characters that were prepended to the serial write.

However, when the Arduino is pinged after the port is opened, nothing seems to come back. The serial write never appears to get to the Arduino--possibly because it's just taking too long for the port to open.

This is backed up by the fact that if I set a breakpoint and step through any function that opens the serial port, the DataReceived handler will fire. So it seems to be a question of some required delay.

Angelo Stavrow

Okay, final update.

I moved all of the `comPort.Open()` calls into a new method:

Code: [Select]

private void OpenPort()
{
    comPort.Open();
    Thread.Sleep(1000);
}


This of course blocks the main thread / UI when it's called, but since we're not opening and closing the port willy-nilly (once, maybe twice, during the application's lifecycle), it's not really a big deal.

Doing so will force the application to wait one second until the serial port is ready, and thus the DataReceived handler fires reliably.

Aside: I've also realized that this topic should probably be in the Interfacing w/ Software on the Computer forum--would a moderator be so kind as to move it there and mark it as solved?

PaulS

Quote
would a moderator be so kind as to move it there and mark it as solved?
You can change the thread title, to add Solved.

You can also use the Report to moderator link in every reply to communicate more directly with the moderators, rather than just hoping they read this thread.
The art of getting good answers lies in asking good questions.

Angelo Stavrow

You can change the thread title, to add Solved.

You can also use the Report to moderator link in every reply to communicate more directly with the moderators, rather than just hoping they read this thread.
Will do, thanks! :)

econjack

I used this code in VS some time ago, but haven't checked it with a current version of VS. As I recall, it expects the Arduino to send ACKNOWLEDGE to test the link:

Code: [Select]

 
  private void SetComPort()
  {
    try
    {
      string[] ports = SerialPort.GetPortNames();
      foreach (string port in ports)
      {
        currentPort = new SerialPort(port, 19200);
        currentPort.PortName = port;
        serialPort1.PortName = port;
        txtPortName.Text = port;
        currentPort.Parity = Parity.None;
        currentPort.DataBits = 8;
        currentPort.StopBits =  (StopBits) 1;


        if (currentPort != null)
        {
          lblPortSearch.Text = "Using Port:";
          break;
        }
        else
        {
 //         portFound = false;
        }
      }
    }
    catch (Exception ex)
    {
      MessageBox.Show(ex.Message, "Error Reading Port");
    }
  }
  private bool DetectArduino()
  {
    try
    {
      //The below setting are for the Hello handshake
       
      byte[] buffer = new byte[5];
      buffer[0] = (byte) '[';

      int intReturnASCII = 0;
      char charReturnValue = (char)intReturnASCII;
      currentPort.Open();
      currentPort.Write(buffer, 0, 1);
      Thread.Sleep(1000);
      int count = currentPort.BytesToRead;
      string returnMessage = "";
      while (count > 0)
      {
        intReturnASCII = currentPort.ReadByte();
        returnMessage = returnMessage + Convert.ToChar(intReturnASCII);
        count--;
      }
     
      currentPort.Close();
      if (returnMessage.Contains("ACKNOWLEDGE"))
      {
        return true;
      }
      else
      {
        return false;
      }
    }
    catch (Exception e)
    {
      MessageBox.Show(e.Message, "Error Reading Port");
      return false;
    }
  }

Angelo Stavrow

@econjack, that appears to all run on the main thread, and I notice that you're also calling `Thread.Sleep()` for one second after opening the port.

I suspect that without that delay your code may not work as intended, would you recall if that's the case?

Go Up