2018-01-15

RS-485 circuitry and software control

My RPi-as-HMI communicates with the MODBUS Slave upon the RS-485 line. It's a well-known fact that there is a strong necessity to use isolated solutions in the industry, medical devices and for a noisy environment (Intersil note on the issue is just an example). So I have to follow the trend.
I want to note the hardware and software sides of the Raspberry-RS-485 topic.
Below I note, step-by-step, my research. Most of it happened to be in vain. The result is simple. But I want to keep all branches, just for myself.

Hardware 

Here is an extended illustration of the RPi-as-HMI structure:



Let's take a look at the signals.
We need, at least, RxD and TxD UART signals from Broadcom SoC (Raspberry Pi side of the driver). It's a heart of a serial line.
Since RS-485 is a half-duplex system, the output circuitry may actively drive the line only during the transmission. When the driver listens to the line, it must disable the transmitter, usually switching it to the hi-impedance state. So, the industry-standard RS-485 driver has a special control input TxE (it may be called DE in some docs) - Tx Enable.
The symmetrical control signal for the receiver - RxE. Its existence is not a mere bow to the aesthetic side of the question (well, some sort of symmetry). The reason is completely technical. If the receiver is always enabled, the Raspberry UART will receive not only the signals from the outer world (responses) but its own bytes, which are transmitted as requests. Is it good or harmful? It depends. But mostly disabling of the receiver is considered as appropriate.
The active level of RxE signal is LOW (in all chips I've met), so the simple connecting of TxE and RxE allows to control the transmitter and the receiver with just the single line. When it's HIGH, the driver transmits, when it's LOW, the driver receives.
All Raspberry signals are isolated, as well as the power line (VCC & GND). There are various technologies and circuitry to perform the task. Here is a figure that helps with most of the cases:


The basic elements are signal decouplers, RS-485 driver and DC-DC converter.
The RTS signal is effectively the TxE, with the inverted RxE connected to it. The name I use, RTS is of RS-323 terminology, since exactly this signal can be used to enable the RS-485 transmitter. For example, if you use the complete UART or some sort of FTDI chip to obtain UART from USB - there is such a signal.
So we need a 3-channel signal isolator. The usual optocoupled chips (optocouplers) may be used, some time aggregated in a single case. I placed a hysteresis sign on the output side of it - to stress that as a rule, we need a relatively fast optocoupler. Such a circuits contain a Schmitt trigger as the output stage, having the hysteresis in the transfer function.
Several digital signals isolators in one chip are called digital isolators and may use various technologies inside them (capacitance, inductive, giant magnetoresistance and so on). I mention here the relatively cheap ADuM1301, or ISO7231, or costly Si8631.

Granted our 3 signals are isolated, we can use a standard RS-485 driver. The idea to combine the isolators and the driver lies on the surface - ADM2483 and MAX14852 are typical examples of isolated RS-485 drivers.

To power this system on the RS-485 side of it, we can use external source or DC-DC converter (power isolator). So, the most integrated solution is the isolated driver with DC-DC converter included. Such chips are ADM2582 and more costly LTN2881 or SL32704E are the few examples of self-powered isolated RS-485 drivers.

What solution to choose? Let's look at the software part of our task first.

Software

RPi has 2 UARTS, one of them is a full-featured ARM PL011 and the other is so-called miniUART. There are several considerations against the use of miniUART (one of them is no-parity, that is against the strict following to the MODBUS protocol), so I decided to restore RPi 2 configuration and to use PL011. I'll return to the procedure in the next part.
Well, now the /dev/serial0 is our port for the MODBUS. It's signal on the Raspi's 40-pin header J8 are:
  • RxD is BCM15 according to this system or GPIO15 as here
  • TxD is BCM14
  • RTS is BCM17
So, to use all of these 3 signals (as I do on the FTDI USB/UART converters), I have to enable alternate function 3 for the BCM17 pin. Simply? But!.. Not so easy... There are 2 features, each of which kills the idea completely:
  1. The pin 11 of the J8 header is being used by my LCD monitor (as an interrupt signal). So RTS pin is not free.
  2. The Linux treats RTS in a specific way, as jojopi has explained. And I've checked it, yes, the short 10us RTS pulses arrive only after the 8-byte package is sent (the last time chart in the screenshot below). It is not the RTS we expect!

I could spend the rest of my life to explain the difference between FTDI and Linux RTS output. But I humbly accept the fact. I have to use two and only two UART signal, RxD and TxD, to control RS-485 driver.
Sure, I can try to use any GPIO as the "right" RTS. But my project is a Python one. For the MODBUS I use the modbus_tk library. And if I need to control the GPIO line before/after a request transmit, it involves the Python libraries patching - in RtuMaster class of modbus_rtu module. Not fine at all, IMHO. Let's try some other solution.

Note on SPACE: Trying the passive SPACE auto direction idea (look below), I did patch the library. There was a problem with the Raspi listening to its own output, so the UART input buffer was full of garbage. To begin the response receiving from the clean buffer, I inserted
self._serial.flush()
self._serial.reset_input_buffer()
at the very begin of the RtuMaster._recv(). It helped - Raspi ignored the request (its own output), but the response coming in 20ms was truncated. Raspi just cannot clean the buffer in such a short time.
Do I need to say about it? Sure. It's a good solution if you can clean the buffer (library patch or another library) and you can insert some delay before the Slave response.  

Hardware again

I need the RS-485 driver that uses only RxD and TxD signals. Such a circuitry is called Automatic Direction Control. 
Look at the time charts. There is a byte to be transmitted (TxD) and the minimal RTS pulse to enable the transmitter. The 3-rd chart (called "Classic, with RTS") shows the RS-485 state in the case of RTS is used. As far as the RTS is active, the RS-485 differential signal (A-B) follows TxD signal - the LOW state of TxD causes the MARK state of (A-B). Outside the active RTS, the (A-B) is in IDLE state. As a rule, the biasing resistors on the line drive this state to A-B⩾=+200mV. Since such a voltage belongs to the SPACE region, this line state may be called passive or weak SPACE
  
The conception of the passive SPACE is used in some drivers, called Auto Direction. The TxD line controls the direction (in the opposite to RTS polarity). The last time chart shows the state of the RS-485 line. When TxD is passive (HIGH), the driver is disabled and the line is IDLE. The LOW state of TxD causes active MARK, the HIGH state of TxD returns line to IDLE. As long as the passive state of the RS-485 line is treated as SPACE by all receivers on the line, the technology is good.
MAX13487 is a popular driver using this technic.
I checked such an approach and applied the MODBUS library patch I've noted about earlier (Note on SPACE). If the delay in the Slave response is 40-50ms, everything is fine. But I'd rather search the solution without the library patching and artificial delays in communication.

Well, there exists other implementations of the Auto Direction, based on the RTS autogeneration. For example, TI shows a sample design with the NE555 monostable generator. The idea is to start a pulse with the start bit on TxD and hold the pulse till the byte is transmitted. To my mind, the idea implementation is rather foolish - they try to generate the RTS pulse exactly for the single byte transmission (slightly wider). It may be needed in the very general case, but I have narrower situation.
I do know that Raspi transmits a package of bytes and there is a response lag after the request is completely transmitted. So I can generate RTS with the beginning of the 1-st TxD and keep the active RTS until some time after the last LOW state of TxD, plus the maximal byte transmission duration.
The RTS continues for T1+T2 after the last moment TxD was in the LOW state. T1 is the byte transmit duration without the start bit (e.g. 260us @ 38400), T2 is some safe pause. The stretching of RTS beyond the last LOW state of TxD must be not less than byte duration. On the other side, RTS must become passive when the Slave begins its response. These are not very strong restrictions.
For example, if the RTS generator holds T1+T2=3 ms, we may work with the baud rate from 4800 baud and up. And even at the high baud rates, when T1 is negligibly small, the pause T2 before the Slave's answer may be 3 ms, which is practically not a restraint.
I checked the idea with the SN74HC00 simple monostable generator, but I'm going to use the cheapest microcontroller in my final design. Attiny2313 costs ab. $0.57 in my region. And the program will be just a few lines. The input is TxD, the output is RTS and I think the LED indicating transmission may be driven with one more port.

Result

There are several ways to create isolated MODBUS channel between Raspberry Pi3 as a Master and external device as a Slave. I've tried some of them and I liked the standard RS-485 driver, 2 optocouplers, and DC-DC converter most of all. We take just RxD and TxD, standard MODBUS library, no restrictions on the Slave response time - and we have galvanically isolated serial channel. Life is good!
Maybe I'll have the inspiration to draw the scheme some time. For the time being, I attach the photo shot of my prototype


UPD. Added







No comments:

Post a Comment