Introduction
Whoever work with standard RS232 components from a .NET environment, and need real, embedded-like RS232 communication, realize in a short time that the component needs to be wrapped, or at least, additionally supported by custom software. I designed this tutorial because I couldn’t find something similar on the web. The code is an excerpt from a huge GPS tracking application, and it proved to work nicely. Just to mention, that there are other methods for RS232 communication such as using hand-shaking protocols, and hardware/software enabled pin control.
Background
I won’t go in to the details of basic RS232 communication, except that it is a point-to-point communication between two hosts, and they could be computers or embedded devices. My point here will be the software implementation part, using C# in a .NET 2.0 environment to be precise, but the principles are adoptable to any other language and IDE. Also, the type of data I’m going to send/read is string, but with little more effort, the code can be adopted to send/receive any type of serializable data. One more restriction, the demo code is adopted to receive a huge block of data and then process it. Another way is to continually append read data to some FIFO buffer, and do an online parsing of whatever data has arrived at the time (search for delimiters inside the arrived data before the reading ends), for continual receiving of data without any pause between two packets.
Methods used
We will need as few as the following four methods to do the job:
void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e);
private void ReadData(object s, EventArgs e);
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e);
private void AddRecieve(object s, EventArgs e);
Using the code
The first thing we need for an RS232 communication is a SerialPort
component from the Components tab in the form editor's toolbox. The component itself supports both synchronous and asynchronous (event driven) communication. That means, for reading the port, we can use:
1. Blocking methods
Wait for the hardware to do the job, and then return to the app, so the read operation waits until the reading is finished (a few methods give us different ways of reading the port).
2. Non-blocking methods
We don’t wait in a loop for reading the port, we use interrupts, so when the port has something in an input buffer, the SerialPort
component alarms us via delegate methods, such as DataRecieved
.
Here, we will use event driven communication, because in my belief, this is the only true RS232 robust communication. So, lets begin. Let’s create some private members for our form, like:
private System.Timers.Timer timer1;
private StringBuilder recievedB = new StringBuilder();
private string recievedData;
Now, what we first need to do is a basic setup of the component we put on our form, meaning, set the component name (say, serialPort1
), baudRate
, parity
, stopBits
, etc. On the Events tab in Properties window, we create the delegate for DataRecieved
(actually, the MVS will do that for us when we desire it), and name it serialPort1_DataReceived()
. We will see later what we need the timer for. As for the recieveB
, we use a StringBuilder
class because it has a nice method Append
, which doesn’t recreate the string every time we do a concatenation, rather it uses something like linking lists of strings (actually, you don’t have to know how it works). And finally, when all data has arrived, we will need a string to hold the received data needed for further parsing, recievedData
. Next comes the coding of the delegate method we created. If we intend to update some controls in the form (as is the usual case), we need to call this.Invoke(some_delegate_method)
, because of the cross-thread operations. Check MSDN help for help on Invoke
. We will create the method like this:
private void serialPort1_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
this.Invoke(new EventHandler(AddRecieve));
}
And next, we need to create and code the actual delegate to perform the reading:
private void AddRecieve(object s, EventArgs e)
{
string st = serialPort1.ReadExisting();
recievedB.Append(st);
timer1.Interval = 1000;
timer1.Start();
}
Let’s comment this code a bit. First, we call ReadExisting
, which means reading anything that is in the SerialPorts
input buffer. There can be a few bytes, or as many as a few kilobytes of data. Anyway, we read what’s arrived up to now, and Append
that to our main holder for the received data – the recieveB
(the StringBuilder
class instance).
Now, the second part of this method. How do we know that the other side of the communication link finished sending data, so we can process it? Think logically for yourselves… For those who figured it out, yes, we know this when for some time (predefined time) there is nothing to read, meaning the event serialPort1_DataRecieved()
isn’t firing for some time. This time can vary from 30ms (arguable) up to a minute, it's up to us to decide. In this case, we’ll be using a one second interval so called deadTimePeriod
(there is a term for a similar thing, called readTimeout
). What do we do with this timer? We crate the timer in the form's constructor, add the onElapsed
method for firing up when the time has elapsed, and, I prefer the garbage collector not to destroy my timer. On every firing of the serialPort1_DataReceived()
event, we restart the timer1
to the initial interval (in our case 1s), and start it again. Here is the form's construction:
public Form1()
{
InitializeComponent();
serialPort1.Open();
timer1 = new System.Timers.Timer(1000);
timer1.Elapsed += new System.Timers.ElapsedEventHandler(timer1_Elapsed);
GC.KeepAlive(timer1);
}
The method that will execute on the timer overflow is called timer1_Elapsed
. Following that, we will code the method as:
void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
timer1.Stop();
this.Invoke(new EventHandler(ReadData));
}
First, we stop the timer, and second, we do the final receiving of the data. And again, just in case, we call Invoke
. The actual method for reading the received data and parsing it looks something like:
private void ReadData(object s, EventArgs e)
{
recievedData = recievedB.ToString();
int j = recievedB.Length;
if (recievedData.Contains("RMR0" ))
{
labelCard.Text = "Read " + j.ToString() + " bytes...";
ProcessData();
}
else
{
DialogResult dr = MessageBox.Show("Bad card data detected!," +
" Read good data?", "Error!",
MessageBoxButtons.YesNo,
MessageBoxIcon.Error);
if (dr == DialogResult.Yes)
{
}
}
}
Of course, the method ProcessData()
is open for you to code it. The send method is trivial, on buttonSend_Click
, just send whatever you need - in this case, the text from textBoxSend
. This could be done as an asynchronous method as well, using standard asynchronous method, invoking in .NET 2.0.
Points of Interest
Finally, I feel I need to explain the whole process again. The communication points are A and B. Both sides open their ports, and side B starts sending bytes over the comm. link. A serialPort1
component on side A is receiving data in almost a byte-by-byte basis, and is filling some sort of a buffer. The point here, was knowing when side B has sent us all data it wished to send in this transmission so side A can start examining the data in whole. Point A does this by using a timer which counts the time between two successive receiving of data packets from point B. When this time is, in our case, longer than 1s, point A knows that point B sent one whole packet. Well, this covers the basics of the non-handshaking protocol RS232 communication.
And again, of course, there are other port signals in RS232 such as CTS, RTS, which are also known as signals for implementing hand-shaking protocols, but we intend not to use them, because not every embedded system has a hand-shaking RS232 port enabled.