Click here to Skip to main content
Click here to Skip to main content

SerialComms.Manager - A serial communications plug-in for .NET

, 17 Jun 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Add Serial Communications functionality to a .NET program easily using this component.

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:

UML class diagram

_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:

COM port settings dialog

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:

Client window showing GPS data

The serial port configuration is saved in the current directory in an XML file called SerialPortSettings.xml. This is the format:

<!--?xml version="1.0"?-->
<_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.

Callback

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)
            {
                // Invoke causes deadlock when closing serial port. BeginInvoke() doesn't block the event handler.
                this.BeginInvoke(new EventHandler<_SerialDataBytesArgs>(DataBytesReceived), new object[] { sender, data });
                return;
            }

            const int maxTextLength = 1000; // maximum text length in text box
            if (textBoxData.TextLength > maxTextLength)
                textBoxData.Text = textBoxData.Text.Remove(0, textBoxData.TextLength - maxTextLength);

            // Convert bytes to ASCII text.
            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)
            {
                // Invoke causes deadlock when closing serial port. BeginInvoke() doesn't block the event handler.
                this.BeginInvoke(new EventHandler<_SerialDataStringArgs>(DataStringReceived), new object[] { sender, data });
                return;
            }

            const int maxTextLength = 1000; // maximum text length in text box
            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:

Receiving Arduino data

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

pwasser
Engineer
Australia Australia
Did a couple of years of Electrical Engineering, dropped out then graduated in Physics (and Maths). After hating batch Fortran began real programming in Basic on PDP11, HP9825 and CP/M Z80 - then dBase, Pascal and C. Nowadays C++ and C# on Windows.
Have worked in Geophysics, Biophysics, Cartography, NDT and SCADA. Have an aversion to projects in Defence or those effecting an invasion of privacy.
Of late have become enamoured with Android and Arduino. Sniffing around Tre.

Comments and Discussions

 
QuestionNET 2.0 version PinmemberMember 778797029-Jul-14 0:27 
AnswerRe: NET 2.0 version [modified] Pinmemberpwasser29-Jul-14 18:08 
GeneralMy vote of 3 PinmemberKarstenK23-Jun-14 0:41 
GeneralRe: My vote of 3 [modified] Pinmemberpwasser23-Jun-14 0:48 
QuestionThread safe? Pinmembermartin.nedopil17-Jun-14 23:41 
AnswerRe: Thread safe? Pinmemberpwasser18-Jun-14 0:07 
GeneralRe: Thread safe? Pinmembermartin.nedopil18-Jun-14 0:58 
GeneralRe: Thread safe? Pinmemberpwasser18-Jun-14 1:21 
GeneralRe: Thread safe? PinmemberV.Lorz19-Jun-14 2:55 
GeneralRe: Thread safe? Pinmemberpwasser19-Jun-14 4:23 
GeneralRe: Thread safe? PinmemberV.Lorz19-Jun-14 2:47 
GeneralRe: Thread safe? [modified] Pinmemberpwasser19-Jun-14 3:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411028.1 | Last Updated 18 Jun 2014
Article Copyright 2014 by pwasser
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid