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)