1+ import os
2+ import sys
3+ from machine import I2C , Pin
4+ import time
5+ from micropython import const
6+
7+ BOOTLOADER_I2C_ADDRESS = const (0x64 )
8+
9+ # Define I2C pins and initialize I2C
10+ i2c = I2C (0 , freq = 100000 )
11+
12+ def send_reset (address ):
13+ """
14+ Send a reset command to the I2C device at the given address.
15+
16+ :param address: I2C address of the device.
17+ :return: 0 if the reset command was sent successfully, otherwise -1.
18+ """
19+ buffer = b'DIE'
20+ # Pad buffer to 40 bytes
21+ buffer += b'\x00 ' * (40 - len (buffer ))
22+
23+ try :
24+ print (f"Sending reset command to address { hex (address )} " )
25+ i2c .writeto (address , buffer , True )
26+ except OSError as e :
27+ pass
28+
29+ time .sleep (0.25 )
30+ devices = i2c .scan ()
31+
32+ if address in devices :
33+ return False
34+ elif BOOTLOADER_I2C_ADDRESS in devices :
35+ return True
36+
37+ def execute_command (opcode , command_buffer , command_length , response_buffer , response_length , verbose = True ):
38+ """
39+ Execute an I2C command on the device.
40+
41+ :param opcode: The command opcode.
42+ :param command_buffer: The buffer containing the command data.
43+ :param command_length: The length of the command data.
44+ :param response_buffer: The buffer to store the response data.
45+ :param response_length: The expected length of the response data.
46+ :param verbose: Whether to print debug information.
47+ :return: The number of response bytes read, or -1 if an error occurred.
48+ """
49+ if verbose :
50+ print ("Executing command" )
51+
52+ cmd = bytes ([opcode , 0xFF ^ opcode ])
53+ i2c .writeto (100 , cmd )
54+ if command_length > 0 :
55+ i2c .writeto (100 , command_buffer )
56+
57+ time .sleep (0.1 )
58+ ack = i2c .readfrom (100 , 1 )[0 ]
59+ if ack != 0x79 :
60+ print (f"Error first ack: { hex (ack )} " )
61+ return - 1
62+
63+ if command_length > 0 :
64+ ack = i2c .readfrom (100 , 1 )[0 ]
65+ if ack != 0x79 :
66+ while ack == 0x76 :
67+ time .sleep (0.1 )
68+ ack = i2c .readfrom (100 , 1 )[0 ]
69+ if ack != 0x79 :
70+ print (f"Error second ack: { hex (ack )} " )
71+ return - 1
72+
73+ if response_length > 0 :
74+ data = i2c .readfrom (100 , response_length + 1 )
75+ for i in range (response_length ):
76+ response_buffer [i ] = data [i + 1 ]
77+
78+ ack = i2c .readfrom (100 , 1 )[0 ]
79+ if ack != 0x79 :
80+ print (f"Error: { hex (ack )} " )
81+ return - 1
82+
83+ return response_length
84+
85+ def flash_firmware (firmware , length , verbose = True ):
86+ """
87+ Flash the firmware to the I2C device.
88+
89+ :param firmware: The binary firmware data.
90+ :param length: The length of the firmware data.
91+ :param verbose: Whether to print debug information.
92+ :return: True if the flashing was successful, otherwise False.
93+ """
94+ if verbose :
95+ print ("Flashing firmware" )
96+
97+ response_buffer = bytearray (255 )
98+ if execute_command (0 , None , 0 , response_buffer , 20 , verbose ) < 0 :
99+ print ("Failed :(" )
100+ return False
101+ for byte in response_buffer :
102+ print (hex (byte ))
103+
104+ if verbose :
105+ print ("Getting device ID" )
106+ if execute_command (2 , None , 0 , response_buffer , 3 , verbose ) < 0 :
107+ print ("Failed to get device ID" )
108+ return False
109+ for byte in response_buffer :
110+ print (hex (byte ))
111+
112+ if verbose :
113+ print ("Mass erase" )
114+ erase_buffer = bytearray ([0xFF , 0xFF , 0x0 ])
115+ if execute_command (0x44 , erase_buffer , 3 , None , 0 , verbose ) < 0 :
116+ print ("Failed to mass erase" )
117+ return False
118+
119+ for i in range (0 , length , 128 ):
120+ progress_bar (i , length )
121+ write_buffer = bytearray ([8 , 0 , i // 256 , i % 256 ])
122+ if write_firmware_page (0x32 , write_buffer , 5 , firmware [i :i + 128 ], 128 , verbose ) < 0 :
123+ print (f"Failed to write page { hex (i )} " )
124+ return False
125+ time .sleep (0.01 )
126+
127+ progress_bar (length , length ) # Complete the progress bar
128+
129+ print ("Starting firmware" )
130+ jump_buffer = bytearray ([0x8 , 0x00 , 0x00 , 0x00 , 0x8 ])
131+ if execute_command (0x21 , jump_buffer , 5 , None , 0 , verbose ) < 0 :
132+ print ("Failed to start firmware" )
133+ return False
134+
135+ return True
136+
137+ def write_firmware_page (opcode , command_buffer , command_length , firmware_buffer , firmware_length , verbose = True ):
138+ """
139+ Write a page of the firmware to the I2C device.
140+
141+ :param opcode: The command opcode.
142+ :param command_buffer: The buffer containing the command data.
143+ :param command_length: The length of the command data.
144+ :param firmware_buffer: The buffer containing the firmware data.
145+ :param firmware_length: The length of the firmware data.
146+ :param verbose: Whether to print debug information.
147+ :return: The number of bytes written, or -1 if an error occurred.
148+ """
149+ cmd = bytes ([opcode , 0xFF ^ opcode ])
150+ i2c .writeto (100 , cmd )
151+
152+ if command_length > 0 :
153+ command_buffer [command_length - 1 ] = 0
154+ for i in range (command_length - 1 ):
155+ command_buffer [command_length - 1 ] ^= command_buffer [i ]
156+ i2c .writeto (100 , command_buffer )
157+
158+ ack = i2c .readfrom (100 , 1 )[0 ]
159+ if ack != 0x79 :
160+ print (f"Error first ack: { hex (ack )} " )
161+ return - 1
162+
163+ tmp_buffer = bytearray (firmware_length + 2 )
164+ tmp_buffer [0 ] = firmware_length - 1
165+ tmp_buffer [1 :firmware_length + 1 ] = firmware_buffer
166+ tmp_buffer [firmware_length + 1 ] = 0
167+ for i in range (firmware_length + 1 ):
168+ tmp_buffer [firmware_length + 1 ] ^= tmp_buffer [i ]
169+
170+ i2c .writeto (100 , tmp_buffer )
171+ ack = i2c .readfrom (100 , 1 )[0 ]
172+ if ack != 0x79 :
173+ print (f"Error: { hex (ack )} " )
174+ return - 1
175+
176+ return firmware_length
177+
178+ def progress_bar (current , total , bar_length = 40 ):
179+ """
180+ Print a progress bar to the terminal.
181+
182+ :param current: The current progress value.
183+ :param total: The total progress value.
184+ :param bar_length: The length of the progress bar in characters.
185+ """
186+ percent = float (current ) / total
187+ arrow = '-' * int (round (percent * bar_length ) - 1 ) + '>'
188+ spaces = ' ' * (bar_length - len (arrow ))
189+ sys .stdout .write (f"\r Progress: [{ arrow } { spaces } ] { int (round (percent * 100 ))} %" )
190+ sys .stdout .flush ()
191+
192+ def find_bin_files ():
193+ """
194+ Find all .bin files in the root directory.
195+
196+ :return: A list of .bin file names.
197+ """
198+ return [file for file in os .listdir ('/' ) if file .endswith ('.bin' )]
199+
200+ def select_file (bin_files ):
201+ """
202+ Prompt the user to select a .bin file to flash.
203+
204+ :param bin_files: A list of .bin file names.
205+ :return: The selected .bin file name.
206+ """
207+ if len (bin_files ) == 1 :
208+ confirm = input (f"Found one bin file: { bin_files [0 ]} . Do you want to flash it? (yes/no) " )
209+ if confirm .lower () == 'yes' :
210+ return bin_files [0 ]
211+ else :
212+ print ("Found bin files:" )
213+ for index , file in enumerate (bin_files ):
214+ print (f"{ index + 1 } . { file } " )
215+ choice = int (input ("Select the file to flash (number): " ))
216+ return bin_files [choice - 1 ]
217+
218+ def scan_i2c_devices ():
219+ """
220+ Scan the I2C bus for devices and prompt the user to select one.
221+
222+ :return: The selected I2C device address.
223+ """
224+ devices = i2c .scan ()
225+ print ("I2C devices found:" )
226+ for index , device in enumerate (devices ):
227+ print (f"{ index + 1 } . Address: { hex (device )} " )
228+ choice = int (input ("Select the I2C device to flash (number): " ))
229+ return devices [choice - 1 ]
230+
231+ def setup ():
232+ """
233+ Setup function to initialize the flashing process.
234+ Finds .bin files, scans for I2C devices, and flashes the selected firmware.
235+ """
236+ print ("Starting setup" )
237+
238+ bin_files = find_bin_files ()
239+ if not bin_files :
240+ print ("No .bin files found in the root directory." )
241+ return
242+
243+ bin_file = select_file (bin_files )
244+
245+ device_address = scan_i2c_devices ()
246+ print (f"Resetting device at address { hex (device_address )} " )
247+ if send_reset (device_address ):
248+ print ("Device reset successfully" )
249+ else :
250+ print ("Failed to reset device" )
251+ return
252+
253+ print (f"Flashing { bin_file } to device at address { hex (BOOTLOADER_I2C_ADDRESS )} " )
254+
255+ with open (bin_file , 'rb' ) as file :
256+ firmware = file .read ()
257+
258+ if flash_firmware (firmware , len (firmware )):
259+ print ("PASS" )
260+ else :
261+ print ("FAIL" )
262+
263+ # Start the setup
264+ # setup()
0 commit comments