How to Bit-Bang GPIO Pins with ESP32-Bit-Pirate: A Complete Guide
Bit‑bang GPIO pins on ESP32‑Bit‑Pirate using the built‑in toggle, pulse, and jam CLI commands, or call PinService methods directly for custom firmware extensions. The firmware implements a clean three‑layer architecture—CLI parsing, controller logic, and hardware abstraction—letting you control any GPIO pin at runtime without recompiling.
This guide covers both interactive shell usage and programmatic access. All examples reference the actual source implementation in geo‑tp/ESP32‑Bit‑Pirate.
Architecture Overview
ESP32‑Bit‑Pirate organizes GPIO operations into distinct layers. Understanding this structure helps you choose the right integration point for your use case.
| Layer | Responsibility | Key File |
|---|---|---|
| CLI / Shell | Parses user input into TerminalCommand objects |
DioController::handleTogglePin |
| Controller | Validates arguments, checks pin permissions, executes timing loops | src/Controllers/DioController.cpp |
| Service | Wraps ESP‑IDF GPIO API (gpio_set_level, gpio_get_level, etc.) |
src/Services/PinService.cpp |
| Utility | Provides millisecond timing and non‑blocking input detection | UtilityService |
This separation means you can bit‑bang GPIO from the console for quick tests, or import the same service classes into custom commands without duplicating hardware logic.
Bit-Banging GPIO Pins via CLI Commands
The fastest way to start is through the serial shell. Three commands cover most bit‑banging needs: toggle, pulse, and jam.
Toggle Command: Continuous Square Wave
> toggle 5 250
- GPIO 5 toggles every 250 ms (4 Hz square wave)
- Press Enter to stop the loop
The implementation in [DioController.cpp lines 90‑119](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/pioarduino/src/Controllers/DioController.cpp#L90-L119) enters a tight loop calling pinService.setHigh() and pinService.setLow() while checking utilityService.readChar() for the stop signal.
Pulse Command: Single Timed Pulse
> pulse 12 5000
- Drives GPIO 12 HIGH for 5 ms, then returns to LOW
- Useful for triggering external logic analyzers or reset lines
Jam Command: Random Noise Generation
> jam 4 10 50
- Rapidly toggles GPIO 4 with random pulse widths between 10 µs and 50 µs
- Helpful for EMI testing or protocol fuzzing
All three commands are documented in [HelpShell.cpp lines 244‑255](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/pioarduino/src/Shells/HelpShell.cpp#L244-L255) and validate pin numbers against the board's allowed GPIO mask before execution.
Bit-Banging GPIO Pins Programmatically
For custom firmware features, import PinService and UtilityService directly. This matches the pattern used by the built‑in controllers.
Basic Toggle Loop Implementation
#include "PinService.h"
#include "UtilityService.h"
void simpleToggle(uint8_t pin, uint32_t intervalMs) {
PinService pinService;
UtilityService util;
pinService.setOutput(pin); // Configure GPIO as OUTPUT
bool state = false;
uint32_t lastToggle = util.nowMs();
while (true) {
uint32_t now = util.nowMs();
if (now - lastToggle >= intervalMs) {
lastToggle = now;
state = !state;
state ? pinService.setHigh(pin) : pinService.setLow(pin);
}
// Break on custom condition or use util.readChar() for keypress
if (util.readChar() == '\r') break;
}
}
Key methods from PinService.cpp:
setOutput(uint8_t pin)— Configures pin mode toGPIO_MODE_OUTPUTsetHigh(uint8_t pin)— Callsgpio_set_level(pin, 1)and forces output modesetLow(uint8_t pin)— Callsgpio_set_level(pin, 0)and forces output moderead(uint8_t pin)— Returns current level viagpio_get_levelsetInput(uint8_t pin)/setInputPullup(uint8_t pin)/setInputPullDown(uint8_t pin)— Input configuration variants
Advanced: Applying the Same Pattern to Other Protocols
The bit‑banging architecture extends beyond basic GPIO. The I2cService and SubGhzService demonstrate identical layering for complex protocols.
I²C Bit-Bang Example
#include "I2cService.h"
void sendByte(uint8_t sclPin, uint8_t sdaPin, uint8_t data) {
I2cService i2c;
bool ackReceived;
i2c.i2cBitBangWriteByte(
sclPin, // SCL GPIO
sdaPin, // SDA GPIO
data, // Byte to transmit
5, // Delay between edges (µs)
ackReceived // Out: ACK status
);
}
The i2cBitBangWriteByte function in I2cService.cpp manually clocks SCL while shifting bits onto SDA—pure software I²C using the same PinService primitives.
Sub-GHz Radio Transmission
SubGhzController and SubGhzService apply bit‑banging to RF: startTxBitBang() and stopTxBitBang() toggle the radio's data pin at precise intervals to modulate ASK/OOK signals. This proves the architecture scales from microseconds (I²C) to milliseconds (GPIO toggle) to sub‑carrier timing (RF).
Critical Source Files Reference
| File | Purpose | Direct Link |
|---|---|---|
src/Controllers/DioController.cpp |
toggle, pulse, jam command handlers; toggle loop timing |
View source |
src/Services/PinService.cpp |
ESP‑IDF GPIO wrapper (setOutput, setHigh, setLow, read) |
View source |
src/Shells/HelpShell.cpp |
Command documentation for discoverability | Lines 244‑255 |
src/Services/I2cService.cpp |
I²C bit‑bang implementation using same service pattern | Same directory structure |
src/Controllers/SubGhzController.cpp / src/Services/SubGhzService.cpp |
RF bit‑bang TX control | Controller, Service |
Summary
- CLI commands (
toggle,pulse,jam) provide immediate GPIO bit‑banging without code changes PinServiceencapsulates all ESP‑IDF GPIO operations for safe, reusable hardware accessDioControllerimplements millisecond‑accurate timing loops with non‑blocking stop detection- The same three‑layer pattern (CLI → Controller → Service) applies to I²C, Sub‑GHz, and custom protocols
- All bit‑banging operations validate pins against board‑specific masks and restore safe states on exit
Frequently Asked Questions
What GPIO pins can I use with ESP32-Bit-Pirate bit-banging commands?
Only pins permitted by the board's GPIO mask are accepted. The firmware validates pin numbers in DioController before calling PinService. Consult your specific board definition or check the allowed mask at runtime—attempting to use reserved pins (flash, PSRAM, or strapping pins) returns an error without modifying hardware state.
How accurate is the timing for GPIO bit-banging?
Millisecond‑scale accuracy is reliable for toggle and pulse commands using utilityService.nowMs(). Microsecond precision requires busy‑waiting or ESP‑IDF ets_delay_us(); the jam command and I2cService implement tighter loops for sub‑millisecond edges. For critical timing, disable Wi‑Fi/BT or use the RMT peripheral instead of software loops.
Can I bit-bang GPIO from my own custom command?
Yes. Include PinService.h and UtilityService.h, instantiate the services, and call setOutput, setHigh, and setLow directly. Follow the pattern in DioController::handleTogglePin lines 68‑85 for argument parsing and permission checks, then implement your timing logic. The service layer ensures your code remains portable across ESP32 variants.
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 →