🤖Have you ever tried Chat.M5Stack.com before asking??😎
    M5Stack Community
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Register
    • Login

    Correct Modbus slave response

    UIFlow
    2
    7
    3.3k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • D
      dennis78ac
      last edited by

      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)***

      1 Reply Last reply Reply Quote 0
      • P
        pandian.nano
        last edited by

        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

        1 Reply Last reply Reply Quote 0
        • D
          dennis78ac
          last edited by

          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)

          1 Reply Last reply Reply Quote 0
          • P
            pandian.nano
            last edited by pandian.nano

            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

            1 Reply Last reply Reply Quote 0
            • D
              dennis78ac
              last edited by

              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)

              D 1 Reply Last reply Reply Quote 0
              • D
                dennis78ac @dennis78ac
                last edited by

                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)
                
                1 Reply Last reply Reply Quote 0
                • P
                  pandian.nano
                  last edited by pandian.nano

                  Hi @dennis78ac
                  Happy new year and thanks for your suggestion, we will check and test and after add your code to uiflow

                  1 Reply Last reply Reply Quote 0
                  • First post
                    Last post