Verdin AM62 - RS-485 multi-point/multi-drop

Hello community!

I try to set up the TI AM62’s UART interfaces in linux to operate in RS485 mode with multi-drop partiy mode (see TI’s TRM 12.2.4.4.8.3.3.10 Multi-drop Parity Mode with Address Match). As far as I understood it, a RS485 “Master” is sending data to multiple slaves and uses the bit, that is normally used for the parity bit, as a marker to identify that the databits are an address (parity=1) or databits (parity = 0).
When i try to activate rs485 addressing: (code without this line causes no error in the configuration)
rs485conf.flags |= SER_RS485_ADDRB;
I am always getting the error EINVAL

I think it is not supported by the termios driver? At least, the SoC is possible to do multidrop according to the TRM.

My code so far:

int rt485_open(uint8_t port, int baudrate, int databits, int parity, int stopbits)
{
    struct termios tty;
    
    if (port >= RT485_NUM_DEVICES) //ports numbers are from 0 to 3
        return RT485_ERROR_WRONG_DEV_NUM;

    //create pointer (will point to rt485 port structure)
    rt485_port_t* rt485_port_ptr = NULL;

    //set pointer to selected port structure
    rt485_port_ptr = &(g_rt485_ports[port]);
        
    // Lock port mutex
    rt485_mutex_lock(&(rt485_port_ptr->mutex));

    // Close if already open (do it directly without calling rt485_close to avoid deadlock)
    if (rt485_port_ptr->fd != -1)
    {
        close(rt485_port_ptr->fd);
        rt485_port_ptr->fd = -1;
        rt485_port_ptr->state = RT485_STATE_CLOSED;
    }

    // Open TTY device
    rt485_port_ptr->fd = open(uart_devices[port], O_RDWR | O_NOCTTY);
    if (rt485_port_ptr->fd  < 0)
    {
        rt485_port_ptr->fd = -1;
        //in case of an error, unlock mutex before return
        rt485_mutex_unlock(&(rt485_port_ptr->mutex));
        return RT485_ERROR_OPENING;
    }

    // Configure TTY
    memset(&tty, 0, sizeof(tty));
    if (tcgetattr(rt485_port_ptr->fd, &tty) != 0)
    {
        //in case of an error, close fd, reset it, and unlock mutex before return
        close(rt485_port_ptr->fd);
        rt485_port_ptr->fd = -1;
        rt485_mutex_unlock(&(rt485_port_ptr->mutex));
        return RT485_ERROR_CONFIG;
    }   

    // Set baud rate - convert baudrate parameter to speed_t
    speed_t speed;
    switch (baudrate) {
        case 9600:    speed = B9600; break;
        case 19200:   speed = B19200; break;
        case 38400:   speed = B38400; break;
        case 57600:   speed = B57600; break;
        case 115200:  speed = B115200; break;
        case 230400:  speed = B230400; break;
        case 460800:  speed = B460800; break;
        case 500000:  speed = B500000; break;
        case 576000:  speed = B576000; break;
        default:      speed = B19200; break; // Default fallback
    }
    cfsetospeed(&tty, speed);
    cfsetispeed(&tty, speed);
    
    // Configure parity
    switch (parity) {
        case RT485_PARITY_NONE:
            tty.c_cflag &= ~PARENB;
            break;
        case RT485_PARITY_EVEN:
            tty.c_cflag |= PARENB;
            tty.c_cflag &= ~PARODD;
            break;
        case RT485_PARITY_ODD:
            tty.c_cflag |= PARENB;
            tty.c_cflag |= PARODD;
            break;
        default:
            tty.c_cflag &= ~PARENB;
            break;
    }
    
    // Configure stop bits
    if (stopbits == 2)
        tty.c_cflag |= CSTOPB;  // 2 stop bits
    else
        tty.c_cflag &= ~CSTOPB; // 1 stop bit
    
    // Configure data bits
    tty.c_cflag &= ~CSIZE;
    switch (databits) {
        case 5: tty.c_cflag |= CS5; break;
        case 6: tty.c_cflag |= CS6; break;
        case 7: tty.c_cflag |= CS7; break;
        case 8: tty.c_cflag |= CS8; break;
        default:
            tty.c_cflag |= CS8;
            break;
    }

    tty.c_cflag |= CREAD | CLOCAL;
    
    // Raw mode, no echo
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_oflag &= ~OPOST;
    tty.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL);
    
    // Enable hardware flow control //not necessary
    //tty.c_cflag |= CRTSCTS;

    // Disable hardware flow control
    //tty.c_cflag &= ~CRTSCTS;

    // Apply settings
    if (tcsetattr(rt485_port_ptr->fd, TCSANOW, &tty) != 0)
    {
        printf("Error: tcsetattr failed on port %d\n", port);
        //in case of an error, close fd, reset it, and unlock mutex before return
        close(rt485_port_ptr->fd);
        rt485_port_ptr->fd = -1;
        rt485_mutex_unlock(&(rt485_port_ptr->mutex));
        return RT485_ERROR_SETATTR;
    }    
    
    // Enable RS485 mode if available
    struct serial_rs485 rs485conf;
    memset(&rs485conf, 0, sizeof(rs485conf));

    rs485conf.flags |= SER_RS485_ENABLED; //| SER_RS485_RTS_ON_SEND;
    rs485conf.flags &=~ SER_RS485_RTS_ON_SEND;
    rs485conf.flags |= SER_RS485_RTS_AFTER_SEND; // RTS low after sending (receive mode)

    rs485conf.delay_rts_before_send = 0; //delays are in milliseconds
    rs485conf.delay_rts_after_send = 0;  // probably: 1/baudrate * x

    if(databits == 9)
    {
        // Enable 9-bit addressing mode
        rs485conf.flags |= SER_RS485_ADDRB;
    
        //rs485conf.addr_recv = 0x05;   // This device's address
        //rs485conf.addr_dest = 0x01;      // Send to this address
    }

    if(ioctl(rt485_port_ptr->fd, TIOCSRS485, &rs485conf) < 0)
    {
        switch(errno) {
                case ENOTTY:
                    printf("Warning: RS485 ioctl not supported by this driver (port %d)\n", port);
                    // Driver doesn't support RS485 mode - continue with manual RTS control
                    break;
                case EINVAL:
                    printf("Warning: Invalid RS485 configuration (port %d)\n", port);
                    // Invalid flags or parameters - may need to adjust rs485conf
                    break;
                case ENODEV:
                    printf("Error: Device disconnected (port %d)\n", port);
                    close(rt485_port_ptr->fd);
                    rt485_port_ptr->fd = -1;
                    rt485_mutex_unlock(&(rt485_port_ptr->mutex));
                    return RT485_ERROR_OPENING;
                case EACCES:
                case EPERM:
                    printf("Error: Permission denied for RS485 config (port %d)\n", port);
                    break;
                default:
                    printf("Warning: RS485 ioctl failed with errno %d: %s (port %d)\n", 
                                errno, strerror(errno), port);
                    break;
        }
    }

    rt485_port_ptr->state = RT485_STATE_OPEN;

    rt485_mutex_unlock(&(rt485_port_ptr->mutex));

    return 0;
}

btw my device tree overlay:


&main_uart2 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_sodimm_uart2_pins>;

    uart-has-rtscts;
	linux,rs485-enabled-at-boot-time;
   	rs485-rts-active-low;
	status = "okay";
};


/* Verdin UART_8 */
&main_uart3{
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_sodimm_uart3_pins>;
	uart-has-rtscts;
	linux,rs485-enabled-at-boot-time;
   	rs485-rts-active-low;
	status = "okay";

};

Hi, I’m the Toradex AI Assistant.

I might make mistakes or occasionally get you wrong. Our support team will step in if needed.


RS485 Multi-Drop Addressing Support

The issue you’re encountering with the SER_RS485_ADDRB flag returning EINVAL is likely related to kernel driver support for 9-bit addressing mode on the TI AM62 platform.

Understanding the Problem

The SER_RS485_ADDRB flag is used to enable 9-bit addressing mode for RS485 multi-drop communication, but this feature requires specific kernel driver support that may not be fully implemented or enabled in the current BSP for TI AM62 platforms.

Device Tree Configuration

Your device tree overlay configuration looks correct for basic RS485 operation (1):

&main_uart2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_sodimm_uart2_pins>;
    uart-has-rtscts;
    linux,rs485-enabled-at-boot-time;
    rs485-rts-active-low;
    status = "okay";
};

(1)

Hardware Requirements

For RS485 communication, you need a proper transceiver. The RS-485 standard relies on differential signaling, which standard TTL UART cannot support (2). You must incorporate a transceiver compatible with the voltage levels of your platform (2).

Alternative Approaches

Since the kernel driver may not support the SER_RS485_ADDRB flag, consider these alternatives:

  1. Manual 9-bit Implementation: Handle the address/data distinction in your application layer by managing the parity bit manually
  2. Protocol-Level Addressing: Implement addressing at the application protocol level rather than relying on hardware 9-bit support
  3. Custom Driver Modification: Modify the UART driver to support the specific 9-bit addressing requirements

Troubleshooting Steps

  1. Verify Driver Support: Check if your kernel version and BSP include full support for 9-bit addressing
  2. Test Without ADDRB: Confirm basic RS485 operation works without the SER_RS485_ADDRB flag
  3. Check Kernel Logs: Review dmesg output for any driver-specific error messages

The UART interfaces on Toradex modules provide standard TTY device access under /dev with family-specific naming conventions (3). For TI AM62 platforms, ensure you’re using the correct device paths and that the underlying hardware supports the advanced RS485 features you’re trying to implement.

Best regards, Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.