Troubleshooting SPI Communication on ESP32-Bit-Pirate: A Complete Guide
ESP32-Bit-Pirate users can diagnose SPI communication issues by understanding the layered architecture, proper pin configuration, and the critical requirement to release the SPI bus before switching between master and slave modes.
The ESP32-Bit-Pirate open-source firmware provides a versatile tool for debugging SPI, I2C, and UART interfaces. This guide walks through the complete SPI stack implemented in the geo-tp/ESP32-Bit-Pirate repository, with concrete troubleshooting steps derived from the source code.
Understanding the ESP32-Bit-Pirate SPI Architecture
The firmware organizes SPI functionality into four distinct layers:
- Global State — stores pin assignments, frequency, and mode flags via
GlobalState::getInstance() - Low-Level Driver —
SpiServicewraps the ArduinoSPIAPI andESP32SPISlavelibrary - Controller —
SpiControllerparses commands and coordinates all SPI operations - Shells & Views — interactive UIs for SD card, flash, and EEPROM operations
Understanding this separation is essential when troubleshooting SPI communication on ESP32-Bit-Pirate, as failures typically occur at layer boundaries—particularly when transitioning between master and slave modes.
Configuring SPI Pins and Frequency
The spi config command triggers SpiController::handleConfig(), which validates and stores settings in GlobalState before calling SpiService::configure():
// src/Services/SpiService.cpp
void SpiService::configure(uint8_t mosi, uint8_t miso,
uint8_t sclk, uint8_t cs,
uint32_t frequency) {
end(); // Shut down any previous SPI session
csPin = cs;
spiFrequency = frequency;
SPI.begin(sclk, miso, mosi, cs);
pinMode(cs, OUTPUT);
digitalWrite(cs, HIGH);
}
Notice the critical end() call at line 2. This prevents hardware conflicts when reconfiguring.
Common Configuration Pitfalls
| Symptom | Root Cause | Solution |
|---|---|---|
| No data after sniff/slave session | Pins not released | Always call spiService.end() before mode switches |
| Wrong sniffing direction | sniffMosi flag mishandled |
Verify choice index from userInputManager.readValidatedChoiceIndex |
| Frequency rejection | Value exceeds 80 MHz | Valid range is 1-80 MHz per readValidatedUint8 constraints |
Diagnosing Sniff Mode Failures
The spi sniff command enables passive capture on either MOSI or MISO. The implementation in SpiController::handleSniff() reveals two common failure modes:
// src/Controllers/SpiController.cpp (excerpt)
void SpiController::handleSniff() {
int choice = userInputManager.readValidatedChoiceIndex(
"Select line to sniff", {" MOSI"," MISO"}, 0);
bool sniffMosi = (choice == 0);
// Pin mapping logic
int slaveMisoPin = sniffMosi ? miso : -1;
int slaveMosiPin = sniffMosi ? mosi : miso;
spiService.end(); // CRITICAL: release master mode
spiService.startSlave(sclk, slaveMisoPin, slaveMosiPin, cs);
while (true) {
char c = terminalInput.readChar();
if (c == '\n' || c == '\r') break;
auto packets = spiService.getSlaveData();
// ... display hex data
}
// ... restore master configuration
}
Sniff Mode Troubleshooting Checklist
-
Verify physical wiring — The
slaveMosiPinandslaveMisoPinassignments swap based onsniffMosi. When capturing MISO (choice 2),slaveMosiPinreceives the original MISO pin value. -
Confirm CS activation — Data logging only occurs when CS is pulled low (see
handleSnifflines 78-83 in the source). -
Check buffer retrieval —
SpiService::getSlaveData()reads fromESP32SPISlavebuffer and re-queues transactions automatically.
Fixing Slave Mode Transition Errors
The spi slave command requires full-duplex capture, making the mode transition even more sensitive:
// src/Controllers/SpiController.cpp (excerpt)
void SpiController::handleSlave() {
spiService.end(); // Stop master mode
spiService.startSlave(sclk, miso, mosi, cs);
while (!terminalInput.available()) {
delay(10);
}
spiService.stopSlave(sclk, miso, mosi, cs);
spiService.configure(mosi, miso, sclk, cs,
state.getSpiFrequency()); // Restore master
}
The startSlave() method contains a guard clause: if (slave) return;. This silent failure occurs if end() was not called—a frequent source of "slave mode not working" reports.
SD Card, Flash, and EEPROM Interactions
Peripheral operations follow a strict save-restore pattern. For SD card mounting (spi sdcard):
spiService.end(); // Release the bus
sdService.configure(clk, miso, mosi, cs);
sdCardShell.run(); // Interactive filesystem UI
spiService.configure(mosi, miso, sclk, cs, freq); // Restore configuration
The ensureConfigured() helper guarantees SPI bus restoration even when operations fail. Unit test test_sdcard_mount_failure_does_not_run_shell_or_reconfigure validates this error handling.
Executing Byte-Code Sequences
Low-level SPI operations use a byte-code abstraction for portability. The JEDEC ID read demonstrates the pattern:
#include "ByteCode.h"
#include <vector>
std::vector<ByteCode> cmds = {
ByteCode(ByteCodeEnum::Write, 0x9F), // JEDEC ID command
ByteCode(ByteCodeEnum::Read, 3) // Read 3 bytes
};
std::string id = spi.executeByteCode(cmds);
// Returns manufacturer and device ID as hex string
The controller implementation only prints non-empty results:
// src/Controllers/SpiController.cpp
auto result = spiService.executeByteCode(bytecodes);
if (!result.empty()) {
terminalView.println("SPI Read:\n");
terminalView.println(result);
}
Empty responses indicate either bus contention or incorrect command sequencing.
Leveraging the Test Suite for Diagnosis
The repository's unit tests in test/Controllers/test_SpiController.cpp codify expected behaviors:
test_config_updates_state_and_configures_spi_service— validates pin/frequency propagationtest_slave_captures_packets_then_restores_spi_configuration— confirms mode transition integritytest_instruction_delegates_bytecodes_and_prints_result— verifies byte-code execution
When troubleshooting SPI communication on ESP32-Bit-Pirate, referencing these test cases provides authoritative behavioral specifications.
Summary
- Always call
spiService.end()before switching between master and slave modes to prevent driver rejection - Verify pin mapping logic in sniff mode—the MOSI/MISO assignment depends on the
sniffMosiboolean - Trust the test suite—unit tests document correct behavior for all SPI command paths
- Use
ensureConfigured()after peripheral operations to guarantee bus restoration - Check choice indices carefully—off-by-one errors in
readValidatedChoiceIndexcause silent misconfiguration
Frequently Asked Questions
Why does SPI sniff mode show no data?
The most likely cause is CS line inactivity. The ESP32-Bit-Pirate firmware only logs bytes when CS is pulled low during transactions. Verify physical CS connectivity and confirm the target device drives CS correctly. Also ensure you selected the correct line (MOSI vs MISO) at the prompt—choice index 0 is MOSI, 1 is MISO.
How do I switch from slave mode back to master mode?
The SpiController::handleSlave() method demonstrates the proper sequence: call spiService.stopSlave(), then re-invoke spiService.configure() with your original pin assignments and frequency. The GlobalState singleton retains these values via getSpiFrequency() and related getters.
What is the maximum SPI frequency supported?
The firmware constrains frequency to 1-80 MHz through readValidatedUint8 validation in handleConfig(). Exceeding 80 MHz causes immediate rejection at the configuration prompt. For reliable communication with most flash and EEPROM devices, 20-40 MHz is recommended.
Why does SD card mounting fail to restore my SPI settings?
This should not occur in normal operation—the ensureConfigured() method guards restoration. If observed, check for stack overflow or memory corruption. The unit test test_sdcard_mount_failure_does_not_run_shell_or_reconfigure specifically validates that early abort leaves pins untouched, so persistent issues likely indicate hardware instability rather than firmware bugs.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →