Go Down

Topic: Sending floats over serial (Read 13732 times) previous topic - next topic

J335

I'm sure this has been asked numerous times so sorry about that, but I really didn't find anything that worked from google. Also, this is my first post, so hello!
I have two arduino unos: one is supposed to be keeping track of some weather meters I have hooked up to it, and the other one is supposed to "poll" the sensor values from it and send them to internet. You might ask why I want to have two arduinos? Well, the little features I wanted didn't quite fit in one.
So far I have successfully managed to create a really low-level communication protocol, which consists of: send a sensor number (0-9) and receive the value followed with "§". I have spent the last few hours looking for solution on how to properly turn the received string to float value.

The code I have on the sensor-arduino's loop:
Code: [Select]

if (Serial.available()) {
    int humidity;
    float temperature;
    int sensstatus = DHT.read22(A0); //Get the temperature and humidity from sensor
    temperature = DHT.temperature;
    humidity = DHT.humidity;
      int inByte = Serial.read();
      if (inByte == 167) { //If the received byte equals "§", send "§" back. Kinda like "ping".
        Serial.write(167);
        goto revert; //Goes back to loop
      }
    switch (inByte) {
    case '5':   
      Serial.print(rainmm);
      Serial.write('§');
      break;
    case '6':   
      Serial.print(avgwindspeed);
      Serial.write('§');
      break;
    case '7':   
      Serial.print(avgwindspeed2);
      Serial.write('§');
      break;
    case '2':   
      Serial.print(humidity);
      Serial.write('§');
      break;
    case '1':   
      Serial.print(temperature);
      Serial.write('§');
      break;
}
}


And the function receiving the values on the other arduino:
Code: [Select]

float query(byte x) { //<- byte x is the number of the sensor I want to poll
  while(sSerial.available()) sSerial.read(); //the serial is called sSerial because I use software serial.
  sSerial.print(x); //Send the sensor number.
  String input; //Create variable for the incoming data
  byte inbyte;
  while(inbyte != 167) { //Go to loop until the received char equals "§".
    if (sSerial.available()) {
      inbyte = sSerial.read();
      if (inbyte != 167) {
        char inchar = inbyte; //Turn the byte to char.
        input += inchar; //Add the char to the String.
    }
  }
  }
  char outchar[6]; //Create char array out of the string
  input.toCharArray(outchar, 6);
  Serial.println(input); //Just for debug
  return(atoi(outchar)); //Turn the value to float. Which doesn't work.
 
}


The "return(atoi(outchar));" doesn't return decimals at all. And that is the problem.
The result of using that function is:
Code: [Select]

2.54 //This one is caused by the "Serial.println(input)" thingy I used for debugging.
2.00 //And this is the value returned.

I have tried to split the variable to two, and added the splitted variables to the float. First the whole numbers and then the decimals. But that wasn't really very cost-effective and I also ran in to problems when the length of the variables changed.

So my question is, is there any magical way for converting I could use for the float over serial transmission?

pYro_65

Look up serialization. You can send the float as binary rather than doing text conversions. Strings are not nice to RAM.

Code: [Select]

float f_Temp = 123.456f;

//Serialise data.
char *c_Data = ( char* ) &f_Temp;
for( char c_Index = 0 ; c_Index < sizeof( float ) ; Serial.write( c_Data[ c_Index++ ] ) );


This is an example using serial. Just invert the process when rebuilding the float on the other end.
Forum Mod anyone?
https://arduino.land/Moduino/

J335

Thank you, pyro.

I will try that out as soon as possible.

wildbill

Quote
Well, the little features I wanted didn't quite fit in one


Post what you have - it's possible that someone will have suggestions to remedy this & then you'll have a spare arduino!

J335

I have all the basic weather station features + wind turbine simulator + sd card datalogging + pachube (cosm?) broadcast + 3g modem + terminal-like interface for changing settings and such. I had all that (excluding the terminal and wind turbine simulator) in one arduino, but I thought it would be easier to split the workload for two. Also, I thought it would be easier to have one of the arduinos at the top of the pole housing the meters and one closer the ground, which I could interface with.

majenko

Quote
The "return(atoi(outchar));" doesn't return decimals at all. And that is the problem.


That's because "atoi" means "ASCII to Integer".  Integer's don't have a decimal component.

Personally I would decide upon a required precision, say 3 decimal places, then multiply the value up before sending it - then divide it upon reception.

For example, to send the value 6.4827643:

Code: [Select]

// Sending end:
float value = 6.4827643;
int sendvalue;

sendvalue = value * 1000; // sendvalue now contains 6482

// At the receiving end:
int receivedvaue = atoi(valueFromSerial);
float value = sendvalue / 1000.0;  // value now contains 6.482


J335

#6
Jun 03, 2012, 03:49 pm Last Edit: Jun 03, 2012, 03:52 pm by Eceneics Reason: 1
I just found a quick solution for the problem!
I replaced "return(atoi(outchar))" with "return(atof(outchar))"!
It seems to output a float with two decimals.

The code looks like this now:
Code: [Select]

float query(byte x) {
 while(sSerial.available()) sSerial.read();
 sSerial.print(x);
 Serial.print("QUERY/");
 Serial.print(x);
 Serial.print(": ");
 String input;
 byte inbyte;
 while(inbyte != 167) {
   if (sSerial.available()) {
     inbyte = sSerial.read();
     if (inbyte != 167) {
       char inchar = inbyte;
       input += inchar;
   }
 }
 }
 char outchar[6];
 input.toCharArray(outchar, 6);
 Serial.println(input);
 return(atof(outchar));
}
 


Only had to change one letter... Come oon

majenko

I'd define myself a structure and send it using a binary based protocol - much lighter weight both from a transmission POV and a processing POV.

Code: [Select]

struct transfer {
  float temperature;
  float rainfall;
  float humidity;
  float wind1;
  float wind2;
};

// ...

struct transfer myData;

myData.temperature = 27.434;
myData.rainfall = 3.9;
myData.humidity = 87.3;
myData.wind1 = 4.98;
myData.wind2 = 5.22;

Serial.print("\001\002");
Serial.write((const uint8_t *)&myData,sizeof(struct transfer));
Serial.print("\003\004");


Then at the receiving end you look for character 1 (Start of Header) followed by character 2 (Start of Text), then read sizeof(struct transfer) bytes into a struct transfer variable's address, and confirm it's right by expecting a character 3 (End of Text) followed by a character 4 (End of Transmission).

Those characters are standard characters reserved for just that purpose.  You could transfer some packet identifying data (like a packet number, etc) between the SOH and SOT characters if you like...

nickgammon

Some examples of sending numbers and stuff over serial here:

http://www.gammon.com.au/serial
Please post technical questions on the forum, not by personal message. Thanks!

More info: http://www.gammon.com.au/electronics

J335

Thank you for your answers!
I will look at the binary stuff and report later if I have succeeded.

J335

#10
Jun 04, 2012, 02:26 pm Last Edit: Jun 04, 2012, 02:33 pm by Ecneics Reason: 1

then read sizeof(struct transfer) bytes into a struct transfer variable's address

What do you exactly mean by that? I understand it like: "wait for this many (sizeof(struct transfer)) bytes and insert them in the struct", am I correct? I googled around but didn't find a way to input the data in the struct.
So far I have managed to receive the first two characters like this:
Code: [Select]

#include <SoftwareSerial.h>

SoftwareSerial sSerial(7, 8);

void setup() {
 Serial.begin(9600);
 sSerial.begin(9600);
}

void loop() {
 if(sSerial.available()) {
   if (sSerial.read() == '\001') {
     Serial.println("FIRST");
     while(!sSerial.available());
     if (sSerial.read() == '\002') {
       Serial.println("SECOND");
       while(!sSerial.available());
       byte inByte;
       while(inByte != '\003') {
         inByte = sSerial.read();
         //And I thought the magic should happen here.
       }
       Serial.println("DONE");
     }
   }
 }
}

I basically copy-pasted the code you provided to the other arduino, and made it send the values every 10 seconds.

Thanks in advance.

majenko

You treat the structure variable as if it were a string of characters.  The & operator returns the address of the variable in memory.

You can use "(char *)&myStructVariable" and it will be the same as using any char * variable.

The sizeof() returns the number of bytes the variable is made up from, so no matter how you change the structure it will always expect the right number of bytes.

You could use something along the lines of:

Code: [Select]


char *ptr;
struct myStruct myStructVariable;
int count;
int size;

size = sizeof(struct myStruct);
ptr = (char *)&myStructVariable;

for(count=0; count<size; count++)
{
  *(ptr+count) = Serial.read();
}


That will read "size" bytes from the serial and store them in the memory that is occupied by the "myStructVariable" variable.

Go Up