UWP Serial Port

With the advent of UWP Microsoft changed the way serial ports were accessed. This was partly done to make COM ports work across supported platforms in a common way but also to introduce the asynchronous programing model so that applications could continue whilst data is being sent and received from a connected device without blocking or the need to create additional monitoring threads. This is great for new applications designed from the ground up to use the async model but for ports of code from standard Win32 Forms; the UWP serial port interface can create a bit of a headache.

The code described in this article represents a simplified interface to the new UWP serial port mechanism and wraps it up in a basic class that allows more traditional applications to work in the way they were originally designed. It is also a good place to start when creating a new UWP application that needs serial port functionality but in this case the synchronous wrapping should be reviewed. Please note that throughout this article all work was done in Visual Studio 2015 and it is assumed that a blank UWP application was created. The source code can be downloaded as a zip file to save copying and pasting the individual methods shown below.

An important aspect of this method of serial port communication, which is why is it still very much used today, many years after the original RS232 electrical standard was published is that it maps directly to virtual COM ports connected between USB devices under the serial device CDC (Communication Device Class) umbrella. This makes it possible for physical RS232 devices to still be supported and for serial data to be tunnelled between applications and user devices over a single USB cable. In Windows 10, Microsoft have helped out greatly by supporting a generic serial COM port USB class that means that device manufacturers no longer have to develop their own custom USB serial port drivers in order for them be recognised as a serial port device. This greatly increases application support, reduces development costs and removes the need for buggy custom drivers to be installed on machines that need to communicate with new devices.

Project setup

The first thing to do is to enable application support for the serial port and this is done by opening the Package.appxmanifest file in the UWP solution. This can be done by selecting it and pressing F7 to ‘View Code’ in the solution explorer. The default visual manager representation does not have an option for serial communications therefore this must be carried out manually.

Other capabilities that may be listed here, represented by ‘…’ must be left unchanged so care needs to be taken not to delete them. Copy and past the above code into the appropriate location, merge with other capability settings and then save the file. Serial communication is now enabled.

Serial port implementation

There are three main UWP objects that need to be created in order to work with, read and write data in and out of a UWP serial port. They are SerialDevice, DataReader and DataWriter and in order to use them their namespace should be referenced. Additional data types and collections are also required in order to make use of these types and these have been included in the following list: –

The three main data types used in the interface implementation are declared as shown below: –

Opening the port

The most involved coding task in creating a serial device wrapper is to open the port for communication. The open method takes all of the usual control properties of an electrical level RS232 interface because they are still required for opening this kind of device today but if they are not needed on a virtual port for example then sensible values are provided as default parameters; only the port name is required in this case.

It is worthwhile noting that the open method returns a Task<bool> type which means that although it is marked as an asynchronous method it will not return to the caller until the end and the return value is known. It is not expected that this method will ever take any real time to execute and can be used to ‘find’ ports by speculatively trying to open a number of them sequentially.

The first thing the method does is to close a currently open port with Close(). This action prevents the wrapper from leaving a port open and overwriting it by mistake.  It then obtains the selector string for the specified COM port device using GetDeviceSelector(). Even if the requested COM port does not exist a selector string is prepared based on the information provided about the COM port. The full device information for the device associated with the items in the selector string is then obtained from the FindAllAsync() method and the success of this operation is checked with a call to .Any(). Only one item of device information will exist for a COM port therefore the First() one is extracted from the list.

At this point it would appear that the port has been found and is valid but this is not the case. It is only when the static call in SerialDevice to FindIdAsync is made that the validity and existance of a real or virtual COM port is known. If the port is not valid then null is returned and the method drops out and returns false.

For a valid SerialDevice the electrical communication properties (assumed to be always required) are then populated and the necessary supporting  DataWriter and DataReader objects are created too. The input stream option of Partial is set in order to enable data received to be extracted at any length even when the buffer is not full.

The port is now open and the IsOpen state flag is set. The value is also returned to the caller to indicate the state of the open operation but as mentioned this is done in order to force the method to behave synchronously.

Closing the port

The close operation is relatively straightforward in comparison but care needs to be taken to check the associated objects in case the port is already closed. It might be more efficient to just check the state of the IsOpen flag and return if this is set to false but for the sake of 3 simple if statements it is probably the safest way of ensuring that the port is closed cleanly and reliably.

For each of the main objects associated with an open COM port they are tested to be not null variables and then the Dispose() method is called to free up all resources associated with them. Finally, each variable is set to null in order to ensure that additional closes do not try to repeat the Dispose() operation.

Writing data

The write operation in keeping with common byte based serial communications takes an array of bytes and adds them to the writer object queue with the WriteBytes() method. The data at this point however is only held and queued. In order to transfer it from the stream writer to the serial device StoreAsync() needs to be called. StoreAsync() will then trigger sending the data as soon as it is convenient for the serial port and in practice this happens immediately. The final call to FlushAsync() (if supported by the COM port device) kicks off the actual sending of data.

It is worthwhile noting that this method is an async method therefore it will probably return to the caller before the data has actually been sent. Typical uses of such communication is for sending small blocks of data relatively infrequently but in cases where data is streamed to a serial COM port device some form of queue management may be required. If for example an application tries to send a 1GB file to a device in a few seconds it may be impossible for it to handle it or the link speed may be slow and the data would fill the streamer buffer. Also, it is worthwhile noting that real RS232 COM ports need a minimum of 3 wires to operate bi-directionally however in order to manage their own limited capabilities many such devices use additional hand shaking wires to stop the sender from transferring more data than it can handle at that point in time.

Reading data

The read operation consists of 3 methods and in order to support the reliable reading of data from the serial port the number of bytes available to read must always be checked before reading. When BytesToRead() is called it returns the number of unconsumed byes in the input stream. However, these bytes are not loaded into the stream automatically and the code first calls the LoadSerialData() method that upon finding that there is no unconsumed data left to load attempts to load the next block from the serial device.

When there is no data available the call the LoadAsync() throws an exception so this is handled and the exception is absorbed. Also, bearing in mind that the objective of this wrapper is to make it easier to port older Win32 code to the new serial device interface the call to LoadAsync() is forced into a synchronous call so that the async/await pair does not need to be propagated up through the calling code.

Assuming that there is data to read, ReadByte() returns a single byte from the input stream. This then needs to be interpreted and processed within the context of the application consuming the data and is therefore by necessity low level pulling only 1 byte at a time from the stream.

It may be more efficient to directly handle other types such as 32 bit int‘s in certain applications and there are a number of different data reader methods that can be called. Knowledge about the size of the data type being read should be factored into the call to BytesToRead() to make sure the method does not block or throw an exception.

In Use

The Visual Studio 2015 demo C# project attached to this article allows a COM port to be selected and opened. It can then be closed or a different COM port opened. It allows a string to be sent to the connected device and for strings to be displayed that have been returned from the connected device. In order to automatically pull in and process data in a more useful application some form of looping or background task is required. It is assumed that this functionality has already been provided as part of the project being updated to run as a UWP.


About Open Technologies Limited

OTL specialise in the design and development of real-time software and hardware applications. We strive to innovate and bring our customers both cutting edge technologies and efficient solutions to their problems. With many years of experience working in industrial and consumer sectors we are always conscious of the need for reliable, maintainable and sustainable products. We are always looking to work with new clients and welcome the opportunity to discuss how we may be able to help you.

Contact OTL