Skip to content

Commit de6f82f

Browse files
committed
Added firmware flashing scripts and instructions
1 parent fc4b4bd commit de6f82f

4 files changed

Lines changed: 1605 additions & 0 deletions

File tree

firmware/firmware_update.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
2+
# To make the Pi not drop I2C clockspeed to 250kHz from 400kHz, one can say:
3+
# echo "performance" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
4+
5+
# Usage: python firmware_update.py filename.hex
6+
7+
import binascii
8+
9+
import sys
10+
import time
11+
12+
13+
from smbus2 import SMBus, i2c_msg
14+
15+
16+
# Small nuvoton:
17+
bootloader_id = 0xB001
18+
bootloader_version = 172
19+
bootloader_i2c_addr = 0x6F
20+
flash_size = 16384-2048
21+
22+
I2C_WRITE_MAX_SIZE = 32
23+
24+
REG_APROM_FLASH = 0x10
25+
REG_FLASH_PAGE = 0xF0
26+
REG_CHIP_ID_L = 0xfa
27+
REG_CHIP_ID_H = 0xfb
28+
REG_VERSION = 0xfc
29+
REG_DEBUG = 0xF8
30+
REG_BOOTLOADER_CMD = 0xF3
31+
32+
REG_CTRL = 0xfe
33+
MASK_CTRL_FREAD = 0x4
34+
MASK_CTRL_FWRITE = 0x8
35+
36+
PAGE_SIZE = 128
37+
38+
i2c_dev = SMBus(1)
39+
40+
def i2c_write_bytes(address, start_reg, valuelist):
41+
assert( len(valuelist) <= I2C_WRITE_MAX_SIZE )
42+
msg_w = i2c_msg.write(address, [start_reg] + valuelist)
43+
i2c_dev.i2c_rdwr(msg_w)
44+
45+
46+
def i2c_read_bytes(address, start_reg, length):
47+
assert( length <= I2C_WRITE_MAX_SIZE )
48+
msg_w = i2c_msg.write(address, [start_reg])
49+
i2c_dev.i2c_rdwr(msg_w)
50+
msg_r = i2c_msg.read(address, length)
51+
i2c_dev.i2c_rdwr(msg_r)
52+
return list(msg_r)
53+
54+
55+
def i2c_read8(address, reg):
56+
"""Read a single (8bit) register from the device."""
57+
msg_w = i2c_msg.write(address, [reg])
58+
i2c_dev.i2c_rdwr(msg_w)
59+
msg_r = i2c_msg.read(address, 1)
60+
i2c_dev.i2c_rdwr(msg_r)
61+
return list(msg_r)[0]
62+
63+
def i2c_write8(address, reg, value):
64+
"""Write a single (8bit) register to the device."""
65+
msg_w = i2c_msg.write(address, [reg, value])
66+
i2c_dev.i2c_rdwr(msg_w)
67+
68+
def enter_bootloader(address):
69+
print("Entering bootloader...")
70+
i2c_write8(address, REG_CTRL, 0xA0)
71+
i2c_write8(address, REG_DEBUG, 0xA3)
72+
time.sleep(0.1)
73+
74+
def set_default_boot(target):
75+
i2c_write8(bootloader_i2c_addr, REG_BOOTLOADER_CMD, target)
76+
time.sleep(0.02)
77+
78+
def set_default_boot_bootloader():
79+
set_default_boot(2)
80+
print("Changed default boot to bootloader")
81+
82+
def set_default_boot_main_program():
83+
set_default_boot(4)
84+
print("Changed default boot to main program")
85+
86+
87+
def get_chip_id(address):
88+
"""Get the IOE chip ID."""
89+
return (i2c_read8(address, REG_CHIP_ID_H) << 8) | i2c_read8(address, REG_CHIP_ID_L)
90+
91+
def confirm_id(address, id):
92+
chip_id = get_chip_id(address)
93+
print(f"confirming I2C:{hex(address)}, Chip ID:{hex(id)}, Read ID:{hex(chip_id)}")
94+
assert( chip_id == id )
95+
96+
def confirm_bootloader_version(version):
97+
assert( i2c_read8(bootloader_i2c_addr, REG_VERSION) == version )
98+
print("confirmed bootloader version", version)
99+
100+
def set_flash_page(page):
101+
i2c_write8(bootloader_i2c_addr, REG_FLASH_PAGE, page)
102+
# print "Page set to", page
103+
104+
def write_page_to_aprom():
105+
i2c_write8(bootloader_i2c_addr, REG_CTRL, MASK_CTRL_FWRITE)
106+
while True:
107+
time.sleep(0.01)
108+
try:
109+
i2c_read8(bootloader_i2c_addr, 0x00)
110+
break
111+
except:
112+
print("waiting for page write to finish...")
113+
114+
def read_page_from_aprom():
115+
i2c_write8(bootloader_i2c_addr, REG_CTRL, MASK_CTRL_FREAD)
116+
time.sleep(0.001)
117+
118+
def jump_to_main_program():
119+
i2c_write8(bootloader_i2c_addr, REG_BOOTLOADER_CMD, 1)
120+
print("Jumped to start main program execution")
121+
122+
123+
def firmware_update(bin_data, i2c_address, chip_id):
124+
assert( len(bin_data) % PAGE_SIZE == 0)
125+
assert( PAGE_SIZE <= len(bin_data) <= flash_size)
126+
127+
try:
128+
i2c_read8(bootloader_i2c_addr, 0x00)
129+
except:
130+
# We're not in bootloader yet, let's enter first
131+
confirm_id(i2c_address, chip_id)
132+
enter_bootloader(i2c_address)
133+
134+
# Ok, we should be in bootloader now
135+
136+
# Ensure bootloader is running with 24MHz clock
137+
assert( i2c_read8(bootloader_i2c_addr, 0xF7) & 0x10 )
138+
139+
confirm_id(bootloader_i2c_addr, bootloader_id)
140+
confirm_bootloader_version(bootloader_version)
141+
142+
set_default_boot_bootloader()
143+
144+
# Flash the program!
145+
for page in range(len(bin_data)//PAGE_SIZE):
146+
i=0
147+
# for i in range(PAGE_SIZE):
148+
while i<PAGE_SIZE:
149+
start_offset = page*PAGE_SIZE + i
150+
next_offset = page*PAGE_SIZE + i + I2C_WRITE_MAX_SIZE
151+
# print("Writing bytes from", start_offset, "to", next_offset-1)
152+
i2c_write_bytes( bootloader_i2c_addr, REG_APROM_FLASH+i, bin_data[ start_offset : next_offset ] )
153+
i += I2C_WRITE_MAX_SIZE
154+
# i2c_write8(bootloader_i2c_addr, REG_APROM_FLASH+i, bin_data[page*PAGE_SIZE + i])
155+
set_flash_page(page)
156+
write_page_to_aprom()
157+
print(f"Page {page} written to APROM")
158+
159+
print("Code written, verifying...")
160+
for page in range(len(bin_data)//PAGE_SIZE):
161+
print("Verifying page", page)
162+
set_flash_page(page)
163+
read_page_from_aprom()
164+
# for i in range(PAGE_SIZE):
165+
i=0
166+
while i< PAGE_SIZE:
167+
read_length = min(I2C_WRITE_MAX_SIZE, PAGE_SIZE-i)
168+
read_data = i2c_read_bytes(bootloader_i2c_addr, REG_APROM_FLASH+i, read_length)
169+
170+
start_offset = page*PAGE_SIZE + i
171+
wr_data = bin_data[ start_offset : start_offset+read_length ]
172+
173+
# print("read_data, wr_data", read_data)
174+
# print (wr_data)
175+
176+
if (read_data != wr_data):
177+
print("Flash verification failed, exiting..")
178+
return
179+
i += read_length
180+
181+
print("Flash verified, setting the newly flashed program as the default boot option and running it...")
182+
183+
set_default_boot_main_program()
184+
185+
jump_to_main_program()
186+
187+
188+
189+
if __name__ == "__main__":
190+
filename = sys.argv[1];
191+
# if bin_filename.endswith(".bin"):
192+
# bin_data = open(bin_filename, "rb").read()
193+
# bin_data = [ord(x) for x in bin_data]
194+
# elif bin_filename.endswith(".hex"):
195+
196+
from intelhex import IntelHex
197+
ih = IntelHex()
198+
ih.fromfile(filename,format='hex')
199+
200+
target_i2c = ih[200000]
201+
target_chipid = ih[200001]
202+
target_chipid += ih[200002] << 8
203+
nuvoton_class = ih[200003]
204+
saved_crc = ih[200004]
205+
saved_crc += ih[200005] << 8
206+
207+
assert( nuvoton_class in [16, 32] )
208+
209+
if nuvoton_class == 32:
210+
bootloader_id = 0xB004
211+
bootloader_version = 211
212+
flash_size = 32768-2048
213+
214+
del ih[200004]
215+
del ih[200005]
216+
217+
bin_data = list(ih.tobinarray())
218+
crc = binascii.crc_hqx(bytearray(bin_data), 0)
219+
220+
# print("crc, saved_crc", hex(crc), hex(saved_crc))
221+
assert( saved_crc == crc )
222+
223+
for addr in range(200000, 200004):
224+
del ih[addr]
225+
226+
bin_data = list(ih.tobinarray())
227+
228+
print("Firmware length", len(bin_data))
229+
# print(repr(bin_data[:30]))
230+
231+
# chip_id = int( bin_filename.split(".")[0].split("_")[-1] , 16)
232+
# i2c_address = int( bin_filename.split(".")[0].split("_")[-2] , 16)
233+
# file_crc = int( bin_filename.split(".")[0].split("_")[-3] , 16)
234+
# crc = binascii.crc_hqx(str(bin_data), 0)
235+
# print("Calculated CRC:", hex(crc))
236+
# assert( file_crc == crc )
237+
238+
firmware_update(bin_data, target_i2c, target_chipid)

firmware/readme.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Inventor HAT Mini - Firmware Flashing
2+
3+
## Issue
4+
5+
It was recently reported that a number of [Inventor HAT Mini](https://shop.pimoroni.com/products/inventor-hat-mini) boards were unable to read
6+
the encoders of any connected motors, instead returning zero. After much investigation it was discovered that these boards had been flashed
7+
at our factory with an older firmware version than what was intended, that lacked the encoder subsystem. All other features of the boards are unaffected.
8+
9+
If you encounter this issue with your board, you can either reflash the board yourself using the instructions below, or reach out to Pimoroni support for a replacement at `support@pimoroni.com` (or via the form at https://pimoroni.freshdesk.com/support/tickets/new)
10+
11+
## Instructions
12+
13+
To be able to flash your Inventor HAT Mini, it needs its I2C address to be `0x16`. By default Inventor HAT Mini ships with address `0x17`, so this will need to be changed before flashing, and then changed back afterwards.
14+
15+
### Identify Address
16+
17+
First, identify your board's I2C address. This can be done by running `i2cdetect -y 1` on your Pi. You should see a printout similar to this:
18+
19+
```
20+
0 1 2 3 4 5 6 7 8 9 a b c d e f
21+
00: -- -- -- -- -- -- -- --
22+
10: -- -- -- -- -- -- -- 17 -- -- -- -- -- -- -- --
23+
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
24+
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
25+
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
26+
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
27+
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
28+
70: -- -- -- -- -- -- -- --
29+
```
30+
31+
This confirms the board has address `0x17`.
32+
33+
### Change Address
34+
35+
Next, change the board's address by running the `set_i2c_address.py` script that is in this directory.
36+
37+
```
38+
python3 ./set_i2c_address 0x17 0x16
39+
```
40+
41+
You should see an output similar to this:
42+
```
43+
Waiting for flash writing to start..
44+
flash write ongoing
45+
flash write ongoing
46+
...
47+
flash write ongoing
48+
flash write ongoing
49+
flash write finished
50+
```
51+
52+
:information-source: If you see `flash write ongoing` printed out repeatedly for more than a few seconds, it is likely the address changed worked but the acknowledgement was missed. So cancel the program by pressing CTRL+C on your keyboard.
53+
54+
Run `i2cdetect -y 1` again to confirm the change:
55+
56+
```
57+
0 1 2 3 4 5 6 7 8 9 a b c d e f
58+
00: -- -- -- -- -- -- -- --
59+
10: -- -- -- -- -- -- 16 -- -- -- -- -- -- -- -- --
60+
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
61+
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
62+
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
63+
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
64+
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
65+
70: -- -- -- -- -- -- -- --
66+
```
67+
68+
### Flash the Firmware
69+
70+
With the Inventor HAT Mini having an address of `0x16` it can now be flashed. To do this run the `firmware_update.py` script that is in this directory, giving it the firmware file:
71+
72+
:warning: You will need to `pip install intelhex` for this to work
73+
74+
```
75+
python ./firmware_update.py "super_io.fw"
76+
```
77+
78+
You should see an output similar to this:
79+
80+
```
81+
Firmware length 30720
82+
confirming I2C:0x16, Chip ID:0x510e, Read ID:0x510e
83+
Entering bootloader...
84+
confirming I2C:0x6f, Chip ID:0xb004, Read ID:0b004
85+
confirmed bootloader version 211
86+
Changed default boot to bootloader
87+
Page 0 written to APROM
88+
Page 1 written to APROM
89+
...
90+
Page 238 written to APROM
91+
Page 239 written to APROM
92+
Code written, verifying...
93+
Verifying page 0
94+
Verifying page 1
95+
...
96+
Verifying page 238
97+
Verifying page 239
98+
Flash verified, setting the newly flashed program as the default boot option and running it...
99+
Changed default boot to main program
100+
Jumped to start main program execution
101+
```
102+
103+
If you get an error message like `Flash verification failed, exiting..`, try re-running the firmware update command again. If it still fails, reach out to Pimoroni support.
104+
105+
106+
### Restoring the Address
107+
108+
With the firmware successfully flashed, the Inventor HAT Mini's address can be restored back to `0x17` (or any other value you wish to use) by following the same steps as in the [Change Address](#change-address) step but with the values reversed:
109+
110+
```
111+
python ./set_i2c_address 0x16 0x17
112+
```
113+
114+
Now your Inventor HAT Mini has been flashed with the latest firmware, letting its encoders be used.

0 commit comments

Comments
 (0)