WinXP, RS485 and Delphi (part 2)
The first part of this article can be found here.
To summarize
From the need to get a Windows XP based communication program to work correctly with a dumb RS232-RS485 converter, we need to have a mechanism that allows us to directly access the PC hardware (the UART registers in order to switch RTS). A normal Windows XP user program cannot do that and rightfully so as the operating system should normally be the only agent to handle the allocation of resources.
PortTalk kernel mode driver
The way to go here is no less than to create a special kernel mode driver. A very interesting and practical article on this subject is given by the work of Craig Peacock on BeyondLogic's PortTalk driver. You can download the pertinent source code on the BeyondLogic site but be aware that for re-creation of the driver itself you need to have the Windows DDK. Luckily, the distribution also contains a precompiled version of porttalk.sys to experiment with.
The porttalk.sys driver needs to be installed - which requires Administration privileges - and then a user program can communicate with it through IOCTL calls providing read and write accesses to given port addresses. There are other ways to use PortTalk (involving manipulation of the I/O Permission Bitmap) but these are not needed at this point.
UART registers
So, porttalk.sys, when installed, will give access to the UART hardware registers of a given serial COM port. The UART itself will propably be based on a 16550 and the place to look then is the Modem Control Register (MCR) where bit 1 determines the Request To Send (RTS) line. See for example this introduction on serial UARTs or refer to a 16550 datasheet. The trick now is to observe bit 6 of the Line Status Register (LSR) providing the Transmitter Empty (TEMPT) indication; it will go to a logic 1 whenever the Transmitter Holding Register (THR) and the Transmitter Shift Register (TSR) are both empty. It is reset to a logic 0 whenever either the THR or TSR contains a data character. In the FIFO mode this bit is set to one whenever the transmitter FIFO and shift register are both empty. Any way, at the moment the TEMPT bit goes HIGH, the transmitter is completely finished sending whatever it had to send and in an RS485 application it will be that moment that RTS should be switched.
Serial communication
A Win9x serial comms component would exactly do this. See for example the TComport component from Dejan Crnila. There's no interrupt associated with the TEMPT bit. You can have an interrupt for Transmit Holding Register Empty (THRE), but this occurs too early for our purpose: it's intention is to signal an application that more data can be placed into THR (or the FIFO) but at that time the latest byte will still be busy shifting out. One approach would be for a comms component to wakeup a separate observed thread when THRE fires (while nothing more has to be send) that would loop to inspect TEMPT, wait for it to go HIGH, then switch RTS en then to go to sleep again.. Okay, so this involves the two direct port accesses that WinXP will allow only when going through a Kernel mode driver as PortTalk.
COM ports?
But WinXP offers one more challenge here, and that is to find the base-address of the UART registers for a given COM port (all registers are in fixed relation to the base address). Even quite more involved is the fact that WinXP can have all kind of COM ports that are actually virtual and meant to provide standardized access to, for example, USB or Bluetooth based devices. It is essential for a 'dumb RS485 converter application' to operate only with COM ports associated with real serial UARTS.. Take a look at my detection routine in EnumCom.pas (converted to HTML) to get a feel of what must be done to detect physical COM ports and the associated base addresses.
TPortTalk
Coming back to PortTalk, I have also created my own Delphi component for it; see the (HTML) listing of PortTalk.pas. The TPortTalk class for Delphi implements an IOCTL call wrapper for the porttalk driver. The class is designed to automatically create and install the driver at start of use and to clean up everything afterwards. This requires Administration rights of the application user. An Admin facility is present to manually load/unload the driver under Admin log-in in order to make it available to TPortTalk when running later for a normal User without admin rights. The driver itself is linked into the unit as a resource so that no extra files are required.
Available for download is the PortTalkDemo.zip package that contains the above units, as well as the BeyondLogic sources of PortTalk plus a Delphi demo project PtDemoPrj. The executable of this demo is also included. The screen dump below shows the demo application. After using button 'Open', the PortTalk driver will be automatically installed from a resource that is integrated with the executable. Using button 'Test' will then first write and read from a fixed port (378 hex) and then it will output one single character (55 hex) to the first physical COM port the EnumCom.pas routine has detected while switching RTS for the duration of shifting out the character. You can observe the (fast!) behaviour on a scope.

The second screen dump below shows the Adminstration dialog as build with the TPortTalk component itself and accessed by the 'Admin' button of the demo application. Point to note here is that the PortTalk component will install the kernel mode driver when opening TPortTalk, but only if the driver is not yet present. When closing TPortTalk, the driver will be again removed but only if it was installed by the current PortTalk session. Note that this actually involved placing/removing the porttalk.sys driver in the windows system directory and registering/de-registering a Service for it. As said earlier, this requires Administrative rights. In case a program needs to be run under restricted user rights, the dialog can be used to first permanently install (or later remove) the driver and service under Adminstrative rights.

It is my hope that this software may be of use to anyone struggling with the same problems as I did getting fast RS485 communication done on WinXP using legacy hardware from an older era (or for similar things).
Download code here.