Correct Modbus slave response
-
Hello, currently I am working with the core V2.7 with a COM module. I want to establish a Modbus Slave on the M5Stack by using the library "modbus.slave.rtu" directly available in M5Flow. Although I do not find any good documentation, I was able to realize a simple Modbus Slave with a holding register and and a coil register. I can receive data in LabVIEW and I can also write data (from LabVIEW to the M5Stack). But when writing I receive Error 538172 in LabVIEW (The data returned did not match the data sent for that function), although I try to generate an echo. Probably one parameter (function, address, length?) does not fit. Perhaps the solution is the application of the "Update function", but it works only for the reading functions. I also tried to init different functions (I do not know the exact purpose of the command), but it does not help either.
Has anyone an idea how to generate the correct answer for the writing functions (also for changing addresses and lengths)? Here you find the current Micropython code that works except for the answer:
from m5stack import *
from m5ui import *
from uiflow import *
from modbus.slave.rtu import ModbusSlave
import nvs
import time
import i2c_bus
import machinesetScreenColor(0xFFFFFF)
Label0 = M5TextBox(20, 80, "Holding Register", lcd.FONT_DejaVu18, 0xFF0000, rotate=0)
Label1 = M5TextBox(20, 120, "Coil Register", lcd.FONT_DejaVu18, 0xFF0000, rotate=0)Register_h = [4, 5, 6, 7, 8]
Register_c = [False, True]
i = 0modbus_s = ModbusSlave(1, tx=17, rx=16, baudrate=9600, data_bits=8, stop_bits=1, parity=0, slaveID=1)
modbus_s.function_init(3, 0, 5)
modbus_s.function_init(1, 0, 2)
modbus_s.function_init(6, 0, 1)
modbus_s.function_init(16, 0, 5)while(True):
__buffer = modbus_s.receive_req_create_pdu()
__function = modbus_s.find_function
__address = modbus_s.find_address
__quantity = modbus_s.find_quantity__# Read holding register values
__if function == 3:
____if address >= 0 and (address + quantity <= 5):
______modbus_s.create_slave_response(Register_h[address : address + quantity])
__# Read coils
__elif function == 1:
____if address >= 0 and (address + quantity <= 2):
______modbus_s.create_slave_response(Register_c[address : address + quantity])
__# Write single holding register value
__elif function == 6:
____if address >= 0 and address <= 5:
______Register_h[address] = buffer
______modbus_s.create_slave_response(buffer)
__Write holding register values
__elif function == 16:
____if address >= 0 and (address + quantity <= 5):
______for i in range (address, address + quantity):
________Register_h[i] = buffer[i - address]
______pass
______modbus_s.create_slave_response(buffer)
__Label0.setText(str(Register_h))
__Label1.setText(str(Register_c))
__wait_ms(10)*** -
Hi @dennis78ac ,
please refer to the example code
received the buffer from "receive ADU request" that same buffer is added to the "send ADU response buffer" and If you need to update the value, use "update function" the block
-
Thank you for your answer. Nevertheless I already implied these two commands with:
- buffer = modbus_s.receive_req_create_pdu() --- line after "while(True)"
- modbus_s.create_slave_response(buffer) --- for example in "elif function == 16" at the end
As I already explained LabVIEW receives the answer, but not as expected. As the response does not only contain the data itself (saved in buffer), but also function no., address etc. I think the difference has to be searched within these parameters. Unfortunately I do not find any possibility to adapt the response, especially as the update command for the function only works for the reading functions, but not for the writing functions. So I do not know how to update function no, address and quantity for the response (or just generate the echo as expected)
-
Hi @dennis78ac ,
This is "write single coil" exampleThe "Send ADU Response Buffer" block should add buffer data(All read and all write function is required)
-
Thank you for your help. Your example works well if using only one coil. Nevertheless I could at first not solve the problem as LabVIEW still claimed a false response, if I change the coils the registers to write.
For this reason I displayed the response in LabVIEW in detail. The problem are changing addresses if writing on different coils or holding registers. In your example, the write function is initiated with address 0x00ac. So if you always write on 0x00ac it works fine. But if you write in a second process on 0x00ad, for instance, the M5Stack will nevertheless answer with 0x00ac as address, which is claimed by LabVIEW. Unfortunately the initiated function cannot be upated to the new address, as the update command only works for the reading functions.
As solution that seems to work I initiated now each function at the beginning, covering the complete address area for multiple read/write and indicating the last address for single read/write. Otherwise the M5Stack produces an error, if you want to write on a coil with an address higher the initially indicated. Then I reinitiate after each the buffer read once again the particular function with the current address and quantity and it seems wo work. I just hope not to generate each time a new variable (function) within the M5Stack, but I have the impression that I am just overwriting the old function values. By reinitiating the function address and quantity are adapted for the "Send ADU response buffer" command.
from m5stack import *
from m5ui import *
from uiflow import *
from modbus.slave.rtu import ModbusSlave
import nvs
import time
import i2c_bus
import machinesetScreenColor(0xFFFFFF)
Label0 = M5TextBox(20, 80, "Holding Register", lcd.FONT_DejaVu18, 0xFF0000, rotate=0)
Label1 = M5TextBox(20, 120, "Coil Register", lcd.FONT_DejaVu18, 0xFF0000, rotate=0)
Label2 = M5TextBox(20, 160, "Buffer", lcd.FONT_DejaVu18, 0xFF0000, rotate=0)
Register_h = [4, 5, 6, 7, 8]
Register_c = [False, True]
i = 0modbus_s = ModbusSlave(1, tx=17, rx=16, baudrate=9600, data_bits=8, stop_bits=1, parity=0, slaveID=0x11)
# Initiate each function with maximum quantity / maximum address
modbus_s.function_init(3, 0, 5)
modbus_s.function_init(1, 0, 2)
modbus_s.function_init(6, 4, 1)
modbus_s.function_init(16, 0, 5)
modbus_s.function_init(5, 1, 1)
modbus_s.function_init(15, 0, 2)
while(True):
__buffer = modbus_s.receive_req_create_pdu() # Read buffer
__function = modbus_s.find_function
__address = modbus_s.find_address
__quantity = modbus_s.find_quantity
__modbus_s.function_init(function, address, quantity) # (Re)initiate specific function with current address and quantity
__if function > 0:
____Label2.setText(str(buffer))
__# Read holding register values
__if function == 3:
____if address >= 0 and (address + quantity <= 5):
______modbus_s.create_slave_response(Register_h[address : address + quantity])
__# Read coils
__elif function == 1:
____if address >= 0 and (address + quantity <= 2):
______modbus_s.create_slave_response(Register_c[address : address + quantity])
__# Write single holding register value
__elif function == 6:
____if address >= 0 and address <= 5:
______Register_h[address] = buffer
______modbus_s.create_slave_response(buffer)
__# Write holding register values
__elif function == 16:
____if address >= 0 and (address + quantity <= 5):
______for i in range (address, address + quantity):
________Register_h[i] = buffer[i - address]
______pass
______modbus_s.create_slave_response(buffer)
__elif function == 5:
____if address >= 0 and address <= 1:
______Register_c[address] = buffer > 0
______modbus_s.create_slave_response(buffer)
__elif function == 15:
____if address >= 0 and address + quantity <= 2:
______for i in range (address, address + quantity):
________Register_c[i] = buffer[i - address]
______pass
______modbus_s.create_slave_response(buffer)__Label0.setText(str(Register_h))
__Label1.setText(str(Register_c))
__wait_ms(10) -
Hello and a happy new year!
As I had some difficulties with the given Modbus library, I have a little present for you: I had some time to program an own code based on the "Modbus application protocol specification V1.1b". Of course, I cannot guarantee that everything is conform to the specification, but I seems to work. The current example can read and write on holding and coil registers an is easily adaptable. You can also extend it with an input register or take of the write rights for special addresses by changing the if cases. I tested it on the M5Stack core V2.7 with a COMMU module (M011) and I was able to communicate with “LabVIEW 2023 Q3 32 bit” using the NI Modbus library.
1 from m5stack import * 2 from m5ui import * 3 from uiflow import * 4 5 setScreenColor(0xFFFFFF) 6 7 Label0 = M5TextBox(20, 80, "Holding Register", lcd.FONT_DejaVu18, 0xFF0000, rotate=0) 8 Label1 = M5TextBox(20, 140, "Coil Register", lcd.FONT_DejaVu18, 0xFF0000, rotate=0) 9 10 def Modbus_CRC(data): 11 i0 = 0 12 i1 = 1 13 crc = 0xFFFF 14 for i0 in range(len(data)): 15 crc ^= (data[i0]) 16 for i1 in range(8): 17 if (crc & 0x0001) == 0x0000: 18 crc >>= 1 19 else: 20 crc >>= 1 21 crc ^= 0xA001 22 pass 23 pass 24 return [(crc & 0xFF00) >> 8, crc & 0x00FF] 25 26 def GenerateErrorMessage(ID, function, code): 27 message = [ID, 0x80 + function, code, 0, 0] 28 CRC = Modbus_CRC(message[0 : 3]) 29 message[3] = CRC[1] 30 message[4] = CRC[0] 31 message = bytes(message) 32 return(message) 33 34 @timerSch.event('ModbusSlave') 35 def tModbusSlave(): 36 global uart1, RegisterH, RegisterHStart, RegisterHSize, RegisterC, RegisterCStart, RegisterCSize, SlaveID 37 message = uart1.read() 38 if message != None: 39 CRC = Modbus_CRC(message[0 : len(message) - 2]) 40 if (CRC[0] == message[len(message) - 1] and CRC[1] == message[len(message) - 2] and SlaveID == message[0]): 41 function = message[1] 42 address = (message[2] << 8) + message[3] 43 # Read multiple coils 44 if function == 1: 45 quantity = (message[4] << 8) + message[5] 46 if quantity >= 0x01 and quantity <= 0x07CD0: 47 if address >= RegisterCStart and address + quantity <= RegisterCStart + RegisterCSize: 48 message = [0] * (int((7 + quantity) / 8) + 5) 49 message[0] = SlaveID 50 message[1] = function 51 message[2] = int((7 + quantity) / 8) 52 i0 = 0 53 for i1 in range(message[2]): 54 message[3 + i1] = 0 55 for i2 in range(8): 56 if i0 < quantity: 57 if RegisterC[address - RegisterCStart + i0]: 58 message[3 + i1] += (1 << i2) 59 i0 += 1 60 else: 61 break 62 CRC = Modbus_CRC(message[0 : len(message) - 2]) 63 message[len(message) - 1] = CRC[0] 64 message[len(message) - 2] = CRC[1] 65 message = bytes(message) 66 else: 67 message = GenerateErrorMessage(SlaveID, function, 2) 68 else: 69 message = GenerateErrorMessage(SlaveID, function, 3) 70 # Read multiple holding registers 71 elif function == 3: 72 quantity = (message[4] << 8) + message[5] 73 if quantity >= 0x01 and quantity <= 0x7D: 74 if address >= RegisterHStart and address + quantity <= RegisterHStart + RegisterHSize: 75 message = [0] * (quantity * 2 + 5) 76 message[0] = SlaveID 77 message[1] = function 78 message[2] = 2 * quantity 79 for i0 in range(quantity): 80 message[3 + 2*i0] = (RegisterH[address + i0 - RegisterHStart] & 0xFF00) >> 8 81 message[4 + 2*i0] = RegisterH[address + i0 - RegisterHStart] & 0xFF 82 CRC = Modbus_CRC(message[0 : len(message) - 2]) 83 message[len(message) - 1] = CRC[0] 84 message[len(message) - 2] = CRC[1] 85 message = bytes(message) 86 else: 87 message = GenerateErrorMessage(SlaveID, function, 2) 88 else: 89 message = GenerateErrorMessage(SlaveID, function, 3) 90 # Write single coil 91 elif function == 5: 92 if (message[4] == 0x00 or message[4] == 0xFF) and message[5] == 0x00: 93 if address >= RegisterCStart and address < RegisterCStart + RegisterCSize: 94 RegisterC[address - RegisterCStart] = (message[4] == 0xFF) 95 else: 96 message = GenerateErrorMessage(SlaveID, function, 2) 97 else: 98 message = GenerateErrorMessage(SlaveID, function, 3) 99 # Write single holding register 100 elif function == 6: 101 if address >= 0 and address <= 0xFFFF: 102 if address >= RegisterHStart and address < RegisterHStart + RegisterHSize: 103 RegisterH[address - RegisterHStart] = (message[4] << 8) + message[5] 104 else: 105 message = GenerateErrorMessage(SlaveID, function, 2) 106 else: 107 message = GenerateErrorMessage(SlaveID, function, 3) 108 # Write multiple coils 109 elif function == 15: 110 quantity = (message[4] << 8) + message[5] 111 if (quantity >= 1 and quantity <= 0x07B0 and message[6] == int((quantity + 7) / 8)): 112 if address >= RegisterCStart and address + quantity <= RegisterCStart + RegisterCSize: 113 i0 = 0 114 for i1 in range(message[6]): 115 for i2 in range(8): 116 if i0 < quantity: 117 RegisterC[address + i0 - RegisterCStart] = (message[7 + i1] & (1 << i2) > 0) 118 i0 += 1 119 else: 120 break 121 message = [0] * 8 122 message[0] = SlaveID 123 message[1] = function 124 message[2] = address >> 8 125 message[3] = 0xFF & address 126 message[4] = quantity >> 8 127 message[5] = 0xFF & quantity 128 CRC = Modbus_CRC(message[0 : 6]) 129 message[6] = CRC[1] 130 message[7] = CRC[0] 131 message = bytes(message) 132 else: 133 message = GenerateErrorMessage(SlaveID, function, 2) 134 else: 135 message = GenerateErrorMessage(SlaveID, function, 3) 136 # Write multiple holding registers 137 elif function == 16: 138 quantity = (message[4] << 8) + message[5] 139 if quantity >= 0x01 and quantity <= 0x7B and message[6] == 2 * quantity: 140 if address >= RegisterHStart and address + quantity <= RegisterHStart + RegisterHSize: 141 for i0 in range(quantity): 142 RegisterH[address + i0 - RegisterHStart] = (message[7 + 2 * i0] << 8) + message[8 + 2 * i0] 143 else: 144 message = GenerateErrorMessage(SlaveID, function, 2) 145 else: 146 message = GenerateErrorMessage(SlaveID, function, 3) 147 uart1.write(message) 148 Label0.setText(str(RegisterH)) 149 text = "[" 150 for i0 in range(len(RegisterC) - 1): 151 if RegisterC[i0]: 152 text += "1, " 153 else: 154 text += "0, " 155 if RegisterC[len(RegisterC) - 1]: 156 Label1.setText(text + "1]") 157 else: 158 Label1.setText(text + "0]") 159 pass 160 161 # Start values of holding and coil registers 162 RegisterH = [10, 568, 65534, 21] 163 RegisterHStart = 0x10 164 RegisterHSize = len(RegisterH) 165 RegisterC = [False, True, False, False, True, False, True, True, False] 166 RegisterCStart = 0x20 167 RegisterCSize = len(RegisterC) 168 SlaveID = 0x11 169 170 function = 0 171 address = 0 172 quantity = 0 173 message = None 174 i = 0 175 data = None 176 CRC = None 177 178 uart1 = machine.UART(1, tx=17, rx=16) 179 uart1.init(9600, bits=8, parity=0, stop=1) 180 timerSch.run('ModbusSlave', 100, 0x00)
-
Hi @dennis78ac
Happy new year and thanks for your suggestion, we will check and test and after add your code to uiflow