Products > Embedded Computing

Half-duplex RS485 on RPi with pymodbus, minimalmodbus etc


John B:
I'm trying to set up an RS485 half duplex bus with one RPi master/ MODBUS client and multiple arduino slaves /servers.

I can get the RPi to communicate with a single arduino using the modbus protocol, set up in a simple point to point serial config (no RS485 transceiver). On the RPi side, I have used both the minimalmodbus  and the pymodbus library, while the arduino side uses the official ArduinoModbus and ArduinoRS485 libraries. The arduino libraries have inbuilt functions for toggling the DE/RE pins on an RS485 transceiver chip when the arduino is sending a serial message, but I'm drawing a blank on the RPi side.

Both minimalmodbus and pymodbus have functions for reading/writing modbus coils/inputs/registers which combine a message transmission and receipt, so I can't even bodge in a manual gpio command in python to toggle a pin before and after the master transmits a serial message. I understand this wouldn't be an ideal way to do it even if worked at a low enough baud rate.

I have enabled UART4 to use the RPi's proper UART interface which gives me access to an RTS and CTS pin on the gpio header, but so far I havent made any progress in getting them to do anything useful.

Basically I just need a pin to change state while the RPi transmits a message. I have even seen a reference to hardware solutions, like a 555 timer monitoring the TX line, but sounds like a dodgy way of doing things.

John B:
I just did a few quick tests and at 19200 baud, the arduino responds to a modbus request in about 300us. Currently its a basic test program, that time may increase once I add the rest of the program including an i2c IO expander, but it's certainly faster than I expected. Maybe the hardware TX enable isn't as crazy as I first thought.

The cleanest solution would be for pymodbus to support dependency injection so that you create a  RS485 connector which creates the connection and toggles the direction flag during write() calls.  It looks like pymodbus explicitly constructs a pyserial object so you can't do that as easily but pyserial does support rs485 DEN control, maybe you can just create the modbusclient object and then poke the appropriate flags inside of it's serial port member.

John B:
For anyone in a similar problem... I did end up making good progress on this an ultimately solved it with hardware.

Bascially, the RPi software implementation of transmit enable is almost useless. If you have the RTS/CTS pins enabled on your UART device, pyserial can open the port in an RS485 mode which uses the RTS pin as a transmit enable pin (configurable high or low on transmit).

Unfortunately it is very slow. I have to double check exact numbers, but while the transmit pin enables around 30us or something before the first start bit, the transmit enable pin stays high for around 30 milliseconds after the last stop bit. I'm using the MODBUS protocol on the bus and the server devices are responding in about 130us, so the RPi completely blocks any response message. I did read a blog post of a programmer who was trying to solve the same issue and reduced the turn off delay to around 2-3ms with his own software implementation, but that's still 20-30x too slow.

I ended up using a 555 in a monostable configuration, with a mosfet on the timing cap to reset the charging cycle everytime there is a falling edge on the TX line, giving a maximum turn off time of ~90us after the last rising edge on the TX line. This is designed around a baud rate of 115200 with 10 bits per frame. It ensures that a message can be arbitrarily long, and even if the message data bits are all 1, the transmit enable signal will stay high for the entirety of the message.

The last problem is the propagation delay between the TX line start bit going low, and the output of the 555 going high. The solution is a little crude, but I ended up using a string of LM393 comparators to introduce a delay of around 1us (4x ~240ns) to the TX signal being applied to the MAX485 transceiver. The comparators have a symmetrical delay on rising and falling edges so the signal keeps all the transition timings and just delays the signal. From the MAX485 datasheet, if I'm reading it correctly, it only needs around 70ns between DE/RE going high and data being applied to the DI pin.

This has all worked fine so far at 115200 baud on crappy unknown sourced 555 timers. The final design will incorporate an LMC555 and an optoisolated MAX485 transceiver, and the transmit pin should still be enabled at least 900ns before the serial message.

By the way, the internet is full of really dodgy designs for auto direction control. Most seem to only enable the RE/DE pin when the TX line is low, leaving the bus only passively pulled up on high states, plus there's a delay between the data being applied to the transceiver and the RE/DE pin going high, which then affects the timing of the signal on the receiving device  :scared:


[0] Message Index

There was an error while thanking
Go to full version