Skip to content

Commit 26cdb18

Browse files
committed
Merge branch 'bugfix-custom-hard-reset-sequence' into 'master'
feat: Support custom_hard_reset_sequence in config Closes IDFGH-17261 See merge request espressif/esp-idf-monitor!111
2 parents f062485 + 773b63d commit 26cdb18

File tree

5 files changed

+82
-39
lines changed

5 files changed

+82
-39
lines changed

README.md

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Other advanced topics like configuration file will be described in the following
1616
- [Configuration File](#configuration-file)
1717
- [File Location](#file-location)
1818
- [Configuration Options](#configuration-options)
19-
- [Custom Reset Sequence](#custom-reset-sequence)
19+
- [Custom Reset Sequences](#custom-reset-sequences)
2020
- [Share Configuration Across Tools](#share-configuration-across-tools)
2121
- [Syntax](#syntax)
2222
- [Embedded Command Execution](#embedded-command-execution)
@@ -89,25 +89,26 @@ A different location for the configuration file can be specified with the `ESP_I
8989

9090
Below is a table listing the available configuration options:
9191

92-
| Option Name | Description | Default Value |
93-
|----------------------------|----------------------------------------------------------|----------------|
94-
| `menu_key` | Key to access the main menu. | `T` |
95-
| `exit_key` | Key to exit the monitor. | `]` |
96-
| `chip_reset_key` | Key to initiate a chip reset. | `R` |
97-
| `recompile_upload_key` | Key to recompile and upload. | `F` |
98-
| `recompile_upload_app_key` | Key to recompile and upload just the application. | `A` |
99-
| `toggle_output_key` | Key to toggle the output display. | `Y` |
100-
| `toggle_log_key` | Key to toggle the logging feature. | `L` |
101-
| `toggle_timestamp_key` | Key to toggle timestamp display. | `I` |
102-
| `chip_reset_bootloader_key`| Key to reset the chip to bootloader mode. | `P` |
103-
| `exit_menu_key` | Key to exit the monitor from the menu. | `X` |
104-
| `skip_menu_key` | Pressing the menu key can be skipped for menu commands. | `False` |
105-
| `reconnect_delay` | Delay between reconnect retries (in seconds) | 0.5 |
106-
| `custom_reset_sequence` | Custom reset sequence for resetting into the bootloader. | N/A |
107-
108-
#### Custom Reset Sequence
109-
110-
For more advanced users or specific use cases, IDF Monitor supports the configuration of a custom reset sequence using [configuration file](#configuration-file). This is particularly useful in extreme edge cases where the default sequence may not suffice.
92+
| Option Name | Description | Default Value |
93+
|------------------------------|----------------------------------------------------------|----------------|
94+
| `menu_key` | Key to access the main menu. | `T` |
95+
| `exit_key` | Key to exit the monitor. | `]` |
96+
| `chip_reset_key` | Key to initiate a chip reset. | `R` |
97+
| `recompile_upload_key` | Key to recompile and upload. | `F` |
98+
| `recompile_upload_app_key` | Key to recompile and upload just the application. | `A` |
99+
| `toggle_output_key` | Key to toggle the output display. | `Y` |
100+
| `toggle_log_key` | Key to toggle the logging feature. | `L` |
101+
| `toggle_timestamp_key` | Key to toggle timestamp display. | `I` |
102+
| `chip_reset_bootloader_key` | Key to reset the chip to bootloader mode. | `P` |
103+
| `exit_menu_key` | Key to exit the monitor from the menu. | `X` |
104+
| `skip_menu_key` | Pressing the menu key can be skipped for menu commands. | `False` |
105+
| `reconnect_delay` | Delay between reconnect retries (in seconds). | 0.5 |
106+
| `custom_reset_sequence` | Custom reset sequence for resetting into the bootloader. | N/A |
107+
| `custom_hard_reset_sequence` | Custom reset sequence for hard resetting the chip. | N/A |
108+
109+
#### Custom Reset Sequences
110+
111+
For more advanced users or specific use cases, IDF Monitor supports the configuration of custom reset sequences using [configuration file](#configuration-file). This is particularly useful in extreme edge cases where the default sequence may not suffice.
111112

112113
The sequence is defined with a string in the following format:
113114

@@ -128,7 +129,7 @@ Example:
128129
custom_reset_sequence = U0,1|W0.1|D1|R0|W0.5|D0
129130
```
130131

131-
Refer to [custom reset sequence](https://docs.espressif.com/projects/esptool/en/latest/esptool/configuration-file.html#custom-reset-sequence) from Esptool documentation for further details. Please note that `custom_reset_sequence` is the only used value from the Esptool configuration, and others will be ignored in IDF Monitor.
132+
Refer to [custom reset sequence](https://docs.espressif.com/projects/esptool/en/latest/esptool/configuration-file.html#custom-reset-sequence) from Esptool documentation for further details. Please note that `custom_reset_sequence` and `custom_hard_reset_sequence` are the only used values from the Esptool configuration, and others will be ignored in IDF Monitor.
132133

133134
##### Share Configuration Across Tools
134135

@@ -143,10 +144,11 @@ skip_menu_key = True
143144

144145
[esptool]
145146
custom_reset_sequence = U0,1|W0.1|D1|R0|W0.5|D0
147+
custom_hard_reset_sequence = R1|W0.1|R0
146148
```
147149

148150
> [!NOTE]
149-
> When using the `custom_reset_sequence` parameter in both the `[esp-idf-monitor]` section and the `[esptool]` section, the configuration from the `[esp-idf-monitor]` section will take precedence in IDF Monitor. Any conflicting configuration in the `[esptool]` section will be ignored.
151+
> When using the `custom_reset_sequence` or `custom_hard_reset_sequence` parameter in both the `[esp-idf-monitor]` section and the `[esptool]` section, the configuration from the `[esp-idf-monitor]` section will take precedence in IDF Monitor. Any conflicting configuration in the `[esptool]` section will be ignored.
150152
>
151153
> This precedence rule also applies when the configuration is spread across multiple files. The global esp-idf-monitor configuration will take precedence over the local esptool configuration.
152154

esp_idf_monitor/base/reset.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,30 @@ def __init__(self, serial_instance: serial.Serial, chip: str) -> None:
4545
self._load_config()
4646

4747
def _load_config(self) -> None:
48-
"""Load configuration for custom reset sequence
49-
Look for custom_reset_sequence in esp-idf-monitor config, if not found fallback to esptool config
48+
"""Load configuration for custom reset sequences.
49+
Look for custom_reset_sequence and custom_hard_reset_sequence
50+
in esp-idf-monitor config, if not found fallback to esptool config.
5051
"""
5152
custom_cfg = Config()
5253
custom_config, self.config_path = custom_cfg.load_configuration()
5354
# try to get the custom reset sequence from esp-idf-monitor
54-
self.esptool_config = False
55+
self.bootloader_reset_from_esptool = False
56+
self.hard_reset_from_esptool = False
5557
self.custom_seq = custom_config['esp-idf-monitor'].get('custom_reset_sequence')
58+
self.custom_hard_seq = custom_config['esp-idf-monitor'].get('custom_hard_reset_sequence')
5659
if self.config_path is None:
5760
# config for esp-idf-monitor was not found, looking for esptool configuration
5861
# this is required in case the config file doesn't contain esp-idf-monitor section at all
5962
custom_cfg = Config(config_name='esptool')
6063
custom_config, self.config_path = custom_cfg.load_configuration()
6164
if self.custom_seq is None and 'esptool' in custom_config.keys():
62-
# get reset sequence from esptool section
65+
# get bootloader reset sequence from esptool section
6366
self.custom_seq = custom_config['esptool'].get('custom_reset_sequence')
64-
self.esptool_config = True
67+
self.bootloader_reset_from_esptool = self.custom_seq is not None
68+
if self.custom_hard_seq is None and 'esptool' in custom_config.keys():
69+
# get hard reset sequence from esptool section
70+
self.custom_hard_seq = custom_config['esptool'].get('custom_hard_reset_sequence')
71+
self.hard_reset_from_esptool = self.custom_hard_seq is not None
6572

6673
def _get_port_pid(self) -> Optional[int]:
6774
"""Get port PID to differentiate between JTAG and UART reset sequences"""
@@ -98,28 +105,33 @@ def _setDTRandRTS(self, dtr: bool = HIGH, rts: bool = HIGH) -> None:
98105
status &= ~TIOCM_RTS
99106
fcntl.ioctl(self.serial_instance.fileno(), TIOCMSET, struct.pack('I', status))
100107

101-
def _parse_string_to_seq(self, seq_str: str) -> str:
108+
def _parse_string_to_seq(self, seq_str: str, option_name: str = 'custom_reset_sequence') -> str:
102109
"""Parse custom reset sequence from a config"""
103110
try:
104111
cmds = seq_str.split('|')
105112
fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds]
106113
except Exception as e:
107-
error_print(f'Invalid "custom_reset_sequence" option format: {e}')
114+
error_print(f'Invalid "{option_name}" option format: {e}')
108115
return ''
109116
return '\n'.join(fn_calls_list)
110117

111118
def hard(self) -> None:
112119
"""Hard reset chip"""
113-
self._setRTS(LOW) # EN=LOW, chip in reset
114-
time.sleep(self.chip_config['reset'])
115-
self._setRTS(HIGH) # EN=HIGH, chip out of reset
120+
if self.custom_hard_seq:
121+
source = 'esptool ' if self.hard_reset_from_esptool else ''
122+
note_print(f'Using custom hard reset sequence from {source}config file: {self.config_path}')
123+
exec(self._parse_string_to_seq(self.custom_hard_seq, 'custom_hard_reset_sequence'))
124+
else:
125+
self._setRTS(LOW) # EN=LOW, chip in reset
126+
time.sleep(self.chip_config['reset'])
127+
self._setRTS(HIGH) # EN=HIGH, chip out of reset
116128

117129
def to_bootloader(self) -> None:
118130
"""Reset chip into bootloader"""
119131
if self.custom_seq:
120132
# use custom reset sequence set in config file
121-
source = 'from esptool ' if self.esptool_config else ''
122-
note_print(f'Using custom reset sequence {source}config file: {self.config_path}')
133+
source = 'esptool ' if self.bootloader_reset_from_esptool else ''
134+
note_print(f'Using custom reset sequence from {source}config file: {self.config_path}')
123135
exec(self._parse_string_to_seq(self.custom_seq))
124136
elif self.port_pid == USB_JTAG_SERIAL_PID:
125137
# use reset sequence for JTAG

esp_idf_monitor/base/serial_reader.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,12 @@ def open_serial(self, reset: bool) -> None:
108108

109109
self.serial.open()
110110

111-
# set DTR/RTS into expected HIGH state, but set the RTS first to avoid reset
112-
self.reset_strategy._setRTS(HIGH)
113-
self.reset_strategy._setDTR(HIGH)
111+
# When using custom hard reset, skip before reset so the sequence can control lines;
112+
# when --no-reset is used, use the default behavior even if custom hard reset is set
113+
if not self.reset_strategy.custom_hard_seq or not reset:
114+
# set DTR/RTS into expected HIGH state, but set the RTS first to avoid reset
115+
self.reset_strategy._setRTS(HIGH)
116+
self.reset_strategy._setDTR(HIGH)
114117
if reset:
115118
self.reset_strategy.hard()
116119

esp_idf_monitor/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
'skip_menu_key',
2323
'reconnect_delay',
2424
'custom_reset_sequence', # from esptool config
25+
'custom_hard_reset_sequence',
2526
]
2627

2728

test/host_test/test_monitor.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ def test_custom_sequence_precedence(self):
644644

645645
with open(err) as f_err:
646646
stderr = f_err.read()
647-
msg = f'--- Using custom reset sequence config file: {os.path.join(os.getcwd(), "config.cfg")}'
647+
msg = f'--- Using custom reset sequence from config file: {os.path.join(os.getcwd(), "config.cfg")}'
648648
assert msg in stderr
649649
# remove everything before message about using custom config to remove starting reset sequence
650650
log_seq = stderr.split(msg)[1]
@@ -673,9 +673,34 @@ def test_invalid_custom_sequence(self):
673673
with open(err) as f_err:
674674
stderr = f_err.read()
675675
# check for error message that reset sequence was invalid
676-
assert f'--- Using custom reset sequence config file: {os.path.join(os.getcwd(), "config.cfg")}' in stderr
676+
assert f'--- Using custom reset sequence from config file: {os.path.join(os.getcwd(), "config.cfg")}' in stderr
677677
assert '--- Error: Invalid "custom_reset_sequence" option format: \'F\'' in stderr
678678

679+
def test_custom_hard_reset_sequence(self):
680+
"""Use custom hard reset sequence"""
681+
# create custom config with custom hard reset sequence
682+
self.create_config({'custom_hard_reset_sequence': 'R1|W0.1|R0'})
683+
# run monitor
684+
_, err = self.run_monitor_async(args=['--no-reset'])
685+
# hard reset chip
686+
self.send_control('TR')
687+
# wait for command to apply
688+
time.sleep(0.5)
689+
assert self.close_monitor_async() == 0
690+
with open(err) as f_err:
691+
stderr = f_err.read()
692+
msg = f'--- Using custom hard reset sequence from config file: {os.path.join(os.getcwd(), "config.cfg")}'
693+
assert msg in stderr
694+
# remove everything before message about using custom config to remove starting reset sequence
695+
log_seq = stderr.split(msg)[1]
696+
# check in pyserial log that custom hard reset sequence was used (Note: we cannot test the wait part)
697+
my_seq = [
698+
'INFO:pySerial.socket:ignored _update_rts_state(1)', # R1
699+
'INFO:pySerial.socket:ignored _update_dtr_state(False)', # expected workaround for windows RTS setting
700+
'INFO:pySerial.socket:ignored _update_rts_state(0)', # R0
701+
]
702+
assert '\n'.join(my_seq) in log_seq
703+
679704

680705
class TestCStyleConversion(TestBaseClass):
681706
"""Test C-style conversion"""

0 commit comments

Comments
 (0)