RS485 serial port writing but not reading?

Colibri iMX6 DL 512MB IT
running c code on Torizon

Hi guys

I am stumped by this… wasted 2 days already…

I am able to get my RS485 port to write to a terminal program, so I know I have MOST of my settings correct anyways, for example
9600 baud
1 stop bit
no parity
8 data bits
no flow control

and not so many days ago, I was actually able to get it to read from the serial port as well.

(So this means that at least my device-tree should be in good shape… which is a relief in itself.)

Since then however, I did some major refactoring and broke the program. I was able to roll back to a point where writing to the serial port is working again, but I simply can’t get read() to work agiain.

I can see when I type on the keyboard, that the TxD light on my EasySync RS485-USB adaptor is blinking so there should be data making it to the board. Pretty sure my wires are correct (had A and B flipped a while ago, and RS485 simply doesn’t work if you do that, I have learned)

I have basically simplified my code down to a single function now for debugging purposes, and to share it with you here.

Any ideas what I can be missing?

/*    start */ 
int InitModbus(){

// adapted from https://gist.github.com/amarburg/07564916d8d32e20e6ae375c1c83a995

int err         = 0;
unsigned int i  = 0;
char buf[80]    = {0};
char buf2[80]   = {0};
int enable      = 1;
// int enable = atoi( argv[2] );
int flags = O_RDWR | O_NOCTTY | O_NONBLOCK;

// int modbusFileDescriptor moved to global variable.

#if DISABLE_MODBUS == 1
#else


modbusFileDescriptor = open("/dev/colibri-uartb", flags);
if (modbusFileDescriptor < 0) {
    /* Error handling. See errno. */
        fprintf( stderr, "Error opening port /dev/colibri-uartb (%d): %s\n", errno, strerror( errno ));
        //exit(-1);
        err |= ERROR_MODBUS;
}

struct serial_rs485 rs485conf;

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

printf("Port currently RS485 mode is %s\n", (rs485conf.flags & SER_RS485_ENABLED) ? "set" : "NOT set");

if( enable ) {
        printf("RS485 mode will be SET\n");
        rs485conf.flags |= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND;

       // rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND); // 
        rs485conf.flags |= SER_RS485_RX_DURING_TX;   // Enable receiver during sending, required for i.MX devices 
        rs485conf.flags |= SER_RS485_RTS_AFTER_SEND; // Set logical level for RTS pin equal to 1 after sending: 
} else {
        printf("RS485 mode will be UNSET\n");
        rs485conf.flags &= ~SER_RS485_ENABLED;
}

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

/* Use read() and write() syscalls here... */

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

printf("Confirm RS485 mode is %s\n", (rs485conf.flags & SER_RS485_ENABLED) ? "set" : "NOT set");


// SET UP THE RS485 PARAMETERS USING TERMIOS.H
    // see: https://blog.mbedded.ninja/programming/operating-systems/linux/linux-serial-ports-using-c-cpp/#basic-setup-in-c

struct termios tty;

//  for use with termios tty;
if(tcgetattr(modbusFileDescriptor, &tty) != 0) {
     printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
}

//struct termios2 tty;   // must comment out #include termios.h to use this.
//     for use with termios2 tty;
//  ioctl(modbusFileDescriptor, TCGETS2, &tty); // get the settings

// modify the settings
tty.c_cflag &= ~PARENB;     // Clear parity bit, disabling parity (most common)
tty.c_cflag &= ~CSTOPB;     // Clear stop field, only one stop bit used in communication (most common)
tty.c_cflag |= CS8;         // 8 bits per byte (most common)
tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
tty.c_lflag &= ~ICANON;     // In canonical mode, input is processed when a new line character is received... and so we normally want to disable canonical mode.
tty.c_lflag &= ~ECHO;       // Disable echo
tty.c_lflag &= ~ECHOE;      // Disable erasure
tty.c_lflag &= ~ECHONL;     // Disable new-line echo
tty.c_lflag &= ~ISIG;       // When the ISIG bit is set, INTR, QUIT and SUSP characters are interpreted. We don’t want this with a serial port, so clear this bit:
tty.c_iflag &= ~(IXON | IXOFF | IXANY); //  Turn off s/w flow ctrl (Clearing IXOFF, IXON and IXANY disables software flow control, which we don’t want)
tty.c_oflag &= ~OPOST;      // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR;      // Prevent conversion of newline to carriage return/line feed
// tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX)
// tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX)
tty.c_cc[VTIME] = 10;       // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
tty.c_cc[VMIN] = 0;      
// If you want to remain UNIX compliant, the baud rate must be chosen from one of the following:
// B0,  B50,  B75,  B110,  B134,  B150,  B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800
cfsetispeed(&tty, B9600);  // Set in/out baud rate to be 9600
cfsetospeed(&tty, B9600);

 //ioctl(modbusFileDescriptor, TCSETS2, &tty);   // Now we're done, write terminal settings to the file descriptor.

if (tcsetattr(modbusFileDescriptor, TCSANOW, &tty) != 0) {
    printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
    return 1;
}


// make the baud more human-readable..
int baudrate[16393]  = {0};
baudrate[B2400]   = 2400;
baudrate[B4800]   = 4800;
baudrate[B9600]   = 9600;
baudrate[B19200]  = 19200;
baudrate[B38400]  = 38400;
baudrate[B57600]  = 57600;
baudrate[B115200] = 115200;

printf("Baud Rate : %d\n", baudrate[tty.c_ospeed]);
if ((tty.c_cflag & PARENB ) == PARENB) {
        printf("Parity    : ON\n" );
} else {
        printf("Parity    : OFF\n" );   
}
if ((tty.c_cflag & CSTOPB) == 0){
        printf("Stop bits : %d\n", 1 ); 
} else {
        printf("Stop bits : %d\n", 2 );
}
if ((tty.c_cflag & CSIZE ) ==  CS8 ){
        printf("Data bits : %d\n", 8  );
} else if ((tty.c_cflag & CSIZE ) ==  CS7 ){
        printf("Data bits : %d\n", 7  );
}

    int numBytes = 0;

/* Do something on the serial port... send the values 0 to 100*/
for( i = 0; i < 10000; ++i ) {
        snprintf( buf,79, "%d\r\n", i );
        write( modbusFileDescriptor, buf, strlen(buf));
        printf("%d\n",i);
        fflush(stdout);
        sleep(1);

if (tcsetattr(modbusFileDescriptor, TCSANOW, &tty) != 0) {    // set attributes 
    printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
    return 1;
}

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

        memset(buf2,0,sizeof(buf2));
        numBytes = read(modbusFileDescriptor, buf2,1);
        if (strncmp(buf2,"\0",1) == MATCH) {
            printf("numBytes %d\n", numBytes);
            // do nothing

        } else {    
           printf("%s\n",buf2);
           
        }
        fflush(stdout);

}

Update:

I can see signals on the screen when I type in the terminal, but they look a little different depending on if I am reading or writing… (forgive the crude graphics)

Writing only:

    _________   _   __   _   _     _   _   _   ________
 2.5V _      | | | |  | | | | |   | | | | | | |
             | | | |  | | | | |   | | | | | | |
 0      ->   --- ---  --- --- ----- --- --- ---

Reading only:

    _________   _   __   _   _     _   _   _   ________
 2.5V _      |_| |_|  |_| |_| |___| |_| |_| |_|

 0      -> ----------------------------------------------------

I wonder if the channels are somehow “fighting” because I am trying to transmit and recieve at the same time, so it gets to exactly half the voltage??

I found this thread:

which seems to explain my situation exactly.

Seems I am not releasing the drive enable. I will check on the scope to see what that pin is doing on my board…

Please don’t be offended, but I’m going to share a couple of links with lots of reading. From your post it sounds like you are trying to use RS-485 just like RS-232 and that is not really how it works.

https://www.cuidevices.com/blog/rs-485-serial-interface-explained

Read through the entire thing and in particular pay attention to this section.

Data Link Layer of the OSI Model

Really focus on this paragraph.

At the data layer RS-485 generally uses a UART for serial communication where the host UART drives and receives the serial communication in full duplex. It is connected to the RS-485 differential transceiver that makes up the physical layer and converts the signals into the half-duplex differential format for use on the RS-485 bus. The host will then communicate with the RS-485 through the UART, and it will tell the transceiver when to switch between transmit and receive. Slave devices will also use their UART in the same way.

I have never used this library for RS-485, just RS-232, but it layers on what you are attempting to use and will default away “stupid mistakes.”

Did you change how your program is run?

Most people trying serial comm for the first time really blunder. In particular, if you come from the MS DOS world, you don’t have to deal with the Linux security. Back in the day, when everyone had to connect via serial terminal, everyone had to have access to serial ports. Now, almost nobody does, so dialout is a group where most serial ports belong.

Some distros are nice. All serial devices are created within the dialout group and root is automatically a member. Some device tree software will create serial ports with specific owner and custom group and really muck things up by not adding root to the group.

I suspect you don’t have duplex configured properly so writing a simple stand-alone program using the library I mentioned should let you communicate bi-directionally.

If you changed how you are running, i.e. you were root from the OS console and now you are running inside of a Docker container or you are running as a mere mortal user you need to be given access. Your user needs to be a member of dialout and your Docker container needs to have access to the actual devices.

First thing is first. Create a stand alone (or use one of the examples) using that library and see if you can communicate in both directions. If you can’t, then your environment is hosed. Time to track down security, permission, group issues. If you can successfully communicate, see if you can use that library for full RS-485 multi-drop communication and if so, just use it.

Serial comm for a first timer (even an old timer like me) is best done with a library that has sane defaults for the things you forget to set.

Hi,

No offense taken… I have tried to read through this material before and its the sort of problem where you don’t know enough to understand what you are reading. Reading it a second time has helped a little, though…

So it seems like my colibri iMX6 is not ever putting the UART RTS (or RS485 DE) signal low, even when I don’t transmit at all, and even if I set the flags in a way that should not turn on the RTS…



I don’t know how I managed to recieve in the past, because I swear it was working (I am pretty sure I was even transmitting and receiving at the same time.

From the schematics, as UART RTS is SODIMM_34, am I supposed to configure this as a GPIO and drive it myself, or are the libraries <linux/serial.h> or <terminos.h> supposed to handle this for me, and I just have not found the right setting?

Yes, it is supposed to be half-duplex, just to confirm.

Can you share what you have in your device tree for that uart?
The rs485 device tree stuff is particularly confusing regarding polarity and function (and individual driver support).
You did see this one right? UART (Linux) | Toradex Developer Center

Hi,

Okay we have:

 //        imx6dl-colibri-eval-v3.dts
    &uart2 {
    
    status = "okay";
    
    };

/* Colibri UART_B */

&uart2 {   
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2_dte>;
fsl,dte-mode;
uart-has-rtscts;
status = "disabled";

};

pinctrl_uart2_dte: uart2dtegrp {
	fsl,pins = <
		MX6QDL_PAD_SD4_DAT4__UART2_TX_DATA	0x1b0b1
		MX6QDL_PAD_SD4_DAT7__UART2_RX_DATA	0x1b0b1
		MX6QDL_PAD_SD4_DAT6__UART2_RTS_B	0x1b0b1
		MX6QDL_PAD_SD4_DAT5__UART2_CTS_B	0x1b0b1
	>;
};

and in imx6qdl.dtsi

serial0 = &uart1;|
serial1 = &uart2;|
serial2 = &uart3;|
serial3 = &uart4;|

uart2: serial@21e8000 {
	compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
	reg = <0x021e8000 0x4000>;
	interrupts = <0 27 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6QDL_CLK_UART_IPG>,
		 <&clks IMX6QDL_CLK_UART_SERIAL>;
	clock-names = "ipg", "per";
	dmas = <&sdma 27 4 0>, <&sdma 28 4 0>;
	dma-names = "rx", "tx";
	status = "disabled";
};

And in imx6qdl-colibri.dtsi

/* Colibri UART_B */
&uart2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2_dte>;
fsl,dte-mode;
uart-has-rtscts;
status = "disabled";
};

		uart2: serial@21e8000 {
			compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
			reg = <0x021e8000 0x4000>;
			interrupts = <0 27 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&clks IMX6QDL_CLK_UART_IPG>,
				 <&clks IMX6QDL_CLK_UART_SERIAL>;
			clock-names = "ipg", "per";
			dmas = <&sdma 27 4 0>, <&sdma 28 4 0>;
			dma-names = "rx", "tx";
			status = "disabled";
		};

Not really sure how these things “hang together” but I’m sure you do… I see one thing which is interesting that " uart-has-rtscts;" but It does not have CTS hooked up to anything…

Greetings @leighjboyd,

You might have some of the pins misunderstood here. As @DaveM said the polarity and function for these pins are not quite intuitive.

In the device tree as you’ve seen uart2 has 2 important to note fields. These fields being fsl,dte-mode and uart-has-rtscts

As per the documentation: fsl-imx-uart.txt « serial « bindings « devicetree « Documentation - linux-toradex.git - Linux kernel for Apalis, Colibri and Verdin modules

The uart-has-rtscts field has the following effect:

In case you use “uart-has-rtscts” the signal that controls
the transceiver is actually CTS_B, not RTS_B.

If you double-check the pinctrl_uart2_dte node then you can see that the RTS pin (MX6QDL_PAD_SD4_DAT6__UART2_RTS_B) is actually SODIMM pin 32 not 34.

Also on further inspection of this node you’ll notice the RX and TX pins are also inverted from what you might think. For example the TX pin in the device tree ( MX6QDL_PAD_SD4_DAT4__UART2_TX_DATA) is actually SODIMM pin 38 not 36, which is what you’d see if you looked at a pure hardware datasheet that didn’t take into account the software configuration.

Also there is the effects of the fsl,dte-mode. This puts the entire interface in DTE mode, which as per our datasheet has the following effects:

In DTE mode, the UARTx_RX_DATA port is transmitting data from the SoC while the UARTx_TX_DATA port is receiving

This chart from NXP’s iMX6 reference manual may also help clear some stuff up between DTE and DCE modes:

Best Regards,
Jeremias

@jeremias.tx

Thanks for taking the baton! He got too close to the hardware for me.

Woah…

Okay clearly a lot I missed… A miracle I could get anything working at all without understanding any of this beforehand.

I am trying to convert the information you gave me into concrete actions on my side.

So, if I understand you correctly then

The uart-has-rtscts field has the following effect:
In case you use “uart-has-rtscts” the signal that controls
the transceiver is actually CTS_B, not RTS_B.

So, I should replace uart-has-rtscts with rts-gpios, (since I MUST pick one, as per your docs.) so that RTS controls the transceiver. Seems pretty straight forward. This might be the silver bullet to fix the problem, since I see with my scope that this signal (pins 2 and 3 on my 485 chip) is always high.

If you double-check the pinctrl_uart2_dte node then you can see that the RTS pin (MX6QDL_PAD_SD4_DAT6__UART2_RTS_B ) is actually SODIMM pin 32 not 34.

I looked up pinctrl_uart2_dte like you said… I Can see this:
image

what you’re saying is that RTS is actually pin 32, which seems to conflict with
image

So you’re basically saying we can’t always trust our schematics, and that we must learn to read the device tree files… And that’s find, I’m up for the challenge! (Can you teach me?)

So the line pinctrl-0 = <&pinctrl_uart2_dte> specifies that we are using the pinouts as defined in the above structure uart2dtegrp, which lists the 4 UART signals, but then it’s a dead end for me… If I search for UART2_RTS_B, one of the defined pins from this list, I don’t see SODIMM_32 or SODIMM_34. In fact, searching for SODIMM_32 or SODIMM_34 only returns a list inside &gpio2 {…}. How does one link the SODIMM_pin# to a certain pinctrl-0 assignment in uart2dtegrp?

Anyways, long-story-short, you are saying RTS and CTS may be swapped in our hook-up.

Also on further inspection of this node you’ll notice the RX and TX pins are also inverted from what you might think.

In our schematic we have assigned the UART pins Rx = 36, Tx = 38, but you say this is the reverse of “what I might think”. I didnt design our circuit board, but it seems to agree, and this seems to agree with the Colibri Eval Carrier board… why might I think otherwise? Is it saying something different somewhere else? Oh I see now, in the colibri datasheet:

So it seems we lucked out and wired up Rx and Tx correctly anyways, which would explain why I don’t get garbage when I hook up a terminal program with my RS484 adapter, I guess?

my conclusion: this is correctly hooked up according to our schematic.

Also there is the effects of the fsl,dte-mode. This puts the entire interface in DTE mode, which as per our datasheet has the following effects:
In DTE mode, the UARTx_RX_DATA port is transmitting data from the SoC while the UARTx_TX_DATA port is receiving

I don’t see how things could be “partly working” with this mode enabled, unless I am wrong about #3) above, and this swap also fixes the problem?

A colleague of mine looked at this and wondered if this was only applicable if we are using flow control, but I don’t think I am using flow control on the RS485 comms. Does it make sense? (Are we talking about the same flow control even?)

  1. I read about some other settings here UART (Linux) | Toradex Developer Center

which i seemed to miss entirely… I guess I should add these as well?

Do you think I should add/change these settings one at a time, or just go “all in” :slight_smile: ?

Im wondering also if I should back up a step and try to get things working on the Colibri Evaluation board first, and then proceed to make necessary changes from there…

Well here are my thoughts, given you said you have at least writes working that leads me to believe you have the TX & RX pins more or less correct. Since if you had these wrong or flipped then neither reads or writes should have worked for you.

That said I suggest to focus on the CTS/RTS pins as my educated guess would be that the problem lies here most likely.

So, I should replace uart-has-rtscts with rts-gpios, (since I MUST pick one, as per your docs.) so that RTS controls the transceiver. Seems pretty straight forward. This might be the silver bullet to fix the problem, since I see with my scope that this signal (pins 2 and 3 on my 485 chip) is always high.

This is what I thinking as well, you mentioned earlier that you never measured the RTS pin going low. If this uart-has-rtscts property really has the effect it said it does then this observation of yours would make sense.

How does one link the SODIMM_pin# to a certain pinctrl-0 assignment in uart2dtegrp?

So let’s start by looking at this pin group:

pinctrl_uart2_dte: uart2dtegrp {
		fsl,pins = <
			MX6QDL_PAD_SD4_DAT4__UART2_TX_DATA	0x1b0b1
			MX6QDL_PAD_SD4_DAT7__UART2_RX_DATA	0x1b0b1
			MX6QDL_PAD_SD4_DAT6__UART2_RTS_B	0x1b0b1
			MX6QDL_PAD_SD4_DAT5__UART2_CTS_B	0x1b0b1
		>;
	};

For simplicity let’s just look at one of the listed pins: MX6QDL_PAD_SD4_DAT6__UART2_RTS_B

The pin definition here is made up of three parts:

  • The SoC name, which is obviously this part MX6QDL_PAD
  • The ball name which is unique to this particular pin SD4_DAT6
  • And finally the pin function or what this pin is being configured for. In this as you can guess it’s being configured for RTS on UART2 UART2_RTS_B

Now to connect this to a SODIMM pin you want to look at the ball name as it should be unique. So the ball name here is SD4_DAT6, if you look in the Colibri i.MX6 datasheet this corresponds to SODIMM 32 as I said earlier.

Anyways, long-story-short, you are saying RTS and CTS may be swapped in our hook-up.

I have a suspicion though now that I think about it. In the device tree SODIMM 34 is configured as CTS, but this uart-has-rtscts property makes it so that CTS controls the transceiver not RTS. So I guess functionally SODIMM 34 is “RTS” in this situation(?) So maybe you don’t have it actually swapped but I guess it wouldn’t hurt to check the other way.

So it seems we lucked out and wired up Rx and Tx correctly anyways, which would explain why I don’t get garbage when I hook up a terminal program with my RS484 adapter, I guess?

I believe so as well. Like I said the fact you got at least writes working makes me believe you have TX and RX correct.

which i seemed to miss entirely… I guess I should add these as well?

In the article you’re referencing you can either enable RS485 via userspace IOTCL or via device tree. In your code it looks like you’re doing the userpspace IOTCL approach so the device tree changes highlighted are optional.

In conclusion I suspect the issue is with your RTS/CTS though I’m not entirely sure. By the way there is also a simple RS485 program example in the Linux kernel documentation that may be simpler to debug: serial-rs485.txt « serial « Documentation - linux-toradex.git - Linux kernel for Apalis, Colibri and Verdin modules

Though make sure you set the right flags as we suggest here: UART (Linux) | Toradex Developer Center

Best Regards,
Jeremias

In this thread there is a comment about adding an inverter to the carrier board. It could be that I don’t have that inverter on my board, which would also muddy things up a bit. Is there an inverter now on the colibri eval board?

High active RTS in RS485 mode - Technical Support - Toradex Community

So, it seems I am root, (needed to be for CAN bus), and since I could see signals comming from my hardware to the terminal program on my PC I assume that I successfully installed the UART B device in my container, would you agree?

No.

You’ve got an awful lot going on we are not aware of. This is the first I’ve heard of you actually being inside of a container. Is this inside of a Docker container? If so the container needs access.

Seeing output on a serial port doesn’t mean you have anything configured correctly. Lots of stuff can be routed to that port depending on device tree and hardware configuration.

You need to strip this down the the barest of next to nothing.

@jeremias.tx gave you a very good link.

Get that to build and deploy to your target, not in a container, just as an executable you can run after SSH-ing into your target. The comments explain a lot in that link.

You’ve been trying an awful lot of stuff, but not proceeding in a logical manner. Start with that minimal program running at the host OS level, not inside of a container.

RS-232 != RS-485

How are you connected to your “terminal?” Most NULL-Modem cables and adapters wire CTS to RTS. Bad, bad, bad for RS-485. Really read the comments in that short program. RTS, CTS, and DTR need to be connected properly, not with a jumper hack to make an ordinary terminal work. This is not a situation where you can make a 3-wire cable function.

RS-485 is industrial communications.

If you wish to communicate with a “terminal” you need to make a 9-pin cable where TD/RD are swapped but all other pins are wired straight through. Your “terminal” needs to actually honor the other pins. Do not disable flow control.

Please don’t try to “just fix what you’ve got.” Start over with this new simple program and a properly constructed cable. Verify your “terminal” actually supports the hardware. Many/most software terminals do not or they do a very poor job of it.

The situation you are in is why you see people paying $200 or more for a vintage green screen dumb terminal like this one. They honored the hardware control and newer versions like that one tended to have a great little setup utility built in.

Serial comm is far too often faked in the modern world.

Hey @leighjboyd A few tidbits from someone who has successfully done rs485 many times:

  • I used the colibri eval board first to test things out. There is an inverter on RTS on that board.

  • Get that going first by probing tx/rx/rts and making sure you see the rts pulse during tx. Note that polarity and see if you can flip it using SER_RS485_RTS_ON_SEND vs SER_RS485_RTS_AFTER_SEND. If you don’t see anything toggling, look at the driver code. Not sure of the capabilities of the serial driver in the distro you’re using (I replaced the serial driver in mine to get all capabilities required by our application and carrier boards, namely delays and full polarity control).

  • Once you figure out what polarity you need, you should set that in your device tree so you’re not driving the transceiver until you take control of it.

  • Your schematic is set up for fsl,dte-mode regarding tx/rx/rts, but the lack of an inverter on rts means you’ll need to change code between the eval board and you’re own board. If that ON/AFTER code test works above, you should be ok there.

  • Some serial drivers have different behavior for uart-has-rtscts vs a more “brute force” rts-gpios. Some support it, some don’t. This will be confusing but:

    • some serial drivers will utilize the hardware’s logical rts/cts behavior when using “has-rtscts”. You won’t have control of the polarity this way. Again, not sure about your distros serial driver capability here.
    • when using rts-gpios, you can typically control the polarity yourself using that ON/AFTER bit in the flags. Note you’ll also have to change the device tree to configure the pin as a GPIO vs configuring it as an RTS pin.
  • You’re going to need to familiarize yourself with the serial driver in your distro. Not usually any way around that part if you want to know exactly what to do. pr_debug is your friend.

  • And like @seasoned_geek said, minimize the heck out of stuff and move forward in very small steps.

Moving forwards in very small steps was probably the best advice anyone could ever give me. I often tend to change to much at a time.

I got things working now by a lot of trial and error and just taking things slow. Reading the datasheets, reading about the RS485-USB adaptor and changing no more than one thing at a time.

A colleague said to me: if you got it working once, why are you looking at device tree? its probably hardware…

In the end, here is what I did:
changed device tree to a version which has rts-gpios and not much else.
I can set rx-during-tx in the applicaton, enabled-at-boot-time I could also live without. The only thing I thought I might play with was rts-active-low but first I would test it out having only changed that one line.

Anyways it didn’t work, so I put device tree like I had it last week, but its difficult to say why, since a lot of the reasoning in this blog seemed sound enough. I was also making some other mistakes, of course as we will see… and I am half-wondering what would happen if I put back rts-gpios…

Now somewhere back when I was 20 years old I learned that:
rs485conf.flags |= SER_RS485_RTS_AFTER_SEND; sets a bit and
rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND); clears the bit.
but somewhere in my haste I decided that:
//rs485conf.flags |= SER_RS485_RTS_AFTER_SEND; was also clearing the bit…

I know… this line of code is just a commented line, so it doesn’t “do” anything! I must have thought, “if I don’t set a bit I must be clearing the bit” but I guess that’s what happens when we rush…

From the moment I realized that mistake, things started to improve. I grabbed the whiteboard and made a very big truth table to record exactly what settings I have tried, and commited a separate version in gitHub so I could go back and see it all step by step, making ONE CHANGE AT A TIME. It basically took all day. But here were some highlights:

I set the “echo mode on” on my adapter so I could see that what I was typing was getting through (had the ground pin fall out and for a while there nothing was actually being sent at all, rendering half my truth table invalid!)

Finally, I needed to open up the RS485 adapter and set up the terminating resistor, as recommended. I was using the ES-U-3001-MB and the terminating resistor is enabled by connecting pins 1-2 on JP1. The datasheet is a bit confusing since they try to handle several products in the same datasheet. The correct sheet for me was in chapter 5.5.

I figured I probably didn’t need that terminating resistor since my entire circuit is only 15 cm long, but it actually made a big difference. Strangely enough, now when I try to remove that resistor, my software actually crashes!

I also see that TIOCGRS485 is not the same as TIOCSRS485. The first one is supposedly for the kernel, and the second for user-space. I commented out the lines which were referring to TIOCGRS485 and my code works fine still.

So now I can send and receive, and the RTS/DE line to my tranciever is going high only when transmitting, as it should.

here is a code snippet

int InitRS485(){

// adapted from https://gist.github.com/amarburg/07564916d8d32e20e6ae375c1c83a995

int err             = 0;
unsigned int i      = 0;
unsigned int j      = 0;
char readString[80] = {0};
int enable          = 1;

// int enable = atoi( argv[2] );
int flags = O_RDWR | O_NOCTTY | O_NONBLOCK;

// int modbusFileDescriptor moved to global variable.

#if DISABLE_MODBUS == 1
#else

printf("\r\n"); // to make a blank line before 
printf("UART INITIALIZATION...\n");

modbusFileDescriptor = open("/dev/colibri-uartb", flags);
if (modbusFileDescriptor < 0) {
    /* Error handling. See errno. */
        fprintf( stderr, "Error opening port /dev/colibri-uartb (%d): %s\n", errno, strerror( errno ));
        //exit(-1);
        err |= ERROR_MODBUS;
}

printf("-RS485 Initialization...\n");

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

printf("Currently RS485 mode is %s\n", (rs485conf.flags & SER_RS485_ENABLED) ? "set" : "NOT set");

if( enable ) {

     // https://developer.toradex.com/linux-bsp/how-to/peripheral-access/uart-linux/#colibri-family
     // On Toradex carrier boards the following flags should be used:

        printf("RS485 mode will be SET\n");
        rs485conf.flags |= SER_RS485_ENABLED;

        rs485conf.flags |= SER_RS485_RTS_ON_SEND;          // RTS is equal to 1 when sending: LB Works for me to send and recieve Nov 21/2022
        //rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND);     // RTS is equal to 0 when sending  DON'T use this line, caused RS485 to stop working!!!
         
        //rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;     // Set logical level for RTS pin equal to 1 after sending: 
        rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);    // Set logical level for RTS pin equal to 0 after sending: LB Works for me to send and recieve Nov 21/2022

        rs485conf.flags |= SER_RS485_RX_DURING_TX;         // Enable receiver during sending, required for i.MX devices.  Set this flag if you want to receive data even whilst sending data 
        //rs485conf.flags &= ~SER_RS485_RX_DURING_TX;      // Disable receiver during sending, 
    
    // rs485conf.delay_rts_before_send = ...;          /* Set rts delay before and after send, if needed: 
        // rs485conf.delay_rts_after_send = ...;
    
} else {
        printf("RS485 mode will be UNSET\n");
        rs485conf.flags &= ~SER_RS485_ENABLED;
}

// Enable the RS-485 using ioctl TIOCSRS485 from userspace is described in RS-485 Kernel Documentation.
if (ioctl (modbusFileDescriptor, TIOCSRS485, &rs485conf) < 0) {
        fprintf( stderr, "Error writing ioctl port (%d): %s\n",  errno, strerror( errno ));
        err |= ERROR_MODBUS;
}


// The following is for Kernel-space - I do not use this one.
//     if (ioctl (modbusFileDescriptor, TIOCGRS485, &rs485conf) < 0) {
//             fprintf( stderr, "Error reading ioctl port (%d): %s\n",  errno, strerror( errno ));
//             err |= ERROR_MODBUS;
//     }

printf("Confirm RS485 mode is %s\n", (rs485conf.flags & SER_RS485_ENABLED) ? "set" : "NOT set");


// SET UP THE RS485 PARAMETERS USING TERMIOS.H
    // see: https://blog.mbedded.ninja/programming/operating-systems/linux/linux-serial-ports-using-c-cpp/#basic-setup-in-c

    // see also: https://stackoverflow.com/questions/31999358/how-are-flags-represented-in-the-termios-library

printf("-RS485 setup...\n");
struct termios tty;

//  for use with termios tty - get attributes from modbusFileDescriptor, place them in the tty structure;
if(tcgetattr(modbusFileDescriptor, &tty) != 0) {
     printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
     err |= ERROR_MODBUS;
}

// modify the settings 
tty.c_cflag &= ~PARENB;     // Clear parity bit, disabling parity (most common - Works)
tty.c_cflag &= ~CSTOPB;     // Clear stop field, only one stop bit used in communication (most common - Works)
tty.c_cflag &= ~CSIZE;      // clear the bits used for number of bits before setting them in the next step (Works)
tty.c_cflag |= CS8;         // now set the number of bits.  8 bits per byte (most common - Works)
tty.c_cflag &= ~CRTSCTS;    // Disable RTS/CTS hardware flow control (most common - Works)
//tty.c_cflag |= CRTSCTS;   // Enable RTS/CTS hardware flow control (comment this out!!)
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) (Works)

tty.c_lflag &= ~ICANON;     // In canonical mode, input is processed when a new line character is received... and so we normally want to disable canonical mode.
tty.c_lflag &= ~ECHO;       // Disable echo
tty.c_lflag &= ~ECHOE;      // Disable erasure
tty.c_lflag &= ~ECHONL;     // Disable new-line echo
tty.c_lflag &= ~ISIG;       // When the ISIG bit is set, INTR, QUIT and SUSP characters are interpreted. We don’t want this with a serial port, so clear this bit:

// input flags
tty.c_iflag &= ~(IXON | IXOFF | IXANY); //  Turn off s/w flow ctrl (Clearing IXOFF, IXON and IXANY disables software flow control, which we don’t want)

// output flags
tty.c_oflag &= ~OPOST;      // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR;      // Prevent conversion of newline to carriage return/line feed
// tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX)
// tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX)
tty.c_cc[VTIME] = 10;       // Wait for up to 0.1s (1 deciseconds), returning as soon as any data is received.
tty.c_cc[VMIN] = 0;
// If you want to remain UNIX compliant, the baud rate must be chosen from one of the following:
// B0,  B50,  B75,  B110,  B134,  B150,  B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800
cfsetispeed(&tty,  B9600);  // Set in/out baud rate to be 115200 - same as DSP uses for simplicity
cfsetospeed(&tty,  B9600);

// Now we're done, write terminal settings to the file descriptor.
if (tcsetattr(modbusFileDescriptor, TCSANOW, &tty) != 0) {
    printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
    err |= ERROR_MODBUS;
}

    // convert baudrate to human-readable form
    int baudrate[16393]  = {0};
    baudrate[B2400]   = 2400;
    baudrate[B4800]   = 4800;
    baudrate[B9600]   = 9600;
    baudrate[B19200]  = 19200;
    baudrate[B38400]  = 38400;
    baudrate[B57600]  = 57600;
    baudrate[B115200] = 115200;
   

    // Display settings
    printf("Serial Commication Settings:\n");
    printf("Baud Rate : %d\n", baudrate[tty.c_ospeed]);
    if ((tty.c_cflag & PARENB ) == PARENB) {
            printf("Parity    : ON\n" );
    } else {
            printf("Parity    : OFF\n" );   
    }

    if ((tty.c_cflag & CSTOPB) == 0){
            printf("Stop bits : %d\n", 1 ); 
    } else {
            printf("Stop bits : %d\n", 2 );
    }

    if ((tty.c_cflag & CSIZE ) ==  CS8 ){
            printf("Data bits : %d\n", 8  );
    } else if ((tty.c_cflag & CSIZE ) ==  CS7 ){
            printf("Data bits : %d\n", 7  );
    }

 #endif

 // for testing
 for (j = 0; j< 1000; j++){
      for (i  = 0; i <127 ; i++){
              usleep(500000);
              WriteModbus(i);
              ReadModbus(readString);
              printf("read: %s \n",readString);
              
      }
 }

 return (err);
}


// maybe these functions already exist in libmodbus.h?

int WriteModbus(int i){

    //static int i        = 0;
    char buffer[80]     = {0};

   // i ++;
   // if (i>128) i = 0;
    
    //snprintf( buffer,79, "%d ", i );

     snprintf( buffer,79, "%d\r\n", i );
    

    if (strlen(buffer) != 0){
            write( modbusFileDescriptor, buffer, strlen(buffer));
    } else {
            write( modbusFileDescriptor, buffer, 1);     
    }

    printf("write: %s\n",buffer);
                        
    //usleep(100000);           
   
    return 0;

}

int ReadModbus(char buf_in[]){

char bufferInput[80] = {0};
int i = 0;

 for (i = 0; i <10; i++){
   

    // todo: keep recieving until timeout
memset(bufferInput,0,80);
read( modbusFileDescriptor, bufferInput, 10);


if (strcmp(bufferInput,"\0") == MATCH){    // there was no data in the buffer!
		//do nothing, keep modbusState as RECEIVE_FRAME, since we are a slave...

} else {									// there was data to recieve

    printf("%s\n",bufferInput);
    fflush(stdout);
}
 }

I thank you for all your support, I guess I have 3 more questions:

  1. is it normal that my program would actually crash if the terminating resistor is removed? (and I don’t mean “hot” but actually shut off by removing usb cable, remove the resistor, and turn it back on) That seems a bit flakey to me.
  2. my oscilloscope B+ waveform is typically at 0 V when there is no signal, then it oscillates between 4 and 1 V while transmitting or receiving. Is it possible that I need more pull-ups/pull-downs to correct this?
  3. when would one need the delay-rts-before-send / after-send values? Is it okay if I leave these as zero?

Nope, not normal, but that terminating resistor could be masking a different problem. I don’t see any line polarization on the differential side of your transceiver. Some need it and some don’t. With nothing connected, you’ll want to make sure those A/B- lines are driven correctly if the transceiver doesn’t take of that problem for you.

Those are differential signals on that side of the transceiver, so when driven you should see both toggling around. When not driven, you should see ground on B and a “high” on A. If neither side has polarization on those lines, it could cause you issues (like constant TX/RX from either side.)

Totally depends on your application. The biggie is usually the after_send. If that RTS line turns around too quickly, the other side might not see the complete last byte of your message (cuz the transmitter gets shut down). Sometimes you can just stuff an extra byte on your message to keep the line active (as long as both sides know how to deal with that.) I also saw the word modbus somewhere up there. There are some timing requirements for modbus rtu that need to get satisfied if you want to play by the book.

Btw, I noticed the suffix “ISO” on your schematic. I guess the optoisolator is in a different spot on your schematic?

Good luck!