abse/smart-ring-flex
A tscircuit package created by abse.
- Version
- 0.0.24
- License
- unset
- Stars
- 0
firmware/onboard-programmer/upstream/tools/chprog.py
#!/usr/bin/env python3
# ===================================================================================
# Project: chprog - Programming Tool for CH55x Microcontrollers
# Version: v1.2
# Year: 2022
# Author: Stefan Wagner
# Github: https://github.com/wagiminator
# License: MIT License
# ===================================================================================
#
# Description:
# ------------
# Simple Python tool for flashing CH55x series microcontrollers with bootloader
# versions 1.x and 2.x.x.
#
# References:
# -----------
# chprog is based on chflasher and wchprog:
# - https://ATCnetz.de (Aaron Christophel)
# - https://github.com/atc1441/chflasher (Aaron Christophel)
# - https://github.com/juliuswwj/wchprog
#
# Dependencies:
# -------------
# - pyusb
#
# Operating Instructions:
# -----------------------
# You need to install PyUSB to use chprog. Install it via "python -m pip install pyusb".
#
# Linux users need permission to access the device. Run:
# echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="4348", ATTR{idProduct}=="55e0", MODE="666"' | sudo tee /etc/udev/rules.d/99-ch55x.rules
# sudo udevadm control --reload-rules
#
# On Windows you will need the Zadig tool (https://zadig.akeo.ie/) to install the
# correct driver. Click "Options" and "List All Devices" to select the USB module.
# Then install the libusb-win32 driver.
#
# Connect the CH55x via USB to your PC. The CH55x must be in bootloader mode!
# Run "python3 chprog.py firmware.bin".
import usb.core
import usb.util
import sys
# ===================================================================================
# Main Function
# ===================================================================================
def _main():
if len(sys.argv) != 2:
sys.stderr.write('ERROR: No bin file selected!\n')
sys.exit(1)
try:
print('Connecting to device ...')
isp = Programmer()
isp.detect()
print('FOUND:', isp.chipname, 'with bootloader v' + isp.bootloader + '.')
print('Erasing chip ...')
isp.erase()
print('Flashing', sys.argv[1], 'to', isp.chipname, '...')
with open(sys.argv[1], 'rb') as f: data = f.read()
isp.flash_data(data)
print('SUCCESS:', len(data), 'bytes written.')
print('Verifying ...')
isp.verify_data(data)
print('SUCCESS:', len(data), 'bytes verified.')
isp.exit()
except Exception as ex:
sys.stderr.write('ERROR: ' + str(ex) + '!\n')
sys.exit(1)
print('DONE.')
sys.exit(0)
# ===================================================================================
# Programmer Class
# ===================================================================================
class Programmer:
def __init__(self):
# Find device
dev = usb.core.find(idVendor = CH_USB_VENDOR_ID, idProduct = CH_USB_PRODUCT_ID)
if dev is None:
raise Exception('CH55x not found. Check if device is in BOOT mode')
# Set configuration, find endpoints
try:
dev.set_configuration()
except:
raise Exception('Failed to access USB Device. Check permissions')
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
self.epout = usb.util.find_descriptor(intf, custom_match = lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT)
self.epin = usb.util.find_descriptor(intf, custom_match = lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
assert self.epout is not None
assert self.epin is not None
# Clear USB receive buffer
try: self.epin.read(CH_USB_PACKET_SIZE, 1)
except: None
# Set initial chip parameters
self.device = None
self.xorkey = bytearray(CH_XOR_KEY_LEN)
# Detect, identify and setup chip
def detect(self):
# Identify chip
identanswer = self.__sendcmd(CH_STR_CHIP_DETECT_V2)
if len(identanswer) == 2:
self.chipversion = 1
self.__identchipv1()
elif len(identanswer) == 6:
self.chipversion = 2
self.__identchipv2()
else:
raise Exception('Failed to identify chip')
# Find device in dictionary
self.chipid = (self.chipfamily << 8) + self.chiptype
for d in DEVICES:
if d['id'] == self.chipid:
self.device = d
if self.device is None:
raise Exception('Unsupported chip (ID: 0x%04x)' % self.chipid)
# Set parameters according to dictionary
self.chipname = self.device['name']
self.code_flash_size = self.device['code_size']
self.data_flash_size = self.device['data_size']
# Erase code flash
def erase(self):
if self.chipversion == 1:
self.__erasev1()
else:
self.__erasev2()
# Write binary file to code flash
def flash_bin(self, filename):
with open(filename, 'rb') as f: data = f.read()
self.erase()
self.flash_data(data)
return len(data)
# Compare binary file with code flash
def verify_bin(self, filename):
with open(filename, 'rb') as f: data = f.read()
self.verify_data(data)
return len(data)
# Write data stream to code flash
def flash_data(self, data):
if len(data) > self.code_flash_size:
raise Exception('Not enough memory')
if self.chipversion == 1:
self.__writev1(data, CH_CMD_CODE_WRITE_V1)
else:
self.__writev2(data, CH_CMD_CODE_WRITE_V2)
# Verify data stream in code flash
def verify_data(self, data):
if len(data) > self.code_flash_size:
raise Exception('Not enough memory')
if self.chipversion == 1:
self.__writev1(data, CH_CMD_CODE_VERIFY_V1)
else:
self.__writev2(data, CH_CMD_CODE_VERIFY_V2)
# Reboot and exit
def exit(self):
if self.chipversion == 1:
self.__exitv1()
else:
self.__exitv2()
# Send command and return reply
def __sendcmd(self, cmd):
self.epout.write(cmd)
try: return self.epin.read(CH_USB_PACKET_SIZE, CH_USB_TIMEOUT)
except: return None
def __identchipv1(self):
# Read type of chip
identanswer = self.__sendcmd(CH_STR_CHIP_DETECT_V1)
if len(identanswer) != 2:
raise Exception('Failed to identify chip')
# Read configuration
cfganswer = self.__sendcmd(CH_STR_CONFIG_READ_V1)
if len(cfganswer) != 2:
raise Exception('Failed to read chip configuration')
# Set chip and configuration values
self.chiptype = identanswer[0]
self.chipfamily = identanswer[1]
self.bootloader = str(cfganswer[0] >> 4) + '.' + str(cfganswer[0] & 0xf)
def __identchipv2(self):
# Read type of chip
identanswer = self.__sendcmd(CH_STR_CHIP_DETECT_V2)
if len(identanswer) != 6:
raise Exception('Failed to identify chip')
# Read configuration
cfganswer = self.__sendcmd(CH_STR_CONFIG_READ_V2)
if len(cfganswer) != 30:
raise Exception('Failed to read chip configuration')
# Set chip and configuration values
self.chiptype = identanswer[4]
self.chipfamily = identanswer[5]
self.configdata = cfganswer[6:18]
self.chipuid = cfganswer[22:26]
self.bootloader = '%d.%d.%d' % (cfganswer[19], cfganswer[20], cfganswer[21])
if self.chipfamily != 0x11:
raise Exception('Unsupported chip (ID: 0x%02x%02x)' % (self.chipfamily, self.chiptype))
# Create local encryption key
sum = 0
for x in range(4):
sum += self.chipuid[x]
for x in range(CH_XOR_KEY_LEN - 1):
self.xorkey[x] = sum & 0xff
self.xorkey[CH_XOR_KEY_LEN - 1] = (sum + self.chiptype) & 0xff
# Send encryption key
sum = 0
for x in range(CH_XOR_KEY_LEN):
sum += self.xorkey[x]
stream = bytes((CH_CMD_KEY_SET_V2, 0x1e, 0x00)) + bytes(0x1e)
reply = self.__sendcmd(stream)
if reply[4] != (sum & 0xff):
raise Exception('Failed to set encryption key')
def __erasev1(self):
self.__sendcmd((0xa6, 0x04, 0x00, 0x00, 0x00, 0x00))
for x in range(self.code_flash_size // 1024):
reply = __sendcmd((0xa9, 0x02, 0x00, x * 4))
if reply[0] != 0x00:
raise Exception('Failed to erase chip')
def __erasev2(self):
reply = self.__sendcmd((0xa4, 0x01, 0x00, self.code_flash_size // 1024))
if reply[4] != 0x00:
raise Exception('Failed to erase chip')
def __exitv1(self):
self.epout.write(CH_STR_REBOOT_V1)
def __exitv2(self):
self.epout.write(CH_STR_REBOOT_V2)
def __writev1(self, data, mode):
# Send data in chunks of 60 bytes
data = bytearray(data)
rest = len(data)
offset = 0
while rest > 0:
if rest >= 0x3c: pkt_len = 0x3c
else: pkt_len = rest
stream = bytes((mode, pkt_len))
stream += offset.to_bytes(2, byteorder='little')
stream += data[offset:(offset + pkt_len)]
reply = self.__sendcmd(stream)
if reply[0] != 0x00:
if mode == CH_CMD_CODE_WRITE_V1:
raise Exception('Failed to write at offset 0x%08x' % offset)
elif mode == CH_CMD_CODE_VERIFY_V1:
raise Exception('Failed to verify at offset 0x%08x' % offset)
offset += pkt_len
rest -= pkt_len
def __writev2(self, data, mode):
# Encrypt data
if (len(data) % CH_XOR_KEY_LEN) > 0:
data += b'0xff' * (CH_XOR_KEY_LEN - (len(data) % CH_XOR_KEY_LEN))
data = bytearray(data)
for x in range(len(data)):
data[x] ^= self.xorkey[x % CH_XOR_KEY_LEN]
# Send data in chunks of 56 bytes
rest = len(data)
offset = 0
while rest > 0:
if rest >= 0x38: pkt_len = 0x38
else: pkt_len = rest
stream = bytes((mode, pkt_len + 5, 0))
stream += offset.to_bytes(4, byteorder='little')
stream += (rest & 0xff).to_bytes(1, byteorder='little')
stream += data[offset:(offset + pkt_len)]
reply = self.__sendcmd(stream)
if reply[4] != 0x00 and reply[4] != 0xfe and reply[4] != 0xf5:
if mode == CH_CMD_CODE_WRITE_V2:
raise Exception('Failed to write at offset 0x%08x' % offset)
elif mode == CH_CMD_CODE_VERIFY_V2:
raise Exception('Failed to verify at offset 0x%08x' % offset)
offset += pkt_len
rest -= pkt_len
# ===================================================================================
# CH55x Protocol Constants
# ===================================================================================
CH_USB_VENDOR_ID = 0x4348 # VID
CH_USB_PRODUCT_ID = 0x55e0 # PID
CH_USB_PACKET_SIZE = 64 # packet size
CH_USB_TIMEOUT = 5000 # timeout for USB operations
CH_CMD_CHIP_TYPE_V2 = 0xa2
CH_CMD_REBOOT_V2 = 0xa5
CH_CMD_CODE_ERASE_V2 = 0xa6
CH_CMD_CODE_VERIFY_V1 = 0xa7
CH_CMD_CODE_WRITE_V1 = 0xa8
CH_CMD_CHIP_TYPE_V2 = 0xa1
CH_CMD_REBOOT_V2 = 0xa2
CH_CMD_KEY_SET_V2 = 0xa3
CH_CMD_CODE_ERASE_V2 = 0xa4
CH_CMD_CODE_WRITE_V2 = 0xa5
CH_CMD_CODE_VERIFY_V2 = 0xa6
CH_STR_CHIP_DETECT_V1 = (0xa2, 0x13, 0x55, 0x53, 0x42, 0x20, 0x44, 0x42, 0x47, 0x20, 0x43,
0x48, 0x35, 0x35, 0x39, 0x20, 0x26, 0x20, 0x49, 0x53, 0x50, 0x00)
CH_STR_CONFIG_READ_V1 = (0xbb, 0x00)
CH_STR_CODE_ERASE_V1 = (0xa6, 0x04, 0x00, 0x00, 0x00, 0x00)
CH_STR_REBOOT_V1 = (0xa5, 0x02, 0x01, 0x00)
CH_STR_CHIP_DETECT_V2 = (0xa1, 0x12, 0x00, 0x52, 0x11, 0x4d, 0x43, 0x55, 0x20, 0x49, 0x53,
0x50, 0x20, 0x26, 0x20, 0x57, 0x43, 0x48, 0x2e, 0x43, 0x4e)
CH_STR_CONFIG_READ_V2 = (0xa7, 0x02, 0x00, 0x1f, 0x00)
CH_STR_CODE_ERASE_V2 = (0xa4, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00)
CH_STR_REBOOT_V2 = (0xa2, 0x01, 0x00, 0x01)
CH_XOR_KEY_LEN = 8
# ===================================================================================
# Device definitions
# ===================================================================================
DEVICES = [
{'name': 'CH551', 'id': 0x1151, 'code_size': 10240, 'data_size': 128},
{'name': 'CH552', 'id': 0x1152, 'code_size': 14336, 'data_size': 128},
{'name': 'CH554', 'id': 0x1154, 'code_size': 14336, 'data_size': 128},
{'name': 'CH555', 'id': 0x1155, 'code_size': 61440, 'data_size': 1024},
{'name': 'CH556', 'id': 0x1156, 'code_size': 61440, 'data_size': 1024},
{'name': 'CH557', 'id': 0x1157, 'code_size': 61440, 'data_size': 1024},
{'name': 'CH558', 'id': 0x1158, 'code_size': 61440, 'data_size': 1024},
{'name': 'CH559', 'id': 0x1159, 'code_size': 61440, 'data_size': 1024}
]
# ===================================================================================
if __name__ == "__main__":
_main()