Packet retransmission at client & loss at server

Hello,

I am a student currently studying Bluetooth Low Energy (BLE).

I am working with two Adafruit nRF52832 boards (Adafruit Feather nRF52 Bluefruit LE - nRF52832, link to product: www.adafruit.com/.../3406), setting up a 1-to-1 connection as a server and client. Currently, I'm using the "write without response" method with a 10ms connection interval and a 5ms event length. I have also set the TxPower to 4 and the data rate to 2Mbps. (Arduino Environment)

In this setup, the client writes a packet to the server every 10ms and the packet size is 8 bytes (two float values), with first element containing the time of transmission and the second element containing packet count sent by the client.

I monitored the values written by the client every 10ms and observed what the server receives. I noticed a delay in the packets on the client side, likely due to retransmission. Given the 10ms connection interval, new packets should ideally be sent every 10ms. However, the packet numbers at t+10ms and t+20ms are sometimes identical, indicating a delay in transmission. (Each sample row in my picture represents a 10ms interval)

SERVER

On the server side, I observed that packets were occasionally duplicated or lost. The duplication happens when the server reads the same packet value at both t+10ms and t+20ms, likely due to retransmission. (Each sample row in my picture represents a 10ms interval)

CLIENT

Additionally, packet loss occurs when the expected consecutive packet numbers do not arrive at the server within the expected intervals. (Each sample row in my picture represents a 10ms interval)

CLIENT

Is packet retransmission, delay, or loss a common issue in BLE communication, or could there be something incorrect in my setup? I would greatly appreciate any advice on this matter.
My code is included below.

Thank you in advance for your guidance.

CLINET CODE

#include <bluefruit.h>
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>
#include <Wire.h>
#include <utility/imumaths.h>

#define dataNum 2 // timestampe 포함
#define dataSize dataNum*4 // default: 20, maximum: 224
#define packetTime 8 // unit: 1.25ms
#define SLAVE_ADDRESS 0x61  // I2C 슬레이브 주소

BLEClientService        hrms(UUID16_SVC_HEART_RATE);
BLEClientCharacteristic hrmc(UUID16_CHR_HEART_RATE_MEASUREMENT);

float packetCount = 0;
uint8_t buffer[dataNum * sizeof(float)];

void setup()
{
  Serial.begin(115200);
  while ( !Serial ) delay(10);   // for nrf52840 with native usb

  Serial.println("Bluefruit52 Peripheral Server");
  Serial.println("--------------------------------------\n");

  Serial.println("Initialize the Bluefruit nRF52 module");
  // Change MTU size
  uint16_t requestedMtu = dataSize;
  Bluefruit.configCentralConn(requestedMtu+3, 4, BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT, BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT); // BLE_GAP_EVENT_LENGTH_DEFAULT = 3
  
  Bluefruit.begin(0, 1); // maximum connections as Peripheral = 0, Central = 1
  Bluefruit.setTxPower(4);
  Bluefruit.setName("Bluefruit52 Central");

  // Initialize HRM client
  hrms.begin();
  hrmc.begin();
  
  // Increase Blink rate to different from PrPh advertising mode
  Bluefruit.setConnLedInterval(250);
  
  // Callbacks for Central
  Bluefruit.Central.setConnInterval(packetTime, packetTime);
  Bluefruit.Central.setDisconnectCallback(disconnect_callback);
  Bluefruit.Central.setConnectCallback(connect_callback);

  /* Start Central Scanning
   * - Enable auto scan if disconnected
   * - Interval = 160 ms, window = 80 ms
   * - Don't use active scan
   * - Filter only accept HRM service
   * - Start(timeout) with timeout = 0 will scan forever (until connected)
   */
  Bluefruit.Scanner.setRxCallback(scan_callback);
  Bluefruit.Scanner.restartOnDisconnect(true);
  Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
  Bluefruit.Scanner.filterUuid(hrms.uuid);
  Bluefruit.Scanner.useActiveScan(false);
  Bluefruit.Scanner.start(0);                   // // 0 = Don't stop scanning after n seconds

  /*************************I2C***************************/
  Wire.setPins(25, 26);  // SCL = 25, SDA = 26 핀 설정
  Wire.begin(SLAVE_ADDRESS);  // 슬레이브 모드로 I2C 초기화
  Wire.onRequest(requestEvent);  // 마스터의 읽기 요청이 들어올 때 호출될 콜백 설정
  Wire.setClock(400000);  // Set I2C speed 나중에 필요하면 사용
}

/**************************************************************************/
void readTimedata(float (&timeData)[dataNum]) {
  sensors_event_t accelerometerData;  // 선언된 변수 이름 수정
  timeData[0] = millis();              // timestamp를 imuData의 첫 번째 요소에 저장
  packetCount++;
  memcpy(buffer, timeData, sizeof(buffer));
  timeData[1] = packetCount;
  hrmc.write((uint8_t*)timeData, sizeof(timeData));
}

/**************************************************************************/
void loop() {
  float timeData[dataNum];

  if (Bluefruit.connected()) {
    readTimedata(timeData);
    // Serial.println(imuData[1]);
  }
}

/**********************************callback*********************************/
/**
 * Callback invoked when scanner pick up an advertising data
 * @param report Structural advertising data
 */
void scan_callback(ble_gap_evt_adv_report_t* report)
{
  // Since we configure the scanner with filterUuid()
  // Scan callback only invoked for device with hrm service advertised
  // Connect to device with HRM service in advertising
  Bluefruit.Central.connect(report);
}

/**
 * Callback invoked when an connection is established
 * @param conn_handle
 */
void connect_callback(uint16_t conn_handle)
{
  Serial.println("Connected");
  Serial.print("Discovering Service ... ");

  // If HRM is not found, disconnect and return
  if ( !hrms.discover(conn_handle) )
  {
    Serial.println("Found NONE");

    // disconnect since we couldn't find HRM service
    Bluefruit.disconnect(conn_handle);

    return;
  }

  // Once HRM service is found, we continue to discover its characteristic
  Serial.println("Found it");
  
  Serial.print("Discovering characteristic ... ");
  if ( !hrmc.discover() )
  {
    // chr is mandatory, if it is not found (valid), then disconnect
    Serial.println("not found !!!");  
    Serial.println("characteristic is mandatory but not found");
    Bluefruit.disconnect(conn_handle);
    return;
  }
  Serial.println("Found it");

  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);
  // Request MTU exchange for rx_client
  if (connection) {
    connection->requestMtuExchange(dataSize + 3);  // Request MTU of 247 bytes
  }
}

/**
 * Callback invoked when a connection is dropped
 * @param conn_handle
 * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
 */
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;

  Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX);
}

void requestEvent() {
  // 요청이 있을 때 I2C 마스터에게 데이터를 전송
  Wire.write(buffer, sizeof(buffer));
}


SERVER CODE

#include <bluefruit.h>
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>

#define dataNum 2
#define dataSize dataNum*4 // default: 20, maximum: 224
#define packetTime 8 // * 1.25ms
#define SLAVE_ADDRESS 0x60  // I2C 슬레이브 주소

BLEService        hrms = BLEService(UUID16_SVC_HEART_RATE);
BLECharacteristic hrmc = BLECharacteristic(UUID16_CHR_HEART_RATE_MEASUREMENT);

BLEDis bledis;    // DIS (Device Information Service) helper class instance

volatile bool updateVALUEFlag = false;  // A flag to signal when to read IMU data

float receivedData[dataNum];
float tempData[dataNum];
uint8_t buffer[dataNum * sizeof(float)];

void setup()
{
  Serial.begin(115200);
  while ( !Serial ) delay(10);   // for nrf52840 with native usb
  Serial.println("Bluefruit52 Central client");
  Serial.println("--------------------------------------\n");

  // Initialize the Bluefruit module
  Serial.println("Initialize the Bluefruit nRF52 module");
  uint16_t requestedMtu = dataSize;
  Bluefruit.configPrphConn(requestedMtu+3, 4, BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT, BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT); // MTU set to 247 bytes, BLE_GAP_EVENT_LENGTH_DEFAULT = 3
  Bluefruit.begin(); // maximum connections as Peripheral = 1, Central = 0 (default)
  Bluefruit.setTxPower(4);  // Set transmission power

  // Set the connect/disconnect callback handlers
  Bluefruit.Periph.setConnInterval(packetTime, packetTime);
  Bluefruit.Periph.setConnectCallback(connect_callback);
  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);

  // Configure and Start the Device Information Service
  Serial.println("Configuring the Device Information Service");
  bledis.setManufacturer("Adafruit Industries");
  bledis.setModel("Bluefruit Feather52");
  bledis.begin();

  // Setup the Heart Rate Monitor service using
  // BLEService and BLECharacteristic classes
  Serial.println("Configuring the Heart Rate Monitor Service");
  setupHRM();

  // Setup the advertising packet(s)
  Serial.println("Setting up the advertising payload(s)");
  startAdv();
  Serial.println("\nAdvertising");

  /*************************I2C***************************/
  Wire.setPins(25, 26);  // SCL = 25, SDA = 26 핀 설정
  Wire.begin(SLAVE_ADDRESS);  // 슬레이브 모드로 I2C 초기화
  Wire.onRequest(requestEvent);  // 마스터의 읽기 요청이 들어올 때 호출될 콜백 설정
  Wire.setClock(400000);  // Set I2C speed 나중에 필요하면 사용
  
  // Board-specific Serial initialization delay (없애도 될 것 같음)
  #if defined(__AVR_ATmega32U4__) || defined(__SAM3X8E__)
    while (!Serial) delay(10);
  #endif
}

void startAdv(void)
{
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();

  // Include HRM Service UUID
  Bluefruit.Advertising.addService(hrms);

  // Include Name
  Bluefruit.Advertising.addName();
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds  
}

void setupHRM(void)
{
  hrms.begin();

  // set up callback for receiving measurement
  hrmc.setProperties(CHR_PROPS_WRITE_WO_RESP | CHR_PROPS_READ); // CHR_PROPS_WRITE
  hrmc.setPermission(SECMODE_OPEN, SECMODE_OPEN); // SECMODE_OPEN or SECMODE_NO_ACCESS
  hrmc.setFixedLen(dataSize);
  hrmc.begin();
  hrmc.setWriteCallback(hrm_write_callback);
}

void connect_callback(uint16_t conn_handle)
{
  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);

  char central_name[32] = { 0 };
  connection->getPeerName(central_name, sizeof(central_name));

  // Request MTU exchange for rx_client
  if (connection) {
    connection->requestMtuExchange(dataSize + 3);  // Request MTU of 247 bytes
  }

  uint16_t mtu;
  mtu = connection->getMtu();
  Serial.print("Connected to ");
  Serial.print(central_name);
  Serial.print(" with mtu size: ");
  Serial.print(mtu);
  
  connection->requestPHY(BLE_GAP_PHY_2MBPS);
}

/**
 * Callback invoked when a connection is dropped
 * @param conn_handle connection where this event happens
 * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
 */
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;

  Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX);
  Serial.println("Advertising!");
}

/**************************************************************************/
void loop() {
  if ( Bluefruit.connected() ) {
    updateIMUData();
  }
}

void hrm_write_callback(uint16_t conn_handle, BLECharacteristic* chr, uint8_t* data, uint16_t len)
{
  // 수신된 데이터가 float 배열의 크기인지 확인
  if (len == sizeof(float) * dataNum) {
    // 수신된 데이터를 float 배열 receivedData로 복사
    memcpy(receivedData, data, sizeof(receivedData)); // 배열의 크기만큼 복사
  } else {
    Serial.println("[RX]: 데이터 크기가 다름.");
  }
}

void updateIMUData() {
  memcpy(buffer, receivedData, sizeof(buffer)); // 여기에 IMU 데이터를 읽고 receivedData에 저장하는 로직 추가
}

void requestEvent() {
  // 요청이 있을 때 I2C 마스터에게 데이터를 전송
  Wire.write(buffer, sizeof(buffer));
}

Parents
  • Hi

    I think the delay you're seeing here is the random 0-10 ms delay mandated by the Bluetooth Core specification. It is an integral part of how Bluetooth works, and is there to avoid unnecessary data packet loss due to timing issues for example. 

    Forgive me if I'm misunderstanding and you're doing this within a connection, in which case I'll need to know what SDK you're on, and at what range and environment you're doing the testing in. What could be helpful (if you have an nRF52 DK to spare) is to use it and Wireshark as a Bluetooth sniffer with the nRF Sniffer firmware so we can see what exactly is going on over the air here.

    Best regards,

    Simon

Reply
  • Hi

    I think the delay you're seeing here is the random 0-10 ms delay mandated by the Bluetooth Core specification. It is an integral part of how Bluetooth works, and is there to avoid unnecessary data packet loss due to timing issues for example. 

    Forgive me if I'm misunderstanding and you're doing this within a connection, in which case I'll need to know what SDK you're on, and at what range and environment you're doing the testing in. What could be helpful (if you have an nRF52 DK to spare) is to use it and Wireshark as a Bluetooth sniffer with the nRF Sniffer firmware so we can see what exactly is going on over the air here.

    Best regards,

    Simon

Children
Related