views:

600

answers:

2

We've got a system running XP embedded, with COM2 being a hardware RS485 port.

In my code, I'm setting up the DCB with *RTS_CONTROL_TOGGLE*. I'd assume that would do what it says... turn off RTS in kernel mode once the write empty interrupt happens. That should be virtually instant.

Instead, We see on a scope that the PC is driving the bus anywhere from 1-8 milliseconds longer than the end of the message. The device that we're talking to is responding in about 1-5 milliseconds. So... communications corruptions galore. No, there's no way to change the target's response time.

We've now hooked up to the RS232 port and connected the scope to the TX and RTS lines, and we're seeing the same thing. The RTS line stays high 1-8 milliseconds after the message is sent.

We've also tried turning off the FIFO, or setting the FIFO depths to 1, with no effect.

Any ideas? I'm about to try manually controlling the RTS line from user mode with REALTIME priority during the "SendFile, clear RTS" cycle. I don't have many hopes that this will work either. This should not be done in user mode.

+1  A: 

Any ideas?

You may find that there's source code for the serial port driver in the DDK, which would let you see how that option is supposed to be implemented: i.e. whether it's at interrupt-level, at DPC-level, or worse.

Other possibilities include rewriting the driver; using a 3rd-party RS485 driver if you can find one; or using 3rd-party RS485 hardware with its own driver (e.g. at least in the past 3rd parties used to make "intelligent serial port boards" with 32 ports, deep buffers, and its own microprocessor; I expect that RS485 is a problem that's been solved by someone).

8 milliseconds does seem like a disappointingly long time; I know that XP isn't a RTOS but I'd expect it to (usually) do better than that. Another thing to look at is whether there are other high-priority threads running which may be interfering. If you've been boosting the priorities of some threads in your own application, perhaps instead you should be reducing the priorities of other threads.

I'm about to try manually controlling the RTS line from user mode with REALTIME priority during the "SendFile, clear RTS" cycle.

Don't let that thread spin out of control: IME a thread like that can if it's buggy preempt every other user-mode thread forever.

ChrisW
Yeah, but if all you do in that section is "SendFile, EscapeCommFunction(CLRRTS)", then it shouldn't be buggy, no? as long as the buffer you send it isn't sometimes NULL or anything. This should be safe. I'll see. :)
darron
That's a synchronous SendFile, I take it. "Buggy" would be if that thread doesn't block or even Sleep on anything (if, instead, it just loops) when it returns from EscapeCommFunction.
ChrisW
Two contracts of mine both did the custom serial port driver based on the DDK thing for 9-bit communications. I had a very annoying couple of years swapping device drivers twice a week because of that. Both drivers were for busses that required very tight response times, so virtual machines wouldn't work for me. I've since tried to avoid making custom versions of standard hardware drivers. It's a perfectly valid approach. It's safer, really, if you're at all competent with drivers. I just already have a custom driver on the port. :)
darron
+2  A: 

*RTS_CONTROL_TOGGLE* does not work (has a variable 1-15 millisecond delay before turning it off after transmit) on our embedded XP platform. It's possible I could get that down if I altered the time quantum to 1 ms using timeBeginPeriod(1), etc, but I doubt it would be reliable or enough to matter. (The device responds @ 1 millisecond sometimes)

The final solution is really ugly but it works on this hardware. I would not use it on anything where the hardware is not fixed in stone.

Basically:

1) set the FIFOs on the serial port's device manager page to off or 1 character deep

2) send your message + 2 extra bytes using this code:

int WriteFile485(HANDLE hPort, void* pvBuffer, DWORD iLength, DWORD* pdwWritten, LPOVERLAPPED lpOverlapped)
{
  int iOldClass = GetPriorityClass(GetCurrentProcess());
  int iOldPriority = GetThreadPriority(GetCurrentThread());
  SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

  EscapeCommFunction(hPort, SETRTS);

  BOOL bRet = WriteFile(hPort, pvBuffer, iLength, pdwWritten, lpOverlapped);

  EscapeCommFunction(hPort, CLRRTS);

  SetPriorityClass(GetCurrentProcess(), iOldClass);
  SetThreadPriority(GetCurrentThread(), iOldPriority);

  return bRet;
}

The WriteFile() returns when the last byte or two have been written to the serial port. They have NOT gone out the port yet, thus the need to send 2 extra bytes. One or both of them will get trashed when you do CLRRTS.

Like I said... it's ugly.

darron
+1 for coping with the the port's hardware buffer.
ChrisW
Just a little note that "2 extra bytes" may need to be more if you use faster baud rates.
darron
Why? I thought that having 1 extra byte was because of the 1-byte TX buffer in the serial port hardware, and that the one byte beyond that was to ensure some (any) extra non-infinitessimal additional delay beyond the last valid byte. Faster baud rates wouldn't change that number; but a deeper TX buffer would.
ChrisW
You should be right... I am not entirely sure why I need 2 extra bytes instead of just 1. One is certainly to allow the last byte to make it out the port. The extra is to allow a short delay before the drivers are turned off, which is apparently causing enough of a glitch that the previous byte gets corrupted a little. Since I don't know how much of an additional delay is needed, and that 1 delay character was at a wimpy 9600 baud... I figured cutting that extra delay by 5 or 10 could cause problems for people.
darron
I hate to accept my own questions, but this is how I solved it... and StackOverflow is harassing me to accept something. ChrisW's answer is also perfectly valid, I just don't like custom drivers for standard hardware personally (having had my life made more difficult in the past, but in a very-unlikely-for-anyone-else-to-experience way).
darron