From 9e09e725a49e245d713693df3eb155d0ebedbcd8 Mon Sep 17 00:00:00 2001 From: phatpaul Date: Tue, 30 Dec 2025 16:32:39 -0500 Subject: [PATCH 1/2] Added new option --isp-entry to use DTR and RTS pins to auto enter ISP mode. Fixed bug: KeyError: 'RAMRange'. --- README.md | 10 ++++- src/ispprogrammer/IODevices.py | 38 +++++++++++++++- src/ispprogrammer/ISPConnection.py | 5 ++- src/ispprogrammer/cli.py | 60 ++++++++++++++------------ src/ispprogrammer/parts_definitions.py | 8 +++- 5 files changed, 90 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index c342b5b..8aa22d1 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,13 @@ To prevent bricking the chip during an interrupted write: This ensures that any failure during programming will leave the chip in ISP mode. +### Auto ISP Mode Entry +The `--isp_entry` option controls the UART RTS and DTR lines to automatically enter ISP mode. This option requires: +- DTR connects to chip /Reset pin +- RTS connects to chip ISP_Entry pin + +Alternatively, you can manually enter ISP mode by asserting both /Reset and ISP_Entry pins low, then releasing /Reset. + ## Chip Families Supported: + LPC80x + LPC802 @@ -48,6 +55,7 @@ The configuration file is identical to that used by the [lpctools project](http: ```bash pip install ispprogrammer ``` +(Hint: use pipx on Ubuntu) ### From Source ```bash @@ -62,7 +70,7 @@ pip install . ## Usage ### Erase Entire Flash ```bash -ispprogrammer --device /dev/ttyUSB0 -b 9600 -crystal_frequency 12000 masserase +ispprogrammer --device /dev/ttyUSB0 -b 9600 -crystal_frequency 12000 erase ``` ### Program Flash Image diff --git a/src/ispprogrammer/IODevices.py b/src/ispprogrammer/IODevices.py index 0d3f138..f6b2c3f 100644 --- a/src/ispprogrammer/IODevices.py +++ b/src/ispprogrammer/IODevices.py @@ -1,4 +1,5 @@ import logging +import time from serial import Serial kTimeout = 1 @@ -65,9 +66,44 @@ def __init__( port: str = "/dev/ttyUSB0", baudrate: int = 9600, timeout: float = kTimeout, + isp_entry=False, ): _log.debug("connect serial") - self.uart = Serial(port, baudrate, xonxoff=False, timeout=timeout) + # Create the Serial object without port to avoid automatic opening + self.uart = Serial(port=None, baudrate=baudrate, xonxoff=False, timeout=timeout) + + # Disable RTS and DRT to avoid automatic reset to ISP mode + self.uart.rts = 0 + self.uart.dtr = 0 + + # Select and open the port after RTS and DTR are set to zero + self.uart.port = port + self.uart.open() + + if isp_entry: + self.isp_mode() + + self.flush() + + # put the chip in isp mode by resetting it using RTS and DTR signals + # this is of course only possible if the signals are connected + def isp_mode(self): + self.set_reset_pin_level(0) + time.sleep(.1) + self.set_reset_pin_level(1) + self.set_isp_entry_pin_level(1) + time.sleep(.1) + self.set_reset_pin_level(0) + time.sleep(.1) + self.set_isp_entry_pin_level(0) + + def set_reset_pin_level(self, level): + # reset pin is on dtr + self.uart.dtr = level + + def set_isp_entry_pin_level(self, level): + # ISP entry pin is on rts + self.uart.rts = level def disconnect(self): _log.debug("disconnect serial") diff --git a/src/ispprogrammer/ISPConnection.py b/src/ispprogrammer/ISPConnection.py index c93da74..3ded28d 100644 --- a/src/ispprogrammer/ISPConnection.py +++ b/src/ispprogrammer/ISPConnection.py @@ -912,6 +912,7 @@ def SetupChip( chip_file: str, no_sync: bool = False, settings: Union[Settings, None] = None, + isp_entry: bool = False, ): """ :param int baudrate: The baudrate to set or use. If no_sync is True this baudrate is assumed to already be set @@ -920,6 +921,7 @@ def SetupChip( :param str chip_file: Alternate file to find chip settings :param bool no_sync: Whether or not to synchronize the channel on start :param Settings settings: Time between serial commands + :param bool isp_entry: Whether to use RTS and DTR to control reset and ISP entry :return ISPConnection isp: an already opened link to an isp device :return ChipDescription chip: object describing the targets characteristics @@ -936,7 +938,7 @@ def SetupChip( kStartingBaudRate = baudrate _log.debug("Using baud rate %d", kStartingBaudRate) - iodevice: UartDevice = UartDevice(device, baudrate=kStartingBaudRate) + iodevice: UartDevice = UartDevice(port=device, baudrate=kStartingBaudRate, isp_entry=isp_entry) isp = ISPConnection(iodevice, settings=settings) isp.reset() # print(baudrate, device, crystal_frequency, chip_file) @@ -951,6 +953,7 @@ def SetupChip( time.sleep(isp.settings.set_baudrate_sleep) isp.reset() part_id = isp.ReadPartID() + _log.debug(f"Read Part ID: 0x{part_id:08X}") descriptor: dict[str, str] = get_part_descriptor(chip_file, part_id) _log.debug(f"{part_id}, {descriptor}") diff --git a/src/ispprogrammer/cli.py b/src/ispprogrammer/cli.py index 4cc7d78..7d971e3 100644 --- a/src/ispprogrammer/cli.py +++ b/src/ispprogrammer/cli.py @@ -32,6 +32,7 @@ @click.option("--config-file", "-f", default=_chip_defs, help="Parts definition file") @click.option("--echo", is_flag=True) @click.option("--no-sync", is_flag=True) +@click.option("--isp-entry", is_flag=True, help="Use RTS and DTR to enter ISP mode") @click.option( "--sleep-time", "-s", type=float, default=0.25, help="Sleep time between commands" ) @@ -55,7 +56,7 @@ def gr1(ctx, **kwargs): @gr1.command("sync", help="Synchronize connection to chip") @click.pass_context def cli_sync(ctx): - iodevice = UartDevice(ctx.obj["device"], baudrate=ctx.obj["baud"]) + iodevice = UartDevice(ctx.obj["device"], baudrate=ctx.obj["baud"], isp_entry=ctx.obj["isp_entry"]) isp = ISPConnection(iodevice) isp.SyncConnection() @@ -63,7 +64,7 @@ def cli_sync(ctx): @gr1.command("query-chip", help="Read chip Part ID, UID, and boot code version") @click.pass_context def cli_QueryChip(ctx): - iodevice = UartDevice(ctx.obj["device"], baudrate=ctx.obj["baud"]) + iodevice = UartDevice(ctx.obj["device"], baudrate=ctx.obj["baud"], isp_entry=ctx.obj["isp_entry"]) isp = ISPConnection(iodevice) boot_version = isp.ReadBootCodeVersion() uid = isp.ReadUID() @@ -77,11 +78,12 @@ def cli_QueryChip(ctx): @click.pass_context def cli_MassErase(ctx): isp, chip = SetupChip( - ctx.obj["baud"], - ctx.obj["device"], - ctx.obj["crystal_frequency"], - ctx.obj["config_file"], - ctx.obj["no_sync"], + baudrate = ctx.obj["baud"], + device=ctx.obj["device"], + crystal_frequency=ctx.obj["crystal_frequency"], + chip_file=ctx.obj["config_file"], + no_sync=ctx.obj["no_sync"], + isp_entry=ctx.obj["isp_entry"], ) MassErase(isp, chip) _log.info("Mass Erase Successful") @@ -95,11 +97,12 @@ def cli_MassErase(ctx): @click.pass_context def cli_WriteFlash(ctx, imagein, start_sector): isp, chip = SetupChip( - ctx.obj["baud"], - ctx.obj["device"], - ctx.obj["crystal_frequency"], - ctx.obj["config_file"], - ctx.obj["no_sync"], + baudrate = ctx.obj["baud"], + device=ctx.obj["device"], + crystal_frequency=ctx.obj["crystal_frequency"], + chip_file=ctx.obj["config_file"], + no_sync=ctx.obj["no_sync"], + isp_entry=ctx.obj["isp_entry"], ) image = read_image_file_to_bin(imagein) WriteBinaryToFlash(isp=isp, chip=chip, image=image, start_sector=start_sector) @@ -112,11 +115,12 @@ def cli_WriteFlash(ctx, imagein, start_sector): @click.pass_context def cli_WriteImage(ctx, imagein): isp, chip = SetupChip( - ctx.obj["baud"], - ctx.obj["device"], - ctx.obj["crystal_frequency"], - ctx.obj["config_file"], - ctx.obj["no_sync"], + baudrate = ctx.obj["baud"], + device=ctx.obj["device"], + crystal_frequency=ctx.obj["crystal_frequency"], + chip_file=ctx.obj["config_file"], + no_sync=ctx.obj["no_sync"], + isp_entry=ctx.obj["isp_entry"], ) image = read_image_file_to_bin(imagein) WriteImage(isp, chip, image) @@ -130,11 +134,12 @@ def cli_WriteImage(ctx, imagein): @click.pass_context def cli_FastWriteImage(ctx, imagein): isp, chip = SetupChip( - ctx.obj["baud"], - ctx.obj["device"], - ctx.obj["crystal_frequency"], - ctx.obj["config_file"], - ctx.obj["no_sync"], + baudrate = ctx.obj["baud"], + device=ctx.obj["device"], + crystal_frequency=ctx.obj["crystal_frequency"], + chip_file=ctx.obj["config_file"], + no_sync=ctx.obj["no_sync"], + isp_entry=ctx.obj["isp_entry"], ) image = read_image_file_to_bin(imagein) image_read = ReadImage(isp, chip)[: len(image)] @@ -150,11 +155,12 @@ def cli_FastWriteImage(ctx, imagein): @click.pass_context def cli_ReadImage(ctx, imageout: str): isp, chip = SetupChip( - ctx.obj["baud"], - ctx.obj["device"], - ctx.obj["crystal_frequency"], - ctx.obj["config_file"], - ctx.obj["no_sync"], + baudrate = ctx.obj["baud"], + device=ctx.obj["device"], + crystal_frequency=ctx.obj["crystal_frequency"], + chip_file=ctx.obj["config_file"], + no_sync=ctx.obj["no_sync"], + isp_entry=ctx.obj["isp_entry"], ) image = ReadImage(isp, chip) _log.debug(image) diff --git a/src/ispprogrammer/parts_definitions.py b/src/ispprogrammer/parts_definitions.py index 6aeaf02..6f2858a 100644 --- a/src/ispprogrammer/parts_definitions.py +++ b/src/ispprogrammer/parts_definitions.py @@ -79,7 +79,13 @@ def get_part_descriptor_line(fname: str, partid: int) -> LPCPart: def get_part_descriptor(fname: str, partid: int) -> Dict[str, Any]: - return get_part_descriptor_line(fname, partid).dict() + part = get_part_descriptor_line(fname, partid) + descriptor = part.dict() + # Add computed properties that ChipDescription expects + descriptor["RAMRange"] = list(part.RAMRange) + descriptor["FlashRange"] = list(part.FlashRange) + descriptor["RAMStartWrite"] = part.RAMStartWrite + return descriptor def check_parts_definition(parts: List[LPCPart]) -> bool: From 23da6dc525608152ecd27804d4cd6b7cb24b159c Mon Sep 17 00:00:00 2001 From: Paul Abbott Date: Fri, 9 Jan 2026 16:51:04 +0000 Subject: [PATCH 2/2] Fixed --isp-entry option in README. --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8aa22d1..58df137 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To prevent bricking the chip during an interrupted write: This ensures that any failure during programming will leave the chip in ISP mode. ### Auto ISP Mode Entry -The `--isp_entry` option controls the UART RTS and DTR lines to automatically enter ISP mode. This option requires: +The `--isp-entry` option controls the UART RTS and DTR lines to automatically enter ISP mode. This option requires: - DTR connects to chip /Reset pin - RTS connects to chip ISP_Entry pin @@ -53,24 +53,23 @@ The configuration file is identical to that used by the [lpctools project](http: ### From PyPI ```bash -pip install ispprogrammer +pipx install ispprogrammer ``` -(Hint: use pipx on Ubuntu) ### From Source ```bash git clone https://github.com/snhobbs/isp-programmer.git cd isp-programmer -pip install . +pipx install . --force ``` > Default chip definitions are bundled. For custom chips, use the --config-file flag or copy your lpctools_parts.def to /etc/lpctools_parts.def. ## Usage -### Erase Entire Flash +### Erase Entire Flash with Auto ISP Mode Entry ```bash -ispprogrammer --device /dev/ttyUSB0 -b 9600 -crystal_frequency 12000 erase +ispprogrammer --device /dev/ttyUSB0 --isp-entry erase ``` ### Program Flash Image