From faa8b82236e19eec9f2603f8f696c4a206b8ee45 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 18 Jun 2019 18:35:39 +0200 Subject: [PATCH 1/4] enabling rs485 not longer required --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index d8a3eb0..31a877c 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,7 @@ Remove mentions of `serial0` from `/boot/cmdline`. ## Enable rs485 mode + Use the submodule rpirtscts to enable to alternate functions of the related pins at the RPi MCU. It is submoduled here, can be found directly at https://github.com/mholling/rpirtscts @@ -23,6 +24,7 @@ This needs to be done at every boot. Kudos to danjperron, cmp. https://www.raspberrypi.org/forums/viewtopic.php?f=98&t=224533&hilit=rs+485#p1383709 (Note, please: This software is under the GPL 3.0 license. However, I do not derive from this software, I use it in an unchanged way. It is not integrated into my sources, it just needs to be called once the RPi has booted.) + ## Pinout From fcefd538d8f32f8c847e3ad3905d385c535474d5 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 18 Jun 2019 19:05:27 +0200 Subject: [PATCH 2/4] last notes --- readme.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/readme.md b/readme.md index 31a877c..5f808bd 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,8 @@ # modbusmaster + +![The whole setup](./docs/2019-06-14_22-06-52-1.jpg) + ## Disable Bluetooth on RPi3 Add at the end of `/boot/config`: @@ -26,6 +29,9 @@ Kudos to danjperron, cmp. https://www.raspberrypi.org/forums/viewtopic.php?f=98& (Note, please: This software is under the GPL 3.0 license. However, I do not derive from this software, I use it in an unchanged way. It is not integrated into my sources, it just needs to be called once the RPi has booted.) +The approach to enable the transmitter has been changed due to timing issues with disabling the transmitter after the TX phase when talking to Modbus devices (see below), so this step is not longer required. + + ## Pinout Find a good pinout diagram at https://raw.githubusercontent.com/ppelleti/hs-wiringPi/master/pin-diagram.png. @@ -40,6 +46,10 @@ TX is at GPIO14, RX is at GPIO15 and RTS (control line for transmitter enable) i ## Python snippet to test +### Pure RS485 test + +#### Test 1: Transmit only + (See also `./snippets/test1.py`.) import serial.rs485 @@ -54,4 +64,115 @@ Find an signal screenshot here: Channel 1 in yellow has the TX line (GPIO14), channel 3 in purple has the RTS (GPIO17, transmitter enable line). +#### Test 2: Transmit and receive + +(See also `./snippets/test2.py`.) + +Here in contrast to the last script first an octet is received and then echoed. + + import serial.rs485 + ser=serial.rs485.RS485(port='/dev/ttyAMA0',baudrate=2400) + ser.rs485_mode = serial.rs485.RS485Settings(False,True) + ser.write('a test'.encode('utf-8')) + + while True: + c = ser.read(1) + ser.write(c) + print(c, end='') + +The signal graph is here: + +![Test2 Signals](./docs/osci2.png) + +Channel 1 in yellow has the TX line (GPIO14), channel 2 in blue has the RX line and channel 3 in purple has the RTS (GPIO17, transmitter enable line). + +It is obvious that the DE (transmitter enable line, RTS controlled by `pyserial` in RS485 mode) is hold active a good while after all data already have been transmitted. + + +#### Test 3: First Modbus communication + +(See also `./snippets/test3.py`.) + + from pymodbus.client.sync import ModbusSerialClient + import serial.rs485 + + ser=serial.rs485.RS485(port='/dev/ttyAMA0',baudrate=1200) + ser.rs485_mode = serial.rs485.RS485Settings(rts_level_for_tx=False, + rts_level_for_rx=True, + delay_before_tx=0.005, + delay_before_rx=-0.0) + + client = ModbusSerialClient(method='rtu') + client.socket = ser + client.connect() + result = client.read_holding_registers(address=0x2000, count=2, unit=1) + print(result) + print(result.registers) + client.close() + +Here, the port is initialized in RS485 mode and a device is queried. + +Sometimes this works: + +![Modbus comm ok](./docs/modbus-ok.png) + +But sometimes is does not work: + +![Modbus comm not ok](./docs/modbus-not-ok.png) + +The long hold time of the DE (transmitter enable, RTS line) becomes a problem, the response of the device already starts when the transmitter of the master is still enabled and thus the receiver of the master is still disabled. + +A couple of experiments with deriving from the `RS485` class of `pyserial` and moving the time critical code (disabling the transmitter after the transmit) into C code failed. It wasn't faster at all. It became obvious that the system call `tcdrain`, which waits for all octets in the buffer to be transmitted returns very late. + +Finally, the solution was to get away from the RS485 mode in `pyserial` and instead use the line status register of the UART via a system call to see whether the transmit register is empty and switch the DE line of the transmitter not longer with the RTS functionality but directly using `wiringPi`. + +Derived class from `RS485` in `pyserial` (maybe this can be switched to the regular `Serial` class ...) + + class RS485Ext(serial.rs485.RS485): + def __init__(self, *args, **kwargs): + super(RS485Ext, self).__init__(*args, **kwargs) + self.writec = ctypes.cdll.LoadLibrary('writec.so') + r = self.writec.init() + + def write(self, b): + d = serial.serialutil.to_bytes(b) + r = self.writec.writec(self.fileno(), d, len(d)) + return r + + +C code of the function `writec` in the library `writec`: + + ssize_t writec(int fd, char *buf, size_t count) { + digitalWrite(DE_PIN, HIGH); + ssize_t r = write(fd, buf, count); + uint8_t lsr; + do { + int r = ioctl(fd, TIOCSERGETLSR, &lsr); + } while (!(lsr & TIOCSER_TEMT)); + digitalWrite(DE_PIN, LOW); + + return r; + } + +This change brought the break through: + +![Delta between TX end and DE disable](./docs/rs485_should_be_ok_now.JPG) + +The delta between TX end and DE disable is now only 2.2ms (instead of nearly 20ms before). + +And finally Modbus communication works a lot more reliable: + +![Modbus comm nearly really ok](./docs/modbus-really-ok.png) + +However, still a lot of errors in the Modbus communication, certainly because of the dirty RX signal (blue). Furthermore it appears that the communication fails completely as soon as the termination resistor of 120 Ohms was placed. + +A hint from the application AN-960 from Analog Devices (https://www.analog.com/media/en/technical-documentation/application-notes/AN-960.pdf) helped here. In chapter "Fail-Safe Biasing" it is explained that, when no transmitter is active at all the lines are completely floating and thus the RX signal behind the transceiver becomes dirty, as in the image above. + +Pulling the A line to `Vcc` and the B line to `Gnd` using 1.5kOhm resistors, as proposed, solved this problem. And now also the termination resistor worked as expected. + +![Now it really works](./docs/nice-signals-with-fail-safe-resistors.png) + + + + From d19bc80783a68de9fd4148dfeb6c0f99efdd7319 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 18 Jun 2019 19:06:59 +0200 Subject: [PATCH 3/4] fix --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5f808bd..57bdc87 100644 --- a/readme.md +++ b/readme.md @@ -120,7 +120,7 @@ But sometimes is does not work: ![Modbus comm not ok](./docs/modbus-not-ok.png) -The long hold time of the DE (transmitter enable, RTS line) becomes a problem, the response of the device already starts when the transmitter of the master is still enabled and thus the receiver of the master is still disabled. +The long hold time of about 18ms of the DE (transmitter enable, RTS line) becomes a problem, the response of the device already starts when the transmitter of the master is still enabled and thus the receiver of the master is still disabled. A couple of experiments with deriving from the `RS485` class of `pyserial` and moving the time critical code (disabling the transmitter after the transmit) into C code failed. It wasn't faster at all. It became obvious that the system call `tcdrain`, which waits for all octets in the buffer to be transmitted returns very late. From eee1db510cb33a193174efec72e262a52c4caa92 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 18 Jun 2019 19:08:35 +0200 Subject: [PATCH 4/4] indent fixed --- readme.md | 14 +++++++------- rpirtscts | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) create mode 160000 rpirtscts diff --git a/readme.md b/readme.md index 57bdc87..16dfc2a 100644 --- a/readme.md +++ b/readme.md @@ -143,15 +143,15 @@ Derived class from `RS485` in `pyserial` (maybe this can be switched to the regu C code of the function `writec` in the library `writec`: ssize_t writec(int fd, char *buf, size_t count) { - digitalWrite(DE_PIN, HIGH); - ssize_t r = write(fd, buf, count); - uint8_t lsr; - do { + digitalWrite(DE_PIN, HIGH); + ssize_t r = write(fd, buf, count); + uint8_t lsr; + do { int r = ioctl(fd, TIOCSERGETLSR, &lsr); - } while (!(lsr & TIOCSER_TEMT)); - digitalWrite(DE_PIN, LOW); + } while (!(lsr & TIOCSER_TEMT)); + digitalWrite(DE_PIN, LOW); - return r; + return r; } This change brought the break through: diff --git a/rpirtscts b/rpirtscts new file mode 160000 index 0000000..612b065 --- /dev/null +++ b/rpirtscts @@ -0,0 +1 @@ +Subproject commit 612b065e3832888d024e2bbd6d48c381a42bfbba