How to send and recieve multiple buffers with the SerialTransfer library

Hi all, I was wondering how you can send and receive two buffers (at different times) with the SerialTransfer library - GitHub - PowerBroker2/SerialTransfer: Arduino library to transfer dynamic, packetized data fast and reliably via Serial, I2C, or SPI (with UART)

The buffers that are transmitted and received are anonymous, but if you transfer the data as a struct then one of the data items in the struct could be used to identify the data that it contained and thus allow you to decode it appropriately

Ah I see, but the reason I want to send multiple buffers at different times is to send packets faster

What type of serial link are you using and how fast is it running ?

I'm transmitting and receiving packets over serial using 2 LoRa modules hooked up to a Teensy 4.0 (transmitter) and Teensy 3.2 (receiver), with a baud rate of 9600

Can you please give an example of the type of data that you are sending and the frequency with which it is sent

Using a struct to hold the data and adding a single byte as a buffer type identifier will surely not add much to the time taken to transmit the data

Right, sorry about not providing code beforehand. I'm using this library with the LoRa modules. Mainly I am sending float-type data.

This is the transmitter code

#include "SerialTransfer.h"
#include "EBYTE.h"
#include "BMI088.h"

/* accel object */
Bmi088Accel accel(Wire,0x18);
/* gyro object */
Bmi088Gyro gyro(Wire,0x68);

#define ESerial Serial1
#define PIN_M0 8
#define PIN_M1 3
#define PIN_AX 5
EBYTE Transceiver(&ESerial, PIN_M0, PIN_M1, PIN_AX);

SerialTransfer myTransfer;

struct __attribute__((packed)) STRUCT {
  char z;
  float accx;
  float accy;
  float accz;
} testStruct;

char arr[] = "hello";


void setup()
{
  Serial.begin(9600);
  ESerial.begin(9600);
  Transceiver.init();
  
  myTransfer.begin(ESerial);
  
  accel.begin();
  gyro.begin();

  testStruct.z = '$';
  
}


uint32_t prev_ms;
void loop()
{
  if (millis() - prev_ms >= 250){  
    prev_ms = millis();
    accel.readSensor();
    gyro.readSensor();

    testStruct.accx = accel.getAccelX_mss();
    testStruct.accy = accel.getAccelY_mss();
    testStruct.accz = accel.getAccelZ_mss();
    
    // use this variable to keep track of how many
    // bytes we're stuffing in the transmit buffer
    uint16_t sendSize = 0;

    ///////////////////////////////////////// Stuff buffer with struct
    sendSize = myTransfer.txObj(testStruct, sendSize);

    ///////////////////////////////////////// Stuff buffer with array
    sendSize = myTransfer.txObj(arr, sendSize);

    ///////////////////////////////////////// Send buffer
    myTransfer.sendData(sendSize);
  }
}

This is the receiver-side code

#include "SerialTransfer.h"
#include "EBYTE.h"

#define ESerial Serial1
#define PIN_M0 5
#define PIN_M1 6
#define PIN_AX 7

EBYTE Transceiver(&ESerial, PIN_M0, PIN_M1, PIN_AX);
SerialTransfer myTransfer;

struct __attribute__((packed)) STRUCT {
  char z;
  float accx;
  float accy;
  float accz;
} testStruct;

char arr[6];


void setup()
{
  Serial.begin(9600);
  ESerial.begin(9600);
  Transceiver.init();

  myTransfer.begin(ESerial);
}


void loop()
{
  if(myTransfer.available())
  {
    // use this variable to keep track of how many
    // bytes we've processed from the receive buffer
    uint16_t recSize = 0;

    recSize = myTransfer.rxObj(testStruct, recSize);
    Serial.print("Accel X: "); Serial.println(testStruct.accx);
    Serial.print("Accel Y: "); Serial.println(testStruct.accy);
    Serial.print("Accel Z: "); Serial.println(testStruct.accz);
    Serial.println();

    recSize = myTransfer.rxObj(arr, recSize);
    Serial.print("Text: ");
    Serial.println(arr);
    Serial.println();
  }
}

The project I am working on is an autonomous UAV, and I want to transmit various pieces of data (like the altitude, acceleration, velocity, GPS data, etc), the acceleration data is a high priority so I am sending it first (I can't add any more data or it will compromise how fast I can send the acceleration data).

Basically, I want to send other pieces of data (floats and some ints) like this, but I can't add them to the "testStruct" struct or I will not be able to send all the data as quickly as I want. That's really my question "how do I send all this data as fast as possible", originally my plan was to put different data variables in different structs and send them a couple of milliseconds apart so packets are still sent quickly but if you have a better solution please let me know.

Thanks

If you send different data in different messages then the first challenge is to ensure that you know which type of data has been received. You cannot rely on the order in which the messages are received and if you use your idea of sending some data more frequently than others then you must have a way of knowing what is being received

My suggestion was to put an extra message type byte in the struct. As the acceleration struct already contains 3 floats of 4 bytes each plus a byte for the char then adding a byte is hardly going to matter. What is in the char variable. Is there a spare bit or 2 that could be used as a message type indicator so that the message length does not need to be increased

Another thought. Is the length of each message type the same or could the message type be determined upon receipt by looking at its length ?

Just how often do you need to send the acceleration data? You way you are asking makes it sound like you are sending the data almost to the point of saturating the 9600 baud serial connection, but the sketch you posted only sends the data four times per second.

Yes you are correct if a full set of messaging is sent as a binary blob: bigger == slower.

My take on this, an initial thought and not really studied in experiments:

  • sending a struct is not good as it adds overhead and prevents message prioritization

Keep your messaging at a primitive level and queue in multiple buffers so that you can send the messaging outbound with the lowest overhead.

This essentially allows acceleration data to always be sent from queue if it has changed since last transmission. Thus you could receive or send acceleration multiple times before updates for other messages are handled.

Instead of sending repeating streams of identical messages (big struct), I believe a QoS approach would serve you far better especially at low BAUD rates.

Reference:
https://www.cse.wustl.edu/~jain/cse574-06/ftp/cellular_qos/index.html#:~:text=Some traffic classes should be given higher priority,is still considered as the most important service.

Why would that be ?

My thinking is the more variables added to the struct, increases the size of the struct. Now, serializing over serial (without compression) essentially means a larger data over the limited 9600 BAUD link. Thus, more bytes to move at a fixed rate (seems to me) will require a longer time period since the struct must be reassembled at the receiver only after everything is received.

I suspect bytes over serial could be streamed as bits directly into the struct but this gets outside the scope of this discussion and will require some low-level code. Even then, a single cpu would not be capable of acting on a partial struct unless the receiver was DMA for write and the internal program logic was able to share the struct in read-only mode. Hypothetically speaking.

Adding more variables to the struct of course increases its size, but potentially by only a single byte. If the message type could be snuck into a couple of spare bits in the char variable then there would be no size overhead. These could simply be added to a variable using bitSet() or direct bit manipulation

In practice, as is often the case, we don't know exactly what the requirement is. As has been pointed out, the sketch posted by @constation is only transmitting at 4 times per second

1 Like

Sounds like you are looking for the "packetID" byte in the send and receive functions of the library. Here is an example below which sends 5 different packets of data. Each of the 5 packets has a separate packetID byte, which the receiver uses to know which packet is being sent.

sender:

#include<SoftwareSerial.h>
SoftwareSerial mySerial(6, 7);

#include "SerialTransfer.h"

SerialTransfer myTransfer;

byte packetID = 0;  //packetID byte used to select;
//1. which variable(s) Master will send
//2. which variable(s) Slave will receive

//VARIABLES TO SEND


float singleVariableTX = 3.14;


struct structOne_TX {
  int variable_1_struct_1;
  unsigned long variable_2_struct_1;
  char variable_3_struct_1;
}
structOneTX;


struct structTwo_TX {
  boolean variable_1_struct_2;
  byte variable_2_struct_2;
  int variable_3_struct_2;
  long variable_4_struct_2;
}
structTwoTX;


char charArrayTX[21];


unsigned long previousMillis = 0;
unsigned long interval = 1000;

void setup()
{
  Serial.begin(115200);
  mySerial.begin(9600);
  myTransfer.begin(mySerial);

  structOneTX = {100, 2000, 'a'};
  structTwoTX = {1, 20, 300, 4000};
  strcpy(charArrayTX, "Sent from Arduino A");

}

void loop()
{
  //LOOPING THROUGH 5 PACKETS TO SEND...

  if (millis() - previousMillis >= interval) {
    previousMillis = millis(); //will keep looping until response
    packetID++;
    sending();

    if (packetID == 5) { //4 options to send
      packetID = 0;
    }
  }
}

void sending() {
  // use this variable to keep track of how many
  // bytes we're stuffing in the transmit buffer
  uint16_t sendSize = 0;

  switch (packetID) {//selector byte chooses the variable(s) to send.
    case 1:
      sendSize = myTransfer.txObj(singleVariableTX, sendSize);
      break;
    case 2:
      sendSize = myTransfer.txObj(structOneTX, sendSize);
      break;
    case 3:
      sendSize = myTransfer.txObj(structTwoTX, sendSize);
      break;
    case 4:
      sendSize = myTransfer.txObj(charArrayTX, sendSize);
      break;
    case 5:
      sendSize = myTransfer.txObj(structOneTX, sendSize);
      sendSize = myTransfer.txObj(structTwoTX, sendSize);
      break;

  }
  myTransfer.sendData(sendSize, packetID); //packetID is read by the receiver, to indicate which variables are coming through

  Serial.print("Sent Packet #");
  Serial.println(packetID);
}

Receiver:

#include<SoftwareSerial.h>
SoftwareSerial mySerial(6, 7);

#include "SerialTransfer.h"

SerialTransfer myTransfer;

//VARIABLES TO RECEIVE

float singleVariableRX;


struct structOne_RX {
  int variable_1_struct_1;
  unsigned long variable_2_struct_1;
  char variable_3_struct_1;
}
structOneRX;


struct structTwo_RX {
  boolean variable_1_struct_2;
  byte variable_2_struct_2;
  int variable_3_struct_2;
  long variable_4_struct_2;
}
structTwoRX;

char charArrayRX[21];

byte packetID;
bool flag1 = false;

void setup()
{
  Serial.begin(115200);
  mySerial.begin(9600);
  myTransfer.begin(mySerial);
}

void loop()
{

  readAndParseData();
  showNewData();

}

void readAndParseData() {

  if (myTransfer.available())
  {
    // use this variable to keep track of how many
    // bytes we've processed from the receive buffer
    uint16_t recSize = 0;

    packetID = myTransfer.currentPacketID();

    //searches for the packet ID send by sender.
    switch (packetID) {

      case 1:
        recSize = myTransfer.rxObj(singleVariableRX, recSize);
        break;

      case 2:
        recSize = myTransfer.rxObj(structOneRX, recSize);
        break;

      case 3:
        recSize = myTransfer.rxObj(structTwoRX, recSize);
        break;

      case 4:
        recSize = myTransfer.rxObj(charArrayRX, recSize);
        break;

      case 5:
        recSize = myTransfer.rxObj(structOneRX, recSize);
        recSize = myTransfer.rxObj(structTwoRX, recSize);
        break;
    }

    flag1 = true;

  }
}

void showNewData() {
  if (flag1 == true) {

    Serial.println("//////////////////////////////////");
    Serial.println(" ");
    Serial.print("Packet #");
    Serial.println(myTransfer.currentPacketID());
    Serial.println(" ");

    switch (packetID) {
      case 1:
        Serial.print("Single Float = ");
        Serial.println(singleVariableRX);
        Serial.println("");
        break;

      case 2:
        Serial.println("Struct 1...");
        Serial.print("Int = ");
        Serial.println(structOneRX.variable_1_struct_1);
        Serial.print("Unsigned Long = ");
        Serial.println(structOneRX.variable_2_struct_1);
        Serial.print("Character = ");
        Serial.println(structOneRX.variable_3_struct_1);
        Serial.println("");
        break;

      case 3:
        Serial.println("Struct 2...");
        Serial.print("Boolean = ");
        Serial.println(structTwoRX.variable_1_struct_2);
        Serial.print("Byte = ");
        Serial.println(structTwoRX.variable_2_struct_2);
        Serial.print("int = ");
        Serial.println(structTwoRX.variable_3_struct_2);
        Serial.print("Long = ");
        Serial.println(structTwoRX.variable_4_struct_2);
        Serial.println("");
        break;

      case 4:
        Serial.print("Char array = ");
        Serial.println(charArrayRX);
        Serial.println("");
        break;

      case 5:
        Serial.println(" ");
        Serial.println("Struct 1...");
        Serial.print("Int = ");
        Serial.println(structOneRX.variable_1_struct_1);
        Serial.print("Unsigned Long = ");
        Serial.println(structOneRX.variable_2_struct_1);
        Serial.print("Character = ");
        Serial.println(structOneRX.variable_3_struct_1);
        Serial.println("");

        Serial.println("Struct 2...");
        Serial.print("Boolean = ");
        Serial.println(structTwoRX.variable_1_struct_2);
        Serial.print("Byte = ");
        Serial.println(structTwoRX.variable_2_struct_2);
        Serial.print("int = ");
        Serial.println(structTwoRX.variable_3_struct_2);
        Serial.print("Long = ");
        Serial.println(structTwoRX.variable_4_struct_2);
        Serial.println("");
        break;
    }

    Serial.println("//////////////////////////////////");

    flag1 = false;
  }

}

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