13
13
14
14
import os
15
15
import sys
16
- from machine import I2C , Pin
17
16
import time
18
17
from micropython import const
18
+ from machine import I2C
19
+ from modulino import Modulino
19
20
20
21
BOOTLOADER_I2C_ADDRESS = const (0x64 )
21
22
ACK = const (0x79 )
31
32
32
33
CHUNK_SIZE = const (128 ) # Size of the memory chunk to write
33
34
34
- # Define I2C pins and initialize I2C
35
- i2c = I2C (0 , freq = 100000 )
35
+ bus = None # Change this to the I2C bus you are using on 3rd party host boards
36
36
37
- def send_reset (address ):
38
- """
39
- Send a reset command to the I2C device at the given address.
40
-
41
- :param address: I2C address of the device.
42
- :return: 0 if the reset command was sent successfully, otherwise -1.
43
- """
44
- buffer = b'DIE'
45
- buffer += b'\x00 ' * (8 - len (buffer )) # Pad buffer to 8 bytes
46
-
47
- try :
48
- print (f"🔄 Resetting device at address { hex (address )} " )
49
- i2c .writeto (address , buffer , True )
50
- print ("📤 Reset command sent" )
51
- time .sleep (0.25 ) # Wait for the device to reset
52
- return True
53
- except OSError as e :
54
- # ENODEV can be thrown if either the device reset while writing out the buffer or if the device
55
- # was already in bootloader mode in which case there is no device at the original address
56
- if e .errno == 19 :
57
- time .sleep (0.25 ) # Wait for the device to reset
58
- return True
59
- else :
60
- print (f"Error sending reset command: { e } " )
61
- return False
62
-
63
- def wait_for_ack ():
37
+ def wait_for_ack (bus ):
64
38
"""
65
39
Wait for an acknowledgment from the I2C device.
66
40
67
41
:return: True if an acknowledgment was received, otherwise False.
68
42
"""
69
- res = i2c .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
43
+ res = bus .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
70
44
if res != ACK :
71
45
while res == BUSY :
72
46
time .sleep (0.1 )
73
- res = i2c .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
47
+ res = bus .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
74
48
if res != ACK :
75
49
print (f"❌ Error processing command. Result code: { hex (res )} " )
76
50
return False
77
51
return True
78
52
79
- def execute_command (opcode , command_params , response_length = 0 , verbose = False ):
53
+ def execute_command (bus , opcode , command_params , response_length = 0 , verbose = False ):
80
54
"""
81
55
Execute an I2C command on the device.
82
56
57
+ :param bus: The I2C bus to use.
83
58
:param opcode: The command opcode.
84
59
:param command_params: The buffer containing the command parameters.
85
60
:param response_length: The expected length of the response data frame.
@@ -90,44 +65,45 @@ def execute_command(opcode, command_params, response_length = 0, verbose=False):
90
65
print (f"🕵️ Executing command { hex (opcode )} " )
91
66
92
67
cmd = bytes ([opcode , 0xFF ^ opcode ]) # Send command code and complement (XOR = 0x00)
93
- i2c .writeto (BOOTLOADER_I2C_ADDRESS , cmd , True )
94
- if not wait_for_ack ():
68
+ bus .writeto (BOOTLOADER_I2C_ADDRESS , cmd , True )
69
+ if not wait_for_ack (bus ):
95
70
print (f"❌ Command not acknowledged: { hex (opcode )} " )
96
71
return None
97
72
98
73
if command_params is not None :
99
- i2c .writeto (BOOTLOADER_I2C_ADDRESS , command_params , True )
100
- if not wait_for_ack ():
74
+ bus .writeto (BOOTLOADER_I2C_ADDRESS , command_params , True )
75
+ if not wait_for_ack (bus ):
101
76
print ("❌ Command failed" )
102
77
return None
103
78
104
79
if response_length == 0 :
105
80
return None
106
81
107
- data = i2c .readfrom (BOOTLOADER_I2C_ADDRESS , response_length )
82
+ data = bus .readfrom (BOOTLOADER_I2C_ADDRESS , response_length )
108
83
109
- if not wait_for_ack ():
84
+ if not wait_for_ack (bus ):
110
85
print ("❌ Failed completing command" )
111
86
return None
112
87
113
88
return data
114
89
115
- def flash_firmware (firmware_path , verbose = False ):
90
+ def flash_firmware (device : Modulino , firmware_path , verbose = False ):
116
91
"""
117
92
Flash the firmware to the I2C device.
118
93
119
- :param firmware : The binary firmware data .
120
- :param length : The length of the firmware data .
94
+ :param device : The Modulino device to flash .
95
+ :param firmware_path : The binary firmware path .
121
96
:param verbose: Whether to print debug information.
122
97
:return: True if the flashing was successful, otherwise False.
123
98
"""
124
- data = execute_command (CMD_GET_VERSION , None , 1 , verbose )
99
+ bus = device .i2c_bus
100
+ data = execute_command (bus , CMD_GET_VERSION , None , 1 , verbose )
125
101
if data is None :
126
102
print ("❌ Failed to get protocol version" )
127
103
return False
128
104
print (f"ℹ️ Protocol version: { data [0 ] & 0xF } .{ data [0 ] >> 4 } " )
129
105
130
- data = execute_command (CMD_GET , None , CMD_GET_LENGTH_V12 , verbose )
106
+ data = execute_command (bus , CMD_GET , None , CMD_GET_LENGTH_V12 , verbose )
131
107
if data is None :
132
108
print ("❌ Failed to get command list" )
133
109
return False
@@ -136,7 +112,7 @@ def flash_firmware(firmware_path, verbose=False):
136
112
print ("👀 Supported commands:" )
137
113
print (", " .join ([hex (byte ) for byte in data [2 :]]))
138
114
139
- data = execute_command (CMD_GET_ID , None , 3 , verbose )
115
+ data = execute_command (bus , CMD_GET_ID , None , 3 , verbose )
140
116
if data is None :
141
117
print ("❌ Failed to get device ID" )
142
118
return False
@@ -146,7 +122,7 @@ def flash_firmware(firmware_path, verbose=False):
146
122
147
123
print ("🗑️ Erasing memory..." )
148
124
erase_params = bytearray ([0xFF , 0xFF , 0x0 ]) # Mass erase flash
149
- execute_command (CMD_ERASE_NO_STRETCH , erase_params , 0 , verbose )
125
+ execute_command (bus , CMD_ERASE_NO_STRETCH , erase_params , 0 , verbose )
150
126
151
127
with open (firmware_path , 'rb' ) as file :
152
128
firmware_data = file .read ()
@@ -161,7 +137,7 @@ def flash_firmware(firmware_path, verbose=False):
161
137
checksum ^= b
162
138
start_address .append (checksum )
163
139
data_slice = firmware_data [i :i + CHUNK_SIZE ]
164
- if not write_firmware_page (start_address , data_slice ):
140
+ if not write_firmware_page (bus , start_address , data_slice ):
165
141
print (f"❌ Failed to write page { hex (i )} " )
166
142
return False
167
143
time .sleep (0.01 ) # Give the device some time to process the data
@@ -170,39 +146,40 @@ def flash_firmware(firmware_path, verbose=False):
170
146
171
147
print ("🏃 Starting firmware" )
172
148
go_params = bytearray ([0x8 , 0x00 , 0x00 , 0x00 , 0x8 ])
173
- execute_command (CMD_GO , go_params , 0 , verbose ) # Jump to the application
149
+ execute_command (bus , CMD_GO , go_params , 0 , verbose ) # Jump to the application
174
150
175
151
return True
176
152
177
- def write_firmware_page (command_params , firmware_data ):
153
+ def write_firmware_page (bus , command_params , firmware_data ):
178
154
"""
179
155
Write a page of the firmware to the I2C device.
180
156
157
+ :param bus: The I2C bus to use.
181
158
:param command_params: The buffer containing the command parameters.
182
159
:param firmware_data: The buffer containing the firmware data.
183
160
:return: True if the page was written successfully, otherwise False.
184
161
"""
185
162
cmd = bytes ([CMD_WRITE_NO_STRETCH , 0xFF ^ CMD_WRITE_NO_STRETCH ])
186
- i2c .writeto (BOOTLOADER_I2C_ADDRESS , cmd )
187
- if not wait_for_ack ():
163
+ bus .writeto (BOOTLOADER_I2C_ADDRESS , cmd )
164
+ if not wait_for_ack (bus ):
188
165
print ("❌ Write command not acknowledged" )
189
166
return False
190
167
191
- i2c .writeto (BOOTLOADER_I2C_ADDRESS , command_params )
192
- if not wait_for_ack ():
168
+ bus .writeto (BOOTLOADER_I2C_ADDRESS , command_params )
169
+ if not wait_for_ack (bus ):
193
170
print ("❌ Failed to write command parameters" )
194
171
return False
195
172
196
173
data_size = len (firmware_data )
197
174
tmp_buffer = bytearray (data_size + 2 ) # Data plus size and checksum
198
- tmp_buffer [0 ] = data_size - 1 # Size of the data # TODO Arduino code uses data_size - 1
175
+ tmp_buffer [0 ] = data_size - 1 # Size of the data
199
176
tmp_buffer [1 :data_size + 1 ] = firmware_data
200
177
tmp_buffer [- 1 ] = 0 # Checksum placeholder
201
178
for i in range (data_size + 1 ): # Calculate checksum over size byte + data bytes
202
179
tmp_buffer [- 1 ] ^= tmp_buffer [i ]
203
180
204
- i2c .writeto (BOOTLOADER_I2C_ADDRESS , tmp_buffer )
205
- if not wait_for_ack ():
181
+ bus .writeto (BOOTLOADER_I2C_ADDRESS , tmp_buffer )
182
+ if not wait_for_ack (bus ):
206
183
print ("❌ Failed to write firmware" )
207
184
return False
208
185
@@ -243,7 +220,7 @@ def select_file(bin_files):
243
220
return None
244
221
245
222
if len (bin_files ) == 1 :
246
- confirm = input (f"📄 Found one biary file: { bin_files [0 ]} . Do you want to flash it? (yes/no) " )
223
+ confirm = input (f"📄 Found one binary file: { bin_files [0 ]} . Do you want to flash it? (yes/no) " )
247
224
if confirm .lower () == 'yes' :
248
225
return bin_files [0 ]
249
226
else :
@@ -257,37 +234,41 @@ def select_file(bin_files):
257
234
return None
258
235
return bin_files [choice - 1 ]
259
236
260
- def select_i2c_device () :
237
+ def select_device ( bus : I2C ) -> Modulino :
261
238
"""
262
239
Scan the I2C bus for devices and prompt the user to select one.
263
240
264
- :return: The selected I2C device address.
241
+ :param bus: The I2C bus to scan.
242
+ :return: The selected Modulino device.
265
243
"""
266
- devices = i2c . scan ( )
244
+ devices = Modulino . available_devices ( bus )
267
245
268
246
if len (devices ) == 0 :
269
- print ("❌ No I2C devices found" )
247
+ print ("❌ No devices found" )
270
248
return None
271
249
272
250
if len (devices ) == 1 :
273
- confirm = input (f"🔌 Found one I2C device at address { hex (devices [0 ])} . Do you want to flash it? (yes/no) " )
251
+ device = devices [0 ]
252
+ confirm = input (f"🔌 Found { device .device_type } at address { hex (device .address )} . Do you want to update this device? (yes/no) " )
274
253
if confirm .lower () == 'yes' :
275
254
return devices [0 ]
276
255
else :
277
256
return None
278
257
279
- print ("🔌 I2C devices found:" )
258
+ print ("🔌 Devices found:" )
280
259
for index , device in enumerate (devices ):
281
- print (f"{ index + 1 } . Address: { hex (device )} " )
282
- choice = int (input ("Select the I2C device to flash (number): " ))
260
+ print (f"{ index + 1 } ) { device . device_type } at { hex (device . address )} " )
261
+ choice = int (input ("Select the device to flash (number): " ))
283
262
if choice < 1 or choice > len (devices ):
284
263
return None
285
264
return devices [choice - 1 ]
286
265
287
- def run ():
266
+ def run (bus : I2C ):
288
267
"""
289
268
Initialize the flashing process.
290
269
Finds .bin files, scans for I2C devices, and flashes the selected firmware.
270
+
271
+ :param bus: The I2C bus to use. If None, the default I2C bus will be used.
291
272
"""
292
273
293
274
bin_files = find_bin_files ()
@@ -300,23 +281,25 @@ def run():
300
281
print ("❌ No file selected" )
301
282
return
302
283
303
- device_address = select_i2c_device ( )
304
- if device_address is None :
284
+ device = select_device ( bus )
285
+ if device is None :
305
286
print ("❌ No device selected" )
306
287
return
307
-
308
- if send_reset (device_address ):
288
+
289
+ print (f"🔄 Resetting device at address { hex (device .address )} " )
290
+ if device .enter_bootloader ():
309
291
print ("✅ Device reset successfully" )
310
292
else :
311
293
print ("❌ Failed to reset device" )
312
294
return
313
295
314
296
print (f"🕵️ Flashing { bin_file } to device at address { hex (BOOTLOADER_I2C_ADDRESS )} " )
315
297
316
- if flash_firmware (bin_file ):
298
+ if flash_firmware (device , bin_file ):
317
299
print ("✅ Firmware flashed successfully" )
318
300
else :
319
301
print ("❌ Failed to flash firmware" )
320
302
321
303
if __name__ == "__main__" :
322
- run ()
304
+ print ()
305
+ run (bus )
0 commit comments