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

> Learn how to use ESP32-Bit-Pirate as an SPI slave. This guide details setup and traffic capture from any SPI master, enabling passive logging and analysis.

- Repository: [Geo/ESP32-Bit-Pirate](https://github.com/geo-tp/ESP32-Bit-Pirate)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/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`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/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:

```text
> 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:

```python
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:

```cpp
#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:

```text
[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`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/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:

- **[`src/Controllers/SpiController.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Controllers/SpiController.cpp)** — CLI parsing and capture loop logic
- **[`src/Services/SpiService.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Services/SpiService.cpp)** — Hardware abstraction for slave mode
- **[`src/Shells/HelpShell.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Shells/HelpShell.cpp)** — Help text registration (line 210 shows the "slave" entry)
- **[`platformio.ini`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/platformio.ini)** — Build configuration targeting ESP32-S3

## 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`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/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.