0

I have tried getting my Arduino to communicate with another device, to no avail. The command A, when sent via serial to the device, triggers it to respond with 8 binary bytes with the first 4 representing a floating point number, and the final 4 representing a floating point number. The command only has to be sent once a second, and the aim is to store the 8 incoming bytes in an array, and convert them into their respective floats.

unsigned long previousMillis = 0;
const long interval = 1000;
char incomingByteA[8];

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Serial1.begin(9600);
  while(!Serial1);
}

void loop() {
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis >= interval){
    //1 second timer
    previousMillis = currentMillis;
    Serial1.write('O');
    //Send command 'A' to device *noting Serial1 not Serial
    for(int n = 0; Serial1.available() < 8; n++) {
    incomingByteA[n] = Serial1.read();
    //Add each consecutive byte to an array until all 8 bytes have been added
  }
  float x;
  float y;
  ((byte*)&x)[3] = incomingByteA[0];
  //Convert first four bytes into a float
  ((byte*)&x)[2] = incomingByteA[1];
  ((byte*)&x)[1] = incomingByteA[2];
  ((byte*)&x)[0] = incomingByteA[3];
  ((byte*)&y)[3] = incomingByteA[4];
  //Convert final four bytes into a float
  ((byte*)&y)[2] = incomingByteA[5];
  ((byte*)&y)[1] = incomingByteA[6];
  ((byte*)&y)[0] = incomingByteA[7];
  Serial.println(x, 4);
}
} 

However, when I do Serial.println(x);, it returns "nan". And if I try to do Serial.println(incomingByteA[2]);, it returns "?".

The following is the floating point format as per the device protocol:

31: sign of mantissa, 1=negative. 30-23: exponent, biased by 128. 22-0: mantissa, 24 bits, MSB hidden and always=1.

The mantissa is normalized, 24 bits with the MSB hidden. The MSB is always assumed to be 1, therefore only 23 bits are needed to store the mantissa. The exponent is biased by 128; i.e. a 128 value represents an exponent of zero. A 129 value is an exponent of 1. Example: if the exponent has a 131 value (+3), the number is 2^3 * (mantissa). The mantissa is always less than one, and greater than or equal to 1/2. The number zero is stored as four zero bytes.

I am confident that it is not a fault with the hardware and for what it's worth, I am using a Leonardo. I would like to get the code down pat before I stress about hardware issues. What is this code not doing properly, if at all?

Thanks in advance.

1

2 Answers 2

2

Two things I see wrong:

Serial1.write('O'); //Send command 'A' to device *noting Serial1 not Serial

No, you send O not A... ???

for(int n = 0; Serial1.available() < 8; n++)

While there are less than 8 bytes available in the buffer read one.

Erm... The buffer starts off empty - empty is less than 8. It remains empty (always less than 8) until a byte finally arrives. Then it's still less than 8. I can't really see this ever working, and your array will overflow instantly and clobber the entirety of the rest of your RAM. I guess it finally finished when the serial object itself gets clobbered and available() returns some garbage number. In the meantime your array is just full of nothingness.

You need to completely rewrite that whole section. The simplest way is to wait until there are 8 bytes in the buffer (which will take some time - serial is slooooow):

while (Serial1.available() < 8);

You now have 8 bytes available to read, so you can go ahead and read them.

A better way is to look to see if there is at least one byte available, then read it into the right place in your array - and keep count of how many bytes you have read:

int n = 0;
while (n < 8) {
    if (Serial1.available()) {
        incomingByteA[n++] = Serial1.read();
    }
}

Of course, it's better to add some form of timeout in there too - so if nothing arrives soon enough your program doesn't lock up - or if you miss a byte it doesn't get completely out of sync. Also purging your read buffer before sending the command can help to prevent sync issues:

// Purge any old data
while (Serial1.available()) {
    Serial1.read();
}

Serial1.write('A');

int n = 0;
uint32_t start = millis();
while (n < 8) {
    // 1 second timeout
    if (millis() - start > 1000) {
        break;
    }
    if (Serial1.available()) {
        incomingByteA[n++] = Serial1.read();
    }
}

if (n == 8) {
    // We got the data - do something with it
} else {
    // It timed out
}

Now when it comes to converting to a pair of floats there's better ways than your byte-wise manipulation. For instance, you could use a union:

union FloatPair {
    struct {
        float a;
        float b;
    };
    char bytes[8];
} __attribute__((packed));

FloatPair data;

Now you can use:

data.bytes[n]

to access the memory directly, and

data.a;
data.b;

to access the float values.

You can also do something similar with casting:

struct FloatPair {
    float a;
    float b;
};

FloatPair data;

char *bytes = (char *)&data;

bytes is now a byte-wise view of the memory used by a pair of floats.

You can even work the other way around:

char data[8] __attribute__((aligned(4)));

The aligned attribute will force the address of the array to be on a 4-byte boundary. Not important for a little 8-bitter, but vital if you ever port to a 32-bit MCU since a float always has to be on a 4-byte boundary, so a good habit to get into.

Now you can map floats into that memory space:

float *a = (float *)(&data[0]);
float *b = (float *)(&data[4]);

and access the floats as *a and *b. The * dereferences the pointers back into real numbers.

One thing to note: these methods all depend on the endianness of your data matching the endianness of your MCU - if they differ then you will have no choice but to do it all with manual byte-wise mapping.

7
  • Thanks for the quick reply! I typoed 'O' I did mean to write 'A', I will implement your technique for obtaining and storing the bytes. Could you please elaborate on "endianness"? As far as I know, isn't it just the order in which the most significant bits are arranged? If so, as per the protocol manual, then it's are ordered from left to right as most significant, to least significant. Cheers Oct 10, 2017 at 10:25
  • That sounds like big-endian. I think the AVR CPU is little-endian. If they don't match then you will have to manually swap the bytes around and make sure they get in the right places in your floats.
    – Majenko
    Oct 10, 2017 at 10:26
  • This thread may help you understand.
    – Majenko
    Oct 10, 2017 at 10:28
  • Thanks again for the reply. I tried comprehending that thread. If the CPU is little-endian, doesn't that mean I just work from least significant to most significant instead? Oct 10, 2017 at 10:45
  • Yep. That's right. 0->3, 1->2, 2->1 and 3->0 byte mapping
    – Majenko
    Oct 10, 2017 at 11:01
0

Thank you Majenko for your fast help!

I believe that the code is now alright. I will look at using the union method this weekend. For now, however, this is the final code.

 byte incomingByteA[8];


 void setup() {
 // put your setup code here, to run once:

 Serial.begin(9600);
 while(!Serial);
 Serial1.begin(9600);
 while(!Serial1);

 }

 void loop() {

 // Purge any old data
 while (Serial1.available()) {
 Serial1.read();
 }

 Serial1.write('A');

 int n = 0;
 uint32_t start = millis();
 while (n < 8) {
 // 1 second timeout
 if (millis() - start > 1000) {
    break;
 }
 if (Serial1.available()) {
    incomingByteA[n++] = Serial1.read();
 }
 }
 float x;
 float y;
 if (n == 8) {
 ((byte*)&x)[3] = incomingByteA[0];                             //Convert first four bytes into a float
 ((byte*)&x)[2] = incomingByteA[1];
 ((byte*)&x)[1] = incomingByteA[2];
 ((byte*)&x)[0] = incomingByteA[3];

 ((byte*)&y)[3] = incomingByteA[4];                             //Convert final four bytes into a float
 ((byte*)&y)[2] = incomingByteA[5];
 ((byte*)&y)[1] = incomingByteA[6];
 ((byte*)&y)[0] = incomingByteA[7];

 Serial.println(x);
 } else {
 // Some timeout response
 }

 }

What I find odd, is that Serial.println(x); returns "8.00" followed by several, random lines of "0.00". Yet Serial.println(x); returns just "0.00". I am simply putting this down to the device I am trying to communicate with though.

Harry

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.