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]
|
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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
3
setup.py
3
setup.py
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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!")
|
||||||
|
Loading…
Reference in New Issue
Block a user