Flash Programming
This example demonstrates a complete firmware update workflow: inspect the flash layout, erase, program a firmware image, verify, and reset the target.
Async version
Section titled “Async version”import asyncioimport loggingimport sysfrom pathlib import Path
from openocd import Session, FlashError, ConnectionError
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")log = logging.getLogger("flash-program")
async def flash_firmware(firmware_path: Path): """Program a firmware image and verify it."""
if not firmware_path.exists(): log.error("Firmware file not found: %s", firmware_path) return False
file_size = firmware_path.stat().st_size log.info("Firmware: %s (%d bytes)", firmware_path.name, file_size)
try: session = await Session.connect(timeout=5.0) except ConnectionError as e: log.error("Cannot connect to OpenOCD: %s", e) return False
async with session: # --- Halt the target first --- log.info("Halting target...") await session.target.halt()
# --- Inspect flash layout --- banks = await session.flash.banks() log.info("Found %d flash bank(s):", len(banks)) for bank in banks: log.info(" Bank #%d: %s @ 0x%08X, size=0x%X (%d KB)", bank.index, bank.name, bank.base, bank.size, bank.size // 1024)
# Get detailed sector info for bank 0 bank_info = await session.flash.info(0) log.info("Bank 0 has %d sectors", len(bank_info.sectors)) if bank_info.sectors: first = bank_info.sectors[0] last = bank_info.sectors[-1] log.info(" First sector: offset=0x%X, size=0x%X", first.offset, first.size) log.info(" Last sector: offset=0x%X, size=0x%X", last.offset, last.size)
# Check if firmware fits if file_size > bank_info.size: log.error("Firmware (%d bytes) is larger than flash bank (%d bytes)", file_size, bank_info.size) return False
# --- Erase --- log.info("Erasing flash bank 0...") try: await session.flash.erase_all(bank=0) log.info("Erase complete") except FlashError as e: log.error("Erase failed: %s", e) return False
# --- Program --- log.info("Programming %s...", firmware_path.name) try: await session.flash.write_image( firmware_path, erase=False, # Already erased above verify=True, # Built-in post-write verification ) log.info("Programming and verification complete") except FlashError as e: log.error("Programming failed: %s", e) return False
# --- Optional: second verification pass --- log.info("Running standalone verification...") if firmware_path.suffix in (".bin",): matches = await session.flash.verify(bank=0, path=firmware_path) if matches: log.info("Standalone verification PASSED") else: log.error("Standalone verification FAILED") return False
# --- Reset and run --- log.info("Resetting target to run new firmware...") await session.target.reset(mode="run")
# Brief pause, then check state await asyncio.sleep(0.5) state = await session.target.state() log.info("Target state: %s", state.state)
log.info("Firmware update complete") return True
async def main(): if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} <firmware.bin|hex|elf>") sys.exit(1)
firmware = Path(sys.argv[1]) success = await flash_firmware(firmware) sys.exit(0 if success else 1)
asyncio.run(main())Sync version
Section titled “Sync version”import loggingimport sysfrom pathlib import Path
from openocd import Session, FlashError, ConnectionError
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")log = logging.getLogger("flash-program")
def flash_firmware(firmware_path: Path) -> bool: """Program a firmware image and verify it."""
if not firmware_path.exists(): log.error("Firmware file not found: %s", firmware_path) return False
file_size = firmware_path.stat().st_size log.info("Firmware: %s (%d bytes)", firmware_path.name, file_size)
try: session = Session.connect_sync(timeout=5.0) except ConnectionError as e: log.error("Cannot connect to OpenOCD: %s", e) return False
with session: log.info("Halting target...") session.target.halt()
# Inspect flash banks = session.flash.banks() log.info("Found %d flash bank(s)", len(banks))
bank_info = session.flash.info(0) log.info("Bank 0: %d sectors, %d KB", len(bank_info.sectors), bank_info.size // 1024)
if file_size > bank_info.size: log.error("Firmware too large for flash bank") return False
# Erase log.info("Erasing...") try: session.flash.erase_all(bank=0) except FlashError as e: log.error("Erase failed: %s", e) return False
# Program with built-in verify log.info("Programming %s...", firmware_path.name) try: session.flash.write_image(firmware_path, erase=False, verify=True) log.info("Programming complete") except FlashError as e: log.error("Programming failed: %s", e) return False
# Reset and run log.info("Resetting target...") session.target.reset(mode="run") log.info("Done") return True
def main(): if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} <firmware.bin|hex|elf>") sys.exit(1)
success = flash_firmware(Path(sys.argv[1])) sys.exit(0 if success else 1)
main()Expected output
Section titled “Expected output”INFO: Firmware: firmware.hex (32768 bytes)INFO: Halting target...INFO: Found 1 flash bank(s):INFO: Bank #0: stm32f1x.flash @ 0x08000000, size=0x20000 (128 KB)INFO: Bank 0 has 128 sectorsINFO: First sector: offset=0x0, size=0x400INFO: Last sector: offset=0x1FC00, size=0x400INFO: Erasing flash bank 0...INFO: Erase completeINFO: Programming firmware.hex...INFO: Programming and verification completeINFO: Running standalone verification...INFO: Standalone verification PASSEDINFO: Resetting target to run new firmware...INFO: Target state: runningINFO: Firmware update completeSelective sector erase
Section titled “Selective sector erase”For faster updates when only part of the flash changes, erase only the affected sectors instead of the whole bank:
async with await Session.connect() as session: await session.target.halt()
bank = await session.flash.info(0) firmware_size = Path("firmware.bin").stat().st_size
# Calculate which sectors the firmware occupies last_sector = 0 cumulative = 0 for sector in bank.sectors: cumulative = sector.offset + sector.size if cumulative >= firmware_size: last_sector = sector.index break
log.info("Erasing sectors 0-%d (covers %d bytes)", last_sector, cumulative) await session.flash.erase_sector(bank=0, first=0, last=last_sector) await session.flash.write_image(Path("firmware.bin"), erase=False, verify=True)Protecting the bootloader
Section titled “Protecting the bootloader”After programming, protect the bootloader region so it cannot be accidentally overwritten:
async with await Session.connect() as session: await session.target.halt()
# Program the full image await session.flash.write_image(Path("firmware.hex"), erase=True, verify=True)
# Protect the first 2 sectors (bootloader) await session.flash.protect(bank=0, first=0, last=1, on=True) log.info("Bootloader sectors protected")