Add STC 89/90 series protocol support
This commit is contained in:
parent
ca1bd4dcab
commit
977fb205ed
11
README.md
11
README.md
@ -19,12 +19,13 @@ it is very portable and suitable for automation.
|
||||
Supported MCU models
|
||||
--------------------
|
||||
|
||||
stcgal should fully support STC 10/11/12 series MCUs. STC 15 series
|
||||
support is unfinished, but should support all STC15F10x/STC15F20x
|
||||
series MCU models. STC 89/90 series support is work in progress.
|
||||
stcgal should fully support STC 89/90/10/11/12 series MCUs. STC
|
||||
15 series handling is unfinished, but should work with
|
||||
STC15F10x/STC15F20x MCU models.
|
||||
|
||||
So far, stcgal was tested with the following MCU models:
|
||||
|
||||
* STC89C52RC (BSL version: 4.3C)
|
||||
* STC12C5A60S2 (BSL version: 6.2L)
|
||||
* STC11F08XE (BSL version: 6.5M)
|
||||
* STC15F104E (BSL version: 6.7Q)
|
||||
@ -38,8 +39,8 @@ Features
|
||||
* Program flash memory
|
||||
* Program IAP/EEPROM
|
||||
* Set device options
|
||||
* Read unique device ID
|
||||
* Trim RC oscillator frequency (on STC 15 series)
|
||||
* Read unique device ID (STC 10/11/12/15)
|
||||
* Trim RC oscillator frequency (STC 15)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
1
TODO
1
TODO
@ -1,4 +1,3 @@
|
||||
- some more documentation / comments
|
||||
- private member naming, other style issues
|
||||
- sensible default serial port (e.g. on windows)
|
||||
- split up into base protocol class and variants
|
||||
|
22
doc/stc89-options.txt
Normal file
22
doc/stc89-options.txt
Normal file
@ -0,0 +1,22 @@
|
||||
Model-specific configuration registers
|
||||
Placement of configuration values
|
||||
|
||||
"~" means the bit is a negated boolean. Sometimes values overlap,
|
||||
depending on MCU model.
|
||||
|
||||
In STC89 series, there is only a single MCS byte.
|
||||
|
||||
MCS0
|
||||
----
|
||||
|
||||
MSB 7 6 5 4 3 2 1 0 LSB
|
||||
~WDEN XRAM ALE OSCG ~EERE ~BSLD 0 ~CPU6T
|
||||
|
||||
~WDEN := watchdog enable after power-on-reset
|
||||
XRAM := enable access to internal XRAM
|
||||
ALE := enable ALE pin function (otherwise, it's just regular GPIO)
|
||||
OSCG := high oscillator gain
|
||||
~EREE := enable eeprom erase next time MCU is programmed
|
||||
~BSLD := enable BSL pin detect; i.e. BSL is only enabled if P1.0/P1.1
|
||||
(or others, depends on MCU model) are held low on POR.
|
||||
~CPU6T := enable double speed (6T cycles instead of 12T cycles) mode
|
@ -6,7 +6,9 @@ Differences to STC12:
|
||||
|
||||
* Uses NONE parity instead of EVEN parity.
|
||||
|
||||
* Checksum calculations are different: only a single-byte modular sum is used.
|
||||
* Status packet is sent without frame start magic.
|
||||
|
||||
* Checksum calculations are different: a single-byte modular sum is used.
|
||||
|
||||
* Baudrate handshake isn't initiated with a type 0x50 packet; this is simply
|
||||
skipped.
|
||||
@ -23,12 +25,16 @@ Differences to STC12:
|
||||
is simply skipped.
|
||||
|
||||
* Baudrate handshake
|
||||
- Uses normal speed (/32) UART timing instead of double speed (/16)
|
||||
- Uses normal speed (/32) UART timing in 12T mode and double speed (/16)
|
||||
in 6T mode
|
||||
- IAP delay has some differences (see datasheet)
|
||||
|
||||
* Erase procedure
|
||||
- A different magic sequence is used
|
||||
(6 bytes with value 0x33)
|
||||
- No size/range is supplied
|
||||
- Only a single size is supplied
|
||||
- response code has type 0x80
|
||||
|
||||
* Options
|
||||
- Only a single option byte exists
|
||||
|
||||
|
403
stcgal.py
403
stcgal.py
@ -783,6 +783,83 @@ class BaseOption:
|
||||
return bytes(self.msr)
|
||||
|
||||
|
||||
class Stc89Option(BaseOption):
|
||||
"""Manipulation STC89 series option byte"""
|
||||
|
||||
def __init__(self, msr):
|
||||
self.msr = msr
|
||||
self.options = (
|
||||
("cpu_6t_enabled", self.get_t6, self.set_t6),
|
||||
("bsl_pindetect_enabled", self.get_pindetect, self.set_pindetect),
|
||||
("eeprom_erase_enabled", self.get_ee_erase, self.set_ee_erase),
|
||||
("clock_gain", self.get_clock_gain, self.set_clock_gain),
|
||||
("ale_enabled", self.get_ale, self.set_ale),
|
||||
("xram_enabled", self.get_xram, self.set_xram),
|
||||
("watchdog_por_enabled", self.get_watchdog, self.set_watchdog),
|
||||
)
|
||||
|
||||
def get_msr(self):
|
||||
return self.msr
|
||||
|
||||
def get_t6(self):
|
||||
return not bool(self.msr & 1)
|
||||
|
||||
def set_t6(self, val):
|
||||
val = Utils.to_bool(val);
|
||||
self.msr &= 0xfe
|
||||
self.msr |= 0x01 if not bool(val) else 0x00
|
||||
|
||||
def get_pindetect(self):
|
||||
return not bool(self.msr & 4)
|
||||
|
||||
def set_pindetect(self, val):
|
||||
val = Utils.to_bool(val);
|
||||
self.msr &= 0xfb
|
||||
self.msr |= 0x04 if not bool(val) else 0x00
|
||||
|
||||
def get_ee_erase(self):
|
||||
return not bool(self.msr & 8)
|
||||
|
||||
def set_ee_erase(self, val):
|
||||
val = Utils.to_bool(val);
|
||||
self.msr &= 0xf7
|
||||
self.msr |= 0x08 if not bool(val) else 0x00
|
||||
|
||||
def get_clock_gain(self):
|
||||
gain = bool(self.msr & 16)
|
||||
return "high" if gain else "low"
|
||||
|
||||
def set_clock_gain(self, val):
|
||||
gains = {"low": 0, "high": 0x10}
|
||||
if val not in gains.keys():
|
||||
raise ValueError("must be one of %s" % list(gains.keys()))
|
||||
self.msr &= 0xef
|
||||
self.msr |= gains[val]
|
||||
|
||||
def get_ale(self):
|
||||
return bool(self.msr & 32)
|
||||
|
||||
def set_ale(self, val):
|
||||
val = Utils.to_bool(val);
|
||||
self.msr &= 0xdf
|
||||
self.msr |= 0x20 if bool(val) else 0x00
|
||||
|
||||
def get_xram(self):
|
||||
return bool(self.msr & 64)
|
||||
|
||||
def set_xram(self, val):
|
||||
val = Utils.to_bool(val);
|
||||
self.msr &= 0xbf
|
||||
self.msr |= 0x40 if bool(val) else 0x00
|
||||
|
||||
def get_watchdog(self):
|
||||
return not bool(self.msr & 128)
|
||||
|
||||
def set_watchdog(self, val):
|
||||
val = Utils.to_bool(val);
|
||||
self.msr &= 0x7f
|
||||
self.msr |= 0x80 if not bool(val) else 0x00
|
||||
|
||||
class Stc12Option(BaseOption):
|
||||
"""Manipulate STC10/11/12 series option bytes"""
|
||||
|
||||
@ -1097,6 +1174,36 @@ class StcBaseProtocol:
|
||||
def set_option(self, name, value):
|
||||
self.options.set_option(name, value)
|
||||
|
||||
def connect(self):
|
||||
"""Connect to MCU and initialize communication.
|
||||
|
||||
Set up serial port, send sync sequence and get part info.
|
||||
"""
|
||||
|
||||
self.ser = serial.Serial(port=self.port, baudrate=self.baud_handshake,
|
||||
parity=self.PARITY)
|
||||
|
||||
# conservative timeout values
|
||||
self.ser.timeout = 10.0
|
||||
self.ser.interCharTimeout = 1.0
|
||||
|
||||
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
|
||||
status_packet = None
|
||||
while not status_packet:
|
||||
try:
|
||||
self.pulse()
|
||||
status_packet = self.get_status_packet()
|
||||
except (StcFramingException, serial.SerialTimeoutException): pass
|
||||
print("done")
|
||||
|
||||
self.initialize_status(status_packet)
|
||||
self.initialize_model()
|
||||
self.initialize_options(status_packet)
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect from MCU"""
|
||||
|
||||
@ -1107,6 +1214,263 @@ class StcBaseProtocol:
|
||||
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 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 end code
|
||||
if packet[packet_len+1] != self.PACKET_END[0]:
|
||||
self.dump_packet(packet)
|
||||
raise StcFramingException("incorrect frame end")
|
||||
|
||||
# verify checksum
|
||||
packet_csum = packet[packet_len]
|
||||
calc_csum = sum(packet[2:packet_len]) & 0xff
|
||||
if packet_csum != calc_csum:
|
||||
self.dump_packet(packet)
|
||||
raise StcFramingException("packet checksum mismatch")
|
||||
|
||||
self.dump_packet(packet, receive=True)
|
||||
|
||||
# payload only is returned
|
||||
return packet[5:packet_len]
|
||||
|
||||
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.mcu_magic, = struct.unpack(">H", packet[20:22])
|
||||
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="")
|
||||
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 Stc12Protocol(StcBaseProtocol):
|
||||
"""Protocol handler for STC 10/11/12 series"""
|
||||
|
||||
@ -1116,6 +1480,9 @@ class Stc12Protocol(StcBaseProtocol):
|
||||
"""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)
|
||||
|
||||
@ -1242,36 +1609,6 @@ class Stc12Protocol(StcBaseProtocol):
|
||||
self.options = Stc12Option(status_packet[23:27])
|
||||
self.options.print()
|
||||
|
||||
def connect(self):
|
||||
"""Connect to MCU and initialize communication.
|
||||
|
||||
Set up serial port, send sync sequence and get part info.
|
||||
"""
|
||||
|
||||
self.ser = serial.Serial(port=self.port, baudrate=self.baud_handshake,
|
||||
parity=serial.PARITY_EVEN)
|
||||
|
||||
# conservative timeout values
|
||||
self.ser.timeout = 10.0
|
||||
self.ser.interCharTimeout = 1.0
|
||||
|
||||
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
|
||||
status_packet = None
|
||||
while not status_packet:
|
||||
try:
|
||||
self.pulse()
|
||||
status_packet = self.get_status_packet()
|
||||
except (StcFramingException, serial.SerialTimeoutException): pass
|
||||
print("done")
|
||||
|
||||
self.initialize_status(status_packet)
|
||||
self.initialize_model()
|
||||
self.initialize_options(status_packet)
|
||||
|
||||
def handshake(self):
|
||||
"""Do baudrate handshake
|
||||
|
||||
@ -1618,7 +1955,9 @@ class StcGal:
|
||||
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
if opts.protocol == "stc12":
|
||||
if opts.protocol == "stc89":
|
||||
self.protocol = Stc89Protocol(opts.port, opts.handshake, opts.baud)
|
||||
elif opts.protocol == "stc12":
|
||||
self.protocol = Stc12Protocol(opts.port, opts.handshake, opts.baud)
|
||||
else:
|
||||
self.protocol = Stc15Protocol(opts.port, opts.handshake, opts.baud,
|
||||
@ -1713,7 +2052,7 @@ if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="STC MCU ISP flash tool")
|
||||
parser.add_argument("code_binary", help="code segment binary file to flash", type=argparse.FileType("rb"), nargs='?')
|
||||
parser.add_argument("eeprom_binary", help="eeprom segment binary file to flash", type=argparse.FileType("rb"), nargs='?')
|
||||
parser.add_argument("-P", "--protocol", help="protocol version", choices=["stc12", "stc15"], default="stc12")
|
||||
parser.add_argument("-P", "--protocol", help="protocol version", choices=["stc89", "stc12", "stc15"], 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)
|
||||
|
Loading…
Reference in New Issue
Block a user