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
:
// 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.
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!