SPI Flash Dump via OpenOCD
Scannednpx machina-cli add skill lukejenkins/claude-openocd-spi-dump/spi-flash-dump --openclawSPI Flash Dump via OpenOCD
Overview
This skill enables dumping SPI flash or EEPROM memory through a microcontroller's SPI peripheral using OpenOCD/SWD, without requiring external SPI programming hardware.
When to use this approach:
- SWD/JTAG debug access to an MCU is available (via OpenOCD)
- SPI flash/EEPROM is connected to the MCU's SPI peripheral
- Direct access to the SPI chip is impractical (BGA package, no test points)
- External SPI programmers (CH341A, Bus Pirate) are unavailable
Architecture
┌─────────────┐ SWD ┌─────────────┐ SPI ┌─────────────┐
│ OpenOCD │◄────────────►│ Target MCU │◄────────────►│ SPI Flash │
│ (Host PC) │ │ (SRAM) │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
The approach:
- Load a minimal program (~500 bytes) into MCU's SRAM via OpenOCD
- Execute it to read SPI flash data into a RAM buffer
- Use OpenOCD to read the buffer back to the host
- Repeat until entire flash is dumped
Prerequisites
Before starting, gather this information:
MCU Information:
- MCU family and part number (e.g., ATSAM4S2A, STM32F407)
- SRAM base address and size (typically 0x20000000 for Cortex-M)
- SPI peripheral base address (from datasheet)
- GPIO controller address for chip select pin
SPI Flash Information:
- Part number if known (e.g., AT25DF321A, W25Q32)
- Expected capacity
- SPI pins used (CS, MOSI, MISO, CLK)
Connection:
- Working OpenOCD connection to target
- Ability to halt, resume, read/write memory
Implementation Process
Step 1: Identify SPI Registers
Look up the MCU's datasheet for SPI register addresses. See references/mcu-registers.md for common MCU families.
Key registers to find:
- Control Register (enable/disable SPI)
- Mode Register (master mode, clock settings)
- Transmit Data Register
- Receive Data Register
- Status Register (TX ready, RX complete bits)
Step 2: Identify GPIO for Chip Select
The SPI chip select (CS) is often controlled via GPIO for precise timing. Find:
- GPIO controller base address
- Pin enable register
- Output enable register
- Set/clear output registers
Step 3: Plan Memory Layout
Plan SRAM layout for the dumper:
SRAM Layout (example: 64KB at 0x20000000):
0x20000000 ┌─────────────────┐
│ Vector Table │ 64 bytes (16 Cortex-M vectors)
0x20000040 ├─────────────────┤
│ Code │ ~400-600 bytes typical
0x2000E000 ├─────────────────┤
│ Read Buffer │ 4KB (adjustable)
0x2000FE00 ├─────────────────┤
│ Stack │ 256 bytes (grows down)
0x2000FF00 ├─────────────────┤
│ Comm Area │ 256 bytes
0x20010000 └─────────────────┘
Step 4: Define Communication Protocol
Use fixed memory addresses for host-MCU communication:
| Offset | Name | Purpose |
|---|---|---|
| +0x00 | STATUS | Command/status register |
| +0x04 | FLASH_ADDR | 24-bit flash address to read |
| +0x08 | SIZE | Bytes to read |
| +0x0C | DEST | Destination buffer in SRAM |
| +0x10 | JEDEC_ID | Result of ID read |
| +0x14 | ERROR | Error code if STATUS=ERROR |
| +0x18 | HEARTBEAT | Increments in main loop (proves code is running) |
Status values: 0x00=Idle, 0x01=Busy, 0x02=Done, 0xDEADxxxx=Error
Commands: 0x10=Read flash, 0x20=Get JEDEC ID, 0xFF=Exit
Heartbeat usage: Read this value twice with a short delay between. If it changes, the main loop is executing. If stuck, the code is blocked (likely in SPI polling).
Step 5: Write RAM-Resident Code
Create minimal C code with these components. See examples/spi_dump.c for complete template.
Critical requirements:
- Vector Table - Full Cortex-M vector table at SRAM start
- VTOR Setup - Point VTOR to SRAM:
SCB_VTOR = 0x20000000 - SPI Transfer - Poll status, write TX, wait RX, read RX
- Command Loop - Poll communication area, execute commands
Step 6: Create OpenOCD TCL Script
Create TCL commands for loading and controlling the dumper. See examples/spi_dump.tcl for complete template.
Key procedures:
read_word- Memory read wrapper (usemem2arrayfor compatibility)spi_load- Load binary into SRAMspi_init- Set PC/SP from vector table, resumespi_jedec- Read and display JEDEC IDspi_dump- Dump flash in chunks to file
Step 7: Build and Test
- Compile with arm-none-eabi-gcc, -ffreestanding, -nostdlib
- Link with custom linker script placing code at SRAM base
- Test JEDEC ID first to verify SPI communication
- Full dump only after JEDEC ID succeeds
Critical Implementation Details
VTOR Must Point to SRAM
Without setting VTOR, any exception uses flash vector table, causing crashes:
*(volatile uint32_t*)0xE000ED08 = 0x20000000;
SAM4S/SAM3X: SPI PCS Field Requirement
Even when using GPIO for chip select, SPI won't clock if PCS=0xF:
SPI_MR = SPI_MR_MSTR | SPI_MR_MODFDIS | (0x0E << 16); // PCS=NPCS0
GPIO Chip Select Configuration
Reclaim CS pin for GPIO control:
// SAM4S example for PA11
PIOA_PER = (1 << 11); // Enable PIO control
PIOA_OER = (1 << 11); // Enable output
Watchdog Handling
Feed watchdog during long operations:
for (i = 0; i < size; i++) {
dest[i] = spi_transfer(0x00);
if ((i & 0xFFF) == 0) {
WDT_CR = 0xA5000001; // SAM4S watchdog feed
}
}
SPI Flash Commands
Standard JEDEC commands work with most SPI flash chips:
| Command | Hex | Description |
|---|---|---|
| READ | 0x03 | Read data (24-bit address follows) |
| RDID | 0x9F | Read JEDEC ID (3 bytes returned) |
| RDSR | 0x05 | Read status register |
JEDEC ID format: Byte1=Manufacturer, Byte2=Type, Byte3=Capacity
Common manufacturers: 0x1F=Atmel, 0xEF=Winbond, 0xC2=Macronix, 0x20=Micron
Troubleshooting
See references/troubleshooting.md for detailed solutions. Quick reference:
| Symptom | Likely Cause | Quick Fix |
|---|---|---|
| HardFault immediately | VTOR points to flash | Set SCB_VTOR = SRAM_BASE |
| SPI hangs polling | Clock not running | Check SPI_MR PCS field |
| Code stuck in spi_transfer | Wrong PC at start | Use init_dumper.sh to read PC from vector table |
| JEDEC returns 0x000000 | CS not toggling | Check GPIO config |
| JEDEC returns 0xFFFFFF | No flash response | Check wiring, mode, speed |
| Heartbeat not incrementing | Code blocked | Check PC - if in 0x2000004x, restart properly |
Adapting to New MCUs
To support a new MCU:
- Find SPI registers - Search datasheet for "SPI" section
- Find GPIO registers - Search for "PIO" or "GPIO"
- Check SRAM location - Usually 0x20000000 for Cortex-M
- Check watchdog - Find feed/disable mechanism
- Verify SPI pins - Which GPIO port/pins connect to flash
The core algorithm remains the same—only register addresses change.
Additional Resources
Reference Files
references/mcu-registers.md- Detailed register maps for SAM4S, SAM3X, STM32F1, STM32F4, nRF52, LPC1768references/troubleshooting.md- Comprehensive troubleshooting guide with solutions
Example Files
Complete, working templates in examples/:
Source code:
spi_dump.c- RAM-resident C source with vector table and heartbeatspi_dump.ld- Linker script for SRAM executionspi_dump.tcl- OpenOCD TCL commands with JEDEC ID decoding
Automation scripts (MCU-agnostic):
init_dumper.sh- Properly loads and initializes the dumper (reads SP/PC from vector table)dump.sh- Automated flash dump with parameterized memory addressesverify_dump.sh- Verifies dump integrity, detects stuck data lines
Typical Workflow
Quick start with shell scripts:
# 1. Compile for your MCU (customize spi_dump.c first)
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -ffreestanding \
-nostdlib -T spi_dump.ld -o spi_dump.elf spi_dump.c
arm-none-eabi-objcopy -O binary spi_dump.elf spi_dump.bin
# 2. Initialize the dumper (reads vector table automatically)
./init_dumper.sh spi_dump.bin
# 3. Dump the flash
./dump.sh firmware.bin 0x400000
# 4. Verify the dump
./verify_dump.sh firmware.bin 0x400000
For LPC1768 (SRAM at 0x10000000):
SRAM_BASE=0x10000000 ./init_dumper.sh spi_dump.bin
SRAM_BASE=0x10000000 ./dump.sh firmware.bin 0x100000
Important: Proper Initialization
The dumper MUST be started from its reset vector, not an arbitrary address. The init_dumper.sh script handles this automatically by reading SP and PC from the vector table:
# WRONG - skips spi_init(), code will hang polling SPI
reg pc 0x20000041
resume
# CORRECT - starts from reset vector, runs full initialization
mem2array sp_arr 32 0x20000000 1
reg sp $sp_arr(0)
mem2array pc_arr 32 0x20000004 1
reg pc $pc_arr(0)
resume
For a guided interactive session, use the /spi-dump command which walks through the entire process step-by-step.
Source
git clone https://github.com/lukejenkins/claude-openocd-spi-dump/blob/main/skills/spi-flash-dump/SKILL.mdView on GitHub Overview
This skill enables dumping SPI flash or EEPROM memory through a microcontroller's SPI peripheral using OpenOCD/SWD, without requiring external SPI programming hardware. It is ideal when SWD/JTAG access is available and direct access to the SPI chip is impractical.
How This Skill Works
A minimal program (~500 bytes) is loaded into the MCU's SRAM via OpenOCD and executed to read SPI flash data into a RAM buffer. The host then retrieves the buffer over OpenOCD and repeats the process until the entire flash content is dumped.
When to Use It
- You have SWD/JTAG debug access to the MCU via OpenOCD
- SPI flash/EEPROM is connected to the MCU's SPI peripheral
- Direct access to the SPI chip is impractical (e.g., BGA package, no test points)
- External SPI programmers (CH341A, Bus Pirate) are unavailable
- You need RAM-resident dumping without external hardware
Quick Start
- Step 1: Connect to the target with OpenOCD and verify a stable SWD/JTAG session
- Step 2: Load a minimal loader (~500 bytes) into the MCU's SRAM and run it to read a chunk of SPI data into RAM
- Step 3: Use OpenOCD to read the RAM buffer back to the host and repeat until the full flash is dumped
Best Practices
- Confirm MCU RAM base address, size, and SPI peripheral base addresses from the datasheet before starting
- Plan a small, safe SRAM layout for the loader and a data buffer (~4 KB or larger as needed)
- Use fixed host-to-MCU communication registers for STATUS, ADDRESS, SIZE, and DEST buffers
- Read in chunks with a heartbeat to detect hangs and include error handling for TIMEOUT/CRC
- Test the flow on a development board with known-good flash before attempting on production targets
Example Use Cases
- Dump a W25Q32 SPI flash from an STM32F407 board using OpenOCD over SWD
- Extract firmware from an AT25DF321A EEPROM connected to an ATSAM4S2A via the MCU's SPI
- Dump a 256KB SPI flash on a Cortex-M4 MCU when direct chip access isn’t possible
- Recover firmware from an embedded board’s SPI flash when external programmers are unavailable
- Obtain a ROM image from SPI flash on a microcontroller-based device after a debug boot issue