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:
commit
a8f141584d
30
README.md
30
README.md
@ -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
|
||||
-------
|
||||
|
||||
|
3
setup.py
3
setup.py
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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!")
|
||||
|
Loading…
Reference in New Issue
Block a user