libmodbus icon indicating copy to clipboard operation
libmodbus copied to clipboard

Library should provide hooks for RS-485 Half Duplex pin toggle

Open feinstein opened this issue 5 years ago • 13 comments

libmodbus version

3.1.6

OS and/or distribution

Raspian 10 Buster

Environment

Raspberry Pi Zero W with FTDI USB Serial

Description

I am trying to setup a Half Duplex RS-485 communication using libmodbus on a Raspberry Pi running Raspian Buster, with a FTDI USB to Serial adapter. My FTDI adapter shows as ttyUSB0 when I run ls /dev/.

I tried the following sample code:

#include <modbus.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(void) {
   modbus_t *ctx  = modbus_new_rtu("/dev/ttyUSB0", 19200, 'N', 8, 1);
    if (ctx == NULL) {
        fprintf(stderr, "Unable to create the libmodbus context\n");
        return 0;
    }

    if (modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485) == -1) {
        fprintf(stderr, "Error setting the serial port as RS-485\n");
        fprintf(stderr, "errno: %d\n (EBADF == 9)", errno);
        modbus_free(ctx);
        return 0;
    }
}

Compiled with gcc test1.c -I/usr/include/modbus -lmodbus. And I get errno as 9, or EBADF, even if I run this code with sudo.

I tried to search how to fix this, but I can't find. So far what I find is that Linux is tricky with its automatic toggling of RTS for RS-485 communications on Half Duplex, and this library has many Issues pointing out how tricky this is and how hard to fix it is.

So here's my suggestion:

Provide hooks so we can toggle any GPIO pin ourselves when it's time to send or receive any data, not depending on Linux to do it.

This can be done by providing functions to set a hook fuction that will be called whenever we need to toggle our GPIO pins.

A sample could be:

set_modbus_rs485_send_hook(ctx, send_gpio_toogle);

Where send_gpio_toggle is a function that we implement ouselves.

So this way we just need to define 2 functions with no parameters and no returns, one for sending and one for receiving.

Off course I would be very happy as well if someone could tell my why my code doesn't run and how to fix it :) but I believe those toggle hooks would be very helpful.

feinstein avatar Oct 30 '19 00:10 feinstein

Hello, Just my 2 cent on this one: I think there is already a way to set a custom callback for this: https://github.com/stephane/libmodbus/blob/master/doc/modbus_rtu_set_custom_rts.txt

I've tried toggling RTS on an FTDI chip (FT4232 in my case) with success using: int fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY ); int RTS_flag = TIOCM_RTS; ioctl(fd,TIOCMBIC,&RTS_flag); //Clear RTS pin

int RTS_flag = TIOCM_RTS; ioctl(fd,TIOCMBIS,&RTS_flag); //Set RTS pin

JoelStienlet avatar Oct 30 '19 08:10 JoelStienlet

Also to note that there is a bug when using tcdrain() with FTDI chips (that you can call to wait until all the data have been sent). link to another project where they also see the bug: https://github.com/jzatarski/RespeQt/issues/2#issuecomment-125093317 Looking on my oscilloscope, I could see that bug on kernel 4.20. Don't know if it is corrected yet, or if this is a hadware limitation...

JoelStienlet avatar Oct 30 '19 08:10 JoelStienlet

Looking online I see some people opposing the use of a callback, as a non real-time OS could have a delay setting the lines and trigger a timeout or other communication errors.

I found that some FTDI chips have a TXDEN pin that should work automatically for this purpose, just that most boards don't expose this pin.

I will try your suggestions, thanks a lot!

feinstein avatar Oct 30 '19 15:10 feinstein

I totally agree. After reading the datasheet again, it looks like the TXDEN pin is shared between two functionalities: RI and TXDEN, but sadly RI is the default functionality. To enable TXDEN, the eeprom should be written to select the alternate function ( AN_201 : 7.12 CBUS Mux Control).

JoelStienlet avatar Oct 30 '19 17:10 JoelStienlet

Yes, but depending on the FTDI chip TXDEN is the default. AFAIK it's configurable through FTDI's software, so it shouldn't be a big issue.

feinstein avatar Oct 30 '19 17:10 feinstein

If this is a common public product, i.e. a USB RS-485 dongle and not a self-made hardware, then you could expect that this function is preconfigured accordingly. Usually, you don't need to care about the TXDEN for such devices, just use it in RS-232 default mode, the hardware will care about the other stuff itself. I guess this is the reason, why calls to modbus_rtu_set_serial_mode fail: the driver does not know about the attached RS-485 line driver chip and thus is not able to handle your configuration request.

mhei avatar Oct 30 '19 22:10 mhei

Yes, but this is where theory doesn't meet practice.

In theory RS-485 is just an electrical specification, if you are using it Half Duplex or Full Duplex, with a protocol or without, is up to you. In practice, people use RS-485 with Modbus in Half Duplex so much, that it became a synonym.

feinstein avatar Oct 30 '19 23:10 feinstein

@feinstein still, the usb tty driver must support switching modes if you want to use that. They don't, so you can't call modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485) That call (and the underlyign ioctls) are targetting actual uart peripherals that support rs485 natively. ie, not 8250/16550 uarts.

karlp avatar Nov 01 '19 16:11 karlp

My point is, half duplex shouldn't be synonym to RS-485. People should say "support RS-485 in half duplex mode", instead of just saying "support RS-485".

feinstein avatar Nov 01 '19 16:11 feinstein

For the hardware question: I dit the test setting bit 4 at 0x0B in the eeprom of my FT4232H mini-module board, and indeed the TXDEN pin goes high during transmission (when looking at my oscilloscope), so this test confirms that this pin can be used to grab the line in RS-485 half duplex mode (as stated in the datasheet). Also note that the kernel apparently has no knowledge about this, because I get an "Inappropriate ioctl for device" when calling ioctl TIOCSRS485. So the TXDEN pin behaviour is entirely managed by the FTDI chip, and the kernel doesn't even know it is enabled.

JoelStienlet avatar Nov 01 '19 18:11 JoelStienlet

Yes, the kernel has no idea about it, it's transparent, that's how most USB to RS-485 Half Duplex cheap boards work, they are just a USB to TTL, coupled with a MAX485 Like chip, using a pin like TXDEN to toggle the output.

I just want to add that the FT232RL comes with TXDEN enabled by default (pin 13 of the SSOP chip and pin 10 of the QFN).

feinstein avatar Nov 01 '19 18:11 feinstein

@karlp @feinstein I guess the "correct" solution would be to implement rs485conf callback in ftdi_sio driver.

dangowrt avatar Feb 24 '20 21:02 dangowrt

@dangowrt it woudl certainly be a good option in general, but can also make things confusing. I'm all for more of the serial drivers getting support for this so that the kernel infrastructure can be used, but often, if the right pins are used, you just "don't tell the kernel" about rs485 mode, and it's all fine. ft232 has a pin default to rs485 driver enable mode, so as long as you used that to connect your transceiver it's often "works out of the box" but you have to remember to not try and set rs485 modes on the device. and that's only for the half duplex mode. wanting anything else is very much reason to want support for it all in the driver.

karlp avatar Feb 25 '20 10:02 karlp