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 Reply Children
No Data
Related