Introduction
Data acquisition and displaying it on the screen is a feature which is required in many applications. There are many ways through which one can acquire data such as network sockets, parallel port, serial port and USB. This article will describe how to display data on the user screen while acquiring data at the same time using Serial Port and USB port . First multi-threaded architecture will be discussed then how to use Serial port and USB port and finally how to display data on the screen from a separate thread using windows forms.
Multi-Threaded Design (foreground and background threads)
There can be many ways for designing this type of application. One of the ways is using multiple threads. In simplest way one can assign one thread solely for data acquisition and one thread for data display. In this way the user screen will not hang or become unresponsive because there is a dedicated thread which is just displaying the user screen and doing nothing else. The second thread does all the work for making data available such as reading data from the databases or from ports.
I/O tasks such as reading from the ports or some external resource can be time consuming. If one performs I/O task and screen updating task in the same thread then the screen will not be updated frequently and will not accept user inputs . Therefore when user screen does not get updated or get attention from the thread it will not respond to user inputs and become unresponsive. Therefore it is a good idea to perform all the long running task or I/O task on separate thread and user screen on a separate thread. In this article I will focus on this design and give some code examples to show this design approach.
.Net Serial Port Class
.Net SerialPort class is component provided by the .Net framework to work with serial ports in a PC. One can define all the parameters for UART communication such as baud rate, handshake mode and address of COM port. This class can work with hardware serial port as well as virtual serial port. Virtual serial port is just a name for an adapter which convert data interface from one device to another. .Net SerialPort can work seamlessly both with hardware and serial port.
This class supports three events: Data received, Error Received and Pin changed. Whenever data is available on the port Data Received event is fired. On the handler of this Data Received event one can read the data from the port.
One important thing to note here is that this event handler is raised on a separate thread then the thread which created the Serial Port object which in most of the cases windows forms thread. This is a good thing as I have discussed previously to acquire data in thread different than the user screen thread. Hence SerialPort always receive data in a separate thread.
Following code shows the event handler which get executed when there is some data available:
private void aSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
}
One can receive data in many format such as raw format in which one receive data bytes. Serial port also provide the data stream object which can be passed to classes that uses data stream object for further processing.
Let's see the code for reading raw bytes:
private void aSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] data = new byte[Rs232.BytesToRead];
aSerialPort.Read(data, 0, data.Length);
}
Before going further there are some properties of the SerialPort class that one needs to set in order to get data properly. These properties are ReadBufferSize
and ReceivedBytesThreshold
. ReadBufferSize
is the size of the internal buffer which contain the maximum amount of data. You need to set it large enough so that you will not lose any data.
Second important property is ReceivedBytesThreshold
. This property tells the .Net framework when to raise the DataReceived event. If one set it to the value of 400. Then as soon as 400 bytes are available in the buffer the data received event will fire. This will not raise the event at 400 bytes but at approximately 400 bytes.
Beside raw data one can receive text data. SerialPort supports ASCII , Unicode and UTF encodings. Let's see the code:
private void aSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
String message = aSerialPort.ReadLine();
}
In this example serial port will read the text and block the code execution until and unless it finds the end of line character. This can be “\r\n” or “\n” depending upon the underlying system.
Since code execution will be block if no data available or data stopped due to any other problems If one did not handle this situation then program will be hanged indefinitely. To resolve this block one can use ReadTimeOut
property which takes time in milliseconds. If serial port did not received data within the specified time then it raised the exception which can be used to get out of the situation and inform the user about the problem.
FTDI USB Module
Here in another article in which I have describe how to design the hardware digital data acquisition card using FTDI USB module. In this article I will discuss the software programming for the FTDI module. In the further reading section of this article one can find the links related to FTDI Module.
FTDI Usb module supports 2 modes of operation. One option is using VCP driver with .Net SerialPort class. It depends upon the virtual serial port driver. In this mode when one connects the USB device to PC then the device will communicate with PC as serial port. Any host application can access this virtual serial port to communicate with the device. Although this method is easier to use but the data rate is lower when compare to D2XX driver. D2XX driver is provided by the FTDI and supports higher data rates.
Second method for accessing the USB module is using D2XX drivers. In this mode one can directly access the USB module with the help of the library provided by the FTDI. Following code shows how:
using FTD2XX_NET;
public void Foo()
{
FTDI ftdiDevice = new FTDI();
FTDI.FT_STATUS ftStatus = FTDI.FT_STATUS.FT_OK;
FTDI.FT_DEVICE_INFO_NODE[] ftdiDeviceList;
uint ftdiDeviceCount = 0;
ftStatus = ftdiDevice.GetNumberOfDevices(ref ftdiDeviceCount);
if (ftdiDeviceCount == 0 && ftStatus == FTDI.FT_STATUS.FT_OK)
{
return;
}
ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[ftdiDeviceCount];
ftStatus = ftdiDevice.GetDeviceList(ftdiDeviceList);
ftStatus = ftdiDevice.OpenBySerialNumber(ftdiDeviceList[0].SerialNumber);
}
One can open the device using many parameters such as device index, serial number, location and description.
Here is the code for data reading:
byte[] Read()
{
uint numBytesAvailable = 0;
ftStatus = ftdiDevice.GetRxBytesAvailable(ref numBytesAvailable);
if (numBytesAvailable < 0) return null;
byte[] tempDataBuffer = null;
tempDataBuffer = new byte[numBytesAvailable];
uint numBytesRead = 0;
ftStatus = ftdiDevice.Read(tempDataBuffer, numBytesAvailable, ref numBytesRead);
if (ftStatus != FTDI.FT_STATUS.FT_OK) this.Refresh();
return tempDataBuffer;
}
One difference between the .Net Serial Port class and D2XX method is that it will not call the device by itself. On the other hand Serial Port raises the data received event each time the data is available in the buffer. One way to get around with this is to use a timer class of .net and repeatedly call the read method. Let’s see the code:
private void BeginProducingData()
{
AutoResetEvent autoEvent = new AutoResetEvent(false);
TimerCallback continuousTimerCallback = new TimerCallback(acquireData);
device.Refresh();
threadTimer = new System.Threading.Timer(continuousTimerCallback, autoEvent,0, usbReadTimeDelay);
autoEvent.WaitOne(-1);
}
Complete data acquisition code can be found at this article. One thing to note here is frequency at which one is calling the FTDI device for data. If one called too late and data rate is fast enough then there is chance of losing data. Similarly if one called the device too frequently then there will not be enough data available for each call and there will be useless calls to device. Therefore when designing an optimized application one has to keep this phenomenon in mind.
The device which I have used supports data up to 8mbps and I have tested for around 6 mbps. The FTDI device has a data queue of size 64Kbytes. Minimum data which you can read is about 4Kbytes. If one make a call to device to read the data and currently available data is less then 4Kbytes then one cannot read the data and call to device will return 0 bytes. Similarly if one wait for longer time to read the data buffer then buffer may overflow because maximum size of the buffer is of 64Kbytes.
Updating User Screen
Now as the data acquisition with serial port class has been discussed now let’s see how we can update the screen while we are acquiring the data in the background thread.
As we have discussed the multi-threaded design for data acquisition in the start of the article. There is a separate thread for data acquisition. In normal circumstances two threads can access each others resources but when you are using win-forms the situation is different. The thread in which winforms components are created can not be accessed from another thread. Therefore if one tries to access any winform component from the serial port data received event there will be an error. This is because data received event handler always raised in a separate thread.
This limitation is because .net uses the windows components such as text boxes and forms. These components have a limitation imposed by the windows operating system and that is every component can online be accessed by the thread that created it. This is called single threaded apartment model (STA).
When one created the form in visual studio it is always created by setting the thread property as STA. Following code shows that
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
To resolve this problem winforms components give us some methods that allows us to pass a delegate which can be called by the thread in which winforms components resides. These methods are Control. Begininvoke , Control.Invoke and Control.EndInvoke and a tread safe property control.IsInvokeRequired.
Here I recommend using BeginInvoke() method because it is an asynchronous method and it will not block the call. If you are updating the screen very frequently say at 50 Hz then your screen will be freeze if using synchronous (Control.Invoke()) method. See the further reading section for article on asynchronous method invocation.
Invoke() and BeginInvoke() requires a delegate and arguments which one need to pass. Let's see the code:
public delegate void MessageUpdateCustom(MessageCustom custMsg);
internal void UpdateCustomMessage(MessageCustom custMessage)
{
MessageUpdateCustom msg = new MessageUpdateCustom(UpdateAllTxt);
this.BeginInvoke(msg, new object[] { custMessage });
}
internal void UpdateAllTxt(MessageCustom msg)
{
}
Now call the method UpdateCustomMessage() from the data received event of the .net serial port class. Or any other method which belongs to a separate thread. This will create the delegate and pass it to the control.BeginInvoke method which ultimately be called by the thread in which windows forms components reside. In this way there will be no error when updating the screen and user screen will get updated without becoming non-responsive.
Conclusion
In this article it is described briefly how to acquire data using serial port of .Net and updates the screen at the same time. As well as it is also discussed how to write the code for accessing FTDI device. One can check out the links in the further reading section about FTDI.
Further Readings
FTDI Device Home Page
Asynchronous programming article
FTDI USB based hardware design
Data Acquisition Library