1313
1414import os
1515import sys
16- from machine import I2C , Pin
1716import time
1817from micropython import const
18+ from machine import I2C
19+ from modulino import Modulino
1920
2021BOOTLOADER_I2C_ADDRESS = const (0x64 )
2122ACK = const (0x79 )
3132
3233CHUNK_SIZE = const (128 ) # Size of the memory chunk to write
3334
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
3636
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 ):
6438 """
6539 Wait for an acknowledgment from the I2C device.
6640
6741 :return: True if an acknowledgment was received, otherwise False.
6842 """
69- res = i2c .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
43+ res = bus .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
7044 if res != ACK :
7145 while res == BUSY :
7246 time .sleep (0.1 )
73- res = i2c .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
47+ res = bus .readfrom (BOOTLOADER_I2C_ADDRESS , 1 )[0 ]
7448 if res != ACK :
7549 print (f"❌ Error processing command. Result code: { hex (res )} " )
7650 return False
7751 return True
7852
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 ):
8054 """
8155 Execute an I2C command on the device.
8256
57+ :param bus: The I2C bus to use.
8358 :param opcode: The command opcode.
8459 :param command_params: The buffer containing the command parameters.
8560 :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):
9065 print (f"🕵️ Executing command { hex (opcode )} " )
9166
9267 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 ):
9570 print (f"❌ Command not acknowledged: { hex (opcode )} " )
9671 return None
9772
9873 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 ):
10176 print ("❌ Command failed" )
10277 return None
10378
10479 if response_length == 0 :
10580 return None
10681
107- data = i2c .readfrom (BOOTLOADER_I2C_ADDRESS , response_length )
82+ data = bus .readfrom (BOOTLOADER_I2C_ADDRESS , response_length )
10883
109- if not wait_for_ack ():
84+ if not wait_for_ack (bus ):
11085 print ("❌ Failed completing command" )
11186 return None
11287
11388 return data
11489
115- def flash_firmware (firmware_path , verbose = False ):
90+ def flash_firmware (device : Modulino , firmware_path , verbose = False ):
11691 """
11792 Flash the firmware to the I2C device.
11893
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 .
12196 :param verbose: Whether to print debug information.
12297 :return: True if the flashing was successful, otherwise False.
12398 """
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 )
125101 if data is None :
126102 print ("❌ Failed to get protocol version" )
127103 return False
128104 print (f"ℹ️ Protocol version: { data [0 ] & 0xF } .{ data [0 ] >> 4 } " )
129105
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 )
131107 if data is None :
132108 print ("❌ Failed to get command list" )
133109 return False
@@ -136,7 +112,7 @@ def flash_firmware(firmware_path, verbose=False):
136112 print ("👀 Supported commands:" )
137113 print (", " .join ([hex (byte ) for byte in data [2 :]]))
138114
139- data = execute_command (CMD_GET_ID , None , 3 , verbose )
115+ data = execute_command (bus , CMD_GET_ID , None , 3 , verbose )
140116 if data is None :
141117 print ("❌ Failed to get device ID" )
142118 return False
@@ -146,7 +122,7 @@ def flash_firmware(firmware_path, verbose=False):
146122
147123 print ("🗑️ Erasing memory..." )
148124 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 )
150126
151127 with open (firmware_path , 'rb' ) as file :
152128 firmware_data = file .read ()
@@ -161,7 +137,7 @@ def flash_firmware(firmware_path, verbose=False):
161137 checksum ^= b
162138 start_address .append (checksum )
163139 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 ):
165141 print (f"❌ Failed to write page { hex (i )} " )
166142 return False
167143 time .sleep (0.01 ) # Give the device some time to process the data
@@ -170,39 +146,40 @@ def flash_firmware(firmware_path, verbose=False):
170146
171147 print ("🏃 Starting firmware" )
172148 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
174150
175151 return True
176152
177- def write_firmware_page (command_params , firmware_data ):
153+ def write_firmware_page (bus , command_params , firmware_data ):
178154 """
179155 Write a page of the firmware to the I2C device.
180156
157+ :param bus: The I2C bus to use.
181158 :param command_params: The buffer containing the command parameters.
182159 :param firmware_data: The buffer containing the firmware data.
183160 :return: True if the page was written successfully, otherwise False.
184161 """
185162 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 ):
188165 print ("❌ Write command not acknowledged" )
189166 return False
190167
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 ):
193170 print ("❌ Failed to write command parameters" )
194171 return False
195172
196173 data_size = len (firmware_data )
197174 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
199176 tmp_buffer [1 :data_size + 1 ] = firmware_data
200177 tmp_buffer [- 1 ] = 0 # Checksum placeholder
201178 for i in range (data_size + 1 ): # Calculate checksum over size byte + data bytes
202179 tmp_buffer [- 1 ] ^= tmp_buffer [i ]
203180
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 ):
206183 print ("❌ Failed to write firmware" )
207184 return False
208185
@@ -243,7 +220,7 @@ def select_file(bin_files):
243220 return None
244221
245222 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) " )
247224 if confirm .lower () == 'yes' :
248225 return bin_files [0 ]
249226 else :
@@ -257,37 +234,41 @@ def select_file(bin_files):
257234 return None
258235 return bin_files [choice - 1 ]
259236
260- def select_i2c_device () :
237+ def select_device ( bus : I2C ) -> Modulino :
261238 """
262239 Scan the I2C bus for devices and prompt the user to select one.
263240
264- :return: The selected I2C device address.
241+ :param bus: The I2C bus to scan.
242+ :return: The selected Modulino device.
265243 """
266- devices = i2c . scan ( )
244+ devices = Modulino . available_devices ( bus )
267245
268246 if len (devices ) == 0 :
269- print ("❌ No I2C devices found" )
247+ print ("❌ No devices found" )
270248 return None
271249
272250 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) " )
274253 if confirm .lower () == 'yes' :
275254 return devices [0 ]
276255 else :
277256 return None
278257
279- print ("🔌 I2C devices found:" )
258+ print ("🔌 Devices found:" )
280259 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): " ))
283262 if choice < 1 or choice > len (devices ):
284263 return None
285264 return devices [choice - 1 ]
286265
287- def run ():
266+ def run (bus : I2C ):
288267 """
289268 Initialize the flashing process.
290269 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.
291272 """
292273
293274 bin_files = find_bin_files ()
@@ -300,23 +281,25 @@ def run():
300281 print ("❌ No file selected" )
301282 return
302283
303- device_address = select_i2c_device ( )
304- if device_address is None :
284+ device = select_device ( bus )
285+ if device is None :
305286 print ("❌ No device selected" )
306287 return
307-
308- if send_reset (device_address ):
288+
289+ print (f"🔄 Resetting device at address { hex (device .address )} " )
290+ if device .enter_bootloader ():
309291 print ("✅ Device reset successfully" )
310292 else :
311293 print ("❌ Failed to reset device" )
312294 return
313295
314296 print (f"🕵️ Flashing { bin_file } to device at address { hex (BOOTLOADER_I2C_ADDRESS )} " )
315297
316- if flash_firmware (bin_file ):
298+ if flash_firmware (device , bin_file ):
317299 print ("✅ Firmware flashed successfully" )
318300 else :
319301 print ("❌ Failed to flash firmware" )
320302
321303if __name__ == "__main__" :
322- run ()
304+ print ()
305+ run (bus )
0 commit comments