Bluetooth

August 2, 2026 · View on GitHub

Home · Getting Started: Linux · Getting Started: MCU · Troubleshooting

The host runs the Bluetooth application and host stack; the co-processor runs the Bluetooth controller and radio. ESP-Hosted carries HCI packets between them, so it is stack-agnostic — bring your own stack if you prefer.


Host support

Linux hostMCU host
YesYes

Bluetooth/BLE works on both hosts — the integration differs:

  • Linux host — the ESP-Hosted kernel module presents a standard HCI interface to the Linux Bluetooth subsystem, so you run your normal host stack (e.g. BlueZ) directly on it. HCI rides the same SDIO/SPI transport as Wi-Fi. You do not need the ESP-Hosted Bluetooth examples on Linux — those target MCU/ESP-IDF hosts.
  • MCU host — the host firmware runs a BT host stack (NimBLE or BlueDroid) and ESP-Hosted carries HCI to the controller. That path is covered in the rest of this page.

Note

Classic Bluetooth requires an ESP32 as the co-processor. All other ESP chips provide BLE only. On an MCU host with an ESP32 co-processor, the host stack must also be Bluetooth v4.2.


Linux host: use BlueZ directly

Once the transport is up on Linux (see Getting Started: Linux and your bus page), the kernel module exposes a standard HCI device to the Linux Bluetooth stack. From there, Bluetooth is managed with the ordinary BlueZ tooling — nothing ESP-Hosted-specific:

hciconfig hci0 up      # bring the HCI interface up
bluetoothctl           # scan, pair, connect as usual

Because the kernel presents a normal HCI interface, existing BlueZ applications work unchanged. The ESP-Hosted Bluetooth examples are not used on Linux — they exist for MCU/ESP-IDF hosts that must supply their own host stack.


MCU host: choose stack and HCI transport

Host stack — NimBLE vs BlueDroid:

  • NimBLE (esp-nimble) — BLE only. Smaller code and RAM footprint; recommended for BLE-only use cases.
  • BlueDroid (esp-bluedroid) — Classic BT and BLE. Use this when you need Classic Bluetooth.

HCI transport — Hosted-HCI vs standard UART-HCI:

Hosted-HCIStandard HCI (UART)
WiringReuses the existing SPI/SDIO transportNeeds a dedicated UART + extra GPIOs
MultiplexingShares the link with Wi-Fi and other trafficDedicated link, BT only
OverheadAdds an ESP-Hosted header to each HCI frameTransparent — raw HCI, no extra bytes
Best forEasiest setup once a transport existsPortability; swap in any HCI controller

Note

Hosted-HCI and standard HCI are mutually exclusive — enabling one requires disabling the other.

flowchart LR
    A[Host BT stack<br/>NimBLE / BlueDroid] --> B{HCI transport}
    B -->|Hosted-HCI| C[SPI / SDIO<br/>shared link]
    B -->|Standard HCI| D[dedicated UART]
    C --> E[BT controller on co-processor]
    D --> E

Enable / disable

Pick the HCI transport in menuconfig. Hosted-HCI (the default, over the existing SPI/SDIO link) needs no BT-specific menuconfig; for standard HCI over UART, set the controller HCI mode to UART(H4) on the co-processor:

Co-processor (standard HCI over UART):
Component config
└── Bluetooth
     └── Controller Options
          └── HCI mode
               └── (X) UART(H4)                              <------- Enable this
Example Configuration
└── HCI UART Settings                                             (set UART Tx/Rx/flow pins)

Hosted-HCI and standard UART-HCI are mutually exclusive — enabling one disables the other. Make sure any UART GPIOs do not clash with the ESP-Hosted transport pins.

For Hosted-HCI, enable the HCI byte-pipe and pick the host stack with the standard IDF BT Kconfig — the esp_hosted_bt_host_stack stack adapter is built into the esp_hosted component, there is no separate bridge component and no hosted BT-port knob:

CONFIG_ESP_HOSTED_HOST_FEAT_BT=y     # HCI byte-pipe over the transport
CONFIG_BT_NIMBLE_ENABLED=y           # …or CONFIG_BT_BLUEDROID_ENABLED — chooses the stack

The app makes one explicit call, esp_hosted_bt_host_stack_setup(), before initialising the stack itself — no auto-init, no background task:

esp_hosted_bt_host_stack_cfg_t cfg = ESP_HOSTED_BT_HOST_STACK_CONFIG_DEFAULT();  // stack from Kconfig
ESP_ERROR_CHECK(esp_hosted_bt_host_stack_setup(&cfg));   // MAC (opt) + controller up + HCI bound
// then the stack's own lifecycle:
//   NimBLE:   nimble_port_init();
//   BlueDroid: esp_bluedroid_init(); esp_bluedroid_enable();

ESP_HOSTED_BT_HOST_STACK_CONFIG_DEFAULT() selects NimBLE or BlueDroid from the IDF BT Kconfig above (neither ⇒ a custom stack — see Bluetooth Design → Porting a BT stack). setup() (optionally) applies the BT MAC, brings up the CP controller, and binds the chosen stack to the HCI pipe; esp_hosted_bt_host_stack_teardown() is the symmetric counterpart.

setup() drives the controller lifecycle for you. The controller is disabled by default (since ESP-Hosted-MCU v2.5.2) so the BT MAC can be set first — supply it via cfg.bt_mac (runtime) or CONFIG_ESP_HOSTED_HOST_FEAT_BT_MAC (build-time). A bring-your-own-driver app that skips the adapter drives feat_bt directly instead:

  1. esp_hosted_connect_to_slave() — initialise the transport.
  2. (Optional) esp_hosted_iface_mac_addr_get() / esp_hosted_iface_mac_addr_set() — read/set the BT MAC (temporary until reset).
  3. esp_hosted_bt_controller_init() then esp_hosted_bt_controller_enable().
  4. Initialise your BT host stack (NimBLE or BlueDroid).

To disable / tear down: call esp_hosted_bt_host_stack_teardown() after de-initing the host stack — or, on the manual path, esp_hosted_bt_controller_disable() then esp_hosted_bt_controller_deinit().


Start here

  • Linux host — no ESP-Hosted BT example needed. Bring up the transport via Getting Started: Linux, then use BlueZ (bluetoothctl) directly.
  • MCU hostBluetooth Examples: NimBLE and BlueDroid samples over Hosted-HCI and UART.

See also