Get the FREE Ultimate OpenClaw Setup Guide →

SPI Flash Dump via OpenOCD

Scanned
npx machina-cli add skill lukejenkins/claude-openocd-spi-dump/spi-flash-dump --openclaw
Files (1)
SKILL.md
9.8 KB

SPI 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:

  1. Load a minimal program (~500 bytes) into MCU's SRAM via OpenOCD
  2. Execute it to read SPI flash data into a RAM buffer
  3. Use OpenOCD to read the buffer back to the host
  4. 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:

OffsetNamePurpose
+0x00STATUSCommand/status register
+0x04FLASH_ADDR24-bit flash address to read
+0x08SIZEBytes to read
+0x0CDESTDestination buffer in SRAM
+0x10JEDEC_IDResult of ID read
+0x14ERRORError code if STATUS=ERROR
+0x18HEARTBEATIncrements 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:

  1. Vector Table - Full Cortex-M vector table at SRAM start
  2. VTOR Setup - Point VTOR to SRAM: SCB_VTOR = 0x20000000
  3. SPI Transfer - Poll status, write TX, wait RX, read RX
  4. 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 (use mem2array for compatibility)
  • spi_load - Load binary into SRAM
  • spi_init - Set PC/SP from vector table, resume
  • spi_jedec - Read and display JEDEC ID
  • spi_dump - Dump flash in chunks to file

Step 7: Build and Test

  1. Compile with arm-none-eabi-gcc, -ffreestanding, -nostdlib
  2. Link with custom linker script placing code at SRAM base
  3. Test JEDEC ID first to verify SPI communication
  4. 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:

CommandHexDescription
READ0x03Read data (24-bit address follows)
RDID0x9FRead JEDEC ID (3 bytes returned)
RDSR0x05Read 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:

SymptomLikely CauseQuick Fix
HardFault immediatelyVTOR points to flashSet SCB_VTOR = SRAM_BASE
SPI hangs pollingClock not runningCheck SPI_MR PCS field
Code stuck in spi_transferWrong PC at startUse init_dumper.sh to read PC from vector table
JEDEC returns 0x000000CS not togglingCheck GPIO config
JEDEC returns 0xFFFFFFNo flash responseCheck wiring, mode, speed
Heartbeat not incrementingCode blockedCheck PC - if in 0x2000004x, restart properly

Adapting to New MCUs

To support a new MCU:

  1. Find SPI registers - Search datasheet for "SPI" section
  2. Find GPIO registers - Search for "PIO" or "GPIO"
  3. Check SRAM location - Usually 0x20000000 for Cortex-M
  4. Check watchdog - Find feed/disable mechanism
  5. 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, LPC1768
  • references/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 heartbeat
  • spi_dump.ld - Linker script for SRAM execution
  • spi_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 addresses
  • verify_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

  1. Step 1: Connect to the target with OpenOCD and verify a stable SWD/JTAG session
  2. 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
  3. 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

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers