Creating a Serial Port Interface with C#
A Serial Port Interface is a Graphical User Interface program that runs on a personal computer and can be used to connect to devices with a serial interface like microcontrollers.
In this article a detailed step by step description with source code how to create a simple Serial Port GUI using C# that can be used to connect a PC to serial devices.
Start a new C# project and change the Form text to “Serial Port Interface” as shown below.
From the toolBox on the left-hand side, under the Components sub-section, drag and drop SerialPort Control on the form and it shall appear below it. This control will enable us to send or receive data through the COM Port (Serial port) of the PC.
Start building the interface by dragging controls from the ToolBox onto the form as shown below:
- Drag and drop two GroupBox controls from the ToolBox under the Containers sub-Section.
Change their Text properties (from the Properties pane. Remember before you change properties of a control you have to select it first ) to “COM Serial Port Settings” and “Data Mode“.
We use a GroupBox control to group controls, in our case, configuration settings controls. - Drag and drop three Button controls from the ToolBox under the Common Controls sub-Section.
Change the first button Text properties to “Connect” and the Name properties to “btnConnect“. We will use this button to connect or disconnect to selected serial ports.
Change the second button Text properties to “Send”, the Name properties to “btnSend” and Enabled properties to “False”. We will use this button to send data typed on the screen to the Serial Port.
We set its Enable Properties to false to prevent a user from clicking it before we connect to a port this would generate an error.
Change the last button Text properties to “Clear” and the Name properties to “btnClear“.
We will use this button to clear the screen. - Drag and drop one TextBox, one Rich TextBox and one Label control from the ToolBox under the Common Controls sub-Section. Change the Rich TextBox Name properties to “rtxtDataArea“, ReadOnly properties to “True” (To avoid people typing in there) and re-size. This is where we will receive data from the Serial port and any data sent to serial port will be displayed here as well.
Change the TextBox Name properties to “txtSend” and change the Label Text properties to “Send Data”.
This is where we are going to type our data and click on Send button to send it to the serial port.
Add the rest of the controls inside COM Serial Port Settings and Data mode GroupBoxes to complete our
Graphical User Interface design.
- Drag and drop five Label controls inside the COM Serial Port Settings GroupBox and change their Text Properties to “COM Port:”, “Baude Rate:”, “Parity:”, “Data Bits:” and “Stop Bits:”
- Drag and drop five ComboBox controls inside the COM Serial Port Settings GroupBox as well as shown above (a ComboBox is also under the Common controls sub-Section). Place these ComboBoxes next to their respective labels and change their properties:
- Change the Name properties of the first one to “cmbPortName” and Text Properties to “Select Port Name”,
- Change the Name properties of the second one to “cmbBaudeRate“, Text Properties to “Select Baude and in the Items properties, open the Collections and put these Baude Rate values inside that String collection Editor:
1200
2400
4800
9600
19200
38400
57600
115200
Click Ok. - Change the Name properties of the third one to “cmbParity“, Text Properties to “Select Parity” and in the Items properties, open the Collections and put these Parity values inside that String collection editor:
None
Even
Odd
Click Ok. - Change the Name properties of the fourth one to “cmbDataBits“, Text Properties to “Select Data Bits” and in the Items properties, open the Collections and put these Data Bits values inside that String collection Editor:
7
8
9
Click Ok. - Change the Name properties of the fifth one to “cmbStopBits“, Text Properties to “Select Stop Bits” and in the Items properties, open the Collections and put these Stop Bits values inside that String collection Editor:
1
2
3
Click Ok.
- Drag and drop two RadioButton controls inside the Data Mode GroupBox.
Change the Text properties of the first to “Text” and its Name properties to “rbText” and the second RadioButton Text properties to “Hex” and Name properties to “rbHex“.
To view the code, right click inside your form and click “View Code”, a window similar to this one below will open (Form1.cs):
The first thing to do is to add “using System.IO.Ports” namespace to be able to access serial port commands as shown below on line 20.
Note that two forward slashes ( // )in C programming are used to write a comment. You will see a lot of them in this tutorial to try and explain what the codes are doing.
The next thing to do is to update the COM Port ComboBox with all the Ports available on the computer when this application runs.
To do that, go back to your form design view (click on Form1.cs[Design] or right click inside your code and select View designer) and double click on top of the form or anywhere inside the form but not on any control, this will open the Code View again with a Form1 load event (line 31). Any code written inside those two curly bracket { } will be executed every time this form loads (start).
Let us create a function “private void updatPorts()” (line 36) to retrieve all the port names inside your computer.
Call this function in Form1 Load event (line 33).
Run the application from the Debug Menu then Start debugging or click on the green triangle icon on the ToolBar.
Your application should run without any errors and your Port Name combo box should display the list of ports on your PC (make sure your PC has at least one COM port if not connect a USB to RS232 converter as shown below otherwise your application will generate an error (You can use Ty-Catch statement to prevent your application from crashing)).
Buy Serial to USB Converters from our Online Shop
Alternatively, you can use Virtual Serial Port Driver Pro to generate virtual COM ports fro your application.
Virtual Serial Port Driver Pro is a professional utility that allows creating virtual serial ports which completely emulate the behavior and parameters of hardware COM interfaces. With the software, you can create as many virtual serial ports as you need. It’s an efficient solution for managing real and virtual serial ports.
You can learn more and download the Virtual Serial Port Driver software from Eltima Software website.
If you want to learn how to control your microcontroller from a PC Graphical User Interface, please read this article:
Controlling a PIC Microcontroller from a PC Graphical User Interface (GUI)
Watch the Video Tutorial
Connecting to a COM Port
To connect to a com port is very easy, the “System.IO.Ports” namespace has all you need to communicate.
To start a communication you need:
1.
1 2 | //Initialise your port configurations: SerialPort ComPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One); |
2.
1 2 | //Open Port: ComPort.Open(); |
3.
1 2 | // To close a Port: ComPort.Close(); |
with these two lines you can open COM1 for communication but because your computer might have more than one port especially if you are using a USB to serial converter it is better to let the program populate all the installed ports on your computer and allow you to choose one.
Writing Data Out to Serial Port
To writing Data out you must make sure a com port is already open that is why we disabled the “Send Button” so before the connection.
1. If you are going to write a Text data, you can write data out straight to the port:
1 | ComPort.Write("Your Text..."); |
2. If you are going to write a series of Hex data, convert it first to a set of bytes.
1 | ComPort.Write(new byte[] {0x0A, 0x11, 0xFF}, 0, 3); |
Reading Data from the Serial Port
To read from the serial port, we need to create and listen to an event continuously for incoming data.
When data is received, it raises the event.
In the Connect function after we have opened the serial port, create ComPort.DataReceived event:
1 2 | ComPort.DataReceived += SerialPortDataReceived; //Check for received data. When there is data in the receive buffer, //it will raise this event, we need to subscribe to it to know when there is data |
Create the SerialPortDataReceived function that will be called when there is data in the receive buffer:
1 2 3 4 5 6 | private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e) { var serialPort = (SerialPort)sender; var data = serialPort.ReadExisting(); SetText(data); } |
To avoid cross thread error when reading form a different thread, set the CheckForIllegalCrossThreadCalls to false in the Form1_Load event:
1 | CheckForIllegalCrossThreadCalls = false; |
Data recived from the serial port is coming from another thread context than the UI thread.
Instead of reading the content directly in the SerialPortDataReceived, we need to use a delegate.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | delegate void SetTextCallback(string text); private void SetText(string text) { //invokeRequired required compares the thread ID of the calling thread to the thread of the creating thread. // if these threads are different, it returns true if (this.rtxtDataArea.InvokeRequired) { rtxtDataArea.ForeColor = Color.Green; //write text data in Green colour SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.rtxtDataArea.AppendText(text ); } } |
Learn more about the SerialPort Class from Microsoft website:
Full project code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | /* * Project: Serial Port Interface with C# * Company: StudentCompanion * Created: April 2013 * Updated: February 2020 * Visual Studio: 2019 * .NET Framework: 4.6.2 * Notes: This is a simple demontration on how to use the SerialPort control for * communicating with your PC's COM Port. * This is for educational purposes only and not for any commercial use. * For more on this, please visit: https://www.studentcompanion.co.za/creating-a-serial-port-interface-with-c/ * */ 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; //This is a namespace that contains the SerialPort class namespace Serial_Communication { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { updatePorts(); //Call this function everytime the page load //to update port names CheckForIllegalCrossThreadCalls = false; } private void updatePorts() { // Retrieve the list of all COM ports on your Computer string[] ports = SerialPort.GetPortNames(); foreach (string port in ports) { cmbPortName.Items.Add(port); } } private SerialPort ComPort = new SerialPort(); //Initialise ComPort Variable as SerialPort private void connect() { bool error = false; // Check if all settings have been selected if (cmbPortName.SelectedIndex != -1 & cmbBaudRate.SelectedIndex != -1 & cmbParity.SelectedIndex != -1 & cmbDataBits.SelectedIndex != -1 & cmbStopBits.SelectedIndex != -1) { //if yes than Set The Port's settings ComPort.PortName = cmbPortName.Text; ComPort.BaudRate = int.Parse(cmbBaudRate.Text); //convert Text to Integer ComPort.Parity = (Parity)Enum.Parse(typeof(Parity), cmbParity.Text); //convert Text to Parity ComPort.DataBits = int.Parse(cmbDataBits.Text); //convert Text to Integer ComPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cmbStopBits.Text); //convert Text to stop bits try //always try to use this try and catch method to open your port. //if there is an error your program will not display a message instead of freezing. { //Open Port ComPort.Open(); ComPort.DataReceived += SerialPortDataReceived; //Check for received data. When there is data in the receive buffer, //it will raise this event, we need to subscribe to it to know when there is data } catch (UnauthorizedAccessException) { error = true; } catch (System.IO.IOException) { error = true; } catch (ArgumentException) { error = true; } if (error) MessageBox.Show(this, "Could not open the COM port. Most likely it is already in use, has been removed, or is unavailable.", "COM Port unavailable", MessageBoxButtons.OK, MessageBoxIcon.Stop); } else { MessageBox.Show("Please select all the COM Serial Port Settings", "Serial Port Interface", MessageBoxButtons.OK, MessageBoxIcon.Stop); } //if the port is open, Change the Connect button to disconnect, enable the send button. //and disable the groupBox to prevent changing configuration of an open port. if (ComPort.IsOpen) { btnConnect.Text = "Disconnect"; btnSend.Enabled = true; if (!rdText.Checked & !rdHex.Checked) //if no data mode is selected, then select text mode by default { rdText.Checked = true; } groupBox1.Enabled = false; } } // Call this function to close the port. private void disconnect() { ComPort.Close(); btnConnect.Text = "Connect"; btnSend.Enabled = false; groupBox1.Enabled = true; } //whenever the connect button is clicked, it will check if the port is already open, call the disconnect function. // if the port is closed, call the connect function. private void btnConnect_Click(object sender, EventArgs e) { if (ComPort.IsOpen) { disconnect(); } else { connect(); } } private void btnClear_Click(object sender, EventArgs e) { //Clear the screen rtxtDataArea.Clear(); txtSend.Clear(); } // Function to send data to the serial port private void sendData() { bool error = false; if (rdText.Checked == true) //if text mode is selected, send data as tex { // Send the user's text straight out the port ComPort.Write(txtSend.Text ); // Show in the terminal window rtxtDataArea.ForeColor = Color.Green; //write sent text data in green colour txtSend.Clear(); //clear screen after sending data } else //if Hex mode is selected, send data in hexadecimal { try { // Convert the user's string of hex digits (example: E1 FF 1B) to a byte array byte[] data = HexStringToByteArray(txtSend.Text); // Send the binary data out the port ComPort.Write(data, 0, data.Length); // Show the hex digits on in the terminal window rtxtDataArea.ForeColor = Color.Blue; //write Hex data in Blue rtxtDataArea.AppendText(txtSend.Text.ToUpper() + "\n"); txtSend.Clear(); //clear screen after sending data } catch (FormatException) { error = true; } // Inform the user if the hex string was not properly formatted catch (ArgumentException) { error = true; } if (error) MessageBox.Show(this, "Not properly formatted hex string: " + txtSend.Text + "\n" + "example: E1 FF 1B", "Format Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } } //Convert a string of hex digits (example: E1 FF 1B) to a byte array. //The string containing the hex digits (with or without spaces) //Returns an array of bytes. </returns> private byte[] HexStringToByteArray(string s) { s = s.Replace(" ", ""); byte[] buffer = new byte[s.Length / 2]; for (int i = 0; i < s.Length; i += 2) buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16); return buffer; } private void btnSend_Click(object sender, EventArgs e) { sendData(); } //This event will be raised when the form is closing. private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (ComPort.IsOpen) ComPort.Close(); //close the port if open when exiting the application. } //Data recived from the serial port is coming from another thread context than the UI thread. //Instead of reading the content directly in the SerialPortDataReceived, we need to use a delegate. delegate void SetTextCallback(string text); private void SetText(string text) { //invokeRequired required compares the thread ID of the calling thread to the thread of the creating thread. // if these threads are different, it returns true if (this.rtxtDataArea.InvokeRequired) { rtxtDataArea.ForeColor = Color.Green; //write text data in Green colour SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.rtxtDataArea.AppendText(text ); } } private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e) { var serialPort = (SerialPort)sender; var data = serialPort.ReadExisting(); SetText(data); } private void btnRefresh_Click(object sender, EventArgs e) { updatePorts(); //Call this function to update port names } } } |
All the files are zipped, you will need to unzip them.
Download Serial Port Communication Visual Studio Project