From 8bc9d8925714f862c9ab5e8c313ecfb9b0aafd44 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 23 Oct 2017 23:52:10 +0200 Subject: [PATCH 1/4] Add preliminary STC8 support What works: * Frequency calibration of internal RC oscillator * Flash/EEPROM programming What doesn't work yet: * Everything else --- doc/stc8-protocol.txt | 133 ++++++++++++++++++++++++++++++++++++++++++ stcgal/frontend.py | 6 +- stcgal/protocols.py | 120 ++++++++++++++++++++++++++++++++++++- 3 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 doc/stc8-protocol.txt diff --git a/doc/stc8-protocol.txt b/doc/stc8-protocol.txt new file mode 100644 index 0000000..fdd2e33 --- /dev/null +++ b/doc/stc8-protocol.txt @@ -0,0 +1,133 @@ +Overview of changes +------------------- + +The following changes have been observed compared to STC15: + +- Many differences in the status packet +- At least some differences in MCS +- Different challenge + - no separate program speed + - clock division was introduced; calibration always in the ~20-30 MHz range, lower clocks + use division + - the meaning of the calibration ranges and trim has changed + +The good: + +- Erase, Program, etc. operations are apparently unchanged. :) + + +Status packet +------------- + +46 B9 68 00 30 50 00 54 62 58 5D 00 04 FF FD 8B BF FF 27 4A F7 FE 73 55 00 F6 28 09 85 E3 5F 80 07 20 20 20 01 00 00 FE 05 3A 17 05 25 91 FF 10 AE 16 + ^^^^^ wakeup clock + +Clock set to 20 MHz by STC-ISP (encoding is different compared to STC15): + +46 B9 68 00 30 50 01 31 2E 90 38 01 01 FF FD 8B BF FF 27 35 F7 FE 73 55 00 F6 28 09 85 E3 5F 80 07 20 20 20 01 00 00 FE 05 3A 17 05 25 91 FF 10 54 16 +46 B9 68 00 30 50 01 31 2E 90 38 01 01 FF FD 8B BF FF 27 3B F7 FE 73 55 00 F6 28 09 85 E3 5F 80 07 20 20 20 01 00 00 FE 05 3A 17 05 25 91 FF 10 5A 16 + ^^^^^ some 24 MHz reference or other clk measurement? + ^^^^^ trim/adjust? + ^^ clkdiv + ^^^^^^^^^^^ clk + + +Disconnect +---------- + +Uses FF command byte. + + +Basic challenge operation +------------------------- + +Host sends a challenge of some kind, followed by 0xfe pulsing + +46 B9 6A 00 0C 00 02 00 00 80 00 00 F8 16 + +Much simpler than in STC15 + +MCU sends back some response: + +46 B9 68 00 0C 00 02 36 AD 4E 83 02 2A 16 + +Host now sends some longer challenge, followed by more pulses: + +46 B9 6A 00 20 00 0C 7C 00 7C 01 7C 02 7C 03 7D 00 7D 01 7D 02 7D 03 7E 00 7E 01 7E 02 7E 03 06 84 16 + +MCU sends back some response: + +46 B9 68 00 20 00 0C 4D C6 4D DB 4D E7 4D F3 4D F6 4E 0E 4E 11 4E 26 4E 26 4E 32 4E 41 4E 56 09 DC 16 + +Host now seems to initiate a baud switch or something like that + +46 B9 6A 00 0E 01 00 00 FF CC 01 7C 80 03 41 16 + +MCU acknowlegdes it: + +46 B9 68 00 07 01 00 70 16 + +Now the MCU switches to the new baud rate. + + +Challenges observed +------------------- + +6 MHz: + +46B96A0020000C 1400 1401 1402 1403 1500 1501 1502 1503 1600 1601 1602 1603 01A416 + +5.5 MHz: + +46B96A0020000C 5C00 5C01 5C02 5C03 5D00 5D01 5D02 5D03 5E00 5E01 5E02 5E03 050416 + + +11 MHz: + +46B96A0020000C 5B00 5B01 5B02 5B03 5C00 5C01 5C02 5C03 5D00 5D01 5D02 5D03 04F816 + +20 MHz: + +46B96A0020000C 3600 3601 3602 3603 3700 3701 3702 3703 3800 3801 3802 3803 033C16 + +24 MHz: + +46B96A0020000C 7C00 7C01 7C02 7C03 7D00 7D01 7D02 7D03 7E00 7E01 7E02 7E03 068416 + +27 MHz: + +46B96A0020000C B000 B001 B002 B003 B100 B101 B102 B103 B200 B201 B202 B203 08F416 + + +Ranges vs trim value +-------------------- + +46 B9 6A 00 20 00 0C 00 00 80 00 FF 00 00 01 80 01 FF 01 00 02 80 02 FF 02 00 03 80 03 FF 03 06 A4 16 +46 B9 68 00 20 00 0C 36 9B 4E 92 65 E4 36 CB 4E 7D 66 29 36 D1 4E 83 66 05 36 CB 4E C2 66 47 0A EA 16 + +first byte determines general trim value... range of ~16 to ~30 MHz, the second byte (00..03) is a fine adjustment. + + +Clock division? +--------------- + +5.5 MHz vs 11 Mhz: challenge is about the same. it's likely some kind of clock divider is used! + +5.5 Mhz switch: 01 00 00 FF CC 01 5C 80 clkdiv = 4? +11 MHz switch: 01 00 00 FF CC 01 5B 80 clkdiv = 2? +22 MHz switch: 01 00 00 FF CC 01 5C 80 clkdiv = 1? + +22 Mhz option packet: 0400005AA5FFFFFF00FFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FF01516D405D0201FFFDFFFFFF8BBFF7FE +11 MHz option packet: 0400005AA5FFFFFF00FFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FF00A8AF985D0102FFFDFFFFFF8BBFF7FE +5.5 MHz option packet: 0400005AA5FFFFFF00FFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FF005462585D0004FFFDFFFFFF8BBFF7FE + ^^ clkdiv? + ^^^^^^^^ clkspeed + +Always 24 MHz for programming +----------------------------- + +Calibration for anything but 24 Mhz (and around that) fails when switching baud. Another observation is that there is no +programming speed being calibrated anymore. This may suggest that a fixed speed is used for programming. + +Adjusting BRT calculation to 24 MHz in the switch packet seems to work. So it is really using 24 MHz by default; +probably some pre-calibrated value. \ No newline at end of file diff --git a/stcgal/frontend.py b/stcgal/frontend.py index da151c7..6ac8f50 100644 --- a/stcgal/frontend.py +++ b/stcgal/frontend.py @@ -32,6 +32,7 @@ from stcgal.protocols import Stc12Protocol from stcgal.protocols import Stc15Protocol from stcgal.protocols import Stc15AProtocol from stcgal.protocols import StcUsb15Protocol +from stcgal.protocols import Stc8Protocol from stcgal.protocols import StcAutoProtocol from stcgal.protocols import StcProtocolException from stcgal.protocols import StcFramingException @@ -60,6 +61,9 @@ class StcGal: elif opts.protocol == "stc15": self.protocol = Stc15Protocol(opts.port, opts.handshake, opts.baud, round(opts.trim * 1000)) + elif opts.protocol == "stc8": + self.protocol = Stc8Protocol(opts.port, opts.handshake, opts.baud, + round(opts.trim * 1000)) elif opts.protocol == "usb15": self.protocol = StcUsb15Protocol() else: @@ -216,7 +220,7 @@ def cli(): parser.add_argument("-a", "--autoreset", help="cycle power automatically by asserting DTR", action="store_true") parser.add_argument("-r", "--resetcmd", help="Use this shell command for board power-cycling (instead of DTR assertion)", action="store") parser.add_argument("-P", "--protocol", help="protocol version (default: auto)", - choices=["stc89", "stc12a", "stc12b", "stc12", "stc15a", "stc15", "usb15", "auto"], default="auto") + choices=["stc89", "stc12a", "stc12b", "stc12", "stc15a", "stc15", "stc8", "usb15", "auto"], default="auto") 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) diff --git a/stcgal/protocols.py b/stcgal/protocols.py index 2ae0e82..1f14579 100644 --- a/stcgal/protocols.py +++ b/stcgal/protocols.py @@ -372,7 +372,8 @@ class StcAutoProtocol(StcBaseProtocol): ("stc12b", r"STC12(C|LE)(52|56)"), ("stc12", r"(STC|IAP)(10|11|12)\D"), ("stc15a", r"(STC|IAP)15[FL][012]0\d(E|EA|)$"), - ("stc15", r"(STC|IAP|IRC)15\D")] + ("stc15", r"(STC|IAP|IRC)15\D"), + ("stc8", r"(STC|IAP|IRC)8")] for protocol_name, pattern in protocol_database: if re.match(pattern, self.model.name): @@ -1546,6 +1547,123 @@ class Stc15Protocol(Stc15AProtocol): print("Target UID: %s" % Utils.hexstr(self.uid)) +class Stc8Protocol(Stc15Protocol): + """Protocol handler for STC8 series""" + + def __init__(self, port, handshake, baud, trim): + Stc15Protocol.__init__(self, port, handshake, baud, trim) + + def program_options(self): + # XXX: not yet implemented + pass + + def initialize_status(self, packet): + """Decode status packet and store basic MCU info""" + + self.mcu_clock_hz, = struct.unpack(">I", packet[1:5]) + # XXX: external clock not supported nor tested + self.external_clock = False + # all ones means no calibration + # new chips are shipped without any calibration + # XXX: somehow check if that still holds + if self.mcu_clock_hz == 0xffffffff: self.mcu_clock_hz = 0 + + # pre-calibrated trim adjust for 24 MHz, range 0x40 + self.freq_count_24 = packet[4] + + # wakeup timer factory value + self.wakeup_freq, = struct.unpack(">H", packet[23:25]) + + bl_version, bl_stepping = struct.unpack("BB", packet[17:19]) + bl_minor = packet[22] & 0x0f + self.mcu_bsl_version = "%d.%d.%d%s" % (bl_version >> 4, bl_version & 0x0f, + bl_minor, chr(bl_stepping)) + self.bsl_version = bl_version + + def calibrate(self): + """Calibrate selected user frequency frequency and switch to selected baudrate.""" + + # handle uncalibrated chips + if self.mcu_clock_hz == 0 and self.trim_frequency <= 0: + raise StcProtocolException("uncalibrated, please provide a trim value") + + # determine target counter + user_speed = self.trim_frequency + if user_speed <= 0: user_speed = self.mcu_clock_hz + target_user_count = round(user_speed / (self.baud_handshake/2)) + + # calibration, round 1 + # XXX: challenges need work. the ranges and how they related to clock frequency + # is different. A clock divider is used for lower frequencies. + print("Trimming frequency: ", end="") + sys.stdout.flush() + packet = bytes([0x00]) + packet += struct.pack(">B", 12) + packet += bytes([11, 0x00, 11*2, 0x00, 11*3, 0x00]) + packet += bytes([11*4, 0x00, 11*5, 0x00, 11*6, 0x00]) + packet += bytes([11*7, 0x00, 11*8, 0x00, 11*9, 0x00]) + packet += bytes([11*10, 0x00, 11*11, 0x00, 0x80, 0x00]) + self.write_packet(packet) + self.pulse(b"\xfe", timeout=1.0) + response = self.read_packet() + if len(response) < 2 or response[0] != 0x00: + raise StcProtocolException("incorrect magic in handshake packet") + + # select ranges and trim values + user_trim = self.choose_range(packet, response, target_user_count) + if user_trim == None: + raise StcProtocolException("frequency trimming unsuccessful") + + # calibration, round 2 + packet = bytes([0x00]) + packet += struct.pack(">B", 12) + for i in range(user_trim[0] - 2, user_trim[0] + 2): + packet += bytes([i & 0xff, 0x00]) + for i in range(user_trim[0] - 2, user_trim[0] + 2): + packet += bytes([i & 0xff, 0x01]) + for i in range(user_trim[0] - 2, user_trim[0] + 2): + packet += bytes([i & 0xff, 0x02]) + self.write_packet(packet) + self.pulse(b"\xfe", timeout=1.0) + response = self.read_packet() + if len(response) < 2 or response[0] != 0x00: + raise StcProtocolException("incorrect magic in handshake packet") + + # select final values + user_trim, user_count = self.choose_trim(packet, response, target_user_count) + self.trim_value = user_trim + self.trim_frequency = round(user_count * (self.baud_handshake / 2)) + print("%.03f MHz" % (self.trim_frequency / 1E6)) + + # switch to programming frequency + print("Switching to %d baud: " % self.baud_transfer, end="") + sys.stdout.flush() + packet = bytes([0x01, 0x00, 0x00]) + bauds = self.baud_transfer * 4 + packet += struct.pack(">H", int(65535 - 24E6 / bauds)) + packet += bytes([user_trim[1], user_trim[0]]) + iap_wait = self.get_iap_delay(user_speed) + packet += bytes([iap_wait]) + self.write_packet(packet) + response = self.read_packet() + if len(response) < 1 or response[0] != 0x01: + raise StcProtocolException("incorrect magic in handshake packet") + self.ser.baudrate = self.baud_transfer + + def initialize_options(self, status_packet): + """Initialize options""" + # XXX: not implemented yet + pass + + def disconnect(self): + """Disconnect from MCU""" + + # reset mcu + packet = bytes([0xff]) + self.write_packet(packet) + self.ser.close() + print("Disconnected!") + class StcUsb15Protocol(Stc15Protocol): """USB should use large blocks""" PROGRAM_BLOCKSIZE = 128 From 85e815366c6d20fba2b17256520cefa58bb1a929 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Wed, 27 Jun 2018 01:10:46 +0200 Subject: [PATCH 2/4] stc8: try dividers 2 and 4 for frequency calibration --- stcgal/protocols.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/stcgal/protocols.py b/stcgal/protocols.py index 1f14579..d2f1964 100644 --- a/stcgal/protocols.py +++ b/stcgal/protocols.py @@ -1599,10 +1599,10 @@ class Stc8Protocol(Stc15Protocol): sys.stdout.flush() packet = bytes([0x00]) packet += struct.pack(">B", 12) - packet += bytes([11, 0x00, 11*2, 0x00, 11*3, 0x00]) - packet += bytes([11*4, 0x00, 11*5, 0x00, 11*6, 0x00]) - packet += bytes([11*7, 0x00, 11*8, 0x00, 11*9, 0x00]) - packet += bytes([11*10, 0x00, 11*11, 0x00, 0x80, 0x00]) + packet += bytes([0x00, 0x00, 23*1, 0x00, 23*2, 0x00]) + packet += bytes([23*3, 0x00, 23*4, 0x00, 23*5, 0x00]) + packet += bytes([23*6, 0x00, 23*7, 0x00, 23*8, 0x00]) + packet += bytes([23*9, 0x00, 23*10, 0x00, 255, 0x00]) self.write_packet(packet) self.pulse(b"\xfe", timeout=1.0) response = self.read_packet() @@ -1610,8 +1610,11 @@ class Stc8Protocol(Stc15Protocol): raise StcProtocolException("incorrect magic in handshake packet") # select ranges and trim values - user_trim = self.choose_range(packet, response, target_user_count) - if user_trim == None: + for divider in (1, 2, 4): + user_trim = self.choose_range(packet, response, target_user_count * divider) + if user_trim is not None: + break + if user_trim is None: raise StcProtocolException("frequency trimming unsuccessful") # calibration, round 2 From 0ffcbd197b90cd1699d5318470e1cad0fe2bab3e Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 20 Aug 2018 23:47:42 +0200 Subject: [PATCH 3/4] stc8: finish up frequency calibration This is a collection of various changes: * Also try divider of 5 (it's used by STC-ISP) * Use the correct IAP delay for the 24 MHz programming frequency * Also try a trim adjust value of 0x03 and adjust trim ranges * Fix display of calibrated frequency --- stcgal/protocols.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/stcgal/protocols.py b/stcgal/protocols.py index d2f1964..6b87176 100644 --- a/stcgal/protocols.py +++ b/stcgal/protocols.py @@ -1552,6 +1552,7 @@ class Stc8Protocol(Stc15Protocol): def __init__(self, port, handshake, baud, trim): Stc15Protocol.__init__(self, port, handshake, baud, trim) + self.trim_divider = None def program_options(self): # XXX: not yet implemented @@ -1610,9 +1611,10 @@ class Stc8Protocol(Stc15Protocol): raise StcProtocolException("incorrect magic in handshake packet") # select ranges and trim values - for divider in (1, 2, 4): + for divider in (1, 2, 3, 4, 5): user_trim = self.choose_range(packet, response, target_user_count * divider) if user_trim is not None: + self.trim_divider = divider break if user_trim is None: raise StcProtocolException("frequency trimming unsuccessful") @@ -1620,12 +1622,14 @@ class Stc8Protocol(Stc15Protocol): # calibration, round 2 packet = bytes([0x00]) packet += struct.pack(">B", 12) - for i in range(user_trim[0] - 2, user_trim[0] + 2): + for i in range(user_trim[0] - 1, user_trim[0] + 2): packet += bytes([i & 0xff, 0x00]) - for i in range(user_trim[0] - 2, user_trim[0] + 2): + for i in range(user_trim[0] - 1, user_trim[0] + 2): packet += bytes([i & 0xff, 0x01]) - for i in range(user_trim[0] - 2, user_trim[0] + 2): + for i in range(user_trim[0] - 1, user_trim[0] + 2): packet += bytes([i & 0xff, 0x02]) + for i in range(user_trim[0] - 1, user_trim[0] + 2): + packet += bytes([i & 0xff, 0x03]) self.write_packet(packet) self.pulse(b"\xfe", timeout=1.0) response = self.read_packet() @@ -1635,7 +1639,7 @@ class Stc8Protocol(Stc15Protocol): # select final values user_trim, user_count = self.choose_trim(packet, response, target_user_count) self.trim_value = user_trim - self.trim_frequency = round(user_count * (self.baud_handshake / 2)) + self.trim_frequency = round(user_count * (self.baud_handshake / 2) / self.trim_divider) print("%.03f MHz" % (self.trim_frequency / 1E6)) # switch to programming frequency @@ -1645,7 +1649,7 @@ class Stc8Protocol(Stc15Protocol): bauds = self.baud_transfer * 4 packet += struct.pack(">H", int(65535 - 24E6 / bauds)) packet += bytes([user_trim[1], user_trim[0]]) - iap_wait = self.get_iap_delay(user_speed) + iap_wait = self.get_iap_delay(24E6) packet += bytes([iap_wait]) self.write_packet(packet) response = self.read_packet() From fe60e647bf0fa292d9d59d123e60c9d8a820ec44 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Tue, 21 Aug 2018 01:48:09 +0200 Subject: [PATCH 4/4] stc8: implement option handling Implement option handling for STC8 series, based on STC8A8K64S4A12 reverse engineering. This mostly wraps up all important parts of the STC8 implementation. Interoperability was tested with STC-ISP V6.86O. v2: update documentation --- README.md | 17 +++-- doc/stc8-options.txt | 71 ++++++++++++++++++ doc/stc8-protocol.txt | 4 + stcgal/options.py | 167 ++++++++++++++++++++++++++++++++++++++++++ stcgal/protocols.py | 30 ++++++-- 5 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 doc/stc8-options.txt diff --git a/README.md b/README.md index 7fa88fb..15d5c3c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ suitable for automation. Supported MCU models -------------------- -stcgal should fully support STC 89/90/10/11/12/15 series MCUs. +stcgal should fully support STC 89/90/10/11/12/15 series MCUs. Support for STC8 series MCUs is work in progress. So far, stcgal was tested with the following MCU models: @@ -47,6 +47,7 @@ So far, stcgal was tested with the following MCU models: * STC15L2K16S2 (BSL version: 7.2.4S) * STC15W408AS (BSL version: 7.2.4T) * STC15W4K56S4 (BSL version: 7.3.4T, UART and USB mode) +* STC8A8K64S4A12 (BSL version: 7.3.9U) Compatibility reports, both negative and positive, are welcome. @@ -59,8 +60,8 @@ Features * Program flash memory * Program IAP/EEPROM * Set device options -* Read unique device ID (STC 10/11/12/15) -* Trim RC oscillator frequency (STC 15) +* Read unique device ID (STC 10/11/12/15/8) +* Trim RC oscillator frequency (STC 15/8) * Automatic power-cycling with DTR toggle or a custom shell command * Automatic UART protocol detection @@ -126,6 +127,7 @@ and MCU series is as follows: * ```stc12``` Most STC10/11/12 series * ```stc15a``` STC15x104E and STC15x204E(A) series * ```stc15``` Most STC15 series +* ```stc8``` STC8 series * ```usb15``` USB support on STC15W4 series * ```auto``` Automatic detection of UART based protocols (default) @@ -257,17 +259,20 @@ Option key | Possible values | Protocols/Models | Descri ```por_reset_delay``` | short/long | STC12+ | Power-on reset (POR) delay ```low_voltage_threshold``` | 0...7 | STC15A+ | Low-voltage detection threshold. Model specific. ```eeprom_lvd_inhibit``` | true/false | STC15A+ | Ignore EEPROM writes in low-voltage situations -```rstout_por_state``` | low/high | STC15+ | RSTOUT pin state after power-on reset +```rstout_por_state``` | low/high | STC15+ | RSTOUT/RSTSV pin state after power-on reset +```uart1_remap``` | true/false | STC8 | Remap UART1 pins (P3.0/P3.1) to UART2 pins (P3.6/P3.7) ```uart2_passthrough``` | true/false | STC15+ | Pass-through UART1 to UART2 pins (for single-wire UART mode) ```uart2_pin_mode``` | push-pull/normal | STC15+ | Output mode of UART2 TX pin ```cpu_core_voltage``` | low/mid/high | STC15W+ | CPU core voltage (low: ~2.7V, mid: ~3.3V, high: ~3.6V) +```epwm_open_drain``` | true/false | STC8 | Use open-drain pin mode for EPWM pins after power-on reset +```program_eeprom_split``` | 512 - 65024 | STC8A8 w/ 64 KB | Select split between code flash and EEPROM flash (in 512 byte blocks) ### Frequency trimming If the internal RC oscillator is used (```clock_source=internal```), stcgal can execute a trim procedure to adjust it to a given value. This -is only supported by STC15 series. The trim values are stored with -device options. Use the ```-t``` flag to request trimming to a certain +is only supported by STC15 series and newer. The trim values are stored +with device options. Use the ```-t``` flag to request trimming to a certain value. Generally, frequencies between 4 and 35 MHz can be achieved. If trimming fails, stcgal will abort. diff --git a/doc/stc8-options.txt b/doc/stc8-options.txt new file mode 100644 index 0000000..944a959 --- /dev/null +++ b/doc/stc8-options.txt @@ -0,0 +1,71 @@ +MCS bytes +========= + + 46 b9 6a 00 33 04 00 00 5a a5 ff ff ff 00 ff ff + 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + 00 ff 01 31 20 80 34 00 01 ff ff ff ff ff 8b bf + ^^^^^^^^^^^ ^^ ^^ ^^ ^^ + frequency clkdiv 5) 1) 3) + ^^^^^ + trim? + f7 fe 1f cc 16 + ^^ ^^ + 4) 2) + +1) not stricty related to some register +aka MCS1 +bit 0: ? always 1 +bit 1: oscillator high gain +bit 2: EPWM push-pull enabled +bit 3: p2.0 state after boot +bit 4: TXD signal source from RXD +bit 5: p3.7 push-pull enabled +bit 6: UART1 remap enabled +bit 7: long power-on reset delay + +2) not strictly related to some register +aka MCS4 +eeprom size / code space upper limit (in pages) +only seems to apply to devices with max. flash size +e.g. fe -> 63.5K, e0 -> 56K + +3) like RSTCFG? inverted? +aka MCS2 +bit 0: LVD0 +bit 1: LVD1 +bit 2: ? always 1 +bit 3: ? always 1 +bit 4: ~reset pin enabled +bit 5: ? always 1 +bit 6: ~enable lvd reset +bit 7: ? always 1 + +LVD: +2.20V -> 0xbf +2.40V -> 0xbe +2.70V -> 0xbd +3.00V -> 0xbc + +4) like WDT_CONTR +aka MCS3 +bit 0: WDPS0 +bit 1: WDPS1 +bit 2: WDPS2 +bit 3: ~stop wdt in idle +bit 4: ? always 1 +bit 5: ~enable wdt on por +bit 6: ? always 1 +bit 7: ? always 1 + +WDPS like in datasheet + +5) +aka MCS0 +bit 0: ? ~BSLD / bootloader enabled +bit 1: erase eeprom enabled +bit 2: ? +bit 3: ? +bit 4: ? +bit 5: ? +bit 6: ? +bit 7: ? \ No newline at end of file diff --git a/doc/stc8-protocol.txt b/doc/stc8-protocol.txt index fdd2e33..436e33e 100644 --- a/doc/stc8-protocol.txt +++ b/doc/stc8-protocol.txt @@ -31,6 +31,10 @@ Clock set to 20 MHz by STC-ISP (encoding is different compared to STC15): ^^ clkdiv ^^^^^^^^^^^ clk +MCS bytes + +46 B9 68 00 30 50 01 31 2E 90 38 01 01 FF FD 8B BF FF 27 35 F7 FE 73 55 00 F6 28 09 85 E3 5F 80 07 20 20 20 01 00 00 FE 05 3A 17 05 25 91 FF 10 54 16 + ^^^^^^^^ ^^^^^ Disconnect ---------- diff --git a/stcgal/options.py b/stcgal/options.py index 8106bce..703f288 100644 --- a/stcgal/options.py +++ b/stcgal/options.py @@ -622,3 +622,170 @@ class Stc15Option(BaseOption): if val not in volt_vals.keys(): raise ValueError("must be one of %s" % list(volt_vals.keys())) self.msr[4] = volt_vals[val] + +class Stc8Option(BaseOption): + def __init__(self, msr): + super().__init__() + assert len(msr) >= 5 + self.msr = bytearray(msr) + + self.options = ( + ("reset_pin_enabled", self.get_reset_pin_enabled, self.set_reset_pin_enabled), + ("clock_gain", self.get_clock_gain, self.set_clock_gain), + ("watchdog_por_enabled", self.get_watchdog, self.set_watchdog), + ("watchdog_stop_idle", self.get_watchdog_idle, self.set_watchdog_idle), + ("watchdog_prescale", self.get_watchdog_prescale, self.set_watchdog_prescale), + ("low_voltage_reset", self.get_lvrs, self.set_lvrs), + ("low_voltage_threshold", self.get_low_voltage, self.set_low_voltage), + ("eeprom_erase_enabled", self.get_ee_erase, self.set_ee_erase), + ("bsl_pindetect_enabled", self.get_pindetect, self.set_pindetect), + ("por_reset_delay", self.get_por_delay, self.set_por_delay), + ("rstout_por_state", self.get_p20_state, self.set_p20_state), + ("uart1_remap", self.get_uart1_remap, self.set_uart1_remap), + ("uart2_passthrough", self.get_uart_passthrough, self.set_uart_passthrough), + ("uart2_pin_mode", self.get_uart_pin_mode, self.set_uart_pin_mode), + ("epwm_open_drain", self.get_epwm_pp, self.set_epwm_pp), + ("program_eeprom_split", self.get_flash_split, self.set_flash_split), + ) + + def get_reset_pin_enabled(self): + return not bool(self.msr[2] & 16) + + def set_reset_pin_enabled(self, val): + val = Utils.to_bool(val) + self.msr[2] &= 0xef + self.msr[2] |= 0x10 if not bool(val) else 0x00 + + def get_clock_gain(self): + gain = bool(self.msr[1] & 0x02) + return "high" if gain else "low" + + def set_clock_gain(self, val): + gains = {"low": 0, "high": 1} + if val not in gains.keys(): + raise ValueError("must be one of %s" % list(gains.keys())) + self.msr[1] &= 0xfd + self.msr[1] |= gains[val] << 1 + + def get_watchdog(self): + return not bool(self.msr[3] & 32) + + def set_watchdog(self, val): + val = Utils.to_bool(val) + self.msr[3] &= 0xdf + self.msr[3] |= 0x20 if not val else 0x00 + + def get_watchdog_idle(self): + return not bool(self.msr[3] & 8) + + def set_watchdog_idle(self, val): + val = Utils.to_bool(val) + self.msr[3] &= 0xf7 + self.msr[3] |= 0x08 if not val else 0x00 + + def get_watchdog_prescale(self): + return 2 ** (((self.msr[3]) & 0x07) + 1) + + def set_watchdog_prescale(self, val): + val = Utils.to_int(val) + wd_vals = {2: 0, 4: 1, 8: 2, 16: 3, 32: 4, 64: 5, 128: 6, 256: 7} + if val not in wd_vals.keys(): + raise ValueError("must be one of %s" % list(wd_vals.keys())) + self.msr[3] &= 0xf8 + self.msr[3] |= wd_vals[val] + + def get_lvrs(self): + return not bool(self.msr[2] & 64) + + def set_lvrs(self, val): + val = Utils.to_bool(val) + self.msr[2] &= 0xbf + self.msr[2] |= 0x40 if not val else 0x00 + + def get_low_voltage(self): + return 3 - self.msr[2] & 0x03 + + def set_low_voltage(self, val): + val = Utils.to_int(val) + if val not in range(0, 4): + raise ValueError("must be one of %s" % list(range(0, 4))) + self.msr[2] &= 0xfc + self.msr[2] |= 3 - val + + def get_ee_erase(self): + return bool(self.msr[0] & 2) + + def set_ee_erase(self, val): + val = Utils.to_bool(val) + self.msr[0] &= 0xfd + self.msr[0] |= 0x02 if val else 0x00 + + def get_pindetect(self): + return not bool(self.msr[0] & 1) + + def set_pindetect(self, val): + val = Utils.to_bool(val) + self.msr[0] &= 0xfe + self.msr[0] |= 0x01 if not val else 0x00 + + def get_por_delay(self): + delay = bool(self.msr[1] & 128) + return "long" if delay else "short" + + def set_por_delay(self, val): + delays = {"short": 0, "long": 1} + if val not in delays.keys(): + raise ValueError("must be one of %s" % list(delays.keys())) + self.msr[1] &= 0x7f + self.msr[1] |= delays[val] << 7 + + def get_p20_state(self): + return "high" if self.msr[1] & 0x08 else "low" + + def set_p20_state(self, val): + val = Utils.to_bool(val) + self.msr[1] &= 0xf7 + self.msr[1] |= 0x08 if val else 0x00 + + def get_uart_passthrough(self): + return bool(self.msr[1] & 0x10) + + def set_uart_passthrough(self, val): + val = Utils.to_bool(val) + self.msr[1] &= 0xef + self.msr[1] |= 0x10 if val else 0x00 + + def get_uart_pin_mode(self): + return "push-pull" if bool(self.msr[1] & 0x20) else "normal" + + def set_uart_pin_mode(self, val): + modes = {"normal": 0, "push-pull": 1} + if val not in modes.keys(): + raise ValueError("must be one of %s" % list(modes.keys())) + self.msr[1] &= 0xdf + self.msr[1] |= 0x20 if modes[val] else 0x00 + + def get_epwm_pp(self): + return bool(self.msr[1] & 0x04) + + def set_epwm_pp(self, val): + val = Utils.to_bool(val) + self.msr[1] &= 0xfb + self.msr[1] |= 0x04 if val else 0x00 + + def get_uart1_remap(self): + return bool(self.msr[1] & 0x40) + + def set_uart1_remap(self, val): + val = Utils.to_bool(val) + self.msr[1] &= 0xbf + self.msr[1] |= 0x40 if val else 0x00 + + def get_flash_split(self): + return self.msr[4] * 256 + + def set_flash_split(self, val): + num_val = Utils.to_int(val) + if num_val < 512 or num_val > 65024 or (num_val % 512) != 0: + raise ValueError("must be between 512 and 65024 bytes and a multiple of 512 bytes") + self.msr[4] = num_val // 256 \ No newline at end of file diff --git a/stcgal/protocols.py b/stcgal/protocols.py index 6b87176..cec7bc5 100644 --- a/stcgal/protocols.py +++ b/stcgal/protocols.py @@ -34,6 +34,7 @@ from stcgal.options import Stc12Option from stcgal.options import Stc12AOption from stcgal.options import Stc15Option from stcgal.options import Stc15AOption +from stcgal.options import Stc8Option from abc import ABC from abc import abstractmethod import functools @@ -1554,9 +1555,14 @@ class Stc8Protocol(Stc15Protocol): Stc15Protocol.__init__(self, port, handshake, baud, trim) self.trim_divider = None - def program_options(self): - # XXX: not yet implemented - pass + def initialize_options(self, status_packet): + """Initialize options""" + if len(status_packet) < 17: + raise StcProtocolException("invalid options in status packet") + + # create option state + self.options = Stc8Option(status_packet[9:12] + status_packet[15:17]) + self.options.print() def initialize_status(self, packet): """Decode status packet and store basic MCU info""" @@ -1657,10 +1663,20 @@ class Stc8Protocol(Stc15Protocol): raise StcProtocolException("incorrect magic in handshake packet") self.ser.baudrate = self.baud_transfer - def initialize_options(self, status_packet): - """Initialize options""" - # XXX: not implemented yet - pass + def build_options(self): + """Build a packet of option data from the current configuration.""" + + msr = self.options.get_msr() + packet = 40 * bytearray([0xff]) + packet[3] = 0 + packet[6] = 0 + packet[22] = 0 + packet[24:28] = struct.pack(">I", self.trim_frequency) + packet[28:30] = self.trim_value + packet[30] = self.trim_divider + packet[32] = msr[0] + packet[36:40] = msr[1:5] + return bytes(packet) def disconnect(self): """Disconnect from MCU"""