317 lines
9.7 KiB
Plaintext
317 lines
9.7 KiB
Plaintext
---
|
|
title: Flash Programming
|
|
description: Read, write, erase, verify, and protect on-chip flash memory banks through OpenOCD.
|
|
---
|
|
|
|
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
|
|
|
The `Flash` subsystem wraps OpenOCD's `flash` command family for programming on-chip flash memory. It handles bank enumeration, sector-level erase, raw byte read/write through temporary files, high-level firmware image programming, verification, and write protection.
|
|
|
|
Access it through the session:
|
|
|
|
```python
|
|
# Async
|
|
session.flash
|
|
|
|
# Sync
|
|
sync_session.flash
|
|
```
|
|
|
|
## Listing flash banks
|
|
|
|
`banks()` returns a list of `FlashBank` descriptors for every flash bank OpenOCD has configured. These come without detailed sector information -- use `info()` for that.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
import asyncio
|
|
from openocd import Session
|
|
|
|
async def main():
|
|
async with await Session.connect() as session:
|
|
banks = await session.flash.banks()
|
|
for bank in banks:
|
|
print(f"Bank #{bank.index}: {bank.name}")
|
|
print(f" Base: 0x{bank.base:08X}, Size: 0x{bank.size:X}")
|
|
print(f" Bus width: {bank.bus_width}, Chip width: {bank.chip_width}")
|
|
print(f" Target: {bank.target}")
|
|
|
|
asyncio.run(main())
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
from openocd import Session
|
|
|
|
with Session.connect_sync() as session:
|
|
banks = session.flash.banks()
|
|
for bank in banks:
|
|
print(f"Bank #{bank.index}: {bank.name}")
|
|
print(f" Base: 0x{bank.base:08X}, Size: 0x{bank.size:X}")
|
|
print(f" Bus width: {bank.bus_width}, Chip width: {bank.chip_width}")
|
|
print(f" Target: {bank.target}")
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
## Getting bank details with sectors
|
|
|
|
`info(bank=0)` returns a `FlashBank` with its `sectors` list populated. Each sector is a `FlashSector` with index, offset, size, and protection status.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
async with await Session.connect() as session:
|
|
bank = await session.flash.info(0)
|
|
print(f"{bank.name}: {len(bank.sectors)} sectors")
|
|
for sector in bank.sectors:
|
|
prot = "protected" if sector.protected else "unprotected"
|
|
print(f" Sector {sector.index}: offset=0x{sector.offset:X}, "
|
|
f"size=0x{sector.size:X}, {prot}")
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
with Session.connect_sync() as session:
|
|
bank = session.flash.info(0)
|
|
print(f"{bank.name}: {len(bank.sectors)} sectors")
|
|
for sector in bank.sectors:
|
|
prot = "protected" if sector.protected else "unprotected"
|
|
print(f" Sector {sector.index}: offset=0x{sector.offset:X}, "
|
|
f"size=0x{sector.size:X}, {prot}")
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
## Reading flash
|
|
|
|
Two methods for reading flash content:
|
|
|
|
- **`read(bank, offset, size)`** -- returns raw `bytes` by writing to a temp file, then reading the data back through TCL or from the local filesystem.
|
|
- **`read_to_file(bank, path)`** -- dumps the entire bank directly to a file on disk.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
from pathlib import Path
|
|
|
|
async with await Session.connect() as session:
|
|
# Read 256 bytes from the start of bank 0
|
|
data = await session.flash.read(bank=0, offset=0, size=256)
|
|
print(f"Read {len(data)} bytes: {data[:16].hex()}")
|
|
|
|
# Dump the entire bank to a file
|
|
await session.flash.read_to_file(bank=0, path=Path("flash_dump.bin"))
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
from pathlib import Path
|
|
|
|
with Session.connect_sync() as session:
|
|
data = session.flash.read(bank=0, offset=0, size=256)
|
|
print(f"Read {len(data)} bytes: {data[:16].hex()}")
|
|
|
|
session.flash.read_to_file(bank=0, path=Path("flash_dump.bin"))
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
<Aside type="note">
|
|
`read()` uses a temporary file on the host as an intermediary because OpenOCD's `flash read_bank` command writes to a file rather than returning data inline. The temp file is cleaned up automatically.
|
|
</Aside>
|
|
|
|
## Writing flash
|
|
|
|
### Raw byte write
|
|
|
|
`write(bank, offset, data)` writes raw bytes to a flash bank at a given offset. Like `read()`, it uses a temporary file since OpenOCD's `flash write_bank` reads from a file.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
async with await Session.connect() as session:
|
|
config_data = b"\x01\x02\x03\x04"
|
|
await session.flash.write(bank=0, offset=0x1000, data=config_data)
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
with Session.connect_sync() as session:
|
|
config_data = b"\x01\x02\x03\x04"
|
|
session.flash.write(bank=0, offset=0x1000, data=config_data)
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
<Aside type="caution">
|
|
`write()` does not erase sectors first. Flash memory can only transition bits from 1 to 0; writing to unerased flash produces incorrect data. Use `erase_sector()` or `erase_all()` before raw writes, or use `write_image()` which handles this automatically.
|
|
</Aside>
|
|
|
|
### Firmware image programming
|
|
|
|
`write_image(path, erase=True, verify=True)` is the high-level "flash and go" command. It handles erase, write, and verification in one operation. It accepts `.bin`, `.hex`, and `.elf` files.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
from pathlib import Path
|
|
|
|
async with await Session.connect() as session:
|
|
firmware = Path("build/firmware.hex")
|
|
await session.flash.write_image(firmware, erase=True, verify=True)
|
|
print("Firmware programmed and verified")
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
from pathlib import Path
|
|
|
|
with Session.connect_sync() as session:
|
|
firmware = Path("build/firmware.hex")
|
|
session.flash.write_image(firmware, erase=True, verify=True)
|
|
print("Firmware programmed and verified")
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
When `verify=True`, the method runs `verify_image` after writing. If the verification finds a mismatch, it raises `FlashError`.
|
|
|
|
## Erasing flash
|
|
|
|
### Erase a sector range
|
|
|
|
`erase_sector(bank, first, last)` erases sectors from `first` to `last` (both inclusive). Validates that `first <= last` and raises `FlashError` if the range is invalid.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
async with await Session.connect() as session:
|
|
# Erase sectors 0 through 3 in bank 0
|
|
await session.flash.erase_sector(bank=0, first=0, last=3)
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
with Session.connect_sync() as session:
|
|
session.flash.erase_sector(bank=0, first=0, last=3)
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
### Erase an entire bank
|
|
|
|
`erase_all(bank=0)` queries the bank info to find the last sector, then erases the full range.
|
|
|
|
```python
|
|
await session.flash.erase_all(bank=0)
|
|
```
|
|
|
|
## Write protection
|
|
|
|
`protect(bank, first, last, on)` sets or clears hardware write protection on a range of sectors.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
async with await Session.connect() as session:
|
|
# Protect the bootloader (sectors 0-1)
|
|
await session.flash.protect(bank=0, first=0, last=1, on=True)
|
|
|
|
# Unprotect application sectors (2-7)
|
|
await session.flash.protect(bank=0, first=2, last=7, on=False)
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
with Session.connect_sync() as session:
|
|
session.flash.protect(bank=0, first=0, last=1, on=True)
|
|
session.flash.protect(bank=0, first=2, last=7, on=False)
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
## Verifying flash
|
|
|
|
`verify(bank, path)` compares flash contents against a reference binary file and returns `True` if they match, `False` otherwise.
|
|
|
|
<Tabs>
|
|
<TabItem label="Async">
|
|
```python
|
|
from pathlib import Path
|
|
|
|
async with await Session.connect() as session:
|
|
matches = await session.flash.verify(bank=0, path=Path("golden.bin"))
|
|
if matches:
|
|
print("Flash contents match reference file")
|
|
else:
|
|
print("MISMATCH detected")
|
|
```
|
|
</TabItem>
|
|
<TabItem label="Sync">
|
|
```python
|
|
from pathlib import Path
|
|
|
|
with Session.connect_sync() as session:
|
|
matches = session.flash.verify(bank=0, path=Path("golden.bin"))
|
|
if matches:
|
|
print("Flash contents match reference file")
|
|
else:
|
|
print("MISMATCH detected")
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
## Data types
|
|
|
|
### FlashBank
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `index` | `int` | Bank number |
|
|
| `name` | `str` | Bank name (e.g. `stm32f1x.flash`) |
|
|
| `base` | `int` | Base address |
|
|
| `size` | `int` | Total size in bytes |
|
|
| `bus_width` | `int` | Bus width |
|
|
| `chip_width` | `int` | Chip width |
|
|
| `target` | `str` | Associated target or driver name |
|
|
| `sectors` | `list[FlashSector]` | Sector list (empty from `banks()`, populated from `info()`) |
|
|
|
|
### FlashSector
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `index` | `int` | Sector number within the bank |
|
|
| `offset` | `int` | Byte offset from the bank base |
|
|
| `size` | `int` | Sector size in bytes |
|
|
| `protected` | `bool` | Whether write protection is enabled |
|
|
|
|
## Error handling
|
|
|
|
All flash operations raise `FlashError` on failure. The error message includes the OpenOCD response for diagnostics.
|
|
|
|
```python
|
|
from openocd import Session, FlashError
|
|
|
|
try:
|
|
with Session.connect_sync() as session:
|
|
session.flash.write_image(Path("firmware.bin"))
|
|
except FlashError as e:
|
|
print(f"Flash operation failed: {e}")
|
|
```
|
|
|
|
## Method reference
|
|
|
|
| Method | Return Type | Description |
|
|
|--------|-------------|-------------|
|
|
| `banks()` | `list[FlashBank]` | List all configured flash banks |
|
|
| `info(bank=0)` | `FlashBank` | Detailed bank info with sectors |
|
|
| `read(bank, offset, size)` | `bytes` | Read raw flash via temp file |
|
|
| `read_to_file(bank, path)` | `None` | Dump entire bank to file |
|
|
| `write(bank, offset, data)` | `None` | Write raw bytes via temp file |
|
|
| `write_image(path, erase=True, verify=True)` | `None` | High-level flash programming |
|
|
| `erase_sector(bank, first, last)` | `None` | Erase a sector range |
|
|
| `erase_all(bank=0)` | `None` | Erase entire bank |
|
|
| `protect(bank, first, last, on)` | `None` | Set/clear write protection |
|
|
| `verify(bank, path)` | `bool` | Verify flash against a file |
|