RS485: Inappropriate ioctl for device

10.2k views Asked by At

I am using the following code to access the RS485 slave but I get the error:

Error reading ioctl port (25): Inappropriate ioctl for device

My code is as follows:

#include <linux/serial.h>
#include <sys/ioctl.h>


    int fd = open ("/dev/ttyUSB0", O_RDWR);
    if (fd < 0) {
        printf("Error Opening\n");
        exit(0);
    }

    struct serial_rs485 rs485conf;

    /* Enable RS485 mode: */
    rs485conf.flags |= SER_RS485_ENABLED;

    /* Set logical level for RTS pin equal to 1 when sending: */
    rs485conf.flags |= SER_RS485_RTS_ON_SEND;

    /* set logical level for RTS pin equal to 0 after sending: */
    rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);

    /* Set rts delay before send, if needed: */
    rs485conf.delay_rts_before_send = 0;

    /* Set rts delay after send, if needed: */
    rs485conf.delay_rts_after_send = 0;

    /* Set this flag if you want to receive data even whilst sending data */
    rs485conf.flags |= SER_RS485_RX_DURING_TX;

    if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
        fprintf( stderr, "Error reading ioctl port (%d): %s\n",  errno, strerror( errno ));
        exit(0);
    }

    //TODO read and write

    /* Close the device when finished: */
    if (close (fd) < 0) {
        fprintf( stderr, "Error closing device connection (%d): %s\n",  errno, strerror( errno ));
    }

I took the source code from https://www.kernel.org/doc/Documentation/serial/serial-rs485.txt. I am developing my application on the raspberry pi and am connected to the FTDI USB Serial device using Quad RS232-HS chip. What might be the source of the error?\

When USB I connect the USB device, output for

dmesg

is as follow:

[16865.640038] usb 3-2: new high-speed USB device number 10 using xhci_hcd
[16865.780365] usb 3-2: New USB device found, idVendor=0403, idProduct=6011
[16865.780367] usb 3-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[16865.780369] usb 3-2: Product: Quad RS232-HS
[16865.780370] usb 3-2: Manufacturer: FTDI
[16866.377940] usbcore: registered new interface driver usbserial
[16866.377969] usbcore: registered new interface driver usbserial_generic
[16866.377994] usbserial: USB Serial support registered for generic
[16866.384018] usbcore: registered new interface driver ftdi_sio
[16866.384045] usbserial: USB Serial support registered for FTDI USB Serial Device
[16866.384203] ftdi_sio 3-2:1.0: FTDI USB Serial Device converter detected
[16866.384247] usb 3-2: Detected FT4232H
[16866.384373] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB0
[16866.384399] ftdi_sio 3-2:1.1: FTDI USB Serial Device converter detected
[16866.384431] usb 3-2: Detected FT4232H
[16866.384727] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB1
[16866.384751] ftdi_sio 3-2:1.2: FTDI USB Serial Device converter detected
[16866.384786] usb 3-2: Detected FT4232H
[16866.384897] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB2
[16866.384917] ftdi_sio 3-2:1.3: FTDI USB Serial Device converter detected
[16866.384950] usb 3-2: Detected FT4232H
[16866.385385] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB3
2

There are 2 answers

2
Rodney On BEST ANSWER

As mentioned in the comment by @Richard Chambers you are using the structure uninitialised. You should do

struct serial_rs485 rs485conf = {0};

However, as discussed that didn't solve the problem.

Quoting from the Kernel document you referenced

Some CPUs/UARTs (e.g., Atmel AT91 or 16C950 UART) contain a built-in half-duplex mode capable of automatically controlling line direction by toggling RTS or DTR signals. That can be used to control external half-duplex hardware like an RS485 transceiver...

However this device has a dedicated pin called TXDEN for that. Quoting from the Data sheet section 4.3.3

With RS485, the transmitter is only enabled when a character is being transmitted from the UART. The TXDEN pins on the FT4232H are provided for exactly that purpose, and so the transmitter enables are wired to the TXDEN

and you can see that the RS232 outputs RTS / DTR not connected in RS485, this is in contrast to devices where RTS becomes TXDEN which would require an intervention by the driver.

If we pick apart struct serial_rs485 we can see that it is mostly about controlling RTS for this purpose. Since this device has a dedicated TXDEN pin, those control fields are irrelevant.

Another flag that is used in this structure:

  /* Set this flag if you want to receive data even whilst sending data */
rs485conf.flags |= SER_RS485_RX_DURING_TX;

And yes looking at the circuit, what you put out on the line you will also receive. Looks like you cannot turn that off. RS485 is multidrop, so you should be filtering out messages that were not addressed to you anyway. The fact that some of those messages may have originated from you doesn't matter.

Finally (and firstly) we have this

/* Enable RS485 mode: */
rs485conf.flags |= SER_RS485_ENABLED;

That's really saying "enable all the RS485-specific stuff that's controlled by this structure". Except that we've just made all that stuff irrelevant, so enabling it has no effect.

This is why the ioctl is not implemented for that UART.

You have a number of options, these are just suggestions so pick what suits you

  • Delete this section of the initializtion code as it's not needed
  • Compile it conditionally #if RS485_IOCTLS
  • Run it conditionally
  • Treat that errno value ENOTTY as indicating that, in this context the ioctl was not required and you can in fact proceed as if there was no error
2
Marcos G. On

I think it might add some value for others facing similar issues in the future to elaborate a bit more on Rodney's answer from a hardware side.

Although I came here looking for the answer to the thread question (why was I getting an ioctl error trying to activate the RS485 mode on pyserial (which I was actually calling from pyModbus) I found the answer to that question above. Plain and simple, and to provide a shorter answer than Rodney's: you can't. You won't be able to activate the RS485 mode on your FTDI because there is no function to use the RTS signal as a drive enable for your RS485 chip.

Some devices, like those using the atmel_serial driver do implement this functionality, but you won't find that many. Digging into this issue you'll find people saying you can write your own driver based on one of those that support the RS485 mode (see, for instance: automatically changing RTS for RS-485 communication, but in my view they are wrong)

Now that you have the bad news, you can ask yourself why would you need to use the RS485 mode? I imagine that, like me, you were trying to establish a half-duplex link between two devices using a two wire RS485 setup (which is surprisingly frequent in many industrial devices where you have a master-slave configuration, where you have one side sending a request for data and the other side duly responding, as you do with Modbus).

If you are looking for a software solution you can still take the RTS signal high and low manually before starting and after finishing transmitting from each side. This solution will work for testing and just for fun in most environments (at least those I tried, it even works with Python on a Raspberry Pi using a GPIO line to control the Drive Enable/~Read Enable signals) but I would not use it if you want to get good reliability. With any non-real time OS there is no warranty you will get the correct timing and some devices are quite picky and will report a timeout error. If you are free to choose both sides (or all of them if you have more than two stations) of the bus you can always increase your tiemout everywhere and hope for the best.

Good news is, as Rodney said, on the FTDI chip you have the TXDEN signal available on pin 13 so you can use that right away. Unfortunately, most cheap USB to serial adaptor PCBs don't have that signal easily accessible on the board. That's what happened to my Sparkfun board. If you are not up for the challenge of attaching a cable directly to the SMD IC you can do what I did: download mprog 3.5 from FTDI, connect to the EPROM, under I/O Controls select TXDEN instead of TXLED and click save to write the updated firmware to the chip. Then you can easily attach a cable to the LED pad and use it as your Drive Enable/~Read Enable signal (see pictures, the scope snapshot shows it works nicely).

Modified FTDI board with TXDEN signal on TXLED

Scope capture of the modified circuit: blue is TX, yellow TXDEN

Lastly, if you are unlucky enough to run into other chips that don't have this option (TXDEN), like the PL2303 you can always go old school and build a hardware TXDEN with a 555 timer (see here: http://www.embeddedsys.com/subpages/resources/images/documents/microsys_art_RS485.pdf). I tested this circuit and it works fine, at least at 9600 bps (note that I had to use a 39K resistor instead of a 3.9K, I think that's a typo but I might be wrong, I did not go into fine details, I just needed something quick and dirty to pair with my FTDI.