"Best" way to send sensor data via BLE?

Tacca · 10 · 16109

Tacca

  • Jr. Member
  • **
    • Posts: 8
    • View Profile
Hi,

I am facing a problem with my project.
I am trying to send 9axis sensor data via BLE, I managed to do that via UART example by truncating a concatenated message of the data, into chunks of 20 bytes each.
The problem is, the 9 axis data plus fusion becomes 3~4 messages (60 to 80 bytes), and I plan to use 2 9axis sensors (wirelings), which would double the length of the message I need to send. This decresce my throughput by a lot.
Right now, using only one sensor, I can't go below 120ms or I start loosing some of the data.

Is there a best way? Any thoughts are appreciated.
Thanks
« Last Edit: May 30, 2020, 05:37:55 PM by Tacca »


lennevia

  • Administrator
  • Hero Member
  • *****
    • Posts: 437
    • View Profile
Hello Tacca,

Can you include the code you currently have? I can try optimizing it to see if it can be made more efficient.

Do you need the accuracy you have mentioned? What data is most important to you, or do you need all of it?

Let me know!

Thanks,

Réna


Tacca

  • Jr. Member
  • **
    • Posts: 8
    • View Profile
Hello Réna, thanks for the help.
The code I was working with is available at:
https://github.com/brunotacca/knee_kinematic_sensor/blob/master/SensorCoreApp/SensorCoreApp.ino

I kinda mixed both the examples found at tinycircuits learn section.
This: https://github.com/TinyCircuits/TinyCircuits-TinyShield-Sensor-ASD2511/tree/master/examples/9-Axis_TinyShield_example
And this: https://github.com/TinyCircuits/TinyCircuits-TinyShield-BLE-ASD2116/tree/master/examples/STBLE/examples/UARTPassThrough

It's still a bit of a mess with a lot of commented code since I am still at the beginning of my research.

The idea I had was to have a concatenated String with the data I need, as you can see in this part:
Code: [Select]
      String msg = "";
      msg.concat("C");
      msg.concat(sampleCount);
      msg.concat("S0");
      msg.concat(accelData.x());
      msg.concat(accelData.y());
      msg.concat(accelData.z());
      msg.concat(gyroData.x());
      msg.concat(gyroData.y());
      msg.concat(gyroData.z());
      msg.concat(compassData.x());
      msg.concat(compassData.y());
      msg.concat(compassData.z());
      msg.concat("F");
      msg.concat(fusionData.x());
      msg.concat(fusionData.y());
      msg.concat(fusionData.z());
      msg.concat("#");

      sendMessage(msg);

And then, send it all truncating it in chunks of 20 bytes.
Code: [Select]
#define buffer_size 19

void sendMessage(String msg)
{
  uint8_t sendBuffer[msg.length() + 1] = {};
  msg.getBytes(sendBuffer, msg.length() + 1);

  uint8_t sentLength = 0;
  int sizeSendBuffer = sizeof(sendBuffer);

  while (sentLength < sizeSendBuffer)
  {
    int arraySize = buffer_size;
    if ((sizeSendBuffer - sentLength) < buffer_size)
    {
      arraySize = (sizeSendBuffer - sentLength);
    }

    uint8_t sendBufferTruncated[arraySize] = {};
    uint8_t sendLength = 0;

    while (sendLength < arraySize)
    {
      if (sendBuffer[sentLength] != 0)
      {
        sendBufferTruncated[sendLength] = sendBuffer[sentLength];
      }
      sendLength++;
      sentLength++;
    }

    lib_aci_send_data(PIPE_UART_OVER_BTLE_UART_TX_TX, (uint8_t *)sendBufferTruncated, sendLength);
    SerialMonitorInterface.print("SENT> ");
    SerialMonitorInterface.println((char *)sendBufferTruncated);
  }

}

About the accuracy and the data I need... I do need the more messages I can get per second, and at least, all the sensor data (except for the fusion data maybe). I will be tracking knee movements each steps, so average runners cadence falls at 160ish steps per minute, I will be tracking only one knee so, 80ish steps per minute, which means 1.3 steps per second, a step each 650ms.

With the rate and model I have now, I need like around 60 to 80 bytes of data per full message, which means 4 truncated messages and I had a kind of a stable measurement at 120 ~ `130ms per message (4 messages, so it would send a message each 30ms). A full message per 120ms would mean 5,4 messages per step, this is not good enough for what I had in mind.

This considering I am using only 1 sensor. Since I plan to use 2 wirelings, the data length would double and I would get only 2,7 messages per step.

During this meantime, I found some possible workarounds:
1 - Maybe tinycircuits have a BLE 5.0 available? (the Data Length Extension feature would kinda solve my problem)
2 - Implement all my research in Cpp (which is not my strongest haha) and data fusion in the device itself, sending only my own fusion data.

I appreciate any thoughts you may have.
Thank you in advance.
« Last Edit: June 17, 2020, 08:24:54 PM by Tacca »


lennevia

  • Administrator
  • Hero Member
  • *****
    • Posts: 437
    • View Profile
Hello Tacca,

Have you considered using a Micro SD Card TinyShield? You would be able to save all of the necessary data with timestamps to be able to process it later.

I am curious about the range of Bluetooth on a runner.

TinyCircuits is in the process of designing a new processor board that includes a newer Nordic BLE module that may support the Data Length Extension you have mentioned.

Hope that helps give you some ideas on what you can do. In the future, I may have more time to look into the code you are currently developing to give better insight.

Thanks,
Réna


Tacca

  • Jr. Member
  • **
    • Posts: 8
    • View Profile
Hello Réna, thanks for the reply.

Now that you mention it, I had discarded the idea of a SD Card because I didn't want to have the runner plugging-unplugging the sd card for processing.
BUT... giving it another thought, it might be possible to just store (at a fast rate) and sync the data (at a safe rate) using SD card... this would imply in a little delay for the data showing on the device, but, the data amount would be good enough, solid and accurate.

I will probably order it since it might solve the problem.

Thank you again.


Tacca

  • Jr. Member
  • **
    • Posts: 8
    • View Profile
Hello Tacca,

Have you considered using a Micro SD Card TinyShield? You would be able to save all of the necessary data with timestamps to be able to process it later.

I am curious about the range of Bluetooth on a runner.

TinyCircuits is in the process of designing a new processor board that includes a newer Nordic BLE module that may support the Data Length Extension you have mentioned.

Hope that helps give you some ideas on what you can do. In the future, I may have more time to look into the code you are currently developing to give better insight.

Thanks,
Réna

Hello again Réna,

I bought a MicroSD board a while ago and was started testing a couple of days ago, I was having some difficulty with my stack, just now I gave it a little search and found out that the ST BLE Board board might be incompatible with the MicroSD card? is that it?

Is there a way that I can use both?

Thanks


lennevia

  • Administrator
  • Hero Member
  • *****
    • Posts: 437
    • View Profile
Tacca,

The Micro SD card and Bluetooth Shields use the same Chip Select (CS) pin for SPI communication, making the boards incompatible with each other simultaneously. You can see the compatibility chart for our TinyShields here: https://learn.tinycircuits.com/TinyDuino_Overview/#tinyshield-compatibility

I apologize for my previous advice that might have sounded like a recommendation to use the Shields together. I meant that it might be best to use an SD Card instead of the Bluetooth TinyShield since it sounded like accuracy and speed were needed and that Bluetooth could cause a lot of practical problems like the question of distance between the Bluetooth receiver/smartphone and hardware stack while running.

I hope that clears up my former message.

Best,
Réna




Tacca

  • Jr. Member
  • **
    • Posts: 8
    • View Profile
Tacca,

The Micro SD card and Bluetooth Shields use the same Chip Select (CS) pin for SPI communication, making the boards incompatible with each other simultaneously. You can see the compatibility chart for our TinyShields here: https://learn.tinycircuits.com/TinyDuino_Overview/#tinyshield-compatibility

I apologize for my previous advice that might have sounded like a recommendation to use the Shields together. I meant that it might be best to use an SD Card instead of the Bluetooth TinyShield since it sounded like accuracy and speed were needed and that Bluetooth could cause a lot of practical problems like the question of distance between the Bluetooth receiver/smartphone and hardware stack while running.

I hope that clears up my former message.

Best,
Réna

That's fine, I decided to go with a low rate because it's just a hardware limitation that wouldn't invalidate my work, and I can always upgrade the module in the future.
However, I am trying to get the best of the throughput available, BlueNRG-MS limits its packages at 20 bytes and as far as my research went there is no way to increase this.
My message which 2 9DOF sensors data is averaging around 90 bytes.

An example of the msg: 999S0-1.23-4.567.89-1.23-4.567.89-1.23-4.567.89S1-1.23-4.567.89-1.23-4.567.89-1.23-4.567.89#

Do you have any recommendations on how to maximize the throughput of BlueNRG-MS? Have you or your coworkers done something similar already?

I am looking through compression/decompression but it might not be worth the time spent.

Thank you.


Jason

  • Administrator
  • Hero Member
  • *****
    • Posts: 107
  • TinyCircuits Employee
    • View Profile
Hi,

A more efficient way to do this is without using the Arduino String library. Use standard C arrays/buffers instead.

I've made an example project for you, it's attached to this comment as a .zip. The example packs packet IDs, Wireling port number, and the four data vectors you're interested in (accelerometer, gyroscope, compass, & fusion data). Overall, the data is sent in three fragmented packets totaling 52 bytes for each Wireling (so 104 bytes). 52 bytes probably isn't needed, I'll talk about that later, for now, let's talk about the code.

I mentioned that the packets are fragmented. First, packet & Wireling IDs are each 1 byte and can be retrieved from any one packet after being sent; however, the 9-axis library (RTIMU) uses floats to hold sampled data. Each float is 4 bytes. We're sending accelerometer XYZ, gyroscope XYZ, compass XYZ, and fusion XYZ, that's (4*3)*4 = 48 bytes. There's also the one Wireling port ID to send (1 byte), and 3 packet ID bytes (3 bytes). In total there are 48 + 4 = 52 bytes to send in this configuration. Now, as you've found everything needs to be sent in packets of 20 bytes. Because each packet starts with at least an extra byte, and with the way everything lines up, some floats will be split across two packets instead of being contained to one. See the below diagram to see each packet's layout.

PACKET 0:
| ID0 | PORT | AX[0] | AX[1] | AX[2] | AX[3] | AY[0] | AY[1] | AY[2] | AY[3] | AZ[0] | AZ[1] | AZ[2] | AZ[3] | GX[0] | GX[1] |GX[2] | GX[2] | GY[0] | GY[1] |

PACKET 1:
| ID1 | GY[2] | GY[3] | GZ[0] | GZ[1] | GZ[2] | GZ[3] | CX[0] | CX[1] | CX[2] | CX[3] | CY[0] | CY[1] | CY[2] | CY[3] | CZ[0] | CZ[1] | CZ[2] | CZ[3] | FX[0] |

PACKET 2:
| ID2 | FX[1] | FX[2] | FX[3] | FY[0] | FY[1] | FY[2] | FY[3] | FZ[0] | FZ[1] | FZ[2] | FZ[3] |

Looking above, GY[0] + GY[1] are in packet 0 while the other half of the float is in packet 1, keep that in mind. There are some tricks that could be applied to get rid of the fragmentation, but we'll do it the hard way in case more data needs to be added in the future. That's fine though, the attached example takes care of everything when the above data is sent. In any case, I thought it was a good idea to show you how the packets are laid out in case you need to add more data in the future.

I don't know what the receiving side of your project looks like, but keep in mind when rebuilding the floats from each of their 4 bytes, that you need to know how your receiving hardware represents floats as bytes. Google "Endianness" and look at your hardware's datasheet, for both sending and receiving.

For the attached example, my hardware is a TinyScreen+, Wireling adapter shield, BLE Tinyshield, and 2 x 9DOF Wirelings. The Wirelings are connected to ports 0 and 1 on the adapter shield. You're going to be most interested in functions 'SendIMUData()' at line 332, and function 'SpecialUnpackPackets()' at line 237.  Make sure to read the comments in each function. Although the BLE shield is set up, no Bluetooth data is sent in this example by default, but there are some lines in the 'SendIMUData' function you can uncomment to send the packets.

Also, I did not benchmark my example in any way, so see if it works for you in terms of speed. Remove/comment out functions that print to serial when using it in your project, those are there to help you see what's happening in the example. Serial prints slow the program down.

There are more optimizations that can be done. 'malloc' is called for every packet, but since the packets are the same size every time, the memory allocated should only be allocated once and reused every time (a fixed size global buffer). We'll also run into issues with memory fragmentation.

If this still doesn't work, the 9-axis IMU LSM9DS1 on our Wirelings advertises 16-bit output (2 bytes). The RTIMU library must cast this to a float (4 bytes) at some point. If you only needed the accelerometer, gyroscope, and compass data then you could send (2*3)*3 = 18 bytes of data in just one packet! This may mean figuring out how to read the registers of the LSM9DS1 directly or modifying the RTIMU library to be fixed-point and 16-bit (you probably won't be able to do fusion calculations unless that's modified as well to be fixed-point). Or maybe the resulting data from RTIMU can be cast back to 16-bit, we would need to ensure the cast data looks correct. The RTIMU library is supposed to work with many different kinds of IMUs, so floats were probably used to make the library flexible.

There is a lot going on, let me know if you have any problems with the example.

NOTE: See my next reply, the version attached there only allocates memory once for the packets.
« Last Edit: July 14, 2021, 12:28:29 PM by Jason »


Jason

  • Administrator
  • Hero Member
  • *****
    • Posts: 107
  • TinyCircuits Employee
    • View Profile
Attached is V2 of the example. I would use this one instead of the above since it only allocates memory for packets once instead of every time.


 

SMF spam blocked by CleanTalk