Skip to content

Commit bf847aa

Browse files
committed
Test firmware flasher
1 parent f1ed6bb commit bf847aa

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-0
lines changed

examples/buzzer_test.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from modulino import ModulinoBuzzer
2+
from time import sleep
3+
4+
buzzer = ModulinoBuzzer()
5+
6+
buzzer.tone(ModulinoBuzzer.NOTES["E5"], 0, blocking=True)
7+
sleep(2)
8+
buzzer.no_tone()

examples/node_base.bin

14.1 KB
Binary file not shown.

src/modulino/firmware_flasher.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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"\rProgress: [{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

Comments
 (0)