Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Serial Communication using WPF, RS232 and PIC Communication

0.00/5 (No votes)
24 Nov 2010 3  
RS232 Communication from PC to a PIC using WPF

Introduction

This article will show you how to achieve Serial RS232 communication using WPF. Its overall layout and design is simple as the code is what I wished to focus on. In particular, there are key points such as the Dispatcher.Invoke method and the ASCII to HEX conversion that are key stumbling blocks in serial communication to PICs that I have addressed.

Assumed Knowledge

This article expects a developer to have a basic understanding of C# and WPF.

Background

I've spent the last 7 years of my life happily programming in C#. About 5 months ago, I decided to make the move to WPF and it's been an interesting journey. I'm an electrical engineer and spend a majority of my time making my computer talk to the outside world. I was working on a DMX project and decided to utilise a XAML interface in WPF rather than the boring limited C# forms.

So the idea behind it all is to have a WPF project talk to a PIC Micro controller. In C#, this is achieved by utilising System.IO.Ports; library and in WPF it's no different. However, there are some unique problems that any user from a C# background will encounter. This is the lack of an Invoke method, this is only slightly true as is hidden in there, however has been inherited by the Dispatcher Class.

Using the Code

The code is laid out very simply with little attention paid to naming conventions, etc. This will be addressed at a later date, however I wanted to get the basics out here so people can start using the code.

First, we will start of with the Interface. This is very basic and is simply a ComboBox's box for COM Port and Baud Rate a Textbox for Data to send and a Richtextbox to display the received data from the COMS Port with a few buttons for actions. I've not show the code for this as it can be download in the source and the layout is not important.

Basic Style

ScreenShot.JPG

Advanced Style

Screenshot2.JPG

XAML Data Provider

The first important factor is where the COM port Names and Baud rates come from. Now there are ways of getting the relevant data from the computer you are using but I wanted a little more control so I placed all the titles in an XML file called CommsData.xml. This data is provided by a "Data Provider" and each one is placed within the Windows.Resource element. The XPath determines which element I'm looking at and the x:Key is what I referenced in my code.

   <Window.Resources>
        <XmlDataProvider x:Key="ComPorts" Source="CommsData.xml" XPath="/Comms/Ports" />
        <XmlDataProvider x:Key="ComSpeed" Source="CommsData.xml" XPath="/Comms/Baud" />
    </Window.Resources>

To understand a little further, we need to examine the contents of CommsData.xml.

<Comms>
  <Ports>COM1</Ports>
  <Baud>921600</Baud>
</Comms>

The <Comms> is my root element you can only have one of these in a data file. The <Ports> and <Baud> child elements separate the text so my data providers can only see what I want them to see. Each child element can have its own children; all you would have to do is set up the Data Providers correctly to show the data you want. If you want to format the way this data is presented, please search for the use of Data Templates.

The Data Providers are utilised by binding the ItemsSource properties of each ComboBox. Where below ComPorts is the x:Key I selected for my Data Provider:

ItemsSource="{Binding Source={StaticResource ComPorts}}"

Setting up the Serial Port

There are two approaches to this in the C# environment - you can drag an element onto the form and edit its attributes or you could hard code it. In WPF, unfortunately you must hard code the Com Port, however this is very easy to do.

First, we must include a reference to the IO Port library:

using System.IO.Ports;

Now, we can create a serial port. For ease, I have named this one serial however if you are utilising more ports, the naming convention should be changed.

SerialPort serial = new SerialPort();

There are several properties of the serial port you can edit. This occurs within the source code when the Connect button is pressed, however could be set anywhere as long as it is done before the port is opened.

serial.PortName = Comm_Port_Names.Text; //Com Port Name                
serial.BaudRate = Convert.ToInt32(Baud_Rates.Text); //COM Port Sp
serial.Handshake = System.IO.Ports.Handshake.None;
serial.Parity = Parity.None;
serial.DataBits = 8;
serial.StopBits = StopBits.One;
serial.ReadTimeout = 200;
serial.WriteTimeout = 50;

For information about these attributes, please see RS-232 communication protocol. These settings should satisfy a majority of applications, however there are two important attributes. ReadTimeout is the time allowed to read data received on the serial port. If this is set to low, then errors may occur and message can be cut short. WriteTimeout is the time allowed to write data out of the serial port - this can cause errors if you are trying to write long strings of data.

I have found no perfect formula for these values and a trial and error approach is necessary, bearing in mind that the greater the time allowed, the more delay your program will inherit.

Receiving Data

Now that we have a serial port, it important to set up a function call for every time the serial port has data to be read out. This is far more efficient that producing a thread, polling for data and waiting for a time out exception. To do this, we simply introduce the following code:

serial.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(Recieve);

This will call the Recieve function every time data is received. Within this function, we read the data out to a String called recieved_data and then we Invoke a function to write this data to our form. To enable Invoke, we have to include:

using System.Windows.Threading;
private void Recieve(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    // Collecting the characters received to our 'buffer' (string).
    recieved_data = serial.ReadExisting();
    Dispatcher.Invoke(DispatcherPriority.Send, 
	new UpdateUiTextDelegate(WriteData), recieved_data);
}

Now in WPF, we have to use a Dispatcher.Invoke method. The purpose of this is quite simple, the main form is controlled by its own thread. When the serial port announces it has received data, a new thread is created to read this data out. The Invoke method allows the string of data to be passed from the serial data received thread to the form thread when it is possibly preventing any errors.

The data can be written to any control capable of displayed text. In this example, we display it via a RichTextbox. In WPF, you can only write a Flow Document to a RichTextbox so you must crate a Paragraph to write the data in and then add this Paragraph to the Flow Document before displaying it. More complicated than C#, however not impossible to work with.

FlowDocument mcFlowDoc = new FlowDocument();
Paragraph para = new Paragraph();
private void WriteData(string text)
{
    // Assign the value of the plot to the RichTextBox.
    para.Inlines.Add(text);
    mcFlowDoc.Blocks.Add(para);
    Commdata.Document = mcFlowDoc;
}

Sending Data

This is where things may change if you are communicating between computers, the protocol will change and you can send data using the standard serial.Write(string) method as in C#. However, if you are communicating to a P PIC micro-controller, you must convert the text to HEX bytes.

The text held with SerialData TextBox is sent to a SerialCmdSend function. This takes several steps in its encoding of the data string. The first is to check if the serial port is open serial.IsOpen. It's imperative to do so otherwise an error will be flagged. Within the try{} catch{} method is the encoding routine.

 using System.Threading; 
if (serial.IsOpen)
{
    try
    {
        // Send the binary data out the port
        byte[] hexstring = Encoding.ASCII.GetBytes(data);
        foreach (byte hexval in hexstring)
        {
            byte[] _hexval = new byte[] { hexval }; 	// need to convert byte 
						// to byte[] to write
            serial.Write(_hexval, 0, 1);
            Thread.Sleep(1);
        }
    }
    catch (Exception ex)
    {
        para.Inlines.Add("Failed to SEND" + data + "\n" + ex + "\n");
        mcFlowDoc.Blocks.Add(para);
        Commdata.Document = mcFlowDoc;
    }
}

Now it is important to understand what is occurring here and why. The first step is to convert the string hex values in a byte array. For example, "123" will become:

[1]-49 
[2]-50  
[3]-51

Now in computer to computer communication, this can be sent directly using:

serial.Write(hexstring, 0, hexstring.length);

In PIC communication, however, there was a problem with this method. This may be apparent only when using higher Baud Rates but is caused by the computer timing issues as the computer will try and send the data continuously and the PIC will be overloaded. The program on the PIC will crash and need to be reset, in real world applications we can't have this.

The secret is to only send 1 byte at a time ensuring there is a delay in between bytes. The delay only has to be 1 millisecond however if this is not used the PIC will crash. This is done in the loop shown again below. The extra step within this loop to convert each byte to a byte array (byte[]) is annoyingly necessary as the serial.Write method utilised will only send a byte array.

foreach (byte hexval in hexstring)
{
    byte[] _hexval = new byte[] { hexval }; // need to convert byte to byte[] to write
    serial.Write(_hexval, 0, 1);
    Thread.Sleep(1);
}

Finally, there you have it - a working WPF serial communication platform for RS-232 check back for a fully working release and a re-design using Microsoft Expression Blend.

History

  • Release V1.0 Basic style and source
  • Release V1.1 Advanced style and source

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here