1620 lines
59 KiB
Python
1620 lines
59 KiB
Python
#
|
|
# Copyright (c) 2013-2015 Grigori Goronzy <greg@chown.ath.cx>
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
#
|
|
|
|
import serial
|
|
import sys, os, time, struct, re, errno
|
|
import argparse
|
|
import collections
|
|
from stcgal.models import MCUModelDatabase
|
|
from stcgal.utils import Utils
|
|
from stcgal.options import *
|
|
import functools
|
|
|
|
try:
|
|
import usb.core, usb.util
|
|
_usb_available = True
|
|
except ImportError:
|
|
_usb_available = False
|
|
|
|
|
|
class StcFramingException(Exception):
|
|
"""Something wrong with packet framing or checksum"""
|
|
pass
|
|
|
|
|
|
class StcProtocolException(Exception):
|
|
"""High-level protocol issue, like wrong packet type"""
|
|
pass
|
|
|
|
|
|
class StcBaseProtocol:
|
|
"""Basic functionality for STC BSL protocols"""
|
|
|
|
"""magic word that starts a packet"""
|
|
PACKET_START = bytes([0x46, 0xb9])
|
|
|
|
"""magic byte that ends a packet"""
|
|
PACKET_END = bytes([0x16])
|
|
|
|
"""magic byte for packets received from MCU"""
|
|
PACKET_MCU = bytes([0x68])
|
|
|
|
"""magic byte for packets sent by host"""
|
|
PACKET_HOST = bytes([0x6a])
|
|
|
|
PARITY = serial.PARITY_NONE
|
|
|
|
def __init__(self, port, baud_handshake, baud_transfer):
|
|
self.port = port
|
|
self.baud_handshake = baud_handshake
|
|
self.baud_transfer = baud_transfer
|
|
|
|
self.mcu_magic = 0
|
|
self.mcu_clock_hz = 0.0
|
|
self.mcu_bsl_version = ""
|
|
self.options = None
|
|
self.model = None
|
|
self.uid = None
|
|
self.debug = False
|
|
self.status_packet = None
|
|
self.protocol_name = None
|
|
|
|
def dump_packet(self, data, receive=True):
|
|
if self.debug:
|
|
print("%s Packet data: %s" % (("<-" if receive else "->"),
|
|
Utils.hexstr(data, " ")), file=sys.stderr)
|
|
|
|
def read_bytes_safe(self, num):
|
|
"""Read data from serial port with timeout handling
|
|
|
|
Read timeouts should raise an exception, that is the Python way."""
|
|
|
|
data = self.ser.read(num)
|
|
if len(data) != num:
|
|
raise serial.SerialTimeoutException("read timeout")
|
|
|
|
return data
|
|
|
|
def extract_payload(self, packet):
|
|
"""Extract the payload of a packet"""
|
|
|
|
if packet[-1] != self.PACKET_END[0]:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("incorrect frame end")
|
|
|
|
return packet[5:-1]
|
|
|
|
def read_packet(self):
|
|
"""Read and check packet from MCU.
|
|
|
|
Reads a packet of data from the MCU and and do
|
|
validity and checksum checks on it.
|
|
|
|
Returns packet payload or None in case of an error.
|
|
"""
|
|
|
|
# read and check frame start magic
|
|
packet = bytes()
|
|
packet += self.read_bytes_safe(1)
|
|
# Some (?) BSL versions don't send a frame start with the status
|
|
# packet. Let's be liberal and accept that always, just in case.
|
|
if packet[0] == self.PACKET_MCU[0]:
|
|
packet = self.PACKET_START + self.PACKET_MCU
|
|
else:
|
|
if packet[0] != self.PACKET_START[0]:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("incorrect frame start")
|
|
packet += self.read_bytes_safe(1)
|
|
if packet[1] != self.PACKET_START[1]:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("incorrect frame start")
|
|
|
|
# read direction
|
|
packet += self.read_bytes_safe(1)
|
|
if packet[2] != self.PACKET_MCU[0]:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("incorrect packet direction magic")
|
|
|
|
# read length
|
|
packet += self.read_bytes_safe(2)
|
|
|
|
# read packet data
|
|
packet_len, = struct.unpack(">H", packet[3:5])
|
|
packet += self.read_bytes_safe(packet_len - 3)
|
|
|
|
# verify checksum and extract payload
|
|
payload = self.extract_payload(packet)
|
|
|
|
self.dump_packet(packet, receive=True)
|
|
|
|
# payload only is returned
|
|
return payload
|
|
|
|
def print_mcu_info(self):
|
|
"""Print MCU status information"""
|
|
|
|
MCUModelDatabase.print_model_info(self.model)
|
|
print("Target frequency: %.3f MHz" % (self.mcu_clock_hz / 1E6))
|
|
print("Target BSL version: %s" % self.mcu_bsl_version)
|
|
|
|
def pulse(self, character=b"\x7f", timeout=0):
|
|
"""Send a sequence of bytes for synchronization with MCU"""
|
|
|
|
duration = 0
|
|
while True:
|
|
if timeout > 0 and duration > timeout:
|
|
raise serial.SerialTimeoutException("pulse timeout")
|
|
self.ser.write(character)
|
|
self.ser.flush()
|
|
time.sleep(0.015)
|
|
duration += 0.015
|
|
if self.ser.inWaiting() > 0: break
|
|
|
|
def initialize_model(self):
|
|
"""Initialize model-specific information"""
|
|
|
|
self.mcu_magic, = struct.unpack(">H", self.status_packet[20:22])
|
|
try:
|
|
self.model = MCUModelDatabase.find_model(self.mcu_magic)
|
|
except NameError:
|
|
msg = ("WARNING: Unknown model %02X%02X!" %
|
|
(self.mcu_magic >> 8, self.mcu_magic & 0xff))
|
|
print(msg, file=sys.stderr)
|
|
self.model = MCUModelDatabase.MCUModel(name="UNKNOWN",
|
|
magic=self.mcu_magic, total=63488, code=63488, eeprom=0)
|
|
|
|
# special case for duplicated mcu magic,
|
|
# 0xf294 (STC15F104W, STC15F104E)
|
|
# 0xf2d4 (STC15L104W, STC15L104E)
|
|
# duplicated mcu magic can be found using command,
|
|
# grep -o 'magic=[^,]*' models.py | sort | uniq -d
|
|
if self.mcu_magic in (0xF294, 0xF2D4):
|
|
mcu_name = self.model.name[:-1]
|
|
mcu_name += "E" if self.status_packet[17] < 0x70 else "W"
|
|
self.model = self.model._replace(name = mcu_name)
|
|
|
|
protocol_database = [("stc89", r"STC(89|90)(C|LE)\d"),
|
|
("stc12a", r"STC12(C|LE)\d052"),
|
|
("stc12b", r"STC12(C|LE)(52|56)"),
|
|
("stc12", r"(STC|IAP)(10|11|12)\D"),
|
|
("stc15a", r"(STC|IAP)15[FL][01]0\d(E|EA|)$"),
|
|
("stc15", r"(STC|IAP|IRC)15\D")]
|
|
|
|
for protocol_name, pattern in protocol_database:
|
|
if re.match(pattern, self.model.name):
|
|
self.protocol_name = protocol_name
|
|
break
|
|
else:
|
|
self.protocol_name = None
|
|
|
|
def get_status_packet(self):
|
|
"""Read and decode status packet"""
|
|
|
|
packet = self.read_packet()
|
|
if packet[0] == 0x80:
|
|
# need to re-ack
|
|
self.ser.parity = serial.PARITY_EVEN
|
|
packet = (self.PACKET_START
|
|
+ self.PACKET_HOST
|
|
+ bytes([0x00, 0x07, 0x80, 0x00, 0xF1])
|
|
+ self.PACKET_END)
|
|
self.dump_packet(packet, receive=False)
|
|
self.ser.write(packet)
|
|
self.ser.flush()
|
|
self.pulse()
|
|
packet = self.read_packet()
|
|
return packet
|
|
|
|
def get_iap_delay(self, clock_hz):
|
|
"""IAP wait states for STC12A+ (according to datasheet(s))"""
|
|
|
|
iap_wait = 0x80
|
|
if clock_hz < 1E6: iap_wait = 0x87
|
|
elif clock_hz < 2E6: iap_wait = 0x86
|
|
elif clock_hz < 3E6: iap_wait = 0x85
|
|
elif clock_hz < 6E6: iap_wait = 0x84
|
|
elif clock_hz < 12E6: iap_wait = 0x83
|
|
elif clock_hz < 20E6: iap_wait = 0x82
|
|
elif clock_hz < 24E6: iap_wait = 0x81
|
|
|
|
return iap_wait
|
|
|
|
def set_option(self, name, value):
|
|
self.options.set_option(name, value)
|
|
|
|
def reset_device(self, resetcmd=False):
|
|
if not resetcmd:
|
|
print("Cycling power: ", end="")
|
|
sys.stdout.flush()
|
|
self.ser.setDTR(True)
|
|
time.sleep(0.5)
|
|
self.ser.setDTR(False)
|
|
print("done")
|
|
else:
|
|
print("Cycling power via shell cmd: " + resetcmd)
|
|
os.system(resetcmd)
|
|
|
|
print("Waiting for MCU: ", end="")
|
|
sys.stdout.flush()
|
|
|
|
def connect(self, autoreset=False, resetcmd=False):
|
|
"""Connect to MCU and initialize communication.
|
|
|
|
Set up serial port, send sync sequence and get part info.
|
|
"""
|
|
|
|
self.ser = serial.Serial(port=self.port, parity=self.PARITY)
|
|
# set baudrate separately to work around a bug with the CH340 driver
|
|
# on older Linux kernels
|
|
self.ser.baudrate = self.baud_handshake
|
|
|
|
# fast timeout values to deal with detection errors
|
|
self.ser.timeout = 0.5
|
|
self.ser.interCharTimeout = 0.5
|
|
|
|
# avoid glitches if there is something in the input buffer
|
|
self.ser.flushInput()
|
|
|
|
if autoreset:
|
|
self.reset_device(resetcmd)
|
|
else:
|
|
print("Waiting for MCU, please cycle power: ", end="")
|
|
sys.stdout.flush()
|
|
|
|
# send sync, and wait for MCU response
|
|
# ignore errors until we see a valid response
|
|
self.status_packet = None
|
|
while not self.status_packet:
|
|
try:
|
|
self.pulse()
|
|
self.status_packet = self.get_status_packet()
|
|
except (StcFramingException, serial.SerialTimeoutException): pass
|
|
print("done")
|
|
|
|
# conservative timeout values
|
|
self.ser.timeout = 15.0
|
|
self.ser.interCharTimeout = 1.0
|
|
|
|
self.initialize_model()
|
|
|
|
def initialize(self, base_protocol = None):
|
|
if base_protocol:
|
|
self.ser = base_protocol.ser
|
|
self.ser.parity = self.PARITY
|
|
packet = base_protocol.status_packet
|
|
packet = (self.PACKET_START
|
|
+ self.PACKET_MCU
|
|
+ struct.pack(">H", len(packet) + 4)
|
|
+ packet
|
|
+ self.PACKET_END)
|
|
self.status_packet = self.extract_payload(packet)
|
|
self.mcu_magic = base_protocol.mcu_magic
|
|
self.model = base_protocol.model
|
|
|
|
self.initialize_status(self.status_packet)
|
|
self.print_mcu_info()
|
|
self.initialize_options(self.status_packet)
|
|
|
|
def disconnect(self):
|
|
"""Disconnect from MCU"""
|
|
|
|
# reset mcu
|
|
packet = bytes([0x82])
|
|
self.write_packet(packet)
|
|
self.ser.close()
|
|
print("Disconnected!")
|
|
|
|
|
|
class Stc89Protocol(StcBaseProtocol):
|
|
"""Protocol handler for STC 89/90 series"""
|
|
|
|
"""These don't use any parity"""
|
|
PARITY = serial.PARITY_NONE
|
|
|
|
"""block size for programming flash"""
|
|
PROGRAM_BLOCKSIZE = 128
|
|
|
|
def __init__(self, port, baud_handshake, baud_transfer):
|
|
StcBaseProtocol.__init__(self, port, baud_handshake, baud_transfer)
|
|
|
|
self.cpu_6t = None
|
|
|
|
def extract_payload(self, packet):
|
|
"""Verify the checksum of packet and return its payload"""
|
|
|
|
packet_csum = packet[-2]
|
|
calc_csum = sum(packet[2:-2]) & 0xff
|
|
if packet_csum != calc_csum:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("packet checksum mismatch")
|
|
|
|
payload = StcBaseProtocol.extract_payload(self, packet)
|
|
return payload[:-1]
|
|
|
|
def write_packet(self, data):
|
|
"""Send packet to MCU.
|
|
|
|
Constructs a packet with supplied payload and sends it to the MCU.
|
|
"""
|
|
|
|
# frame start and direction magic
|
|
packet = bytes()
|
|
packet += self.PACKET_START
|
|
packet += self.PACKET_HOST
|
|
|
|
# packet length and payload
|
|
packet += struct.pack(">H", len(data) + 5)
|
|
packet += data
|
|
|
|
# checksum and end code
|
|
packet += bytes([sum(packet[2:]) & 0xff])
|
|
packet += self.PACKET_END
|
|
|
|
self.dump_packet(packet, receive=False)
|
|
self.ser.write(packet)
|
|
self.ser.flush()
|
|
|
|
def get_status_packet(self):
|
|
"""Read and decode status packet"""
|
|
|
|
status_packet = self.read_packet()
|
|
if status_packet[0] != 0x00:
|
|
raise StcProtocolException("incorrect magic in status packet")
|
|
return status_packet
|
|
|
|
def initialize_options(self, status_packet):
|
|
"""Initialize options"""
|
|
|
|
self.options = Stc89Option(status_packet[19])
|
|
self.options.print()
|
|
|
|
def calculate_baud(self):
|
|
"""Calculate MCU baudrate setting.
|
|
|
|
Calculate appropriate baudrate settings for the MCU's UART,
|
|
according to clock frequency and requested baud rate.
|
|
"""
|
|
|
|
# timing is different in 6T mode
|
|
sample_rate = 16 if self.cpu_6t else 32
|
|
# baudrate is directly controlled by programming the MCU's BRT register
|
|
brt = 65536 - round((self.mcu_clock_hz) / (self.baud_transfer * sample_rate))
|
|
brt_csum = (2 * (256 - brt)) & 0xff
|
|
baud_actual = (self.mcu_clock_hz) / (sample_rate * (65536 - brt))
|
|
baud_error = (abs(self.baud_transfer - baud_actual) * 100.0) / self.baud_transfer
|
|
if baud_error > 5.0:
|
|
print("WARNING: baudrate error is %.2f%%. You may need to set a slower rate." %
|
|
baud_error, file=sys.stderr)
|
|
|
|
# IAP wait states (according to datasheet(s))
|
|
iap_wait = 0x80
|
|
if self.mcu_clock_hz < 5E6: iap_wait = 0x83
|
|
elif self.mcu_clock_hz < 10E6: iap_wait = 0x82
|
|
elif self.mcu_clock_hz < 20E6: iap_wait = 0x81
|
|
|
|
# MCU delay after switching baud rates
|
|
delay = 0xa0
|
|
|
|
return brt, brt_csum, iap_wait, delay
|
|
|
|
def initialize_status(self, packet):
|
|
"""Decode status packet and store basic MCU info"""
|
|
|
|
self.cpu_6t = not bool(packet[19] & 1)
|
|
|
|
cpu_t = 6.0 if self.cpu_6t else 12.0
|
|
freq_counter = 0
|
|
for i in range(8):
|
|
freq_counter += struct.unpack(">H", packet[1+2*i:3+2*i])[0]
|
|
freq_counter /= 8.0
|
|
self.mcu_clock_hz = (self.baud_handshake * freq_counter * cpu_t) / 7.0
|
|
|
|
bl_version, bl_stepping = struct.unpack("BB", packet[17:19])
|
|
self.mcu_bsl_version = "%d.%d%s" % (bl_version >> 4, bl_version & 0x0f,
|
|
chr(bl_stepping))
|
|
|
|
def handshake(self):
|
|
"""Switch to transfer baudrate
|
|
|
|
Switches to transfer baudrate and verifies that the setting works with
|
|
a ping-pong exchange of packets."""
|
|
|
|
# check new baudrate
|
|
print("Switching to %d baud: " % self.baud_transfer, end="")
|
|
sys.stdout.flush()
|
|
brt, brt_csum, iap, delay = self.calculate_baud()
|
|
print("checking ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x8f])
|
|
packet += struct.pack(">H", brt)
|
|
packet += bytes([0xff - (brt >> 8), brt_csum, delay, iap])
|
|
self.write_packet(packet)
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
response = self.read_packet()
|
|
self.ser.baudrate = self.baud_handshake
|
|
if response[0] != 0x8f:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# switch to baudrate
|
|
print("setting ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x8e])
|
|
packet += struct.pack(">H", brt)
|
|
packet += bytes([0xff - (brt >> 8), brt_csum, delay])
|
|
self.write_packet(packet)
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
response = self.read_packet()
|
|
if response[0] != 0x8e:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# ping-pong test
|
|
print("testing ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x80, 0x00, 0x00, 0x36, 0x01])
|
|
packet += struct.pack(">H", self.mcu_magic)
|
|
for i in range(4):
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x80:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
print("done")
|
|
|
|
def erase_flash(self, erase_size, flash_size):
|
|
"""Erase the MCU's flash memory.
|
|
|
|
Erase the flash memory with a block-erase command.
|
|
flash_size is ignored; not used on STC 89 series.
|
|
"""
|
|
|
|
blks = ((erase_size + 511) // 512) * 2
|
|
print("Erasing %d blocks: " % blks, end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x84, blks, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x80:
|
|
raise StcProtocolException("incorrect magic in erase packet")
|
|
print("done")
|
|
|
|
def program_flash(self, data):
|
|
"""Program the MCU's flash memory.
|
|
|
|
Write data into flash memory, using the PROGRAM_BLOCKSIZE
|
|
as the block size (depends on MCU's RAM size).
|
|
"""
|
|
|
|
print("Writing %d bytes: " % len(data), end="")
|
|
sys.stdout.flush()
|
|
for i in range(0, len(data), self.PROGRAM_BLOCKSIZE):
|
|
packet = bytes(3)
|
|
packet += struct.pack(">H", i)
|
|
packet += struct.pack(">H", self.PROGRAM_BLOCKSIZE)
|
|
packet += data[i:i+self.PROGRAM_BLOCKSIZE]
|
|
while len(packet) < self.PROGRAM_BLOCKSIZE + 7: packet += b"\x00"
|
|
csum = sum(packet[7:]) & 0xff
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x80:
|
|
raise StcProtocolException("incorrect magic in write packet")
|
|
elif response[1] != csum:
|
|
raise StcProtocolException("verification checksum mismatch")
|
|
print(".", end="")
|
|
sys.stdout.flush()
|
|
print(" done")
|
|
|
|
def program_options(self):
|
|
"""Program option byte into flash"""
|
|
|
|
print("Setting options: ", end="")
|
|
sys.stdout.flush()
|
|
msr = self.options.get_msr()
|
|
packet = bytes([0x8d, msr, 0xff, 0xff, 0xff])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x8d:
|
|
raise StcProtocolException("incorrect magic in option packet")
|
|
print("done")
|
|
|
|
|
|
class Stc12AOptionsMixIn:
|
|
def program_options(self):
|
|
print("Setting options: ", end="")
|
|
sys.stdout.flush()
|
|
msr = self.options.get_msr()
|
|
packet = bytes([0x8d, msr[0], msr[1], msr[2], 0xff, msr[3]])
|
|
packet += struct.pack(">I", int(self.mcu_clock_hz))
|
|
packet += bytes([msr[3]])
|
|
packet += bytes([0xff, msr[0], msr[1], 0xff, 0xff, 0xff, 0xff, msr[2]])
|
|
packet += bytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
|
|
packet += struct.pack(">I", int(self.mcu_clock_hz))
|
|
packet += bytes([0xff, 0xff, 0xff])
|
|
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x80:
|
|
raise StcProtocolException("incorrect magic in option packet")
|
|
|
|
# XXX: this is done by STC-ISP on newer parts. not sure why, but let's
|
|
# just replicate it, just to be sure.
|
|
if self.bsl_version >= 0x66:
|
|
packet = bytes([0x50])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x10:
|
|
raise StcProtocolException("incorrect magic in option packet")
|
|
|
|
print("done")
|
|
|
|
|
|
class Stc12AProtocol(Stc12AOptionsMixIn, Stc89Protocol):
|
|
|
|
"""countdown value for flash erase"""
|
|
ERASE_COUNTDOWN = 0x0d
|
|
|
|
def __init__(self, port, baud_handshake, baud_transfer):
|
|
Stc89Protocol.__init__(self, port, baud_handshake, baud_transfer)
|
|
|
|
def initialize_status(self, packet):
|
|
"""Decode status packet and store basic MCU info"""
|
|
|
|
freq_counter = 0
|
|
for i in range(8):
|
|
freq_counter += struct.unpack(">H", packet[1+2*i:3+2*i])[0]
|
|
freq_counter /= 8.0
|
|
self.mcu_clock_hz = (self.baud_handshake * freq_counter * 12.0) / 7.0
|
|
|
|
bl_version, bl_stepping = struct.unpack("BB", packet[17:19])
|
|
self.mcu_bsl_version = "%d.%d%s" % (bl_version >> 4, bl_version & 0x0f,
|
|
chr(bl_stepping))
|
|
|
|
self.bsl_version = bl_version
|
|
|
|
def calculate_baud(self):
|
|
"""Calculate MCU baudrate setting.
|
|
|
|
Calculate appropriate baudrate settings for the MCU's UART,
|
|
according to clock frequency and requested baud rate.
|
|
"""
|
|
|
|
# baudrate is directly controlled by programming the MCU's BRT register
|
|
brt = 256 - round((self.mcu_clock_hz) / (self.baud_transfer * 16))
|
|
if brt <= 1 or brt > 255:
|
|
raise StcProtocolException("requested baudrate cannot be set")
|
|
brt_csum = (2 * (256 - brt)) & 0xff
|
|
baud_actual = (self.mcu_clock_hz) / (16 * (256 - brt))
|
|
baud_error = (abs(self.baud_transfer - baud_actual) * 100.0) / self.baud_transfer
|
|
if baud_error > 5.0:
|
|
print("WARNING: baudrate error is %.2f%%. You may need to set a slower rate." %
|
|
baud_error, file=sys.stderr)
|
|
|
|
# IAP wait states
|
|
iap_wait = self.get_iap_delay(self.mcu_clock_hz)
|
|
|
|
# MCU delay after switching baud rates
|
|
delay = 0x80
|
|
|
|
return brt, brt_csum, iap_wait, delay
|
|
|
|
def initialize_options(self, status_packet):
|
|
"""Initialize options"""
|
|
|
|
# create option state
|
|
self.options = Stc12AOption(status_packet[23:26] + status_packet[29:30])
|
|
self.options.print()
|
|
|
|
def handshake(self):
|
|
"""Do baudrate handshake
|
|
|
|
Initiate and do the (rather complicated) baudrate handshake.
|
|
"""
|
|
|
|
# start baudrate handshake
|
|
print("Switching to %d baud: " % self.baud_transfer, end="")
|
|
sys.stdout.flush()
|
|
brt, brt_csum, iap, delay = self.calculate_baud()
|
|
print("checking ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x8f, 0xc0, brt, 0x3f, brt_csum, delay, iap])
|
|
self.write_packet(packet)
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
response = self.read_packet()
|
|
self.ser.baudrate = self.baud_handshake
|
|
if response[0] != 0x8f:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# switch to the settings
|
|
print("setting ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x8e, 0xc0, brt, 0x3f, brt_csum, delay])
|
|
self.write_packet(packet)
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
response = self.read_packet()
|
|
if response[0] != 0x8e:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# ping-pong test
|
|
print("testing ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x80, 0x00, 0x00, 0x36, 0x01])
|
|
packet += struct.pack(">H", self.mcu_magic)
|
|
for i in range(4):
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x80:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
print("done")
|
|
|
|
def erase_flash(self, erase_size, flash_size):
|
|
"""Erase the MCU's flash memory.
|
|
|
|
Erase the flash memory with a block-erase command.
|
|
"""
|
|
|
|
blks = ((erase_size + 511) // 512) * 2
|
|
size = ((flash_size + 511) // 512) * 2
|
|
print("Erasing %d blocks: " % blks, end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x84, 0xff, 0x00, blks, 0x00, 0x00, size,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00])
|
|
for i in range(0x80, self.ERASE_COUNTDOWN, -1): packet += bytes([i])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x80:
|
|
raise StcProtocolException("incorrect magic in erase packet")
|
|
print("done")
|
|
|
|
|
|
class Stc12OptionsMixIn:
|
|
def program_options(self):
|
|
print("Setting options: ", end="")
|
|
sys.stdout.flush()
|
|
msr = self.options.get_msr()
|
|
# XXX: it's not 100% clear if the index of msr[3] is consistent
|
|
# between devices, so write it to both indices.
|
|
packet = bytes([0x8d, msr[0], msr[1], msr[2], msr[3],
|
|
0xff, 0xff, 0xff, 0xff, msr[3], 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
|
|
|
|
packet += struct.pack(">I", int(self.mcu_clock_hz))
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x50:
|
|
raise StcProtocolException("incorrect magic in option packet")
|
|
print("done")
|
|
|
|
# If UID wasn't sent with erase acknowledge, it should be in this packet
|
|
if not self.uid:
|
|
self.uid = response[18:25]
|
|
|
|
print("Target UID: %s" % Utils.hexstr(self.uid))
|
|
|
|
|
|
class Stc12BaseProtocol(StcBaseProtocol):
|
|
"""Base class for STC 10/11/12 series protocol handlers"""
|
|
|
|
"""block size for programming flash"""
|
|
PROGRAM_BLOCKSIZE = 128
|
|
|
|
"""countdown value for flash erase"""
|
|
ERASE_COUNTDOWN = 0x0d
|
|
|
|
"""Parity for error correction was introduced with STC12"""
|
|
PARITY = serial.PARITY_EVEN
|
|
|
|
def __init__(self, port, baud_handshake, baud_transfer):
|
|
StcBaseProtocol.__init__(self, port, baud_handshake, baud_transfer)
|
|
|
|
def extract_payload(self, packet):
|
|
"""Verify the checksum of packet and return its payload"""
|
|
|
|
packet_csum, = struct.unpack(">H", packet[-3:-1])
|
|
calc_csum = sum(packet[2:-3]) & 0xffff
|
|
if packet_csum != calc_csum:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("packet checksum mismatch")
|
|
|
|
payload = StcBaseProtocol.extract_payload(self, packet)
|
|
return payload[:-2]
|
|
|
|
def write_packet(self, data):
|
|
"""Send packet to MCU.
|
|
|
|
Constructs a packet with supplied payload and sends it to the MCU.
|
|
"""
|
|
|
|
# frame start and direction magic
|
|
packet = bytes()
|
|
packet += self.PACKET_START
|
|
packet += self.PACKET_HOST
|
|
|
|
# packet length and payload
|
|
packet += struct.pack(">H", len(data) + 6)
|
|
packet += data
|
|
|
|
# checksum and end code
|
|
packet += struct.pack(">H", sum(packet[2:]) & 0xffff)
|
|
packet += self.PACKET_END
|
|
|
|
self.dump_packet(packet, receive=False)
|
|
self.ser.write(packet)
|
|
self.ser.flush()
|
|
|
|
def initialize_status(self, packet):
|
|
"""Decode status packet and store basic MCU info"""
|
|
|
|
freq_counter = 0
|
|
for i in range(8):
|
|
freq_counter += struct.unpack(">H", packet[1+2*i:3+2*i])[0]
|
|
freq_counter /= 8.0
|
|
self.mcu_clock_hz = (self.baud_handshake * freq_counter * 12.0) / 7.0
|
|
|
|
bl_version, bl_stepping = struct.unpack("BB", packet[17:19])
|
|
self.mcu_bsl_version = "%d.%d%s" % (bl_version >> 4, bl_version & 0x0f,
|
|
chr(bl_stepping))
|
|
|
|
self.bsl_version = bl_version
|
|
|
|
def calculate_baud(self):
|
|
"""Calculate MCU baudrate setting.
|
|
|
|
Calculate appropriate baudrate settings for the MCU's UART,
|
|
according to clock frequency and requested baud rate.
|
|
"""
|
|
|
|
# baudrate is directly controlled by programming the MCU's BRT register
|
|
brt = 256 - round((self.mcu_clock_hz) / (self.baud_transfer * 16))
|
|
if brt <= 1 or brt > 255:
|
|
raise StcProtocolException("requested baudrate cannot be set")
|
|
brt_csum = (2 * (256 - brt)) & 0xff
|
|
baud_actual = (self.mcu_clock_hz) / (16 * (256 - brt))
|
|
baud_error = (abs(self.baud_transfer - baud_actual) * 100.0) / self.baud_transfer
|
|
if baud_error > 5.0:
|
|
print("WARNING: baudrate error is %.2f%%. You may need to set a slower rate." %
|
|
baud_error, file=sys.stderr)
|
|
|
|
# IAP wait states
|
|
iap_wait = self.get_iap_delay(self.mcu_clock_hz)
|
|
|
|
# MCU delay after switching baud rates
|
|
delay = 0x80
|
|
|
|
return brt, brt_csum, iap_wait, delay
|
|
|
|
def initialize_options(self, status_packet):
|
|
"""Initialize options"""
|
|
|
|
# create option state
|
|
self.options = Stc12Option(status_packet[23:26] + status_packet[27:28])
|
|
self.options.print()
|
|
|
|
def handshake(self):
|
|
"""Do baudrate handshake
|
|
|
|
Initate and do the (rather complicated) baudrate handshake.
|
|
"""
|
|
|
|
# start baudrate handshake
|
|
brt, brt_csum, iap, delay = self.calculate_baud()
|
|
print("Switching to %d baud: " % self.baud_transfer, end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x50, 0x00, 0x00, 0x36, 0x01])
|
|
packet += struct.pack(">H", self.mcu_magic)
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x8f:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# test new settings
|
|
print("testing ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x8f, 0xc0, brt, 0x3f, brt_csum, delay, iap])
|
|
self.write_packet(packet)
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
response = self.read_packet()
|
|
self.ser.baudrate = self.baud_handshake
|
|
if response[0] != 0x8f:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# switch to the settings
|
|
print("setting ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x8e, 0xc0, brt, 0x3f, brt_csum, delay])
|
|
self.write_packet(packet)
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
response = self.read_packet()
|
|
if response[0] != 0x84:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
print("done")
|
|
|
|
def erase_flash(self, erase_size, flash_size):
|
|
"""Erase the MCU's flash memory.
|
|
|
|
Erase the flash memory with a block-erase command.
|
|
"""
|
|
|
|
blks = ((erase_size + 511) // 512) * 2
|
|
size = ((flash_size + 511) // 512) * 2
|
|
print("Erasing %d blocks: " % blks, end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x84, 0xff, 0x00, blks, 0x00, 0x00, size,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00])
|
|
for i in range(0x80, self.ERASE_COUNTDOWN, -1): packet += bytes([i])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x00:
|
|
raise StcProtocolException("incorrect magic in erase packet")
|
|
print("done")
|
|
|
|
# UID, only sent with this packet by some BSLs
|
|
if len(response) >= 8:
|
|
self.uid = response[1:8]
|
|
|
|
def program_flash(self, data):
|
|
"""Program the MCU's flash memory.
|
|
|
|
Write data into flash memory, using the PROGRAM_BLOCKSIZE
|
|
as the block size (depends on MCU's RAM size).
|
|
"""
|
|
|
|
print("Writing %d bytes: " % len(data), end="")
|
|
sys.stdout.flush()
|
|
for i in range(0, len(data), self.PROGRAM_BLOCKSIZE):
|
|
packet = bytes(3)
|
|
packet += struct.pack(">H", i)
|
|
packet += struct.pack(">H", self.PROGRAM_BLOCKSIZE)
|
|
packet += data[i:i+self.PROGRAM_BLOCKSIZE]
|
|
while len(packet) < self.PROGRAM_BLOCKSIZE + 7: packet += b"\x00"
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x00:
|
|
raise StcProtocolException("incorrect magic in write packet")
|
|
print(".", end="")
|
|
sys.stdout.flush()
|
|
print(" done")
|
|
|
|
print("Finishing write: ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x69, 0x00, 0x00, 0x36, 0x01])
|
|
packet += struct.pack(">H", self.mcu_magic)
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x8d:
|
|
raise StcProtocolException("incorrect magic in finish packet")
|
|
print("done")
|
|
|
|
|
|
class Stc12Protocol(Stc12OptionsMixIn, Stc12BaseProtocol):
|
|
"""STC 10/11/12 series protocol handler"""
|
|
|
|
def __init__(self, port, handshake, baud):
|
|
Stc12BaseProtocol.__init__(self, port, handshake, baud)
|
|
|
|
|
|
class Stc12BProtocol(Stc12AOptionsMixIn, Stc12BaseProtocol):
|
|
"""STC 10/11/12 variant protocol handler"""
|
|
|
|
def __init__(self, port, handshake, baud):
|
|
Stc12BaseProtocol.__init__(self, port, handshake, baud)
|
|
|
|
|
|
class Stc15AProtocol(Stc12Protocol):
|
|
"""Protocol handler for STC 15 series"""
|
|
|
|
ERASE_COUNTDOWN = 0x5e
|
|
PROGRAM_BLOCKSIZE = 64
|
|
|
|
def __init__(self, port, handshake, baud, trim):
|
|
Stc12Protocol.__init__(self, port, handshake, baud)
|
|
|
|
self.trim_frequency = trim
|
|
self.trim_data = None
|
|
self.frequency_counter = 0
|
|
|
|
def initialize_options(self, status_packet):
|
|
"""Initialize options"""
|
|
|
|
# create option state
|
|
self.options = Stc15AOption(status_packet[23:36])
|
|
self.options.print()
|
|
|
|
def get_status_packet(self):
|
|
"""Read and decode status packet"""
|
|
|
|
status_packet = self.read_packet()
|
|
if status_packet[0] == 0x80:
|
|
# need to re-ack
|
|
packet = bytes([0x80])
|
|
self.write_packet(packet)
|
|
self.pulse()
|
|
status_packet = self.read_packet()
|
|
if status_packet[0] != 0x50:
|
|
raise StcProtocolException("incorrect magic in status packet")
|
|
return status_packet
|
|
|
|
def initialize_status(self, packet):
|
|
"""Decode status packet and store basic MCU info"""
|
|
|
|
freq_counter = 0
|
|
for i in range(4):
|
|
freq_counter += struct.unpack(">H", packet[1+2*i:3+2*i])[0]
|
|
freq_counter /= 4.0
|
|
self.mcu_clock_hz = (self.baud_handshake * freq_counter * 12.0) / 7.0
|
|
|
|
bl_version, bl_stepping = struct.unpack("BB", packet[17:19])
|
|
self.mcu_bsl_version = "%d.%d%s" % (bl_version >> 4, bl_version & 0x0f,
|
|
chr(bl_stepping))
|
|
|
|
self.trim_data = packet[51:58]
|
|
self.freq_counter = freq_counter
|
|
|
|
def get_trim_sequence(self, frequency):
|
|
"""Return frequency-specific coarse trim sequence"""
|
|
|
|
packet = bytes()
|
|
if frequency < 7.5E6:
|
|
packet += bytes([0x18, 0x00, 0x02, 0x00])
|
|
packet += bytes([0x18, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x18, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x18, 0xff, 0x02, 0x00])
|
|
elif frequency < 10E6:
|
|
packet += bytes([0x18, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x18, 0xff, 0x02, 0x00])
|
|
packet += bytes([0x58, 0x00, 0x02, 0x00])
|
|
packet += bytes([0x58, 0xff, 0x02, 0x00])
|
|
elif frequency < 15E6:
|
|
packet += bytes([0x58, 0x00, 0x02, 0x00])
|
|
packet += bytes([0x58, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x58, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x58, 0xff, 0x02, 0x00])
|
|
elif frequency < 21E6:
|
|
packet += bytes([0x58, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x58, 0xff, 0x02, 0x00])
|
|
packet += bytes([0x98, 0x00, 0x02, 0x00])
|
|
packet += bytes([0x98, 0x80, 0x02, 0x00])
|
|
elif frequency < 31E6:
|
|
packet += bytes([0x98, 0x00, 0x02, 0x00])
|
|
packet += bytes([0x98, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x98, 0x80, 0x02, 0x00])
|
|
packet += bytes([0x98, 0xff, 0x02, 0x00])
|
|
else:
|
|
packet += bytes([0xd8, 0x00, 0x02, 0x00])
|
|
packet += bytes([0xd8, 0x80, 0x02, 0x00])
|
|
packet += bytes([0xd8, 0x80, 0x02, 0x00])
|
|
packet += bytes([0xd8, 0xb4, 0x02, 0x00])
|
|
|
|
return packet
|
|
|
|
def handshake(self):
|
|
"""Initiate and do the frequency adjustment and baudrate
|
|
switch handshake.
|
|
|
|
This rather complicated handshake trims the MCU's calibrated RC
|
|
frequency and switches the baud rate at the same time.
|
|
|
|
Flash programming uses a fixed frequency and that frequency is
|
|
calibrated along with the frequency specified by the user.
|
|
"""
|
|
|
|
user_speed = self.trim_frequency
|
|
if user_speed <= 0:
|
|
user_speed = self.mcu_clock_hz
|
|
program_speed = 22118400
|
|
|
|
user_count = int(self.freq_counter * (user_speed / self.mcu_clock_hz))
|
|
program_count = int(self.freq_counter * (program_speed / self.mcu_clock_hz))
|
|
|
|
# Initiate handshake
|
|
print("Trimming frequency: ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x50, 0x00, 0x00, 0x36, 0x01])
|
|
packet += struct.pack(">H", self.mcu_magic)
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x8f:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# trim challenge-response, first round
|
|
packet = bytes([0x65])
|
|
packet += self.trim_data
|
|
packet += bytes([0xff, 0xff, 0x06, 0x06])
|
|
# add trim challenges for target frequency
|
|
packet += self.get_trim_sequence(user_speed)
|
|
# add trim challenge for program frequency
|
|
packet += bytes([0x98, 0x00, 0x02, 0x00])
|
|
packet += bytes([0x98, 0x80, 0x02, 0x00])
|
|
self.write_packet(packet)
|
|
self.pulse(timeout=1.0)
|
|
response = self.read_packet()
|
|
if response[0] != 0x65:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# determine programming speed trim value
|
|
target_trim_a, target_count_a = struct.unpack(">HH", response[28:32])
|
|
target_trim_b, target_count_b = struct.unpack(">HH", response[32:36])
|
|
m = (target_trim_b - target_trim_a) / (target_count_b - target_count_a)
|
|
n = target_trim_a - m * target_count_a
|
|
program_trim = round(m * program_count + n)
|
|
|
|
# determine trim trials for second round
|
|
trim_a, count_a = struct.unpack(">HH", response[12:16])
|
|
trim_b, count_b = struct.unpack(">HH", response[16:20])
|
|
trim_c, count_c = struct.unpack(">HH", response[20:24])
|
|
trim_d, count_d = struct.unpack(">HH", response[24:28])
|
|
# select suitable coarse trim range
|
|
if count_c <= user_count and count_d >= user_count:
|
|
target_trim_a = trim_c
|
|
target_trim_b = trim_d
|
|
target_count_a = count_c
|
|
target_count_b = count_d
|
|
else:
|
|
target_trim_a = trim_a
|
|
target_trim_b = trim_b
|
|
target_count_a = count_a
|
|
target_count_b = count_b
|
|
# linear interpolate to find range to try next
|
|
m = (target_trim_b - target_trim_a) / (target_count_b - target_count_a)
|
|
n = target_trim_a - m * target_count_a
|
|
target_trim = round(m * user_count + n)
|
|
target_trim_start = min(max(target_trim - 5, target_trim_a), target_trim_b)
|
|
|
|
# trim challenge-response, second round
|
|
packet = bytes([0x65])
|
|
packet += self.trim_data
|
|
packet += bytes([0xff, 0xff, 0x06, 0x0B])
|
|
for i in range(11):
|
|
packet += struct.pack(">H", target_trim_start + i)
|
|
packet += bytes([0x02, 0x00])
|
|
self.write_packet(packet)
|
|
self.pulse(timeout=1.0)
|
|
response = self.read_packet()
|
|
if response[0] != 0x65:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# determine best trim value
|
|
best_trim = 0
|
|
best_count = 65535
|
|
for i in range(11):
|
|
trim, count = struct.unpack(">HH", response[12+4*i:16+4*i])
|
|
if abs(count - user_count) < abs(best_count - user_count):
|
|
best_trim = trim
|
|
best_count = count
|
|
final_freq = (best_count / self.freq_counter) * self.mcu_clock_hz
|
|
print("%.03f MHz" % (final_freq / 1E6))
|
|
self.options.set_trim(best_trim)
|
|
|
|
# finally, switch baudrate
|
|
print("Switching to %d baud: " % self.baud_transfer, end="")
|
|
sys.stdout.flush()
|
|
iap_wait = self.get_iap_delay(program_speed)
|
|
packet = bytes([0x8e])
|
|
packet += struct.pack(">H", program_trim)
|
|
packet += struct.pack(">B", 230400 // self.baud_transfer)
|
|
packet += bytes([0xa1, 0x64, 0xb8, 0x00, iap_wait, 0x20, 0xff, 0x00])
|
|
self.write_packet(packet)
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
response = self.read_packet()
|
|
if response[0] != 0x84:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
print("done")
|
|
|
|
def program_options(self):
|
|
print("Setting options: ", end="")
|
|
sys.stdout.flush()
|
|
msr = self.options.get_msr()
|
|
packet = bytes([0x8d])
|
|
packet += msr
|
|
packet += bytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
|
|
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x50:
|
|
raise StcProtocolException("incorrect magic in option packet")
|
|
print("done")
|
|
|
|
print("Target UID: %s" % Utils.hexstr(self.uid))
|
|
|
|
|
|
class Stc15Protocol(Stc15AProtocol):
|
|
"""Protocol handler for later STC 15 series"""
|
|
|
|
def __init__(self, port, handshake, baud, trim):
|
|
Stc15AProtocol.__init__(self, port, handshake, baud, trim)
|
|
|
|
self.trim_value = None
|
|
|
|
def initialize_options(self, status_packet):
|
|
"""Initialize options"""
|
|
|
|
# create option state
|
|
self.options = Stc15Option(status_packet[5:8] + status_packet[12:13] + status_packet[37:38])
|
|
self.options.print()
|
|
|
|
def initialize_status(self, packet):
|
|
"""Decode status packet and store basic MCU info"""
|
|
|
|
# check bit that control internal vs. external clock source
|
|
# get frequency either stored from calibration or from
|
|
# frequency counter
|
|
self.external_clock = (packet[7] & 0x01) == 0
|
|
if self.external_clock:
|
|
count, = struct.unpack(">H", packet[13:15])
|
|
self.mcu_clock_hz = self.baud_handshake * count
|
|
else:
|
|
self.mcu_clock_hz, = struct.unpack(">I", packet[8:12])
|
|
# all ones means no calibration
|
|
# new chips are shipped without any calibration
|
|
if self.mcu_clock_hz == 0xffffffff: self.mcu_clock_hz = 0
|
|
|
|
# pre-calibrated trim adjust for 24 MHz, range 0x40
|
|
self.freq_count_24 = packet[4]
|
|
|
|
# wakeup timer factory value
|
|
self.wakeup_freq, = struct.unpack(">H", packet[1:3])
|
|
|
|
bl_version, bl_stepping = struct.unpack("BB", packet[17:19])
|
|
bl_minor = packet[22] & 0x0f
|
|
self.mcu_bsl_version = "%d.%d.%d%s" % (bl_version >> 4, bl_version & 0x0f,
|
|
bl_minor, chr(bl_stepping))
|
|
self.bsl_version = bl_version
|
|
|
|
def print_mcu_info(self):
|
|
"""Print additional STC15 info"""
|
|
|
|
StcBaseProtocol.print_mcu_info(self)
|
|
print("Target wakeup frequency: %.3f KHz" %(self.wakeup_freq / 1000))
|
|
|
|
def choose_range(self, packet, response, target_count):
|
|
"""Choose appropriate trim value mean for next round from challenge
|
|
responses."""
|
|
|
|
calib_data = response[2:]
|
|
challenge_data = packet[2:]
|
|
calib_len = response[1]
|
|
|
|
for i in range(calib_len - 1):
|
|
count_a, count_b = struct.unpack(">HH", calib_data[2*i:2*i+4])
|
|
trim_a, trim_b, trim_range = struct.unpack(">BxBB", challenge_data[2*i:2*i+4])
|
|
if ((count_a <= target_count and count_b >= target_count) or
|
|
(count_b <= target_count and count_a >= target_count)):
|
|
m = (trim_b - trim_a) / (count_b - count_a)
|
|
n = trim_a - m * count_a
|
|
target_trim = round(m * target_count + n)
|
|
return (target_trim, trim_range)
|
|
|
|
return None
|
|
|
|
def choose_trim(self, packet, response, target_count):
|
|
"""Choose best trim for given target count from challenge
|
|
responses."""
|
|
|
|
calib_data = response[2:]
|
|
challenge_data = packet[2:]
|
|
calib_len = response[1]
|
|
|
|
best = None
|
|
best_count = sys.maxsize
|
|
for i in range(calib_len):
|
|
count, = struct.unpack(">H", calib_data[2*i:2*i+2])
|
|
trim_adj, trim_range = struct.unpack(">BB", challenge_data[2*i:2*i+2])
|
|
if abs(count - target_count) < best_count:
|
|
best_count = abs(count - target_count)
|
|
best = (trim_adj, trim_range), count
|
|
|
|
return best
|
|
|
|
def calibrate(self):
|
|
"""Calibrate selected user frequency and the high-speed program
|
|
frequency and switch to selected baudrate."""
|
|
|
|
# handle uncalibrated chips
|
|
if self.mcu_clock_hz == 0 and self.trim_frequency <= 0:
|
|
raise StcProtocolException("uncalibrated, please provide a trim value")
|
|
|
|
# determine target counters
|
|
user_speed = self.trim_frequency
|
|
if user_speed <= 0: user_speed = self.mcu_clock_hz
|
|
program_speed = 22118400
|
|
target_user_count = round(user_speed / (self.baud_handshake/2))
|
|
target_prog_count = round(program_speed / (self.baud_handshake/2))
|
|
|
|
# calibration, round 1
|
|
print("Trimming frequency: ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x00])
|
|
packet += struct.pack(">B", 12)
|
|
packet += bytes([0x00, 0xc0, 0x80, 0xc0, 0xff, 0xc0])
|
|
packet += bytes([0x00, 0x80, 0x80, 0x80, 0xff, 0x80])
|
|
packet += bytes([0x00, 0x40, 0x80, 0x40, 0xff, 0x40])
|
|
packet += bytes([0x00, 0x00, 0x80, 0x00, 0xc0, 0x00])
|
|
self.write_packet(packet)
|
|
self.pulse(b"\xfe", timeout=1.0)
|
|
response = self.read_packet()
|
|
if response[0] != 0x00:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# select ranges and trim values
|
|
user_trim = self.choose_range(packet, response, target_user_count)
|
|
prog_trim = self.choose_range(packet, response, target_prog_count)
|
|
if user_trim == None or prog_trim == None:
|
|
raise StcProtocolException("frequency trimming unsuccessful")
|
|
|
|
# calibration, round 2
|
|
packet = bytes([0x00])
|
|
packet += struct.pack(">B", 12)
|
|
for i in range(user_trim[0] - 3, user_trim[0] + 3):
|
|
packet += bytes([i & 0xff, user_trim[1]])
|
|
for i in range(prog_trim[0] - 3, prog_trim[0] + 3):
|
|
packet += bytes([i & 0xff, prog_trim[1]])
|
|
self.write_packet(packet)
|
|
self.pulse(b"\xfe", timeout=1.0)
|
|
response = self.read_packet()
|
|
if response[0] != 0x00:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# select final values
|
|
user_trim, user_count = self.choose_trim(packet, response, target_user_count)
|
|
prog_trim, prog_count = self.choose_trim(packet, response, target_prog_count)
|
|
self.trim_value = user_trim
|
|
self.trim_frequency = round(user_count * (self.baud_handshake / 2))
|
|
print("%.03f MHz" % (self.trim_frequency / 1E6))
|
|
|
|
# switch to programming frequency
|
|
print("Switching to %d baud: " % self.baud_transfer, end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x01])
|
|
packet += bytes(prog_trim)
|
|
# XXX: baud rate calculation is different between MCUs with and without
|
|
# hardware UART. Only one family of models seems to lack a hardware
|
|
# UART, and we can isolate those with a check on the magic.
|
|
# This is a bit of a hack, but it works.
|
|
bauds = self.baud_transfer if (self.mcu_magic >> 8) == 0xf2 else self.baud_transfer * 4
|
|
packet += struct.pack(">H", int(65535 - program_speed / bauds))
|
|
packet += bytes(user_trim)
|
|
iap_wait = self.get_iap_delay(program_speed)
|
|
packet += bytes([iap_wait])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x01:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
|
|
def switch_baud_ext(self):
|
|
"""Switch baudrate using external clock source"""
|
|
|
|
print("Switching to %d baud: " % self.baud_transfer, end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x01])
|
|
packet += bytes([self.freq_count_24, 0x40])
|
|
packet += struct.pack(">H", int(65535 - self.mcu_clock_hz / self.baud_transfer / 4))
|
|
iap_wait = self.get_iap_delay(self.mcu_clock_hz)
|
|
packet += bytes([0x00, 0x00, iap_wait])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x01:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
time.sleep(0.2)
|
|
self.ser.baudrate = self.baud_transfer
|
|
|
|
# for switching back to RC, program factory values
|
|
self.trim_value = (self.freq_count_24, 0x40)
|
|
self.trim_frequency = int(24E6)
|
|
|
|
def handshake(self):
|
|
"""Do the handshake to calibrate frequencies and switch to
|
|
programming baudrate. Complicated by the fact that programming
|
|
can also use the external clock."""
|
|
|
|
# external clock needs special handling
|
|
if self.external_clock:
|
|
self.switch_baud_ext()
|
|
else:
|
|
self.calibrate()
|
|
|
|
# test/prepare
|
|
packet = bytes([0x05])
|
|
if self.bsl_version >= 0x72:
|
|
packet += bytes([0x00, 0x00, 0x5a, 0xa5])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] == 0x0f:
|
|
raise StcProtocolException("MCU is locked")
|
|
if response[0] != 0x05:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
print("done")
|
|
|
|
def erase_flash(self, erase_size, flash_size):
|
|
"""Erase the MCU's flash memory.
|
|
|
|
Erase the flash memory with a block-erase command.
|
|
Note that this protocol always seems to erase everything.
|
|
"""
|
|
|
|
# XXX: how does partial erase work?
|
|
|
|
print("Erasing flash: ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x03, 0x00])
|
|
if self.bsl_version >= 0x72:
|
|
packet += bytes([0x00, 0x5a, 0xa5])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x03:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
print("done")
|
|
|
|
if len(response) >= 8:
|
|
self.uid = response[1:8]
|
|
|
|
def program_flash(self, data):
|
|
"""Program the MCU's flash memory."""
|
|
|
|
print("Writing %d bytes: " % len(data), end="")
|
|
sys.stdout.flush()
|
|
for i in range(0, len(data), self.PROGRAM_BLOCKSIZE):
|
|
packet = bytes([0x22]) if i == 0 else bytes([0x02])
|
|
packet += struct.pack(">H", i)
|
|
if self.bsl_version >= 0x72:
|
|
packet += bytes([0x5a, 0xa5])
|
|
packet += data[i:i+self.PROGRAM_BLOCKSIZE]
|
|
while len(packet) < self.PROGRAM_BLOCKSIZE + 3: packet += b"\x00"
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x02 or response[1] != 0x54:
|
|
raise StcProtocolException("incorrect magic in write packet")
|
|
print(".", end="")
|
|
sys.stdout.flush()
|
|
print(" done")
|
|
|
|
# BSL 7.2+ needs a write finish packet according to dumps
|
|
if self.bsl_version >= 0x72:
|
|
print("Finishing write: ", end="")
|
|
sys.stdout.flush()
|
|
packet = bytes([0x07, 0x00, 0x00, 0x5a, 0xa5])
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x07 or response[1] != 0x54:
|
|
raise StcProtocolException("incorrect magic in finish packet")
|
|
print("done")
|
|
|
|
def build_options(self):
|
|
"""Build a 64 byte packet of option data from the current
|
|
configuration."""
|
|
|
|
msr = self.options.get_msr()
|
|
packet = bytes([0xff] * 23)
|
|
packet += bytes([(self.trim_frequency >> 24) & 0xff,
|
|
0xff,
|
|
(self.trim_frequency >> 16) & 0xff,
|
|
0xff,
|
|
(self.trim_frequency >> 8) & 0xff,
|
|
0xff,
|
|
(self.trim_frequency >> 0) & 0xff,
|
|
0xff])
|
|
packet += bytes([msr[3]])
|
|
packet += bytes([0xff] * 23)
|
|
if len(msr) > 4:
|
|
packet += bytes([msr[4]])
|
|
else:
|
|
packet += bytes([0xff])
|
|
packet += bytes([0xff] * 3)
|
|
packet += bytes([self.trim_value[0], self.trim_value[1] + 0x3f])
|
|
packet += msr[0:3]
|
|
return packet
|
|
|
|
def program_options(self):
|
|
print("Setting options: ", end="")
|
|
sys.stdout.flush()
|
|
|
|
packet = bytes([0x04, 0x00, 0x00])
|
|
if self.bsl_version >= 0x72:
|
|
packet += bytes([0x5a, 0xa5])
|
|
packet += self.build_options()
|
|
self.write_packet(packet)
|
|
response = self.read_packet()
|
|
if response[0] != 0x04 or response[1] != 0x54:
|
|
raise StcProtocolException("incorrect magic in option packet")
|
|
print("done")
|
|
|
|
print("Target UID: %s" % Utils.hexstr(self.uid))
|
|
|
|
|
|
class StcUsb15Protocol(Stc15Protocol):
|
|
"""USB should use large blocks"""
|
|
PROGRAM_BLOCKSIZE = 128
|
|
|
|
"""VID of STC devices"""
|
|
USB_VID = 0x5354
|
|
|
|
"""PID of STC devices"""
|
|
USB_PID = 0x4312
|
|
|
|
def __init__(self):
|
|
# XXX: this is really ugly!
|
|
Stc15Protocol.__init__(self, "", 0, 0, 0)
|
|
self.dev = None
|
|
|
|
def dump_packet(self, data, request=0, value=0, index=0, receive=True):
|
|
if self.debug:
|
|
print("%s bRequest=%02X wValue=%04X wIndex=%04X data: %s" %
|
|
(("<-" if receive else "->"), request, value, index,
|
|
Utils.hexstr(data, " ")), file=sys.stderr)
|
|
|
|
def read_packet(self):
|
|
"""Read a packet from the MCU"""
|
|
|
|
dev2host = usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE | usb.util.CTRL_IN
|
|
packet = self.dev.ctrl_transfer(dev2host, 0, 0, 0, 132).tobytes()
|
|
if len(packet) < 5 or packet[0] != 0x46 or packet[1] != 0xb9:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("incorrect frame start")
|
|
|
|
data_len = packet[2]
|
|
if (data_len) > len(packet) + 3:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("frame length mismatch")
|
|
|
|
data = packet[2:-1]
|
|
csum = functools.reduce(lambda x, y: x - y, data, 0) & 0xff
|
|
if csum != packet[-1]:
|
|
self.dump_packet(packet)
|
|
raise StcFramingException("frame checksum mismatch")
|
|
|
|
self.dump_packet(packet, receive=True)
|
|
return packet[3:3+data_len]
|
|
|
|
def write_packet(self, request, value=0, index=0, data=bytes([0])):
|
|
"""Write USB control packet"""
|
|
|
|
# Control transfers are maximum of 8 bytes each, and every
|
|
# invidual partial transfer is checksummed individually.
|
|
i = 0
|
|
chunks = bytes()
|
|
while i < len(data):
|
|
c = data[i:i+7]
|
|
csum = functools.reduce(lambda x, y: x - y, c, 0) & 0xff
|
|
chunks += c + bytes([csum])
|
|
i += 7
|
|
|
|
self.dump_packet(chunks, request, value, index, receive=False)
|
|
host2dev = usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE | usb.util.CTRL_OUT
|
|
self.dev.ctrl_transfer(host2dev, request, value, index, chunks)
|
|
|
|
def connect(self, autoreset=False, resetcmd=False):
|
|
"""Connect to USB device and read info packet"""
|
|
|
|
# USB support is optional. Provide an error if pyusb is not available.
|
|
if not _usb_available:
|
|
raise StcProtocolException(
|
|
"USB support not available. " +
|
|
"pyusb is not installed or not working correctly.")
|
|
|
|
print("Waiting for MCU, please cycle power: ", end="")
|
|
sys.stdout.flush()
|
|
|
|
self.status_packet = None
|
|
while not self.status_packet:
|
|
try:
|
|
self.dev = usb.core.find(idVendor=self.USB_VID, idProduct=self.USB_PID)
|
|
if self.dev:
|
|
self.dev.set_configuration()
|
|
self.status_packet = self.read_packet()
|
|
if len(self.status_packet) < 38:
|
|
self.status_packet = None
|
|
raise StcFramingException
|
|
else: raise StcFramingException
|
|
except StcFramingException:
|
|
time.sleep(0.5)
|
|
except usb.core.USBError as err:
|
|
if err.errno == errno.EACCES:
|
|
raise IOError(err.strerror)
|
|
|
|
self.initialize_model()
|
|
print("done")
|
|
|
|
def handshake(self):
|
|
print("Initializing: ", end="")
|
|
sys.stdout.flush()
|
|
|
|
# handshake
|
|
self.write_packet(0x01, 0, 0, bytes([0x03]))
|
|
response = self.read_packet()
|
|
if response[0] != 0x01:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
# enable/unlock MCU
|
|
self.write_packet(0x05, 0xa55a, 0)
|
|
response = self.read_packet()
|
|
if response[0] == 0x0f:
|
|
raise StcProtocolException("MCU is locked")
|
|
if response[0] != 0x05:
|
|
raise StcProtocolException("incorrect magic in handshake packet")
|
|
|
|
print("done")
|
|
|
|
def erase_flash(self, code, eeprom):
|
|
print("Erasing flash: ", end="")
|
|
sys.stdout.flush()
|
|
self.write_packet(0x03, 0xa55a, 0)
|
|
# XXX: better way to detect MCU has finished
|
|
time.sleep(2)
|
|
packet = self.read_packet()
|
|
if packet[0] != 0x03:
|
|
raise StcProtocolException("incorrect magic in erase packet")
|
|
self.uid = packet[1:8]
|
|
print("done")
|
|
|
|
def program_flash(self, data):
|
|
"""Program the MCU's flash memory."""
|
|
|
|
print("Writing %d bytes: " % len(data), end="")
|
|
sys.stdout.flush()
|
|
for i in range(0, len(data), self.PROGRAM_BLOCKSIZE):
|
|
packet = data[i:i+self.PROGRAM_BLOCKSIZE]
|
|
while len(packet) < self.PROGRAM_BLOCKSIZE: packet += b"\x00"
|
|
self.write_packet(0x22 if i == 0 else 0x02, 0xa55a, i, packet)
|
|
# XXX: better way to detect MCU has finished
|
|
time.sleep(0.1)
|
|
response = self.read_packet()
|
|
if response[0] != 0x02 or response[1] != 0x54:
|
|
raise StcProtocolException("incorrect magic in write packet")
|
|
print(".", end="")
|
|
sys.stdout.flush()
|
|
print(" done")
|
|
|
|
def program_options(self):
|
|
print("Setting options: ", end="")
|
|
sys.stdout.flush()
|
|
|
|
# always use 24 MHz pre-tuned value for now
|
|
self.trim_value = (self.freq_count_24, 0x40)
|
|
self.trim_frequency = int(24E6)
|
|
|
|
packet = self.build_options()
|
|
self.write_packet(0x04, 0xa55a, 0, packet)
|
|
# XXX: better way to detect MCU has finished
|
|
time.sleep(0.5)
|
|
response = self.read_packet()
|
|
if response[0] != 0x04 or response[1] != 0x54:
|
|
raise StcProtocolException("incorrect magic in option packet")
|
|
print("done")
|
|
|
|
print("Target UID: %s" % Utils.hexstr(self.uid))
|
|
|
|
def disconnect(self):
|
|
if self.dev:
|
|
self.write_packet(0xff)
|
|
print("Disconnected!")
|