Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTU over RS485 timing issues, need to poll UART LSR for TEMT flag #668

Open
marekm72 opened this issue Nov 17, 2022 · 4 comments
Open

RTU over RS485 timing issues, need to poll UART LSR for TEMT flag #668

marekm72 opened this issue Nov 17, 2022 · 4 comments

Comments

@marekm72
Copy link

To work well with some dumb hardware, it is necessary to poll the "Tx empty" flag of the UART. Unfortunately, the typical 16550 UART doesn't support interrupts on this event, and serial port write() call can return early when data is still in the queue to be sent, and even tcdrain() alone doesn't guarantee it is sent completely by the hardware. In my own code (written ~20 years ago, now trying to port to libmodbus mainly to get TCP support) I use the code like below, after sending the frame with write() and before disabling RTS, and I'd suggest to do something similar in libmodbus (if supported by the OS - that ioctl could be Linux-specific). Switching RS485 direction is really timing critical, you either lose the end of request of the start of response if you miss the correct timing. BTW, one rare example of UART where they got it right is part of Atmel (now Microchip) AVR 8-bit MCUs, they implement a separate interrupt for "Tx empty" event in addition to the usual "Tx ready".

static void
serial_wait_tx(int fd)
{
        int lsr = 0;

        tcdrain(fd);
        do {
                if (ioctl(fd, TIOCSERGETLSR, &lsr) == -1)
                        error(1, errno, "ioctl TIOCSERGETLSR");
        } while (!(lsr & TIOCSER_TEMT));
}

@modem-man-gmx
Copy link

why manually handle RTS down after Tx queue sent empty?
AFAIR, there is a handshake option or ioctl combination in both widespread OSs to let the driver do it automatically. Something like 80's RTS/DCD half duplex handshake instead of 90's RTS/CTS full duplex flow control). Damn sure it is in Win32, because I used it for a decade (Nt4 to Longhorn), quite sure it's also in Linux, because a team mate used it on some quad-16550-clone and a Geode x86 up to same time.

@marekm72
Copy link
Author

It really depends on the hardware. My setup was with an old and dumb RS232/RS485 converter, RTS required to switch direction - connected to an ISA serial port card in a Pentium Pro 200MHz machine, back then there was no USB yet. Some better converters switch direction automatically, based on TXD line. Other solutions are possible, some good USB UARTs have a pin to control the RS485 driver automagically.

@modem-man-gmx
Copy link

Hmmm,
can't confirm this.

The i8250, i16550 and i16650 and NS16550 do in fact have TXRDY interrupt. The typical failure is to assume the interrupt comes after last byte left the Transmitter Shift Register (TSR). But this is wrong, if documented somewhere. The IRQ occurs after the last Byte was put from Transmitter Hold Register (THR) into TSR, or from the Tx-FiFo into TSR.
The TSR can't get stopped by handshake, so i'ts on the IRQ handler to calculate how many millisec to add (based on baud rate) from IRQ to last bit left the Tx pin.
Please refer to books like "PC Intern" or "C Programmers Guide to Serial Communication" (2nd issue by SAMS) from mid 90's. Or have a look here, for instance: any spec of 16550.

It's often said the wide spread TI Quad-16550 Derivate has problem with this IRQ, but on e2e ti.com you'll find details how to implement it right. The main pain with some (or all?) multi-channel TI UARTs is the common IRQ for any of the channels, so the typical legacy code or setup expecting IRQ4=ttyS0, IRQ3=ttyS1 does not work with.

Win32 flow control flags can be set to toggle RTS on TXRD. And I am damn sure, this toggling always happens after TSR did it. Sure, because we used it with a modulator which had /CE connected to RTS and never lost even the stop bit of the last Tx byte (or to /RTS, don't remember). At 200 MHz single core x86.

Linux also supports this IRQ, a helpful discussion may be found here stackoverflow questions interrupts-in-uart-16550-and-linux-kernel. A former team mate of me (rest in peace, CSCH!) implemented such sender even on pre-3.xx Linux kernel, without fiddling with the IRQs, just configured it via setserial, IIRC.

Yes, you're right, some UARTS don't work well with this. For instance recent Xilinx Zync as well as mid 90s super-cheap port extender cards. Who cares the >15 years old hardware?
Exotic hardware with Windows does not exist or is replaceable.

So, finally, libmodbus could get problematic on exotic hardware with Linux on Microcontrollers.
That's all I am able to see.

@marekm72
Copy link
Author

"TX ready" is when more data can be written to the UART (there is enough free space in the FIFO) and that works fine of course.
"TX empty" is when the UART is done sending the stop bit of the last byte sent, this is the good time to disable RS485 driver but is not signaled by a separate interrupt on most typical UARTs (except the AVR one, which I successfully used in Modbus RTU slave devices of my own design). Small delays are difficult to do accurately on a non-realtime OS. My ~20 years old hardware is still working, and this is where I have tested my "poll Tx empty flag" code. Still not perfect (an occasional reply is lost and needs to be retried) but good enough. If it ain't broke don't fix it, will be retired soon only to save electricity costs (replace ~50W physical box with a VM talking over the network to a ~5W MikroTik KNOT used as Modbus TCP-to-RTU gateway).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants