nRF52 BLE Code optimization and increase the transmission speed

I am using adafruit feather nrf52840 sense with a customized EMG amplifier (Analog input), and Mic (Analog input). And, I want to plot the data on the mobile application. But, the maximum sampling rate calculated by "millis()" was only 630 Hz.

Could you please check my code and give me some comments to increase the sampling rate? I want to get about a 1 kHz sampling rate. And, also could you please recommend a mobile application to plot and save the received data? Now, I used Bluefruit LE Connect but there is no data save function.

Thank you.

#include <bluefruit.h>
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>

// BLE Service
BLEDfu  bledfu;  // OTA DFU service
BLEDis  bledis;  // device information
BLEUart bleuart; // uart over ble
BLEBas  blebas;  // battery

bool BLEisConnected = false;

unsigned long time_start = 0;

void setup()
{
  Serial.begin(115200);

#if CFG_DEBUG
  // Blocking wait for connection when debug mode is enabled via IDE
  while ( !Serial ) yield();
#endif
  
  Serial.println("Bluefruit52 BLEUART Example");
  Serial.println("---------------------------\n");

  // Setup the BLE LED to be enabled on CONNECT
  // Note: This is actually the default behavior, but provided
  // here in case you want to control this LED manually via PIN 19
  Bluefruit.autoConnLed(true);

  // Config the peripheral connection with maximum bandwidth 
  // more SRAM required by SoftDevice
  // Note: All config***() function must be called before begin()
  Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);

  Bluefruit.begin();
  Bluefruit.setTxPower(4);    // Check bluefruit.h for supported values
  //Bluefruit.setName(getMcuUniqueID()); // useful testing with multiple central connections
  Bluefruit.Periph.setConnectCallback(connect_callback);
  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);

  // To be consistent OTA DFU should be added first if it exists
  bledfu.begin();

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

  // Configure and Start BLE Uart Service
  bleuart.begin();

  // Start BLE Battery Service
  blebas.begin();
  blebas.write(100);

  // Set up and start advertising
  startAdv();

  Serial.println("Please use Adafruit's Bluefruit LE app to connect in UART mode");
  Serial.println("Once connected, enter character(s) that you wish to send");
}

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

  // Include bleuart 128-bit uuid
  Bluefruit.Advertising.addService(bleuart);

  // Secondary Scan Response packet (optional)
  // Since there is no room for 'Name' in Advertising packet
  Bluefruit.ScanResponse.addName();
  
  /* Start Advertising
   * - Enable auto advertising if disconnected
   * - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
   * - Timeout for fast mode is 30 seconds
   * - Start(timeout) with timeout = 0 will advertise forever (until connected)
   * 
   * For recommended advertising interval
   * https://developer.apple.com/library/content/qa/qa1931/_index.html   
   */
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 32);    // 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 loop()
{
  // Forward data from HW Serial to BLEUART
  while (BLEisConnected)
  {

  // Delay to wait for enough input, since we have a limited transmission buffer
  //delay(500);
  char buf[1500];
  
  String value ="";

  for (int i = 0; i<=100; i++)
  {

  time_start = millis();

    
// Read value from pin A0 of the nRF52832
  analogReadResolution(8); // 0-255
  uint8_t num = analogRead(A1); // unsigned char: 0-255 ( 1 byte)
  uint8_t num2 = analogRead(A5); // unsigned char: 0-255 ( 1 byte)

  value += String(time_start);
  value += ",";
  value += String(num);
  value += ",";
  value += String(num2);
  value += "\n";
  }

  // Convert String into an array of characters that will be stored in "buf"
  value.toCharArray(buf, 1500);
  
  // Send the buf (character array) via BLE
  bleuart.write( buf, strlen(buf) );
    
  }
}

// callback invoked when central connects
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));

  Serial.print("Connected to ");
  Serial.println(central_name);
  BLEisConnected = true;
}

/**
 * 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.println();
  Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX);
  BLEisConnected = false;
}

looks like you're collecting 100 samples each iteration of loop() and bleuart write.

if you want to sample at 1kHz, shouldn't you collect a single sample once every 1000 usec?

unsigned usecLst;
unsigned usec;

char buf [80];

void loop()
{
    usec = micros ();
    if (usec - usecLst >= 1000)  {
        usecLst = usec;

        sprintf (buf, "%ld,%d,%d", millis(), analogRead(A1), analogRead(A5));
        Serial.println (buf);
    }
}

void setup()
{
    Serial.begin (2000000);
}

Thank you for your help. I don't know why, but my guess is because of the transmission speed for BLE, the sampling rate was about 222 Hz. Could you please give me some more suggestions to improve the sampling rate of received data?

I think because of the limitation of BLE transmission speed, I am trying to save some amount of data packets and then, send packets to the receiver device. Is this way correct?

makes sense,but there are several questions:

  • what governs the sampling rate?
  • why are the values captured in a String?
  • what does toCharArray() do, what is in buf?
  • why is buf being sent, but the values captured in a String?
  • can the raw sample be sent as binary, instead of ascii string?
  • if samples are collected at the correct rate, why does the value of millis() need to be sent each time? (maybe every 100th message)
  • how long does bleuart.write take? does it interfere with sample collection?

have you tried using the code i posted, sending the buf string instead of printing it using

   bleuart.write( buf, strlen(buf) );

I don't know how exactly BLEUart is implemented there, but chances are it uses default BLE packet length which gets you about 22 bytes of payload per transaction (maybe a couple more or less, don't really remember). If it implements standard mode of one transaction per one BLE event (not a given, but it's possible) - then your maximum rate would be 22 bytes per BLE event, which in turn occurs at maximum once per 7.5 milliseconds (or possibly less often). That gets you about 2900 characters per second, with your way of packaging the data you need 12-15 characters per data point, thus your limit is about 200-230 readings per second.

Since that number matches what you get, I think chances are high that BLEUart works in that default BLE mode I've described.
You can vastly improve that by packaging one timestamp in 4-byte binary form plus, say, 8 consequent readings with 1 byte per value (you are reading ADC in 8-bit mode anyway, so 1 byte is enough). With that approach you'll need 20 bytes for 8 data pairs and with other parameters being the same, you'll get ~1150 Hz data rate

  • what governs the sampling rate?
    -> I think packet and buffer size may affect the sampling rate.
  • why are the values captured in a String?
    -> There is no special reason. So, when I tried your suggestion and added "bleuart.write(buf, strlen(buf)) right after sprintf. It works.
  • what does toCharArray() do, what is in buf?
    -> No reason, I deleted it when I tested your suggestion.
  • why is buf being sent, but the values captured in a String?
    -> I deleted it when I tested your suggestion.
  • can the raw sample be sent as binary, instead of ascii string?
    -> I used Bluefruit Connect (Plotter | Bluefruit LE Connect for iOS and Android | Adafruit Learning System) to check my device. And they said the data format must be ASCII format.
  • if samples are collected at the correct rate, why does the value of millis() need to be sent each time? (maybe every 100th message)
    -> To calculate the sampling rate and plot the graph. Because without millis, I can't get the signal period as a second.
  • how long does bleuart.write take? does it interfere with sample collection?
    -> bleuart.write function is for sending the data to the receiver.

Thank you for your help. I really appreciate it.

First of all, thank you for your help.
But, I'm not familiar with BLE's communication. I am a newbie to BLE device now. So, if you don't mind, could you please explain it in more detail or with code?

BLE as a protocol works in the following way: central sends a request to the peripheral every X milliseconds (where X can't be less than 7.5 by standard), peripheral responds to it with a single packet with useful length of ~22 bytes (if not negotiated otherwise - and I suspect it's not), central sends confirmation. That's the end of transaction. There is no way to send more bytes within that approach.

So the only way to get more data over the same time is to pack them better. Our team actually developed an open source wireless EMG device - you can see how I'm packing the data there: GitHub - ultimaterobotics/uMyo_v2 , check main.c, function prepare_data_packet32 for instance. It won't be clear what exactly is happening there (code is not commented yet), but you should get the general idea of packing a lot of data into a short packet

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.