Using ESP32-Bit-Pirate as an SPI Slave Device: Complete Setup and Capture Guide

The ESP32-Bit-Pirate firmware can operate the ESP32-S3's SPI peripheral in slave mode, allowing the board to passively capture and log traffic from any external SPI master.

The ESP32-Bit-Pirate is an open-source firmware that transforms ESP32-S3 boards into versatile debugging and protocol analysis tools. When configured as an SPI slave device, the board sits silently on the SPI bus, records every byte transmitted by an external master, and displays the captured data in real-time on any connected interface.

How SPI Slave Mode Works in ESP32-Bit-Pirate

The SPI slave implementation follows a clean two-layer architecture that separates command parsing from low-level hardware management.

SpiController: CLI Command Handling

The SpiController class in src/Controllers/SpiController.cpp handles user-facing SPI operations. When you type spi slave, the handleCommand() method (line 31) routes to handleSlave() (lines 28-66), which orchestrates the entire capture session.

Here's what happens internally:

  1. Cleanup: Terminates any active SPI master configuration via spiService.end()
  2. Activation: Calls spiService.startSlave() with configured pin mappings
  3. Capture loop: Polls spiService.getSlaveData() and prints hex-formatted results
  4. Termination: Waits for Enter key press, then invokes spiService.stopSlave()

SpiService: Hardware SPI Management

The SpiService class in src/Services/SpiService.cpp directly controls the ESP32-S3's FSPI peripheral (lines 62-115).

Method Lines Purpose
startSlave(sclk, miso, mosi, cs) 62-73 Configures slave mode, registers transaction callback, starts queue
stopSlave() 75-90 Disables slave, drains pending transactions, releases hardware
getSlaveData() 97-115 Retrieves completed transactions, re-queues immediately for continuous capture

The getSlaveData() implementation is particularly important for reliable capture. It checks transaction completion status, copies received bytes into a std::vector<uint8_t>, and immediately submits the next transaction so no bus traffic is missed between polling calls.

Starting SPI Slave Capture

Interactive CLI Method

Connect via USB-Serial, Web-CLI, or the Cardputer's built-in interface and enter:

> spi slave
SPI Slave: In progress... Press [ENTER] to stop.
 [ℹ️  INFORMATION]
 SPI Slave mode listens passively on the SPI bus.
 Any command sent by a master will be captured
 Data is only captured when CS is active.

[MOSI] 0A 3F 5C 00 ...
[MOSI] 01 02 03 04 ...

Press Enter to exit and return to normal operation.

The [MOSI] prefix identifies Master-Out-Slave-In data—bytes transmitted from the external master toward the ESP32. This labeling matches standard SPI terminology and helps distinguish traffic direction when analyzing logs.

Automated Capture with Python

For integration into test scripts or CI pipelines, use pyserial to control the ESP32-Bit-Pirate programmatically:

import serial
import time

# Adjust port for your system: /dev/ttyUSB0, COM3, etc.

ser = serial.Serial("/dev/ttyUSB0", 115200, timeout=1)

def send_cmd(cmd):
    ser.write((cmd + "\r\n").encode())
    time.sleep(0.1)  # Allow command processing

# Enter SPI slave mode

send_cmd("spi slave")
print("Capturing SPI traffic – press Ctrl-C to stop")

try:
    while True:
        line = ser.readline().decode(errors='ignore')
        if line.startswith("[MOSI]"):
            # Extract hex bytes for further processing

            hex_part = line.strip().replace("[MOSI] ", "")
            bytes_captured = bytes.fromhex(hex_part.replace(" ", ""))
            print(f"Captured {len(bytes_captured)} bytes: {hex_part}")
except KeyboardInterrupt:
    # Send newline to exit slave mode cleanly

    ser.write(b"\r")
    ser.close()
    print("\nStopped")

This pattern supports automated testing: parse captured bytes, validate against expected protocols, and flag anomalies without manual intervention.

Testing with an Arduino SPI Master

To verify your ESP32-Bit-Pirate SPI slave setup, use this Arduino UNO sketch:

#include <SPI.h>

const int csPin = 10;

void setup() {
  SPI.begin();  // Default master mode
  pinMode(csPin, OUTPUT);
  digitalWrite(csPin, HIGH);
  Serial.begin(115200);
}

void loop() {
  digitalWrite(csPin, LOW);
  
  byte data[] = {0xAA, 0x55, 0xFF};
  SPI.transfer(data, sizeof(data));  // Transmit 3 bytes
  
  digitalWrite(csPin, HIGH);
  
  Serial.println("Transmitted: AA 55 FF");
  delay(500);
}

With the ESP32-Bit-Pirate in slave mode, each Arduino loop iteration produces exactly:

[MOSI] AA 55 FF

SPI Slave Configuration Details

Default Pin Mapping

The firmware uses the ESP32-S3's FSPI peripheral with configurable pins defined in the internal state structure. Standard defaults apply unless overridden in the build configuration.

SPI Mode Compatibility

The slave operates in SPI Mode 0 (CPOL=0, CPHA=0):

  • Clock idle low
  • Data sampled on rising edge
  • Data shifted on falling edge

Your external master must use matching polarity and phase settings for reliable capture.

Buffer Sizing

The underlying ESP-IDF SPI slave driver manages DMA-capable transaction buffers. The ESP32-Bit-Pirate implementation in src/Services/SpiService.cpp re-queues transactions immediately after retrieval, ensuring the hardware always has a receive buffer ready—critical for capturing back-to-back transfers without gaps.

Key Source Files Reference

Understanding the codebase enables custom modifications:

Summary

  • Two-layer architecture: SpiController handles CLI commands while SpiService manages the ESP32-S3 FSPI peripheral
  • Continuous capture: getSlaveData() re-queues transactions automatically to prevent data loss
  • Multiple interfaces: Works over USB-Serial, Web-CLI, or Cardputer's standalone UI
  • Mode 0 only: Requires external masters to use CPOL=0, CPHA=0 timing
  • Clean lifecycle: startSlave() → capture loop → stopSlave() → end() returns to normal mode

Frequently Asked Questions

What SPI modes does the ESP32-Bit-Pirate slave support?

The current implementation in src/Services/SpiService.cpp configures the hardware for SPI Mode 0 (CPOL=0, CPHA=0). The ESP32-S3 supports all four modes at the hardware level, but the firmware hardcodes Mode 0 in startSlave(). Modify the SPI_SLAVE_BIT_LSBFIRST and clock phase settings in that function if your application requires a different mode.

Can I capture MOSI and MISO simultaneously?

The current implementation only captures [MOSI] traffic—bytes sent from the master to the ESP32. Full-duplex capture would require modifications to SpiService::getSlaveData() to also read the transmit buffer populated by SPI_SLAVE_TXBIT_LSBFIRST configuration. The ESP32-S3 hardware supports this, but the feature is not implemented in the current firmware version.

How fast can the SPI slave capture data?

The ESP32-S3's FSPI peripheral supports clock rates up to 80 MHz in slave mode, though practical limits depend on your wiring quality and the master's timing. The firmware uses DMA transaction queues with automatic re-queueing, so the limiting factor is typically the 115200 baud serial output rate when displaying captures interactively. For high-speed logging, consider buffering to SD card or modifying SpiController::handleSlave() to suppress live printing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →