Introduction
SerialComms.Manager is a component for .Net written in C# which provides complete functionality
for RS232 serial communications. It is a dll which can simply be added to any project and utilises
the System.IO.Ports.SerialPort class. It provides an interface to select and set a serial port and to
send and receive data. Only a few lines of code are needed to get it working.
Background
PC's no longer come with an on board UART for serial communication accessed through a DB9
plug on the back of the case. Despite this the RS232 protocol is not dead. Many devices can still be
accessed using serial communications. Examples are data logging and medical instruments, GPS devices
and the nowadays ubiquitous maker devices such as Arduino. The access is often through virtual
COM ports implemented as interfaces on USB devices.
Why another serial communications class when there are already a lot of articles about implementing
serial communications. Many provide boilerplate code that may be cut and pasted into the
developer's code, others provide complete applications modelled according to the author's ideas. I
have implemented serial communication functionality many times and found that what I needed
was not always available in other solutions. This motivated me to create a complete component
SerialComms.Manager. This can easily be added to a project, has an easy to use interface and works
right out of the box. I have tried to implement the basic functionality required to use the component
in a real world situation such as data aquisition or configuring a device through it's serial port.
Net provides the System.IO.Ports.SerialPort class to enable serial communications. This is itself a
wrapper for the Win32 classes which C and C++ programmers have been familiar with. SerialPort
implements IDisposable because unmanaged resources must be released on finishing. The
SerialPort class is straight forward to use but any research on the Internet will show there is a lack
of completeness to the implementation. This is probably because RS232 serial communications do
not play the role they used to. The Open() and Close() methods are a case in point. The MSDN note
to Close() states: The best practice for any application is to wait for some amount of time after
calling the Close method before attempting to call the Open method, as the port may not be closed
instantly. How long to wait is not stated and of course nobody knows because a Win32 handle is
being released and the time taken depends on many factors.
There are a couple of ways to receive data from a serial port. One is polling the device continously
from the main thread to check whether data has arrived and then read it - synchronous operation.
The other is to have a thread especially dedicated to reading data and then have the thread blocked
until a data arrival event occurs – asynchonous operation. This thead then processes the data
allowing the main thread to continue doing other things. This is the only way to maintain user
interface response if data is arriving regularly.
Early versions of .Net required the user to set up a worker thread if asynchronous operation was
required but the SerialPort class now provides the DataReceived event for this purpose. The
DataReceived event is raised on a secondary thread when data is received from the SerialPort
object. All that is required to use this is to provide an Event Handler method and attach it to the
DataReceived event. This leaves the main thread available to update the user interface or to send data.
The Solution
What functionality is required for a serial communications component? It must have the ability to:
- Enumerate available ports and get their individual capabilities.
- Select a port and set it's operating parameters.
- Receive data on the port – asynchronously.
- Send data on the port.
- Save and restore the parameters.
The SerialComms.Manager component implements this functionality through these classes:
_SerialCommsManager
provides the interface to the component. It is a container for _Serialport
which itself contains a System.IO.Ports.SerialPort. It uses _SerialPortSettingsXML
to save its settings as XML. During construction the XML file is retrieved from a default location and the settings used to instantiate a _Serialport
(default settings are used if the file does not exist). If required the _SerialPortSettingsForm
dialog may be invoked to select a port and modify it's settings. The settings are returned as a _SerialPortSettings
object. This is how the settings dialog appears:
Once a port is chosen and the settings are correct the connection may be opened and data received
and sent. The following screenshot shows a WinForm client displaying GPS NMEA data:
The serial port configuration is saved in the current directory in an XML file called SerialPortSettings.xml. This is the format:
<_SerialPortSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<_useWMI>true
<_BaudRate>19200
<_Handshake>None
<_Parity>None
<_PortName>COM3
<_StopBits>One
<_DataBits>8
How it works
The available serial port names are obtained using System.IO.Ports.SerialPort.GetPortNames. This provides an array of names such as COM1, COM2. Additional information about the ports is obtained using a WMI query if possible. This is the port description in the settings dialog. Reflection can be used to access the SerialPort BaseStream and obtain a COMMPROP structure. This provides some enumerations of the port capabilities. These are used to refine the port property selection lists.
System.IO.Ports.SerialPort implements a DataReceived event. This event is not exposed through the interface. Instead 2 new event types are defined and these are exposed through delegates. When the constructor for _SerialCommsManager is called the appropriate event handler is passed as a parameter. When the DataReceivedEvent is fired it is processed by the DataReceivedHandler. This in turn fires 2 new events, NewSerialDataBytesReceived
and NewSerialDataStringReceived
. These are the events that may be subscribed to by the application. The event handler runs in a separate thread so any processing that can be done here frees the other threads.
Using the code
The component is very straightforward to use. The Winforms SerialCommsClient example is provided to give a practical example. It is not necessary to have a form to host the _SerialCommsManager
object.
To incorporate the component into a user program follow these steps.
- In code add a reference to the
_SerialCommsManager
class.
private _SerialCommsManager serialCommsManager = null;
- Define one event handler having the signature required. This one will handle data bytes events:
void DataBytesReceived(object sender, _SerialDataBytesArgs data)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler<_SerialDataBytesArgs>(DataBytesReceived), new object[] { sender, data });
return;
}
const int maxTextLength = 1000;
if (textBoxData.TextLength > maxTextLength)
textBoxData.Text = textBoxData.Text.Remove(0, textBoxData.TextLength - maxTextLength);
string str = Encoding.ASCII.GetString(data.BytesOut, 0, data.NumBytes);
textBoxData.AppendText(str);
}
This one will handle data string events:
void DataStringReceived(object sender, _SerialDataStringArgs data)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler<_SerialDataStringArgs>(DataStringReceived), new object[] { sender, data });
return;
}
const int maxTextLength = 1000;
if (textBoxData.TextLength > maxTextLength)
textBoxData.Text = textBoxData.Text.Remove(0, textBoxData.TextLength - maxTextLength);
textBoxData.AppendText(data._String);
}
- Create an instance of the _SerialCommsManager class. This will subscribe to the chosen event type:
serialCommsManager = new _SerialCommsManager(DataBytesReceived);
This is all that is required to instantiate the _SerialCommsManager
class. If the manager has been run previously the saved configuration will be restored from the XML file SerialPortSettings.xml. If the hardware configuration has not changed - the USB serial port adaptor is still plugged in for example then SCM_Start
may be called to open the port. This will enable data to be received immediately. SCM_Stop
will close the port stopping the processing of data. This screenshot shows sensor data from an Arduino being displayed:
SCM_Send
may also be called to send data. In the example application data may be typed into the Send Window. Data is sent one line at a time. Each time "Enter" is pressed the data on the current line is sent. The cursor may be placed on an existing line of data where double clicking resends it.
If this is the first time the application has run or if the hardware configuration has changed a serial port must be set up. To select and set up an available serial port call SCM_GetSettingsGUI
. This will open the settings dialog to allow the selection to be made.
Points of Interest
There are 2 downloads available.
- SerialComms.zip - This solution contains the project to create SerialComms.Manager.dll. It also contains a client project to demonstrate how to incorporate the component into an application.
- SerialCommsApp.zip - Executables only (release) which may be run as a complete application. Use this to see what the dll does.
A future article is planned showing how this component may be used as the basis of a data acquisition application using Arduino.
History
V1.0 Article published - 18 June 2014