ESP32P4 C6 Bluetooth pairing failed.

Author: Quency-DCreated Aug 28, 2026Updated Sep 17, 2026
LabelsStatus: Awaiting triageArea: BLE

Board

A board I drew myself.

Device Description

A board I drew myself.

Hardware Configuration

#define SDIO2_CLK 19 #define SDIO2_CMD 18 #define SDIO2_D0 20 #define SDIO2_D1 21 #define SDIO2_D2 22 #define SDIO2_D3 23 #define SDIO2_RST 45

Version

v3.3.11

Type

Task

IDE Name

Arduino IDE

Operating System

win11

Flash frequency

80MHz

PSRAM enabled

yes

Upload speed

921600

Description

When using IDF to compile the esp_hosted/examples/bluetooth/esp_hosted_nimble/bleprph_gatt/mcu_host example and downloading it to the C6 and P4, Bluetooth can connect normally. But when using the 'Secure server with static passkey' example in Arduino 3.3.11, the pairing always fails. The firmware on the C6 has been updated to 2.12.11.

Sketch

cpp
/*
  Secure server with static passkey

  This example demonstrates how to create a secure BLE server with no
  IO capability using a static passkey.
  The server will accept connections from devices that have the same passkey set.
  The example passkey is set to 123456.
  The server will create a service and a secure and an insecure characteristic
  to be used as example.

  This server is designed to be used with the Client_secure_static_passkey example.

  After successful bonding, the example demonstrates how to retrieve the
  peer's and local Identity Resolving Key (IRK) in multiple formats:
  - Comma-separated hex format: 0x1A,0x1B,0x1C,...
  - Base64 encoded (for Home Assistant Private BLE Device service)
  - Reverse hex order (for Home Assistant ESPresense)

  WARNING: THE IRK IS A LONG-TERM IDENTIFIER OF THE DEVICE. ANYONE WITH THE IRK CAN
  USE IT TO TRACK OR IMPERSONATE THE DEVICE IN BLE PRESENCE SYSTEMS. USE WITH CAUTION.

  Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
  Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
  This means that in NimBLE you can read the insecure characteristic without entering
  the passkey. This is not possible in Bluedroid.

  IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
  to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
  "Just Works" pairing method (with encryption if secure connection is enabled).

  Based on examples from Neil Kolban and h2zero.
  Created by lucasssvaz.
*/

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLESecurity.h>
#include <nvs_flash.h>
#include <string>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID                 "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define INSECURE_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define SECURE_CHARACTERISTIC_UUID   "ff1d2614-e2d6-4c87-9154-6625d39ca7f8"

// This is an example passkey. You should use a different or random passkey.
#define SERVER_PIN 123456

// Print an IRK buffer as hex with leading zeros and ':' separator
static void printIrkBinary(uint8_t *irk) {
  for (int i = 0; i < 16; i++) {
    if (irk[i] < 0x10) {
      Serial.print("0");
    }
    Serial.print(irk[i], HEX);
    if (i < 15) {
      Serial.print(":");
    }
  }
}

static void get_local_irk() {
  Serial.println("\n=== Retrieving local IRK (this device) ===\n");

  String irkString = BLEDevice::getLocalIRKString();
  String irkBase64 = BLEDevice::getLocalIRKBase64();
  String irkReverse = BLEDevice::getLocalIRKReverse();

  if (irkString.length() > 0) {
    Serial.println("Successfully retrieved local IRK in multiple formats:\n");
    Serial.print("IRK (comma-separated hex): ");
    Serial.println(irkString);
    Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
    Serial.println(irkBase64);
    Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
    Serial.println(irkReverse);
    Serial.println();
  } else {
    Serial.println("!!! Failed to retrieve local IRK !!!");
  }

  Serial.println("==========================================\n");
}

static void get_peer_irk(BLEAddress peerAddr) {
  Serial.println("\n=== Retrieving peer IRK (Client) ===\n");

  uint8_t irk[16];

  // Get IRK in binary format
  if (BLEDevice::getPeerIRK(peerAddr, irk)) {
    Serial.println("Successfully retrieved peer IRK in binary format:");
    printIrkBinary(irk);
    Serial.println("\n");
  }

  // Get IRK in different string formats
  String irkString = BLEDevice::getPeerIRKString(peerAddr);
  String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
  String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);

  if (irkString.length() > 0) {
    Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
    Serial.print("IRK (comma-separated hex): ");
    Serial.println(irkString);
    Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
    Serial.println(irkBase64);
    Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
    Serial.println(irkReverse);
    Serial.println();
  } else {
    Serial.println("!!! Failed to retrieve peer IRK !!!");
    Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
    Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
  }

  Serial.println("=======================================\n");
}

// Security callbacks to print IRKs once authentication completes
class MySecurityCallbacks : public BLESecurityCallbacks {
#if defined(CONFIG_BLUEDROID_ENABLED)
  void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
    // desc.bd_addr is the peer's connection address (may be a Resolvable Private Address).
    // getPeerIRK() will also search by the stored identity address as a fallback.
    BLEAddress peerAddr(desc.bd_addr);
    get_peer_irk(peerAddr);
  }
#endif

#if defined(CONFIG_NIMBLE_ENABLED)
  void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
    // peer_id_addr is always the resolved identity address in NimBLE
    BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
    get_peer_irk(peerAddr);
  }
#endif
};
#define SDIO2_CLK 19
#define SDIO2_CMD 18
#define SDIO2_D0  20
#define SDIO2_D1  21
#define SDIO2_D2  22
#define SDIO2_D3  23
#define SDIO2_RST 45

#define PIN_VEXT_EN 8
#define PIN_VEXT_EN_ACTIVE HIGH

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  pinMode(PIN_VEXT_EN, OUTPUT);
  digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE);


  // Enable Station Interface
  // WARNING: nvs_flash_erase() wipes the entire NVS, including the Bluetooth identity keys (IR/IRK).
  // This causes the BLE stack to generate a new IRK on every boot, so the IRK will change each time.
  // This is intentional here to force fresh authentication for testing purposes.
  //
  // For production use where a stable IRK is needed (e.g. Home Assistant presence detection),
  // remove these lines. The server's IRK will remain the same across reboots as long as NVS is intact.
  // To clear only bond data without affecting identity keys, use esp_ble_remove_bond_device() (Bluedroid)
  // or ble_store_util_delete_all() (NimBLE) instead.
  Serial.println("Clearing NVS pairing data...");
  nvs_flash_erase();
  nvs_flash_init();

  Serial.print("Using BLE stack: ");
  Serial.println(BLEDevice::getBLEStackString());
  BLEDevice::setPins(SDIO2_CLK, SDIO2_CMD, SDIO2_D0, SDIO2_D1, SDIO2_D2, SDIO2_D3, SDIO2_RST);

  BLEDevice::init("Secure BLE Server");

  // Display this device's own local IRK
  get_local_irk();

  BLESecurity *pSecurity = new BLESecurity();

  // Set security parameters
  // Default parameters:
  // - IO capability is set to NONE
  // - Initiator and responder key distribution flags are set to both encryption and identity keys.
  // - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it.
  // - Key size is set to 16 bytes

  // Set static passkey
  // The first argument defines if the passkey is static or random.
  // The second argument is the passkey (ignored when using a random passkey).
  pSecurity->setPassKey(true, SERVER_PIN);

  // Set IO capability to DisplayOnly
  // We need the proper IO capability for MITM authentication even
  // if the passkey is static and won't be shown to the user
  // See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
  pSecurity->setCapability(ESP_IO_CAP_OUT);

  // Set authentication mode
  // Enable bonding, MITM (for password prompts), and secure connection for this example
  pSecurity->setAuthenticationMode(true, true, true);

  // Set callbacks to handle authentication completion and print IRKs
  BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());

  BLEServer *pServer = BLEDevice::createServer();
  pServer->advertiseOnDisconnect(true);

  BLEService *pService = pServer->createService(SERVICE_UUID);

  uint32_t insecure_properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE;
  uint32_t secure_properties = insecure_properties;

  // NimBLE uses properties to secure characteristics.
  // These special permission properties are not supported by Bluedroid and will be ignored.
  // This can be removed if only using Bluedroid (ESP32).
  // Check the BLECharacteristic.h file for more information.
  secure_properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_WRITE_AUTHEN;

  BLECharacteristic *pSecureCharacteristic = pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties);
  BLECharacteristic *pInsecureCharacteristic = pService->createCharacteristic(INSECURE_CHARACTERISTIC_UUID, insecure_properties);

  // Bluedroid uses permissions to secure characteristics.
  // This is the same as using the properties above.
  // NimBLE does not use permissions and will ignore these calls.
  // This can be removed if only using NimBLE (any SoC except ESP32).
  pSecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM);
  pInsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);

  // Set value for secure characteristic
  pSecureCharacteristic->setValue("Secure Hello World!");

  // Set value for insecure characteristic
  // When using NimBLE you will be able to read this characteristic without entering the passkey.
  pInsecureCharacteristic->setValue("Insecure Hello World!");

  pService->start();
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMaxPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  delay(2000);
}

Debug Message

plain
Chip Info:
------------------------------------------
  Model             : ESP32-P4
  Package           : 0
  Revision          : 3.01
  Cores             : 2
  CPU Frequency     : 400 MHz
  XTAL Frequency    : 40 MHz
  Features Bitfield : 0000000000
  Embedded Flash    : No
  Embedded PSRAM    : No
  2.4GHz WiFi       : No
  Classic BT        : No
  BT Low Energy     : No
  IEEE 802.15.4     : No
------------------------------------------
INTERNAL Memory Info:
------------------------------------------
  Total Size        :   462360 B ( 451.5 KB)
  Free Bytes        :   426320 B ( 416.3 KB)
  Allocated Bytes   :    31088 B (  30.4 KB)
  Minimum Free Bytes:   421180 B ( 411.3 KB)
  Largest Free Block:   368628 B ( 360.0 KB)
------------------------------------------
SPIRAM Memory Info:
------------------------------------------
  Total Size        : 33554432 B (32768.0 KB)
  Free Bytes        : 33551856 B (32765.5 KB)
  Allocated Bytes   :        0 B (   0.0 KB)
  Minimum Free Bytes:        0 B (   0.0 KB)
  Largest Free Block: 33030132 B (32256.0 KB)
  Bus Mode          : QSPI
------------------------------------------
Flash Info:
------------------------------------------
  Chip Size         : 16777216B (16 MB)
  Block Size        :    65536B (  64.0 KB)
  Sector Size       :     4096B (   4.0 KB)
  Page Size         :      256B (   0.2 KB)
  Bus Speed         : 80 MHz
  Flash Frequency   : 80 MHz (source: 80 MHz, divider: 1)
  Bus Mode          : QIO
------------------------------------------
Partitions Info:
------------------------------------------
                nvs : addr: 0x00009000, size:    20.0 KB, type: DATA, subtype: NVS
            otadata : addr: 0x0000E000, size:     8.0 KB, type: DATA, subtype: OTA
               app0 : addr: 0x00010000, size:  2048.0 KB, type:  APP, subtype: OTA_0
               app1 : addr: 0x00210000, size:  2048.0 KB, type:  APP, subtype: OTA_1
               ffat : addr: 0x00410000, size: 12160.0 KB, type: DATA, subtype: FAT
           coredump : addr: 0x00FF0000, size:    64.0 KB, type: DATA, subtype: COREDUMP
------------------------------------------
Software Info:
------------------------------------------
  Compile Date/Time : Aug 27 2026 15:27:09
  Compile Host OS   : windows
  ESP-IDF Version   : v5.5.5
  Arduino Version   : 3.3.11
------------------------------------------
Board Info:
------------------------------------------
  Arduino Board     : ESP32P4_DEV
  Arduino Variant   : esp32p4
  Arduino FQBN      : esp32:esp32:esp32p4:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,FlashFreq=80,FlashMode=qio,FlashSize=16M,PartitionScheme=fatflash,DebugLevel=verbose,PSRAM=enabled,EraseFlash=none,JTAGAdapter=default,ChipVariant=postv3
============ Before Setup End ============
[   778][V][esp32-hal-uart.c:1085] uartSetPins(): UART0: Driver not yet installed, storing pins for later attachment (RX:38, TX:37)
[   778][I][esp32-hal-periman.c:170] perimanSetPinBus(): Pin 24 already has type USB_DM (46) with bus 0x4ff55158
[   779][I][esp32-hal-periman.c:170] perimanSetPinBus(): Pin 25 already has type USB_DP (47) with bus 0x4ff55158
Starting BLE work!
[   779][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x4000d2f0
[   780][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 8 successfully set to type GPIO (1) with bus 0x9
Clearing NVS pairing data...
Using BLE stack: NimBLE
[  1037][I][BLEDevice.cpp:293] init(): Initializing BLE stack: NimBLE
[  1037][I][esp32-hal-hosted.c:367] hostedInitBLE(): Initializing ESP-Hosted for BLE
[  1037][I][esp32-hal-hosted.c:290] hostedInit(): Initializing ESP-Hosted
[  1037][D][esp32-hal-hosted.c:291] hostedInit(): SDIO pins: clk=19, cmd=18, d0=20, d1=21, d2=22, d3=23, rst=45
[  1038][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type ESP_HOSTED_SDIO_CLK (58) successfully set to 0x4000d526
[  1038][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type ESP_HOSTED_SDIO_CMD (59) successfully set to 0x4000d526
[  1038][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type ESP_HOSTED_SDIO_D0 (60) successfully set to 0x4000d526
[  1039][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type ESP_HOSTED_SDIO_D1 (61) successfully set to 0x4000d526
[  1039][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type ESP_HOSTED_SDIO_D2 (62) successfully set to 0x4000d526
[  1039][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type ESP_HOSTED_SDIO_D3 (63) successfully set to 0x4000d526
[  1040][V][esp32-hal-periman.c:267] perimanSetBusDeinit(): Deinit function for type ESP_HOSTED_SDIO_RST (64) successfully set to 0x4000d526
[  1040][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 19 successfully set to type ESP_HOSTED_SDIO_CLK (58) with bus 0x4ff51cc4
[  1041][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 18 successfully set to type ESP_HOSTED_SDIO_CMD (59) with bus 0x4ff51cc4
[  1041][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 20 successfully set to type ESP_HOSTED_SDIO_D0 (60) with bus 0x4ff51cc4
[  1041][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 21 successfully set to type ESP_HOSTED_SDIO_D1 (61) with bus 0x4ff51cc4
[  1042][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 22 successfully set to type ESP_HOSTED_SDIO_D2 (62) with bus 0x4ff51cc4
[  1042][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 23 successfully set to type ESP_HOSTED_SDIO_D3 (63) with bus 0x4ff51cc4
[  1042][I][esp32-hal-ldo.c:118] io_ldo_try_acquire(): periman IO LDO auto: enabled channel 4 @ 3300 mV
[  1043][V][esp32-hal-periman.c:192] perimanSetPinBus(): Pin 45 successfully set to type ESP_HOSTED_SDIO_RST (64) with bus 0x4ff51cc4
[  3049][I][esp32-hal-hosted.c:125] hostedHasUpdate(): Host firmware version: 2.12.11
[  3049][I][esp32-hal-hosted.c:126] hostedHasUpdate(): Slave firmware version: 2.12.11
[  3050][I][esp32-hal-hosted.c:133] hostedHasUpdate(): Versions Match!
[  3203][I][BLEDevice.cpp:1497] host_task(): NimBLE host task started
[  3213][D][BLEDevice.cpp:1519] onSync(): onSync
=== Retrieving local IRK (this device) ===
[  3416][V][BLEDevice.cpp:899] getLocalIRK(): >> BLEDevice::getLocalIRK()
[  3416][V][BLEDevice.cpp:927] getLocalIRK(): << BLEDevice::getLocalIRK()
[  3417][V][BLEDevice.cpp:899] getLocalIRK(): >> BLEDevice::getLocalIRK()
[  3417][V][BLEDevice.cpp:927] getLocalIRK(): << BLEDevice::getLocalIRK()
[  3417][V][BLEDevice.cpp:899] getLocalIRK(): >> BLEDevice::getLocalIRK()
[  3417][V][BLEDevice.cpp:927] getLocalIRK(): << BLEDevice::getLocalIRK()
Successfully retrieved local IRK in multiple formats:
IRK (comma-separated hex): 0xd1,0xa2,0x55,0xc7,0x5a,0x53,0x79,0xed,0xc0,0x5e,0xa7,0xff,0xb8,0xa2,0x79,0xfc
IRK (Base64 for Home Assistant Private BLE Device): 0aJVx1pTee3AXqf/uKJ5/A==
IRK (reverse hex for Home Assistant ESPresense): FC79A2B8FFA75EC0ED79535AC755A2D1
==========================================
[  3418][D][BLESecurity.cpp:97] BLESecurity(): BLESecurity: Initializing
[  3418][D][BLESecurity.cpp:133] setInitEncryptionKey(): setInitEncryptionKey: init_key=3
[  3418][D][BLESecurity.cpp:147] setRespEncryptionKey(): setRespEncryptionKey: resp_key=3
[  3418][D][BLESecurity.cpp:119] setCapability(): setCapability: iocap=3
[  3419][D][BLESecurity.cpp:175] setPassKey(): setPassKey: staticPasskey=1, passkey=123456
[  3419][W][BLESecurity.cpp:181] setPassKey(): *WARNING* Using default passkey: 123456
[  3419][W][BLESecurity.cpp:182] setPassKey(): *WARNING* Please use a random passkey or set a different static passkey
[  3420][D][BLESecurity.cpp:119] setCapability(): setCapability: iocap=0
[  3420][D][BLESecurity.cpp:240] setAuthenticationMode(): setAuthenticationMode: bonding=1, mitm=1, sc=1
[  3420][V][BLEDevice.cpp:183] createServer(): >> createServer
[  3420][V][BLEDevice.cpp:203] createServer(): << createServer
[  3421][V][BLEServer.cpp:133] createService(): >> createService - 4fafc201-1fb5-459e-8fcc-c5c9c331914b
[  3421][V][BLEService.cpp:123] executeCreate(): >> executeCreate() - Creating service with uuid: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
[  3422][V][BLEService.cpp:142] executeCreate(): << executeCreate
[  3422][V][FreeRTOS.cpp:155] give(): Semaphore giving: name: CreateEvt (0x4ff8437c), owner: <N/A>
[  3422][V][BLEServer.cpp:157] createService(): << createService
[  3422][V][BLEService.cpp:257] addCharacteristic(): >> addCharacteristic()
[  3423][D][BLEService.cpp:258] addCharacteristic(): Adding characteristic: uuid=ff1d2614-e2d6-4c87-9154-6625d39ca7f8 to service: UUID: 4fafc201-1fb5-459e-8fcc-c5c9c331914b, handle: 0xffff
[  3423][V][BLEService.cpp:281] addCharacteristic(): << addCharacteristic()
[  3424][V][BLEService.cpp:257] addCharacteristic(): >> addCharacteristic()
[  3424][D][BLEService.cpp:258] addCharacteristic(): Adding characteristic: uuid=beb5483e-36e1-4688-b7f5-ea07361b26a8 to service: UU