BLE reconnect after light sleep takes too long (XIAO ESP32C3)

I have a BLE remote control project. It acts as a server and the Android TV box is the peer. Initial pairing takes around 5-10 seconds which is pretty good. The remote is sitting idle most of the time thus I’m putting it on light sleep after 60 seconds. The problem is even though the MCU wakes up almost instantly, it takes at least 4-5 seconds for bluetooth connection to resume. Obviously this is very annoying for a remote control. Since I know many BLE remote controls that can have very fast wake up times (including the NVIDIA Shield Remote). I’m using NimBLE-Arduino. I’m not married to it so if there is a better alternative I could also try that.

One improvement I found is that instead of just waking up and resuming the application, restarting the ESP32 actually improves the times at least 1-2 seconds. But it’s still in total 2-3 seconds to reconnect and not acceptable for a remote control.

My BLE code is here: RemoteBOY/Arduino/RemoteBOY/BLERemote.cpp at main · tapir/RemoteBOY · GitHub

Any help would be appreciated

Hi there,
What are the Connection parameters ? Are you setting things like advertisement time, Latency, etc. What speed PHY are you using? NimBLE allows tuning of them so You’ll need to adjust those to get close to real time response.
Cool project.
HTH
GL :slight_smile: PJ :v:

static const uint8_t _hidReportDescriptor[]
    = {
          USAGE_PAGE(1), 0x0C,                  // USAGE_PAGE (Consumer)
          USAGE(1), 0x01,                       // USAGE (Consumer Control)
          COLLECTION(1), 0x01,                  // COLLECTION (Application)
          REPORT_ID(1), BLE_CONSUMER_REPORT_ID, //   REPORT_ID (1)
          LOGICAL_MINIMUM(1), 0x00,             //   LOGICAL_MINIMUM (0)
          LOGICAL_MAXIMUM(1), 0x01,             //   LOGICAL_MAXIMUM (1)
          REPORT_SIZE(1), 0x01,                 //   REPORT_SIZE (1)
          REPORT_COUNT(1), 0x0C,                //   REPORT_COUNT (12)
          USAGE(1), 0x30,                       //   Power
          USAGE(1), 0x41,                       //   Menu Pick (Select)
          USAGE(1), 0x46,                       //   Menu Escape (Back)
          USAGE(2), 0x23, 0x02,                 //   Home
          USAGE(1), 0xE9,                       //   Volume Increase
          USAGE(1), 0xEA,                       //   Volume Decrease
          USAGE(1), 0xE2,                       //   Volume Mute
          USAGE(1), 0x40,                       //   Menu
          USAGE(1), 0x42,                       //   Menu Up
          USAGE(1), 0x43,                       //   Menu Down
          USAGE(1), 0x44,                       //   Menu Left
          USAGE(1), 0x45,                       //   Menu Right
          HIDINPUT(1), 0x02,                    //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
          REPORT_SIZE(1), 0x04,                 //   REPORT_SIZE (4) - Padding 4 bit
          REPORT_COUNT(1), 0x01,                //   REPORT_COUNT (1)
          HIDINPUT(1), 0x03,                    //   INPUT
          END_COLLECTION(0)                     // END COLLECTION
      };

void BLERemote::taskServer(void* pvParameter) {
    BLERemote* BLERemoteInstance = (BLERemote*)pvParameter;

    NimBLEDevice::init(BLERemoteInstance->deviceName);
    NimBLEDevice::setSecurityAuth(true, false, false);

    NimBLEServer* pServer = NimBLEDevice::createServer();
    pServer->setCallbacks(BLERemoteInstance->connectionStatus);
    BLERemoteInstance->hid           = new NimBLEHIDDevice(pServer);
    BLERemoteInstance->inputConsumer = BLERemoteInstance->hid->inputReport(BLE_CONSUMER_REPORT_ID);
    BLERemoteInstance->hid->manufacturer()->setValue(BLERemoteInstance->deviceManufacturer);
    BLERemoteInstance->hid->pnp(BLE_VEND_SRC, BLE_VEND_ID, BLE_PROD_ID, BLE_PROD_VER);
    BLERemoteInstance->hid->hidInfo(0x00, 0x01);
    BLERemoteInstance->hid->setBatteryLevel(BLERemoteInstance->batteryLevel);
    BLERemoteInstance->hid->reportMap((uint8_t*)_hidReportDescriptor, sizeof(_hidReportDescriptor));
    BLERemoteInstance->hid->startServices();
    // BLERemoteInstance->onStarted(pServer);

    NimBLEAdvertising* pAdvertising = pServer->getAdvertising();
    pAdvertising->setAppearance(BLE_HID_REMOTE_APPEARANCE);
    pAdvertising->addServiceUUID(BLERemoteInstance->hid->hidService()->getUUID());
    pAdvertising->start();

    vTaskDelay(portMAX_DELAY);
}

Only relevent parts of the code is above. Looks like I’m uaing the default parameters. Which parameters should I start experimenting with?

Hi there,
OK, Sure I know what you need to change. I did give you a hint, but instead of denying :index_pointing_at_the_viewer: you the Thrill of learning something and the journey to obtain the power the knowledge delivers. :star_struck:
LOL, Left my spoon in the kitchen… Check it

So I G’d the term for you “what parameters affect the response of the BLE connection”
I’ll give you some sample but it’s not rocket science, go read the Links and You’ll know why and what to change (your using all the defaults)

The primary parameters that affect the response of a Bluetooth Low Energy (BLE) connection are the Connection Interval, the Peripheral Latency, and the Supervision Timeout. These parameters are used to balance low power consumption with high data throughput and low latency. The connection interval determines how often the central will ask for data from the peripheral. The lower the slave latency and faster the connection interval, the faster the effective data transfer rate between the peripheral and central.

The best Guide for this is THIS ONE Ultimate Guide to Managing Your BLE Connection | Punch Through.
" The connection interval and slave latency typically affect the performance of a BLE link the most.
HTH
GL :slight_smile: PJ :v:

1 Like

Love the response thanks :smiley: ! I’ll check it out

1 Like

Hi there,
Have you looked here?
More applicable too! What is NimBLE?

HTH
GL :smile: PJ :v:

Yes unfortunately the documentation doesn’t explain any of the parameters so I will have to do a lot of experimentation.

In the mean while I’m thinking if I should just switch to XIAO NRF52 and if it would make a difference

Hi there,
No, not necessarily the example was just for the NimBLE stack.
The point is the same connection parameters apply.(what’s supported by the lib)
Most notable are the “Connection Interval, the Peripheral Latency”
see if you can set those prior to the Advertise portion
HTH
GL :slight_smile: PJ

BTW; I would get it working , then try the other Xiao, prove the concept and then refine in and get the LOWEST power consumption you can (Nrf52840 @ uA’s Sleep) batteries can last years.

I might be misunderstanding somethings.

After looking at the interval and latency descriptions I was very excited because it looked like I was thinking about it all wrong.

My initial idea was to optimize how fast my device reconnects however the definition of latency gave me the idea that the actual trick is to make the client device think that the connection didn’t dropped/end at all.

Thus I’m now requesting a connection parameter update from the client like this

void BLEStatus::onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
    Serial.println("onConnect");
    size_t numClients = NimBLEDevice::getClientListSize();
    if (numClients > 0) {
        std::list<NimBLEClient*>* clientList = NimBLEDevice::getClientList();
        for (auto it = clientList->begin(); it != clientList->end(); it++) {
            if ((*it)->isConnected()) {
                // pServer->updateConnParams((*it)->getConnId(), 16, 24, 18000, 65535);
                (*it)->updateConnParams(16, 24, 18000, 65535);
            }
        }
    }
}

According to the number’s I have used

  • 16*1.25 = 20 ms min communication interval.
  • 24*1.25 = 30 ms max communication interval.
  • 18000 * 20 = 6 minutes minimum and 18000 * 30 = 9 minutes maximum time until client can say that the connection is no more active.
  • 65535*10 = 10.9 minutes until the connection is considered completely shut off.

So I assumed, unless I’m sleeping more than 6 minutes (let’s say a timer wakes the device up and sends a packet every 3-4 minutes), whenever I wakeup the connection should be instant.

However this is not what I see. Even If I wake up instantly after sleep, the whole connection will be reset and the process will start from scratch which takes 5 seconds all-in-all to reconnect.

Any ideas?

Hi there,
So I don’t think the C3 supports Lite-Sleep. AFAIK that would be needed to keep the radio ON,
like the S3, or Nrf52840 does in those cases. The C6 I believe allows a ULP connection but super low Sleep current.
What about just advertising the data, without a connection? just a thought?
Check the C3 data-sheet and be sure you are able to do what your thinking sleep wise, and are you using the BLE connection callbacks in your code? if so you may need to add a STUB or modify what you have so upon waking it doesn’t reset, just reconnects.
HTH
GL :slight_smile: PJ :v: