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 machine

    setScreenColor(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 = 0

    modbus_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
    0_1703037122278_1dcffd6d-ca31-479c-b248-93b82c360bd9-image.png
    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
    0_1703037023969_b1df53e2-f25e-4b7f-8ec2-fa2caf961bfb-image.png



  • 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" example

    0_1703123947895_d6c44f86-0c89-492c-967d-695403586345-image.png

    0_1703123768462_aa266d4b-66ac-46a6-85a6-cbcc170cdb99-image.png

    The "Send ADU Response Buffer" block should add buffer data(All read and all write function is required)

    0_1703124472585_ead8b77c-f388-4f02-ba35-eeca37d9abd5-image.png



  • 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 machine

    setScreenColor(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 = 0

    modbus_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