Add STC 89/90 series protocol support

This commit is contained in:
Grigori Goronzy 2014-01-23 23:55:41 +01:00
parent ca1bd4dcab
commit 977fb205ed
5 changed files with 408 additions and 41 deletions

View File

@ -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
View File

@ -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
View 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

View File

@ -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
View File

@ -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)