diff --git a/docs/options.md b/docs/options.md
index b2fe62a62..58407f5ff 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -234,6 +234,22 @@ Whether to preserve existing flash content for ranges of sectors that will be er
with new data.
+
| load.pre_reset |
+str |
+No default |
+
+Specify the type of reset to perform before programming. The value must be one of
+'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'.
+ |
+
+| load.post_reset |
+str |
+No default |
+
+Specify the type of reset to perform after programming. The value must be one of
+'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'.
+ |
+
| logging |
str, dict |
No default |
diff --git a/pyocd/core/options.py b/pyocd/core/options.py
index 4a10c73a2..fbb8a275f 100644
--- a/pyocd/core/options.py
+++ b/pyocd/core/options.py
@@ -44,7 +44,7 @@ class OptionInfo(NamedTuple):
"target memory."),
OptionInfo('chip_erase', str, "sector",
"Whether to perform a chip erase or sector erases when programming flash. The value must be"
- " one of \"auto\", \"sector\", or \"chip\"."),
+ " one of 'auto', 'sector', or 'chip'."),
OptionInfo('cmsis_dap.prefer_v1', bool, False,
"If a device provides both CMSIS-DAP v1 and v2 interfaces, use the v1 interface in preference of v2. "
"Normal behaviour is to prefer the v2 interface. This option is primarily intended for testing."),
@@ -89,6 +89,12 @@ class OptionInfo(NamedTuple):
OptionInfo('keep_unwritten', bool, False,
"Whether to preserve existing flash content for ranges of sectors that will be erased but not "
"written with new data. Default is False."),
+ OptionInfo('load.pre_reset', str, None,
+ "Specify the type of reset to perform before programming. The value must be one of"
+ " 'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'."),
+ OptionInfo('load.post_reset', str, None,
+ "Specify the type of reset to perform after programming. The value must be one of"
+ " 'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'."),
OptionInfo('logging', (str, dict), None,
"Logging configuration dictionary, or path to YAML file containing logging configuration."),
OptionInfo('no_config', bool, False,
@@ -171,7 +177,7 @@ class OptionInfo(NamedTuple):
OptionInfo('rtos.name', str, None,
"Name of the RTOS plugin to use. If not set, all RTOS plugins are given a chance to load."),
OptionInfo('semihost_console_type', str, 'telnet',
- "If set to \"telnet\" then the semihosting telnet server will be started, otherwise "
+ "If set to 'telnet' then the semihosting telnet server will be started, otherwise "
"semihosting will print to the console."),
OptionInfo('semihost_use_syscalls', bool, False,
"Whether to use GDB syscalls for semihosting file access operations."),
diff --git a/pyocd/coresight/cortex_m.py b/pyocd/coresight/cortex_m.py
index 7cd23ce94..b3c51ac0a 100644
--- a/pyocd/coresight/cortex_m.py
+++ b/pyocd/coresight/cortex_m.py
@@ -873,24 +873,22 @@ def _get_actual_reset_type(self, reset_type: Optional[Target.ResetType]) -> Targ
# Select the actual reset type to use, based on priority:
# 1. reset_type parameter
- # 2. cbuild-run user option
- # 3. session option
+ # 2. session option
+ # 3. cbuild-run user option (modifies default_reset_type)
# 4. core default_reset_type property
if reset_type is None:
# No explicit reset_type parameter provided, check user options.
- if self.session.options.is_set('cbuild_run'):
+ option_reset_type = self.session.options.get('reset_type')
+ if option_reset_type == 'default':
_reset_type = self.default_reset_type
- elif self.session.options.get('reset_type') != 'default':
- option_reset_type = self.session.options.get('reset_type')
+ else:
try:
_reset_type = cmdline.convert_reset_type(option_reset_type)
except ValueError:
LOG.warning("invalid reset type '%s' specified in user options; falling back to 'default'",
option_reset_type)
_reset_type = self.default_reset_type
- else:
- _reset_type = self.default_reset_type
reset_type = _reset_type
@@ -980,7 +978,7 @@ def _post_reset_core_accessibility_test(self) -> None:
else:
LOG.debug("Core %d did not come out of reset within timeout", self.core_number)
- def reset_hook(self, reset_type: Target.ResetType) -> Optional[bool]:
+ def reset_hook(self, reset_type: Target.ResetType) -> None:
# Map our reset type to a reset sequence name.
result = self.call_delegate('will_reset', core=self, reset_type=reset_type)
@@ -988,14 +986,20 @@ def reset_hook(self, reset_type: Target.ResetType) -> Optional[bool]:
# Check if the reset type is DEFAULT, SYSTEM, CORE, or HARDWARE.
# These map to standard debug sequence names. Other reset types
# are handled directly by _perform_reset().
- if reset_type in (Target.ResetType.DEFAULT,
- Target.ResetType.HARDWARE,
- Target.ResetType.SYSTEM,
- Target.ResetType.CORE):
+ STANDARD_RESET_SEQUENCES = {
+ 'ResetHardware': Target.ResetType.HARDWARE,
+ 'ResetSystem': Target.ResetType.SYSTEM,
+ 'ResetProcessor': Target.ResetType.CORE,
+ }
+ if reset_type in (Target.ResetType.DEFAULT, *STANDARD_RESET_SEQUENCES.values()):
# Check if a custom reset sequence is defined.
if reset_type == Target.ResetType.DEFAULT:
# Use the core's default reset sequence.
reset_sequence_name = self.debug_sequence_delegate.default_reset_sequence(self.node_name)
+ # See if the default reset sequence name maps to a standard reset type.
+ if reset_sequence_name in STANDARD_RESET_SEQUENCES:
+ # Update reset_type so we can use it later in _perform_reset().
+ reset_type = STANDARD_RESET_SEQUENCES[reset_sequence_name]
elif reset_type == Target.ResetType.HARDWARE:
reset_sequence_name = 'ResetHardware'
elif reset_type == Target.ResetType.SYSTEM:
@@ -1008,13 +1012,18 @@ def reset_hook(self, reset_type: Target.ResetType) -> Optional[bool]:
LOG.debug("running '%s' debug sequence, core %d", reset_sequence_name, self.core_number)
self.debug_sequence_delegate.run_sequence(reset_sequence_name, pname=self.node_name)
result = True
- elif reset_sequence_name not in ('ResetSystem', 'ResetProcessor', 'ResetHardware'):
+ elif reset_sequence_name not in STANDARD_RESET_SEQUENCES:
# Custom reset sequence was specified but not found. Warn the user, but don't fall back
# to a different reset method.
LOG.error("skipping missing '%s' debug sequence, core %d", reset_sequence_name, self.core_number)
result = True
- return result
+ if not result:
+ # No delegate or debug sequence handled the reset, so do it ourselves.
+ LOG.debug("no delegate or debug sequence handled reset; performing %s reset, core %d",
+ reset_type.name, self.core_number)
+
+ self._perform_reset(reset_type)
def _inner_reset(self, reset_type: Optional[Target.ResetType], is_halting: bool) -> None:
"""@brief Internal routine for resetting the core.
@@ -1029,10 +1038,7 @@ def _inner_reset(self, reset_type: Optional[Target.ResetType], is_halting: bool)
self._run_token += 1
- # Give the delegate a chance to overide reset. If the delegate returns True, then it
- # handled the reset on its own.
- if not self.reset_hook(reset_type):
- self._perform_reset(reset_type)
+ self.reset_hook(reset_type)
# Unless this is a halting reset, make sure the core is not halted. Some DFP debug sequences
# (or user scripts) can leave the core halted after a reset.
diff --git a/pyocd/subcommands/load_cmd.py b/pyocd/subcommands/load_cmd.py
index edf15b689..567a10062 100644
--- a/pyocd/subcommands/load_cmd.py
+++ b/pyocd/subcommands/load_cmd.py
@@ -25,6 +25,7 @@
from ..core.target import Target
from ..flash.file_programmer import FileProgrammer
from ..utility.cmdline import (
+ convert_reset_type,
convert_session_options,
int_base_0,
)
@@ -111,16 +112,24 @@ def invoke(self) -> int:
# Get a list of all secondary cores.
secondary_cores = [c for c in session.target.cores.values() if c != session.target.primary_core]
- try:
- # Set reset catch for all secondary cores.
- for core in secondary_cores:
- core.set_reset_catch()
- # Reset and halt the primary core.
- session.target.reset_and_halt()
- finally:
- # Clear reset catch for all secondary cores.
- for core in secondary_cores:
- core.clear_reset_catch()
+ pre_reset = session.options.get('load.pre_reset')
+ if pre_reset != "off":
+ try:
+ reset_type = convert_reset_type(pre_reset) if pre_reset else None
+ except ValueError:
+ LOG.error("Invalid pre-reset option: %s", pre_reset)
+ return 1
+
+ try:
+ # Set reset catch for all secondary cores.
+ for core in secondary_cores:
+ core.set_reset_catch(reset_type)
+ # Reset and halt the primary core.
+ session.target.reset_and_halt(reset_type)
+ finally:
+ # Clear reset catch for all secondary cores.
+ for core in secondary_cores:
+ core.clear_reset_catch(reset_type)
if not self._args.file and self._args.cbuild_run:
# Populate file list from cbuild-run output if not provided explicitly
@@ -164,7 +173,14 @@ def invoke(self) -> int:
file_format=file_format)
# Reset the target after programming unless --no-reset was specified.
- if not self._args.no_reset:
- session.target.reset(Target.ResetType.NSRST)
+ post_reset = session.options.get('load.post_reset')
+ if not self._args.no_reset and post_reset != 'off':
+ try:
+ reset_type = convert_reset_type(post_reset) if post_reset else None
+ except ValueError:
+ LOG.error("Invalid post-reset option: %s", post_reset)
+ return 1
+
+ session.target.reset(reset_type)
return 0