13
13
from machine import Pin
14
14
import struct
15
15
import time
16
- import machine
17
16
18
17
# custom packages
19
18
from . import const as Const
@@ -96,6 +95,8 @@ def __init__(self,
96
95
:param ctrl_pin: The control pin
97
96
:type ctrl_pin: int
98
97
"""
98
+ # UART flush function is introduced in Micropython v1.20.0
99
+ self ._has_uart_flush = callable (getattr (UART , "flush" , None ))
99
100
self ._uart = UART (uart_id ,
100
101
baudrate = baudrate ,
101
102
bits = data_bits ,
@@ -112,12 +113,16 @@ def __init__(self,
112
113
else :
113
114
self ._ctrlPin = None
114
115
116
+ # timing of 1 character in microseconds (us)
115
117
self ._t1char = (1000000 * (data_bits + stop_bits + 2 )) // baudrate
118
+
119
+ # inter-frame delay in microseconds (us)
120
+ # - <= 19200 bps: 3.5x timing of 1 character
121
+ # - > 19200 bps: 1750 us
116
122
if baudrate <= 19200 :
117
- # 4010us (approx. 4ms) @ 9600 baud
118
- self ._t35chars = (3500000 * (data_bits + stop_bits + 2 )) // baudrate
123
+ self ._inter_frame_delay = (self ._t1char * 3500 ) // 1000
119
124
else :
120
- self ._t35chars = 1750 # 1750us (approx. 1.75ms)
125
+ self ._inter_frame_delay = 1750
121
126
122
127
def _calculate_crc16 (self , data : bytearray ) -> bytes :
123
128
"""
@@ -143,31 +148,35 @@ def _exit_read(self, response: bytearray) -> bool:
143
148
:param response: The response
144
149
:type response: bytearray
145
150
146
- :returns: State of basic read response evaluation
151
+ :returns: State of basic read response evaluation,
152
+ True if entire response has been read
147
153
:rtype: bool
148
154
"""
149
- if response [1 ] >= Const .ERROR_BIAS :
150
- if len (response ) < Const .ERROR_RESP_LEN :
155
+ response_len = len (response )
156
+ if response_len >= 2 and response [1 ] >= Const .ERROR_BIAS :
157
+ if response_len < Const .ERROR_RESP_LEN :
151
158
return False
152
- elif (Const .READ_COILS <= response [1 ] <= Const .READ_INPUT_REGISTER ):
159
+ elif response_len >= 3 and (Const .READ_COILS <= response [1 ] <= Const .READ_INPUT_REGISTER ):
153
160
expected_len = Const .RESPONSE_HDR_LENGTH + 1 + response [2 ] + Const .CRC_LENGTH
154
- if len ( response ) < expected_len :
161
+ if response_len < expected_len :
155
162
return False
156
- elif len ( response ) < Const .FIXED_RESP_LEN :
163
+ elif response_len < Const .FIXED_RESP_LEN :
157
164
return False
158
165
159
166
return True
160
167
161
168
def _uart_read (self ) -> bytearray :
162
169
"""
163
- Read up to 40 bytes from UART
170
+ Read incoming slave response from UART
164
171
165
172
:returns: Read content
166
173
:rtype: bytearray
167
174
"""
168
175
response = bytearray ()
169
176
170
- for x in range (1 , 40 ):
177
+ # TODO: use some kind of hint or user-configurable delay
178
+ # to determine this loop counter
179
+ for x in range (1 , 120 ):
171
180
if self ._uart .any ():
172
181
# WiPy only
173
182
# response.extend(self._uart.readall())
@@ -178,7 +187,7 @@ def _uart_read(self) -> bytearray:
178
187
break
179
188
180
189
# wait for the maximum time between two frames
181
- time .sleep_us (self ._t35chars )
190
+ time .sleep_us (self ._inter_frame_delay )
182
191
183
192
return response
184
193
@@ -194,10 +203,9 @@ def _uart_read_frame(self, timeout: Optional[int] = None) -> bytearray:
194
203
"""
195
204
received_bytes = bytearray ()
196
205
197
- # set timeout to at least twice the time between two frames in case the
198
- # timeout was set to zero or None
206
+ # set default timeout to at twice the inter-frame delay
199
207
if timeout == 0 or timeout is None :
200
- timeout = 2 * self ._t35chars # in milliseconds
208
+ timeout = 2 * self ._inter_frame_delay # in microseconds
201
209
202
210
start_us = time .ticks_us ()
203
211
@@ -210,13 +218,13 @@ def _uart_read_frame(self, timeout: Optional[int] = None) -> bytearray:
210
218
211
219
# do not stop reading and appending the result to the buffer
212
220
# until the time between two frames elapsed
213
- while time .ticks_diff (time .ticks_us (), last_byte_ts ) <= self ._t35chars :
221
+ while time .ticks_diff (time .ticks_us (), last_byte_ts ) <= self ._inter_frame_delay :
214
222
# WiPy only
215
223
# r = self._uart.readall()
216
224
r = self ._uart .read ()
217
225
218
226
# if something has been read after the first iteration of
219
- # this inner while loop (during self._t35chars time )
227
+ # this inner while loop (within self._inter_frame_delay )
220
228
if r is not None :
221
229
# append the new read stuff to the buffer
222
230
received_bytes .extend (r )
@@ -235,32 +243,49 @@ def _send(self, modbus_pdu: bytes, slave_addr: int) -> None:
235
243
"""
236
244
Send Modbus frame via UART
237
245
238
- If a flow control pin has been setup, it will be controller accordingly
246
+ If a flow control pin has been setup, it will be controlled accordingly
239
247
240
248
:param modbus_pdu: The modbus Protocol Data Unit
241
249
:type modbus_pdu: bytes
242
250
:param slave_addr: The slave address
243
251
:type slave_addr: int
244
252
"""
245
- serial_pdu = bytearray ()
246
- serial_pdu . append ( slave_addr )
247
- serial_pdu . extend ( modbus_pdu )
248
-
249
- crc = self . _calculate_crc16 ( serial_pdu )
250
- serial_pdu .extend (crc )
253
+ # modbus_adu: Modbus Application Data Unit
254
+ # consists of the Modbus PDU, with slave address prepended and checksum appended
255
+ modbus_adu = bytearray ( )
256
+ modbus_adu . append ( slave_addr )
257
+ modbus_adu . extend ( modbus_pdu )
258
+ modbus_adu .extend (self . _calculate_crc16 ( modbus_adu ) )
251
259
252
260
if self ._ctrlPin :
253
- self ._ctrlPin (1 )
254
- time .sleep_us (1000 ) # wait until the control pin really changed
255
- send_start_time = time .ticks_us ()
256
-
257
- self ._uart .write (serial_pdu )
261
+ self ._ctrlPin .on ()
262
+ # wait until the control pin really changed
263
+ # 85-95us (ESP32 @ 160/240MHz)
264
+ time .sleep_us (200 )
265
+
266
+ # the timing of this part is critical:
267
+ # - if we disable output too early,
268
+ # the command will not be received in full
269
+ # - if we disable output too late,
270
+ # the incoming response will lose some data at the beginning
271
+ # easiest to just wait for the bytes to be sent out on the wire
272
+
273
+ send_start_time = time .ticks_us ()
274
+ # 360-400us @ 9600-115200 baud (measured) (ESP32 @ 160/240MHz)
275
+ self ._uart .write (modbus_adu )
276
+ send_finish_time = time .ticks_us ()
277
+ if self ._has_uart_flush :
278
+ self ._uart .flush ()
279
+ else :
280
+ sleep_time_us = (
281
+ self ._t1char * len (modbus_adu ) - # total frame time in us
282
+ time .ticks_diff (send_finish_time , send_start_time ) +
283
+ 100 # only required at baudrates above 57600, but hey 100us
284
+ )
285
+ time .sleep_us (sleep_time_us )
258
286
259
287
if self ._ctrlPin :
260
- total_frame_time_us = self ._t1char * len (serial_pdu )
261
- while time .ticks_us () <= send_start_time + total_frame_time_us :
262
- machine .idle ()
263
- self ._ctrlPin (0 )
288
+ self ._ctrlPin .off ()
264
289
265
290
def _send_receive (self ,
266
291
modbus_pdu : bytes ,
@@ -279,7 +304,7 @@ def _send_receive(self,
279
304
:returns: Validated response content
280
305
:rtype: bytes
281
306
"""
282
- # flush the Rx FIFO
307
+ # flush the Rx FIFO buffer
283
308
self ._uart .read ()
284
309
285
310
self ._send (modbus_pdu = modbus_pdu , slave_addr = slave_addr )
0 commit comments