mcbluetooth-esp32/docs/hardware-setup.md
Ryan Malloy 0e7b8c2ef5 Fix docs to match UART0 firmware implementation
The firmware uses UART0 (via USB bridge) with ESP-IDF console disabled,
not UART1 on GPIO4/GPIO5 as originally documented. Updated both docs to
reflect the actual hardware-verified configuration:

- protocol-spec.md: UART peripheral description
- hardware-setup.md: wiring section, monitor section, sdkconfig table,
  troubleshooting steps
2026-02-02 15:38:20 -07:00

6.6 KiB

Hardware Setup Guide

How to set up the ESP32 hardware and build environment for the mcbluetooth-esp32 test harness.

Requirements

  • ESP32 dev board -- must be an original ESP32 (ESP32-D0WD or similar) with Classic Bluetooth support. ESP32-S3, ESP32-C3, ESP32-H2, and ESP32-S2 lack the BR/EDR radio and will not work for Classic BT pairing tests.
  • USB cable (USB-A to micro-USB or USB-C depending on your board)
  • Linux host with BlueZ installed (for the mcbluetooth MCP server on the other side of E2E tests)
  • ESP-IDF v5.x toolchain

Verified Hardware

Property Value
Chip ESP32-D0WD-V3 (rev 3.1)
Flash 4MB
Crystal 40MHz
Features Wi-Fi, Bluetooth (dual-mode), Dual Core, 240MHz

Any ESP32 board based on the original ESP32 chip should work. Commonly available boards include ESP32-DevKitC, ESP32-WROOM-32, and NodeMCU-32S.

Wiring

USB only (default)

A single USB cable handles both flashing and NDJSON protocol communication. The ESP32 dev board's built-in USB-to-UART bridge (typically CP2102 or CH340) connects to UART0 (TX=GPIO1, RX=GPIO3).

The firmware uses UART0 for the NDJSON protocol. The ESP-IDF console is disabled (CONFIG_ESP_CONSOLE_NONE=y) so there is no conflict -- the firmware owns UART0 exclusively. No additional wiring or USB-UART adapters are needed.

The dev board appears as /dev/ttyUSB* on the host. Use this device path for ESP32_SERIAL_PORT.

ESP-IDF Setup

1. Install ESP-IDF v5.x

Follow the official installation guide: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/

On Arch Linux:

# Install dependencies
sudo pacman -S cmake ninja python

# Clone ESP-IDF
mkdir -p ~/esp && cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32

# Activate the environment (add to .bashrc or run each session)
. ~/esp/esp-idf/export.sh

2. Set the target

cd firmware
idf.py set-target esp32

This only needs to be done once per checkout. It configures the build system for the ESP32 chip.

3. Build

idf.py build

Or using the project Makefile from the repository root:

make build

4. Flash

idf.py -p /dev/ttyUSB4 flash

Or:

make flash SERIAL_PORT=/dev/ttyUSB4

5. Monitor (optional)

Open the ESP-IDF serial monitor to watch raw UART traffic. Since the firmware owns UART0 (console is disabled), you will see NDJSON protocol messages rather than ESP-IDF log output:

idf.py -p /dev/ttyUSB4 monitor

Press Ctrl+] to exit the monitor. Note: while the monitor is open, the MCP server cannot use the same serial port.

6. Flash and monitor in one step

make flash-monitor SERIAL_PORT=/dev/ttyUSB4

Quick Verification

Using the MCP server

# Set the serial port and start the server
ESP32_SERIAL_PORT=/dev/ttyUSB4 uvx mcbluetooth-esp32

Then from a Claude Code session with the MCP server configured, call:

  1. esp32_connect -- opens the serial link
  2. esp32_ping -- should return {"pong": true}
  3. esp32_get_info -- should return chip model, firmware version, MAC address

Using the Makefile ping target

make ping SERIAL_PORT=/dev/ttyUSB4

This runs a standalone Python script that connects, sends a ping command, prints the response, and disconnects.

Raw serial check

If everything else fails, use screen or minicom to send raw JSON:

screen /dev/ttyUSB4 115200

Type (all on one line, then press Enter):

{"type":"cmd","id":"1","cmd":"ping"}

You should see:

{"type":"resp","id":"1","status":"ok","data":{"pong":true}}

Press Ctrl+A then K then Y to exit screen.

sdkconfig

The project ships firmware/sdkconfig.defaults with the required Bluetooth configuration pre-set:

Setting Value Purpose
CONFIG_BT_ENABLED y Enable Bluetooth controller
CONFIG_BT_BLUEDROID_ENABLED y Use Bluedroid host stack
CONFIG_BT_CLASSIC_ENABLED y Enable BR/EDR (Classic BT)
CONFIG_BT_BLE_ENABLED y Enable BLE
CONFIG_BT_SPP_ENABLED y Enable Serial Port Profile
CONFIG_BT_GATTS_ENABLE y Enable GATT Server
CONFIG_BTDM_CTRL_MODE_BTDM y Dual-mode controller (Classic + BLE simultaneously)
CONFIG_ESP_CONSOLE_NONE y Disable ESP-IDF console so firmware owns UART0
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE y 1.5MB app partition (dual-mode BT stack needs >1MB)

Do not modify these unless you understand the implications. Disabling CONFIG_BT_CLASSIC_ENABLED breaks all Classic BT pairing tests.

Troubleshooting

Permission denied on /dev/ttyUSB*

Add your user to the dialout group (or uucp on Arch Linux):

# Debian/Ubuntu
sudo usermod -aG dialout $USER

# Arch Linux
sudo usermod -aG uucp $USER

Log out and back in for the group change to take effect.

Device not found

Check what serial devices are present:

ls -la /dev/ttyUSB* /dev/ttyACM*

If nothing appears, verify the USB cable is a data cable (not charge-only) and that the board's USB-UART chip driver is loaded:

dmesg | tail -20

Look for lines mentioning cp210x, ch341, or ftdi_sio.

Flash fails or hangs

Some ESP32 boards require holding the BOOT button during the initial flash sequence. Hold BOOT, press and release EN (reset), then release BOOT. The flash should proceed.

If the board has auto-download circuitry (most DevKitC boards do), this should not be necessary.

No response over UART

  1. Check baud rate. Both sides must use 115200. Verify in screen or your terminal emulator.

  2. Make sure nothing else is using the port. The ESP-IDF monitor, screen, another MCP server instance, or any other serial tool will lock the device. Only one process can open /dev/ttyUSB* at a time.

  3. Send valid JSON. The firmware expects complete JSON objects terminated by \n. A bare ping won't work -- send {"type":"cmd","id":"1","cmd":"ping"}\n.

  4. Verify the firmware booted. After flashing, the firmware should emit a boot event within ~2 seconds. If you see nothing at all, try pressing the EN (reset) button on the board.

Build errors about missing Bluetooth headers

Make sure idf.py set-target esp32 was run. The ESP32-S3 and ESP32-C3 targets do not expose Classic BT APIs, which causes build failures.

NVS errors on first boot

If the console shows NVS partition issue, erasing and re-initializing, this is expected on first flash or after a partition table change. The firmware handles it automatically by erasing and reinitializing NVS.