# ESP32-Bit-Pirate UART to Bit-Bang Conversion: Architecture, Implementation, and Usage

> Learn how ESP32-Bit-Pirate converts UART to bit-bang. Reconfigure GPIO pins for raw RX/TX line sniffing, outputting hex or ASCII streams. Explore architecture, implementation, and usage.

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

---

**The ESP32-Bit-Pirate firmware converts UART signals to bit-bang output by reconfiguring the same GPIO pins from hardware UART mode to input-only sampling mode, enabling raw sniffing of RX/TX lines as hex or ASCII streams.**

The **ESP32-Bit-Pirate** project implements a Bus-Pirate-style analysis tool that bridges standard UART communication with bit-bang signal inspection. This dual-mode architecture allows developers to interact with serial devices while simultaneously capturing and analyzing raw line activity. The implementation spans multiple abstraction layers, from command dispatching through low-level ESP-IDF driver integration.

---

## Architecture Overview

The firmware organizes UART functionality across five distinct layers:

| Layer | Key Component | Primary Responsibility |
|-------|-------------|------------------------|
| Command Dispatcher | `ActionDispatcher::dispatchCommand` | Routes terminal input to UART controller |
| Controller | `UartController::handleCommand` | Implements all UART subcommands |
| Hardware Abstraction | `IUartService` | Wraps ESP-IDF UART configuration and I/O |
| Bit-Bang Engine | `UartSnifferService` | Raw GPIO sampling for sniffing operations |
| User Interface | `DeviceView` / `WebTerminalView` | Renders pin-out diagrams and terminal output |

Each layer maintains clean separation: the controller manages state and user interaction, the service layer handles hardware, and the sniffer provides the bit-bang conversion capability.

---

## How UART to Bit-Bang Conversion Works

### Configuration Phase

Before any conversion occurs, the user establishes baseline UART parameters:

```text
uart config

```

This triggers `UartController::handleConfig()`, which collects baud rate, data bits, parity, stop bits, and GPIO assignments via `state.getUartRxPin()` and `state.getUartTxPin()`. The configuration persists in the `State` object for subsequent operations.

### Bit-Bang Sniffing Implementation

The core conversion happens in `UartSnifferService`, triggered by:

```text
uart sniff raw

```

The execution flow in [`src/Controllers/UartController.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Controllers/UartController.cpp) proceeds as follows:

1. `UartController::handleSniffRaw()` retrieves current UART settings from `State`
2. The controller invokes `UartSnifferService::sniffRaw(rxPin, txPin, baudRate)` 
3. The sniffer service **reconfigures both pins as input-only UART ports** via `configurePorts()`
4. Continuous polling captures bytes from both lines simultaneously
5. Output is interleaved with `[RX]` and `[TX]` tags, displayed as formatted hex

The same GPIO pins serve dual purposes: hardware UART peripheral during normal operation, raw digital inputs during bit-bang sampling. This pin reuse pattern is central to the conversion architecture.

For ASCII-oriented inspection, use:

```text
uart sniff txt

```

This calls `UartSnifferService::sniffText()`, filtering non-printable characters while maintaining the same `[RX]`/`[TX]` line tagging.

### Bridge Mode (Transparent Pass-Through)

The `uart bridge` command creates live bidirectional forwarding without bit-bang conversion:

```text
uart bridge

```

`UartController::handleBridge()` implements a polling loop that copies UART→terminal and terminal→UART using `uartService.read()` and `uartService.write()`. Pins retain their hardware UART configuration—no reconfiguration occurs, preserving signal integrity for active communication.

---

## Auto-Baud Detection

When line parameters are unknown, the auto-baud feature scans for valid signaling:

```text
uart autobaud

```

`UartController::handleAutoBaud()` iterates common baud rates (9600, 19200, 38400, 57600, 115200, etc.), invoking `IUartService::detectBaudByEdge()` to measure pulse widths on the RX pin. Upon successful detection, the user may persist the rate to configuration.

---

## Advanced UART Commands

| Command | Handler Function | Purpose |
|---------|-----------------|---------|
| `uart spam "payload" interval_ms` | `UartController::handleSpam()` | Repeated transmission for stress testing |
| `uart trigger "match_hex" "response_hex"` | `UartController::handleTrigger()` | Pattern-matching auto-responder |
| `uart xmodem send/receive` | Via `IUartService` | File transfer protocol support |

All advanced commands build upon the same `IUartService` infrastructure established during initial configuration.

---

## Complete Command Reference

```text

# Configure UART parameters and GPIO selection

uart config
→ UartController::handleConfig()
→ IUartService::configure(baud, config, rxPin, txPin, inverted)

# Raw bit-bang sniffing with hex output

uart sniff raw
→ UartController::handleSniffRaw()
→ UartSnifferService::sniffRaw(...)

# Text-mode sniffing with ASCII filtering

uart sniff txt
→ UartController::handleSniffTxt()
→ UartSnifferService::sniffText(...)

# Transparent bridge without bit-bang conversion

uart bridge
→ UartController::handleBridge()

# Automatic baud rate detection

uart autobaud
→ UartController::handleAutoBaud()

# Repeated payload transmission

uart spam "Hello World" 500
→ UartController::handleSpam()

# Triggered response on pattern match

uart trigger "0x55 0xAA" "\x00\x01\x02"
→ UartController::handleTrigger()

```

---

## Key Source Files

- **[`src/Controllers/UartController.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Controllers/UartController.cpp)** — Command dispatcher implementing `handleConfig`, `handleSniffRaw`, `handleSniffTxt`, `handleBridge`, `handleAutoBaud`, `handleSpam`, and `handleTrigger`

- **[`src/Services/UartSnifferService.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Services/UartSnifferService.cpp)** — Bit-bang engine with `sniffRaw()` and `sniffText()` methods; handles `configurePorts()` for GPIO mode switching

- **[`src/Interfaces/IUartService.h`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Interfaces/IUartService.h)** — Hardware abstraction interface defining `configure()`, `read()`, `write()`, `detectBaudByEdge()`, and XMODEM operations

- **[`src/Dispatchers/ActionDispatcher.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Dispatchers/ActionDispatcher.cpp)** — Command routing infrastructure

- **[`src/Managers/UserInputManager.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Managers/UserInputManager.cpp)** — Interactive prompt handling for configuration workflows

- **[`src/Views/DeviceView.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Views/DeviceView.cpp)** — Pin-out visualization on ESP32-S3 display during sniffing operations

---

## Summary

- **ESP32-Bit-Pirate** implements **UART to bit-bang conversion** by reconfiguring shared GPIO pins between hardware UART and input-only sampling modes
- The **`UartSnifferService`** provides raw line inspection with `[RX]`/`[TX]` tagged output in hex or ASCII formats
- **`uart sniff raw`** and **`uart sniff txt`** activate bit-bang mode; **`uart bridge`** maintains transparent hardware pass-through
- Auto-baud detection, spam testing, and trigger responses extend the core sniffing capability
- All functionality routes through **`UartController`** with clean separation between command handling, hardware abstraction, and UI rendering

---

## Frequently Asked Questions

### How does ESP32-Bit-Pirate switch between UART and bit-bang modes?

The firmware uses **pin reuse** rather than mode switching. The same RX/TX GPIOs configured for hardware UART are reconfigured as input-only UART ports when sniffing begins. `UartSnifferService::configurePorts()` in [`src/Services/UartSnifferService.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Services/UartSnifferService.cpp) handles this transition without physical pin changes.

### What is the difference between `uart sniff raw` and `uart bridge`?

**`uart sniff raw`** activates bit-bang sampling: pins become inputs, data is captured as raw bytes, and output is formatted with `[RX]`/`[TX]` tags. **`uart bridge`** maintains hardware UART configuration, creating transparent terminal-to-device pass-through without signal analysis.

### Can I sniff both directions simultaneously?

Yes. `UartSnifferService::sniffRaw()` polls both configured pins continuously, queuing bytes from each line separately. The output interleaves both directions with source identifiers, enabling full-duplex analysis on a single terminal.

### Does auto-baud detection modify my saved configuration?

No. `UartController::handleAutoBaud()` tests rates ephemerally. Only explicit user confirmation persists the detected baud rate to `State` via the configuration flow.