Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -46,23 +53,23 @@ The configuration file is identical to that used by the [lpctools project](http:
### From PyPI

```bash
pip install ispprogrammer
pipx install ispprogrammer
```

### 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 masserase
ispprogrammer --device /dev/ttyUSB0 --isp-entry erase
```

### Program Flash Image
Expand Down
38 changes: 37 additions & 1 deletion src/ispprogrammer/IODevices.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import time
from serial import Serial

kTimeout = 1
Expand Down Expand Up @@ -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")
Expand Down
5 changes: 4 additions & 1 deletion src/ispprogrammer/ISPConnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)
Expand All @@ -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}")
Expand Down
60 changes: 33 additions & 27 deletions src/ispprogrammer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -55,15 +56,15 @@ 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()


@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()
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)]
Expand All @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion src/ispprogrammer/parts_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down