Anton
Anton

Reputation: 59

automatically changing RTS for RS-485 communication

I'm trying to setup half duplex communication in my program. I have my RS485 transceiver using the RTS flag (TIOCM_RTS) to toggle back and forth between transmit and receive. And to send/receive data I need to change RTS flag manually:

  1. Set RTS to High.

  2. Send data.

  3. Set RTS to low.

    int setRTS(int level) {
        int status;
        ioctl(ser_port, TIOCMGET, &status);
        if(level) {
            status |= TIOCM_RTS;
        } else {
            status &= ~TIOCM_RTS;
        }
        ioctl(ser_port, TIOCMSET, &status);
        return 1;
    }
    

My question is: shouldn't the linux kernel be able to switch RTS automatically? And how to ensure that data was sent before calling setRTS(0)?

Upvotes: 2

Views: 12753

Answers (2)

sawdust
sawdust

Reputation: 17077

shouldn't the linux kernel be able to switch RTS automatically?

Yes, there is kernel framework for this starting in Linux 3.0.
There are two ioctls in include/uapi/asm-generic/ioctls.h:

#define TIOCGRS485      0x542E
#define TIOCSRS485      0x542F

to retrieve and configure a tty serial port driver in RS-485 mode.
These ioctls use the struct serial_rs485:

 /*
  * Serial interface for controlling RS485 settings on chips with suitable
  * support. Set with TIOCSRS485 and get with TIOCGRS485 if supported by your
  * platform. The set function returns the new state, with any unsupported bits
  * reverted appropriately.
  */
 
 struct serial_rs485 {
         __u32   flags;                  /* RS485 feature flags */
 #define SER_RS485_ENABLED               (1 << 0)        /* If enabled */
 #define SER_RS485_RTS_ON_SEND           (1 << 1)        /* Logical level for
                                                            RTS pin when
                                                            sending */
 #define SER_RS485_RTS_AFTER_SEND        (1 << 2)        /* Logical level for
                                                            RTS pin after sent*/
 #define SER_RS485_RX_DURING_TX          (1 << 4)
         __u32   delay_rts_before_send;  /* Delay before send (milliseconds) */
         __u32   delay_rts_after_send;   /* Delay after send (milliseconds) */
         __u32   padding[5];             /* Memory is cheap, new structs
                                            are a royal PITA .. */
 };

I've used this RS-485 capability on Atmel and Etrax SoCs, but otherwise implementation of these ioctls in Linux UART/USART drivers is very sparse (because it exists to support a somewhat rare hardware feature).
If your driver doesn't have it, then consider implementing it yourself. You could use the implementation in drivers/tty/serial/atmel_serial.c as a guide.

Also read the Linux kernel document for RS-485.

Upvotes: 5

Chris Stratton
Chris Stratton

Reputation: 40407

This can indeed be tricky - to do it pro-actively you need to know when the last byte cleared the UART engine, or at least when it entered (when the buffer went empty) and add a delay calculated from the baud rate and word length. This is indeed something it can be worth implementing in the serial driver itself, where all of that is visible.

However, this problem is most commonly encountered on a shared bus where you also receive everything you transmit. If that is the case, you can use receiving the end of your own transmission (assuming you discover that promptly) as the trigger to disable the driver.

Upvotes: 1

Related Questions