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]
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
memory over a serial link. This is referred to as in-system programming (ISP).
The BSL is also used to configure various (fuse-like) device
memory over a serial link. This is referred to as in-system programming
(ISP). The BSL is also used to configure various (fuse-like) device
options. Unfortunately, this protocol is not publicly documented and
STC only provide a (crude) Windows GUI application for programming.
stcgal is a full-featured Open Source replacement for STC's Windows software;
it supports a wide range of MCUs, it is very portable and suitable for automation.
stcgal is a full-featured Open Source replacement for STC's Windows
software; it supports a wide range of MCUs, it is very portable and
suitable for automation.
[1] http://stcmcu.com/
[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)
* STC15L2K16S2 (BSL version: 7.2.4S)
* 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.
Features
--------
* UART and USB BSL support
* Display part info
* Determine operating frequency
* Program flash memory
@ -58,12 +60,13 @@ Features
* Read unique device ID (STC 10/11/12/15)
* Trim RC oscillator frequency (STC 15)
* Automatic power-cycling with DTR toggle
* Automatic protocol detection
* Automatic UART protocol detection
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
method for permanent installation is to use Python's setuptools. Run
```./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)
* ```stc15a``` STC15x104E and STC15x204E(A) 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 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,
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
-------

View File

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

View File

@ -45,6 +45,8 @@ class StcGal:
elif opts.protocol == "stc15":
self.protocol = Stc15Protocol(opts.port, opts.handshake, opts.baud,
round(opts.trim * 1000))
elif opts.protocol == "usb15":
self.protocol = StcUsb15Protocol()
else:
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("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("-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("-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)

View File

@ -26,6 +26,14 @@ import argparse
import collections
from stcgal.models import MCUModelDatabase
from stcgal.utils import Utils
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"""
@ -1937,15 +1945,12 @@ class Stc15Protocol(Stc15AProtocol):
raise StcProtocolException("incorrect magic in finish packet")
print("done")
def program_options(self):
print("Setting options: ", end="")
sys.stdout.flush()
msr = self.options.get_msr()
def build_options(self):
"""Build a 64 byte packet of option data from the current
configuration."""
packet = bytes([0x04, 0x00, 0x00])
if self.bsl_version >= 0x72:
packet += bytes([0x5a, 0xa5])
packet += bytes([0xff] * 23)
msr = self.options.get_msr()
packet = bytes([0xff] * 23)
packet += bytes([(self.trim_frequency >> 24) & 0xff,
0xff,
(self.trim_frequency >> 16) & 0xff,
@ -1963,6 +1968,16 @@ class Stc15Protocol(Stc15AProtocol):
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:
@ -1970,3 +1985,167 @@ class Stc15Protocol(Stc15AProtocol):
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
"""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!")