Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

VB .NET COMMUNICATIONS #3

In the final part of this series, Dermot Hogan explains the VB .NET way of reading and writing to a serial port…
Requirements:
VB .NET (.NET 1.1)

 

Download The Source Code:
vb6src.zip

See also part two of this series

Last month, we covered the ins and outs, so to speak, of blocking I/O APIs. API calls to input and output devices are termed ‘blocking’ if other API calls are stopped - blocked - by the currently executing API. For example, let’s suppose you have issued an API to a communications port that is waiting for a line signal (DSR, say) to change. Then, on another thread, you issue another API call to set a control signal (DTR, say). You’ll find that the second API call will not complete until the first call returns – even though they are on different threads.

The reason is that the threading mechanisms at a user process level are handled differently from the threading at the driver level. Device drivers do have something like ‘threads’ but they are much lighter weight (that is, cheaper and faster to run) than process level threads. So unless you take explicit action to tell the driver to ‘thread’, in this circumstance, you’ll effectively be running a single threaded I/O application.


Ready to test! Remember to click the Initialize button before you start.

The techniques – signals and mutexes - used in non-blocking I/O are not often used in application level programming, so you may be unfamiliar with them. Also, in Visual Basic 6, because you were essentially restricted to one thread, such niceties didn’t apply. But now, because VB .NET supports threads, it does provide a synchronisation mechanism at a high level that avoids having to deal with the operating system synchronisation primitives. This is the SyncLock ... End SyncLock construct which uses a mutex to prevent two threads from executing the same piece of code at the same time. It’s just that the mutex is hidden by VB .NET.

Now while using non-blocking I/O is useful in high performance backup programs, say - you can read from the disk while writing to the tape and save a bit of time - it’s not vital. But it most certainly is when you have to deal with a serial port. It’s no good at all issuing a read operation which never completes because the far end has disconnected and gone for a beer. And the signal change that tells you about this interesting fact is blocked behind the read!

Tales Of The Unexpected

This month, we’ll look at reading and writing from the serial port. Writing should be easy, it’s almost a ‘fire-and-forget’ operation. Almost. What we have to consider is the case when the remote station pulls the plug before the write completes. This has to be taken into account because serial port writes are not instantaneous. For example, take a 1000 byte write down a serial line at 9600 bps. True, that’s not lightning fast by today’s standards but it’s still common, and it will take about 1 second. For 1 megabyte, that’s 1000 seconds or about 20 minutes – plenty of time for something to go wrong.


Try setting DTR or both ports. You should see DSR detected on both and just as a precaution, click Check to see that that is the case.

Equally, we have to consider the situation where the remote station asks us to stop sending (by whatever mechanism – see Controlling The Flow) and forgets to turn it on again. We want the write to complete with some sensible error code and not just disappear into the void. As a matter of general programming practice, by the way, it’s important to consider all these eventualities. It takes a lot of effort to produce a program that does something sensible when confronted with the unexpected.

So the basic write operation would look something like this:

Public Sub Write( ByRef buf() As Byte)
  Dim bytesWritten As Integer
    WriteFile(h, buf, buf.Length, bytesWritten, 0)
End Sub

The first parameter is the file’s handle, returned when the file is opened by the CreateFile API, the second is the address of the byte array containing the data to be written, the third parameter is the length in bytes and the last parameter is an OVERLAPPED structure, similar to that which we discussed last month.

Now, a functioning Write routine has a couple more wrinkles in it. To start with, we want to write something like this from the Form:

cc1.Write("hello, world")

Here, “hello, world” is a string, which in VB .NET is composed of Chars, each Char being a Unicode character of 16 bits. On the other hand, the Write API deals in good old-fashioned 8-bit bytes. So there needs to be a bit of conversion code:

Dim b(buf.Length - 1) As Byte
For i = 0 To buf.Length - 1
   b(i) = Asc(buf.Chars(i))
Next

Actually, there is a better way to do this using the Encoding class which is used for converting strings into bytes, but the above technique is simple and clear.

Now we can issue the Write itself:

r = WriteFile(h, b, b.Length, bytesWritten, o)
If r <> 1 Then
  r = GetLastError()
  If r <> ERROR_IO_PENDING Then
    Throw New System.Exception("Write failed")
  End If
End If

As we did last month, for an overlapped or asynchronous I/O, we have to check not for a ‘completed’ result, but for an IO_PENDING return code.


It really is essential to pin everything that can be moved by the .NET garbage collector. Not pinning the readBuffer caused intermittent crashes.

The amount of data written to the port is returned in bytesWritten and since this needs to be around when the write completes, it first of all must be in scope – that is, it must exist – and it won’t if it’s declared in the Write procedure itself. And secondly, it must be protected from the .NET garbage collector by pinning it. It’s therefore declared in the class data and pinned in the object initialization code, in New:

GCHandle.Alloc(bytesWritten, GCHandleType.Pinned)

There’s one final piece necessary for a working Write routine. Consider what would happen if more than one write were to be issued at a time. There’s nothing in principle wrong with that, but it’s probably not what you intend to do. Having multiple write requests in progress also makes debugging a bit tricky ... which one went wrong?

So there’s a Boolean flag write_in_progress that acts as a protection against having more than one write going at the same time. The flag is set by the Write routine itself and cleared when the write completes. But even that has a synchronization wrinkle. Whenever a variable needs to be set and read from more than one thread, you must make sure that the variable is properly protected against ‘race-conditions’. You do this by using a SyncLock ... End SyncLock block - and, in the Write routine, the whole block of code has been protected. In fact, this is overkill, since the ‘eventThread’ code only ever sets the write_in_progress flag. Still, it doesn’t cost very much to do and it’s good practice anyway.

Turning to the eventThread code, it turns out that this doesn’t need much modification at all. Only two modifications are required: SyncLocking the write_in_progress flag (as we’ve discussed) and adding a new event to the event mask for the transmit completion. The communications event, EV_TXCOMPLETE, must be ORed into the event mask set up in the OpenWithEvents code. If you forget to do this, you’ll get an obscure looking error, ‘Invalid Parameter’ (return code 87), when WaitCommEvent returns in the eventThread code. Which is a good thing – sort of. Instead of ignoring the event, if you forget to set it in the mask, Windows does return an error. But why ‘Invalid Parameter’?

Reading The Runes

In one sense, reading isn’t anything like as simple as writing. For one thing, when you write a stream of characters to a serial port, it’s under your control (more or less, anyway). But reading a serial port is a whole different ball game. The number of characters that arrive and the speed at which they arrive are not under your control. The line speed sets a maximum rate at which the characters can arrive; but whether they arrive in ones, twos or the-whole-shooting-match-without-a-pause-for-breath is most certainly not under your control.

There are a number of ways of dealing with this, and I’ll just present one here. It may not be the most efficient, but it works. Well, most of the time.

The Read routine itself doesn’t issue a ReadFile API. Instead, it ‘drains’ a buffer of already read characters. These characters get into the buffer courtesy of the ‘eventThread’ code. Both the Read routine and the eventThread code share access to a ‘readBuffer’ – an array of Bytes – and a readBufferPosition giving the next free slot in the readBuffer array. When a new byte is read from the serial port this is where it will be put.

There’s clearly a synchronization issue here: it’s essential that access to the readBuffer and the readBufferPosition be controlled between the two routines. So, in the Read code, we have this:

Dim c As Char()
SyncLock GetType( CommControl)
   ReDim c(readBufferPosition)
   For i = 0 To readBufferPosition
      c(i) = ChrW(readBuffer(i))
   Next
   readBufferPosition = 0
End SyncLock

The array of Chars, c, is re-dimensioned to the current number of bytes in the readBuffer. Then each byte is read from the buffer and converted to a Char ready for conversion into a String later on. Lastly, the pointer is reset to zero – in other words the buffer is drained and left empty.


Note that the read does not complete in one go! The read buffer is empty at one point due to the speed of the transmit operation and so you get two RX events.

Now the other half of the show is in eventThread:

SyncLock GetType( CommControl)
   If evtMask And EV_TXEMPTY Then
       write_in_progress = False
   ElseIf evtMask And EV_RXCHAR Then
      Do
         r = ReadFile(h,readBuffer(readBufferPosition),1,bytesRead,Nothing)
         If r <> 1 Then
            r = GetLastError()
            Throw New System.Exception("ResetEvent failed")
         End If
         If bytesRead <> 1 Then
            Exit Do
         Else
            readBufferPosition += 1
         End If
      Loop
   End If
End SyncLock

In addition to the EV_TXEMPTY event, there’s code which handles the EV_RXCHAR event. Within a Do ... Loop construct, a synchronous ReadFile for a single character is issued and if a character was found (given by the bytesRead parameter), the ReadFile is issued again. Only when no more characters are received is the loop exited.


You absolutely must use SyncLock statements to ensure correct operation when you are using event driven multithreaded code.

Now in the main, it’s pretty much complete, though there’s still quite a lot of detail to see to: there’s no checking on read buffer overflow, no proper timeout control, it’s not very efficient in reading data from the port, no handling of communication line errors, the control doesn’t go into the right control area on the form…

…and I still haven’t got round to doing some decent error handling!


Parallel ports

Why not use a parallel port?

With a serial port, you get a measly two output control lines and four input lines. Plus of course the two serial data lines themselves. But with a parallel port there are 25 lines – 8 data, and a good number of the rest can be used as bi-directional control lines. So it would seem that the parallel port is the better choice if you want to control, say, a stepper motor with typically four control lines (now that’s an interesting project!).

Indeed, a parallel port is a lot better for doing that that sort of thing than a serial port. But there are one or two ‘gotchas’ in there. First, Windows likes to think that parallel ports are used for parallel port type of things – such as printing. It can be mighty hard to persuade Windows that you really, really don’t want to print: plug-and-play can quickly turn into ‘plug-and-pain’.

The second problem is that to control a mechanical device, you need access to the parallel port’s registers in order to turn a bit on or off. This means that you need a device driver that will let you do it (not a printer driver!). You cannot (as you could in DOS and earlier versions of Windows) just poke the port. In Windows XP, that’s an absolute no-no.

The bad news is that standard Windows doesn’t come with such a driver. The good news is that there are several shareware and free drivers around, including one that comes with the Microsoft device driver kit. However, building and configuring the generic port driver, as it’s called, isn’t a task for the faint of heart.

On the other hand, there’s an advantage to using a serial ports – they are more robust. Because serial communications existed a long time before current microprocessors, the signal levels are higher and the chips are built to withstand more abuse. While you can’t connect a serial port to the mains and hope to use it afterwards, they tend to be a lot more forgiving of bad wiring than parallel ports.


The parallel port has many more data and control lines than a serial port. But you need a special device driver to communicate with it.


Controlling The Flow

Why use one method when three will do?

One of the unexplained things about serial communications is the way flow control works. It turns out that there are three ways of doing it: there doesn’t seem to be any good reason for this – it just happened that way.

The first and most basic level is to detect if the remote device is there or not. You do this by looking for DSR and conversely you tell the other party that you are in a fit state by setting DTR. You can use these signals for flow control, but it’s more common to use a similar pair, CTS and RTS to indicate that one side should stop sending.

If you set RTS (‘assert’ in the jargon) you are telling the remote device that you are ready to receive data. The other member of the pair, CTS is set by the remote end (at its end, it is the RTS line). If you see that, the other end is ready to receive data.

So far, so good. But there’s a third mechanism used in software, called XON/XOFF. If you send an XOFF character (ASCII 19), the remote end should stop sending. When you’re ready to start receiving again, you send an XON character (ASCII 17).


Serial port flow control can be done in three ways. In software with XON/XOFF and in hardware with either RTS/CTS or DTR/DSR ‘handshake’ pairs.


Timeouts

Timeouts For Serial Ports Can Be Confusing.

You may have noticed, but there’s something I’ve not mentioned about the ReadFile in the eventThread code. That is, it completes immediately even when there are no characters in the serial port’s input buffer.

That’s because I inserted a SetCommTimout API in the OpenWithEvents routine:

Dim cto As COMMTIMEOUTS
cto.ReadIntervalTimeout = INFINITE
cto.ReadTotalTimeoutConstant = 0
cto.ReadTotalTimeoutMultiplier = 0
cto.WriteTotalTimeoutConstant = 0
cto.WriteTotalTimeoutMultiplier = 0
r = SetCommTimeouts(h, cto)

This uses a COMMTIMEOUTS structure – a simple structure with five integer fields. Setting these fields to various values allows you to control the timeout – that is the amount of time that elapses when nothing is happening before the API (ReadFile or WriteFile) returns.

A common configuration is that given above. Here, with ReadIntervalTimeout set to –1 or &HFFFFFFFF, and the other two read timeouts set to zero, the ReadFile API will return immediately even if there are no characters to be read. If you don’t set these, the API will return with an IO_PENDING status.

The ReadIntervalTimeout parameter determines the allowed interval between any two characters being read. Zero doesn’t mean return immediately, but rather don’t timeout at all!

The multiplier settings just calculate the timeouts by multiplying this with the number of characters to be read.


Communication port timeouts – the interval that can elapse between characters before the operation is ‘timed out’ by Windows) is set using the SetCommTimouts API.

October 2005

 


Home | Archives | Contacts

Copyright © 2006 Dark Neon Ltd. :: not to be reproduced without permission