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"""