Merge branch 'usbisp'

It is still experimental, but the changes are not very invasive, so why
not include it right now. Closes grigorig/stcgal#10.
This commit is contained in:
Grigori Goronzy 2016-05-14 13:26:57 +02:00
commit a8f141584d
4 changed files with 214 additions and 18 deletions

View File

@ -4,15 +4,16 @@ stcgal - STC MCU ISP flash tool
stcgal is a command line flash programming tool for STC MCU Ltd. [1] stcgal is a command line flash programming tool for STC MCU Ltd. [1]
8051 compatible microcontrollers. The name was inspired by avrdude [2]. 8051 compatible microcontrollers. The name was inspired by avrdude [2].
STC microcontrollers have a UART based boot strap loader (BSL). It STC microcontrollers have an UART/USB based boot strap loader (BSL). It
utilizes a packet-based protocol to flash the code memory and IAP utilizes a packet-based protocol to flash the code memory and IAP
memory over a serial link. This is referred to as in-system programming (ISP). memory over a serial link. This is referred to as in-system programming
The BSL is also used to configure various (fuse-like) device (ISP). The BSL is also used to configure various (fuse-like) device
options. Unfortunately, this protocol is not publicly documented and options. Unfortunately, this protocol is not publicly documented and
STC only provide a (crude) Windows GUI application for programming. STC only provide a (crude) Windows GUI application for programming.
stcgal is a full-featured Open Source replacement for STC's Windows software; stcgal is a full-featured Open Source replacement for STC's Windows
it supports a wide range of MCUs, it is very portable and suitable for automation. software; it supports a wide range of MCUs, it is very portable and
suitable for automation.
[1] http://stcmcu.com/ [1] http://stcmcu.com/
[2] http://www.nongnu.org/avrdude/ [2] http://www.nongnu.org/avrdude/
@ -43,13 +44,14 @@ So far, stcgal was tested with the following MCU models:
* IAP15F2K61S2 (BSL version: 7.1.4S) * IAP15F2K61S2 (BSL version: 7.1.4S)
* STC15L2K16S2 (BSL version: 7.2.4S) * STC15L2K16S2 (BSL version: 7.2.4S)
* STC15W408AS (BSL version: 7.2.4T) * STC15W408AS (BSL version: 7.2.4T)
* STC15W4K56S4 (BSL version: 7.3.4T) * STC15W4K56S4 (BSL version: 7.3.4T, UART and USB mode)
Compatibility reports, both negative and positive, are welcome. Compatibility reports, both negative and positive, are welcome.
Features Features
-------- --------
* UART and USB BSL support
* Display part info * Display part info
* Determine operating frequency * Determine operating frequency
* Program flash memory * Program flash memory
@ -58,12 +60,13 @@ Features
* Read unique device ID (STC 10/11/12/15) * Read unique device ID (STC 10/11/12/15)
* Trim RC oscillator frequency (STC 15) * Trim RC oscillator frequency (STC 15)
* Automatic power-cycling with DTR toggle * Automatic power-cycling with DTR toggle
* Automatic protocol detection * Automatic UART protocol detection
Installation Installation
------------ ------------
stcgal requires Python 3.2 (or later) and pySerial. You can run stcgal stcgal requires Python 3.2 (or later) and pySerial. USB support is
optional and requires pyusb 1.0.0b2 or later. You can run stcgal
directly with the included ```stcgal.py``` script. The recommended directly with the included ```stcgal.py``` script. The recommended
method for permanent installation is to use Python's setuptools. Run method for permanent installation is to use Python's setuptools. Run
```./setup.py build``` to build and ```sudo ./setup.py install``` ```./setup.py build``` to build and ```sudo ./setup.py install```
@ -117,7 +120,8 @@ protocols and MCU series is as follows:
* ```stc12``` Most STC10/11/12 series (default) * ```stc12``` Most STC10/11/12 series (default)
* ```stc15a``` STC15x104E and STC15x204E(A) series * ```stc15a``` STC15x104E and STC15x204E(A) series
* ```stc15``` Most STC15 series * ```stc15``` Most STC15 series
* ```auto``` Automatic detection * ```usb15``` USB support on STC15W4 series
* ```auto``` Automatic detection of UART based protocols
The text files in the doc/ subdirectory provide an overview over The text files in the doc/ subdirectory provide an overview over
the reverse engineered protocols used by the BSLs. For more details, the reverse engineered protocols used by the BSLs. For more details,
@ -276,6 +280,14 @@ error, such as a protocol error or I/O error, results in an exit
status of 1. If the the user aborted stcgal by pressing CTRL-C, status of 1. If the the user aborted stcgal by pressing CTRL-C,
that results in an exit status of 2. that results in an exit status of 2.
### USB support
STC15W4 series have an USB-based BSL that can be optionally
used. USB support in stcgal is experimental and might change in the
future. USB mode is enabled by using the ```usb15``` protocol. The
port (```-p```) flag as well as the baudrate options are ignored for
the USB protocol. RC frequency trimming is not supported.
License License
------- -------

View File

@ -29,6 +29,9 @@ setup(
version = stcgal.__version__, version = stcgal.__version__,
packages = find_packages(exclude=["doc"]), packages = find_packages(exclude=["doc"]),
install_requires = ["pyserial"], install_requires = ["pyserial"],
extras_require = {
"usb": ["pyusb>=1.0.0"]
},
entry_points = { entry_points = {
"console_scripts": [ "console_scripts": [
"stcgal = stcgal.frontend:cli", "stcgal = stcgal.frontend:cli",

View File

@ -45,6 +45,8 @@ class StcGal:
elif opts.protocol == "stc15": elif opts.protocol == "stc15":
self.protocol = Stc15Protocol(opts.port, opts.handshake, opts.baud, self.protocol = Stc15Protocol(opts.port, opts.handshake, opts.baud,
round(opts.trim * 1000)) round(opts.trim * 1000))
elif opts.protocol == "usb15":
self.protocol = StcUsb15Protocol()
else: else:
self.protocol = StcBaseProtocol(opts.port, opts.handshake, opts.baud) self.protocol = StcBaseProtocol(opts.port, opts.handshake, opts.baud)
@ -192,7 +194,7 @@ def cli():
parser.add_argument("code_image", help="code segment file to flash (BIN/HEX)", type=argparse.FileType("rb"), nargs='?') parser.add_argument("code_image", help="code segment file to flash (BIN/HEX)", type=argparse.FileType("rb"), nargs='?')
parser.add_argument("eeprom_image", help="eeprom segment file to flash (BIN/HEX)", type=argparse.FileType("rb"), nargs='?') parser.add_argument("eeprom_image", help="eeprom segment file to flash (BIN/HEX)", type=argparse.FileType("rb"), nargs='?')
parser.add_argument("-a", "--autoreset", help="cycle power automatically by asserting DTR", action="store_true") parser.add_argument("-a", "--autoreset", help="cycle power automatically by asserting DTR", action="store_true")
parser.add_argument("-P", "--protocol", help="protocol version", choices=["stc89", "stc12a", "stc12", "stc15a", "stc15", "auto"], default="stc12") parser.add_argument("-P", "--protocol", help="protocol version", choices=["stc89", "stc12a", "stc12", "stc15a", "stc15", "usb15", "auto"], default="stc12")
parser.add_argument("-p", "--port", help="serial port device", default="/dev/ttyUSB0") parser.add_argument("-p", "--port", help="serial port device", default="/dev/ttyUSB0")
parser.add_argument("-b", "--baud", help="transfer baud rate (default: 19200)", type=BaudType(), default=19200) parser.add_argument("-b", "--baud", help="transfer baud rate (default: 19200)", type=BaudType(), default=19200)
parser.add_argument("-l", "--handshake", help="handshake baud rate (default: 2400)", type=BaudType(), default=2400) parser.add_argument("-l", "--handshake", help="handshake baud rate (default: 2400)", type=BaudType(), default=2400)

View File

@ -26,6 +26,14 @@ import argparse
import collections import collections
from stcgal.models import MCUModelDatabase from stcgal.models import MCUModelDatabase
from stcgal.utils import Utils from stcgal.utils import Utils
import functools
try:
import usb.core, usb.util
_usb_available = True
except ImportError:
_usb_available = False
class StcFramingException(Exception): class StcFramingException(Exception):
"""Something wrong with packet framing or checksum""" """Something wrong with packet framing or checksum"""
@ -1937,15 +1945,12 @@ class Stc15Protocol(Stc15AProtocol):
raise StcProtocolException("incorrect magic in finish packet") raise StcProtocolException("incorrect magic in finish packet")
print("done") print("done")
def program_options(self): def build_options(self):
print("Setting options: ", end="") """Build a 64 byte packet of option data from the current
sys.stdout.flush() configuration."""
msr = self.options.get_msr()
packet = bytes([0x04, 0x00, 0x00]) msr = self.options.get_msr()
if self.bsl_version >= 0x72: packet = bytes([0xff] * 23)
packet += bytes([0x5a, 0xa5])
packet += bytes([0xff] * 23)
packet += bytes([(self.trim_frequency >> 24) & 0xff, packet += bytes([(self.trim_frequency >> 24) & 0xff,
0xff, 0xff,
(self.trim_frequency >> 16) & 0xff, (self.trim_frequency >> 16) & 0xff,
@ -1963,6 +1968,16 @@ class Stc15Protocol(Stc15AProtocol):
packet += bytes([0xff] * 3) packet += bytes([0xff] * 3)
packet += bytes([self.trim_value[0], self.trim_value[1] + 0x3f]) packet += bytes([self.trim_value[0], self.trim_value[1] + 0x3f])
packet += msr[0:3] 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) self.write_packet(packet)
response = self.read_packet() response = self.read_packet()
if response[0] != 0x04 or response[1] != 0x54: if response[0] != 0x04 or response[1] != 0x54:
@ -1970,3 +1985,167 @@ class Stc15Protocol(Stc15AProtocol):
print("done") print("done")
print("Target UID: %s" % Utils.hexstr(self.uid)) 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
"""Control transfer from host to device"""
USB_HOST2DEV = usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE | usb.util.CTRL_OUT
"""Control transfer from device to host"""
USB_DEV2HOST = usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE | usb.util.CTRL_IN
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"""
packet = self.dev.ctrl_transfer(self.USB_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)
self.dev.ctrl_transfer(self.USB_HOST2DEV, request, value, index, chunks);
def connect(self, autoreset=False):
"""Connect to USB device and read info packet"""
# USB support is optional. Provide an error if pyusb is not available.
if _usb_available == False:
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()
else:
time.sleep(0.5)
except (StcFramingException, usb.core.USBError): pass
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!")