Sending Analog Sensor Data Between XBees

Hello, Masters! I was wondering if you could help me out on my new project :sweat_smile:

I want to display the rms current sent by another controller wirelessly.

Here's the parts I'm using:
2 - Arduino UNO
2 - Xbee Shield
2 - XBee S2B ZigBee PRO 10mW Wire antenna

I configured the first arduino to be the Zigbee Coordinator API and the other the Zigbee Router AT using XCTU New Gen Software. I also enabled the Router's Channel Verification.

I used dragonrobo7's code [I found [here](Monitoring AC Current with Arduino and ACS712 - Sensors - Arduino Forum)] for the 5A-ACS712 current sensor. It works perfectly, but when I tried separating the calculation(Router Xbee) and the display(Coordinator Xbee) it becomes unstable. It even gives zero values which doesn't happen when I use a single controller.

Coordinator:

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup()
{
  Serial.begin(9600);
  lcd.begin(16, 2);  
}

void loop(){ 
  while(Serial.available() == 0); 
  float current = Serial.read();
  Serial.println(current);
  
  lcd.clear();
  lcd.setCursor(0, 1);
  lcd.print("A0: "); lcd.print(current); lcd.print(" mA");
  delay(150);
  
  //Serial.flush();
}

Router:

int currentPin = 14;

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

void loop(){
  float val = Serial.println(readCurrent(currentPin), 3);
  delay(50);
}

int determineVQ(int pin) {
  long VQ = 0;  //ave VQ
  
  //read 1000 samples to stabilize value
  for (int i = 0; i < 5000; i++) {
    VQ += analogRead(pin);
    delay(1);  //depends on sampling (on filter capacitor), can be 1/80000 (80kHz) max.
  } 
  VQ /= 5000;
  return int(VQ);
}

float readCurrent(int pin)
{
  unsigned long currentAcc = 0;
  unsigned int count = 0;
  unsigned long prevMicros = micros() - sampleInterval ;
  while (count < numSamples)
  {
    if (micros() - prevMicros >= sampleInterval)
    {
      long adc_raw = analogRead(currentPin) - adc_zero;
      currentAcc += (unsigned long)(adc_raw * adc_raw);
      ++count;
      prevMicros += sampleInterval;
    }
  }
  float rms = sqrt((float)currentAcc/(float)numSamples) * (50 / 1024.0);
  return rms;
}
  float val = Serial.println(readCurrent(currentPin), 3);

Serial.println() returns the number of characters written to the serial port. Do you really expect that the function wrote 3.14159 characters to the serial port? Is there a snowball's chance in hell that the number is other than an integer value?

Which XBee shields are you using? Getting the XBee off the hardware serial port would be #1 priority, so I could see what the problem was. Is the problem with reading the sensor? Is it with the calculations? Is it with the transmission?

How far apart are the XBees?

  float current = Serial.read();
  Serial.println(current);

You sent the data as a string. How can you expect to read the whole value by reading one character? Have you bothered to look at what Serial.read() returns? It is NOT a float!

Why are you NOT reading the data the way it was sent?

Edit
You can't just write a float to serial and then read the float out in the receiver
The Serial.read will just be reading 1 character from the serial port, regardless of whether there is any incomming data or not.

Take a look at Arduino Easy Transfer

I'm using this Xbee Shield --> http://www.cutedigi.com/images/09063-03-L-Xbee_shield-1.jpg

Sorry, I don't really know what I'm doing.
I watched 'Xbee Basics' tutorial lesson 1-4 by tunnelsup. I used the code from lesson 4: Reading analog data from remote xbee. Everything seems to be working properly but then I remembered I need to read the sensor value from an xbee with a micro controller. I think I panicked realizing my mistake and thinking about the rms current so I watched another video that uses 2 arduino and 2 xbee and I ended up doing 'that' code.

float temp;

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

void loop(){
  if(Serial.available() >= 21){
    if(Serial.read() == 0x7E){
      for(int i = 1; i < 19; i++){
        byte discardByte = Serial.read();
      }
      int analogMSB = Serial.read();
      int analogLSB = Serial.read();
      int analogReading = analogLSB + (analogMSB * 256);
      temp = analogReading / 1023.0 * 1.23;
      temp = temp - 0.5;
      temp = temp / 0.01;
      temp = temp * 9/5 +32;
      Serial.print(temp);
      Serial.println(" degrees F");
    }
  }
}

I just need to know the sender and receiver code if I'll be using 2 arduino's with xbee.
If you could help me, I would be so happy.

Maybe like this?

Sender:

int sensorPin = 14;
float val;

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

void loop(){
  val = analogRead(sensorPin);
  Serial.print(val); //How to send this as byte?
}

Receiver:

float val;

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

void loop(){
  if(Serial.available() >= 21){
    if(Serial.read() == 0x7E){
      for(int i = 1; i < 19; i++){
        byte discardByte = Serial.read();
      }
      int analogMSB = Serial.read();
      int analogLSB = Serial.read();
      int analogReading = analogLSB + (analogMSB * 256);
      //Is val the value read by the sensor or do I need to divide it by 1023 like the original code?
      val = analogReading; 
      Serial.print(val);
    }
  }
}

I found a code for tranmitter/receiver arduino-xbee here --> Redirecting... but it send an integer instead of float. Could you help me convert it and shorten the code?

I used this for the
Transmitter:

/*

  Adapted from the "XBee Analog Duplex Transmission" example in "Making
  Things Talk", O'Reilly, First Edition, Sep-2007. See pages 193-206.

  This sketch configures an XBee radio via the serial port, 
  reads the voltage value on analog pin 0 (PC0) and transmits
  an ASCII string corresponding to the measured voltage. 

  (* jcl *)

*/

#define SENSOR_PIN 0        // input sensor
#define THRESHOLD  1        // how much change you need to see on 
                            // the sensor before sending

int lastSensorReading = 0;  // previous state of the switch       

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

void set_xbee_destination_address() {
  long count = 0;
  // put the radio in command mode:
  Serial.print("+++");
  // wait for the radio to respond with "OK\r"
  char thisByte = 0;
  while (thisByte != '\r') {
    if (Serial.available() > 0) {
      thisByte = Serial.read(); 
    }
    if (count++ > 100000) break;
  }

  if (count <= 100000) {
     // set the destination address, using 16-bit addressing.
     // if you're using two radios, one radio's destination 
     // should be the other radio's MY address, and vice versa:
     Serial.print("ATDH0, DL5678\r");
     // set my address using 16-bit addressing:
     Serial.print("ATMY1234\r"); 
     // set the PAN ID. If you're working in a place where many people
     // are using XBees, you should set your own PAN ID distinct
     // from other projects.
     Serial.print("ATID1111\r");
     // put the radio in data mode:
     Serial.print("ATCN\r");
  }
}


void loop() {
  // listen for incoming serial data:

  // listen to the potentiometer:

  char sensorValue = readSensor();

  // if there's something to send, send it:
  if (sensorValue > 0) {
    Serial.print(sensorValue, DEC );
    Serial.print("\r");
  } 
}

char readSensor() {
  char message = 0;
  // read the sensor:
  float sensorReading = analogRead(SENSOR_PIN);

  // look for a change from the last reading
  // that's greater than the threshold:
  if (abs(sensorReading - lastSensorReading) > THRESHOLD) {
    message = sensorReading/4;
    lastSensorReading = sensorReading;
  } 
  return message;
}

Receiver:

/*

  Adapted from the "XBee Analog Duplex Sender" program in "Making
  Things Talk", O'Reilly, First Edition, Sep-2007. See pages 193-206.

  This sketch configures an XBee radio via the serial port, 
  reads serial data from XBee and outputs the received
  strings to an ZB1-LCD2 LCD screen.

  (* jcl *)

*/

#include <avr/interrupt.h>    
#include <avr/io.h>  
 

int inByte= -1;             // incoming byte from serial RX
char inString[6];           // string for incoming serial data
int stringPos = 0;          // string index counter

void setup() {
  char *msg = "wiblock MTT XB DEMO";
  Serial.begin(9600); 
  Serial.println(msg);
  // set XBee's destination address:
  setDestination();
}

void setDestination() {
  long count = 0;
  // put the radio in command mode:
  Serial.print("+++");
  // wait for the radio to respond with "OK\r"
  char thisByte = 0;
  while (thisByte != '\r') {
    if (Serial.available() > 0) {
      thisByte = Serial.read(); 
    }
    if (count++ > 100000) break;
  }
  if (count <= 100000) {
  // set the destination address, using 16-bit addressing.
  // if you're using two radios, one radio's destination 
  // should be the other radio's MY address, and vice versa:
  Serial.print("ATDH0, DL1234\r");
  // set my address using 16-bit addressing:
  Serial.print("ATMY5678\r"); 
  // set the PAN ID. If you're working in a place where many people
  // are using XBees, you should set your own PAN ID distinct
  // from other projects.
  Serial.print("ATID1111\r");
  // put the radio in data mode:
  Serial.print("ATCN\r");
  } 
}

void loop() {
  // listen for incoming serial data:
  if (Serial.available() > 0) handleSerial();
}


void handleSerial() {
  inByte = Serial.read();
  // save only ASCII numeric characters (ASCII 0 - 9):
  if ((inByte >= '0') && (inByte <= '9')){
    inString[stringPos] = inByte;
    stringPos++;
  }
  // if you get an ASCII carriage return:

  if (inByte == '\r') {

    inString[5] = 0;

    // output string to LCD

    //lcd.cursorTo(2, 0);  //line=2, x=0.
    Serial.println(inString);

    // clear the string with spaces

    for (int c = 0; c < 6; c++) {
      inString[c] = ' ';
    } 
    // reset the string pointer:
    stringPos = 0;
  }
}

If the XBee is not reading digital or analog sensors connected to any of it's pins, and there are only two XBees, keep it simple and do not use API mode.

The sender should use:

float pi = 3.14159;

Serial.print("<");
Serial.print(pi);
Serial.print(">");

The receiver code would look like this:

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

void setup()
{
   Serial.begin(57600);
   // Other stuff...
}

void loop()
{
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       inData[index] = '\0';
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
        inData[index] = '\0';
      }
    }
  }

  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    // The end of packet marker arrived. Process the packet

    // Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';
  }
}

Where is says "Process the packet", inData would contain "3.14159" and would be properly NULL terminated, so you could add:

float cake = atof(inData);

and cake would equal pi (to a few decimal places).

Thank you Sir PaulS! I'll post the code later.

PaulS:
If the XBee is not reading digital or analog sensors connected to any of it's pins, and there are only two XBees, keep it simple and do not use API mode.

If I were to use 2 or more sender xbees, what should I add to the code? Also, what should I do if the receiver can only listen to one device at a time? Should I set the sender xbees different delay for each?

If I were to use 2 or more sender xbees, what should I add to the code?

If you need to know which sender sent the data, you need to add some identifier before the data.

float pi = 3.14159;

Serial.print("<1, ");
Serial.print(pi);
Serial.print(">");

or:

float temp = 73.6;

Serial.print("<2, ");
Serial.print(temp);
Serial.print(">");

Then, on the receiver, you'd use strtok() to get the ID token, atoi() to convert it to a number, and use strtok() again (with NULL as the 1st argument) to get the value token, and atof() to convert it to a value. The ID might tell you where to store the value. Or not.

Sir PaulS, I placed your code inside dragonrobo7's code

Router:

const int currentPin = 14;

const unsigned long sampleTime = 100000UL; // sample over 100ms, it is an exact number of cycles for both 50Hz and 60Hz mains
const unsigned long numSamples = 250UL;    // choose the number of samples to divide sampleTime exactly, but low enough for the ADC to keep up
const unsigned long sampleInterval = sampleTime/numSamples;  // the sampling interval, must be longer than then ADC conversion time

int adc_zero;  //auto adjusted relative digital zero

void setup(){
  Serial.begin(9600);
  adc_zero = determineVQ(currentPin);	//Quiescent output voltage - the average voltage ACS712 shows with no load (0 A)
  delay(1000);
}

void loop(){
  float current = (readCurrent(currentPin), 3);
  Serial.print("<");
  Serial.print(current);
  Serial.print(">");
}

int determineVQ(int PIN) {
  long VQ = 0;  //ave VQ
  
  //read 1000 samples to stabilize value
  for (int i = 0; i < 1000; i++) {
    VQ += analogRead(PIN);
    delay(1);  //depends on sampling (on filter capacitor), can be 1/80000 (80kHz) max.
  } 
  VQ /= 1000;
  return int(VQ);
}

float readCurrent(int PIN)
{
  unsigned long currentAcc = 0;
  unsigned int count = 0;
  unsigned long prevMicros = micros() - sampleInterval ;
  while (count < numSamples)
  {
    if (micros() - prevMicros >= sampleInterval)
    {
      long adc_raw = analogRead(currentPin) - adc_zero;
      currentAcc += (unsigned long)(adc_raw * adc_raw);
      ++count;
      prevMicros += sampleInterval;
    }
  }
  float rms = sqrt((float)currentAcc/(float)numSamples) * (50 / 1024.0);
  return rms;
}

Coordinator:

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

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

void loop(){  
  while(Serial.available() > 0){
   char inChar = Serial.read();
   if(inChar == SOP){
     index = 0;
     inData[index] = '\0';
     started = true;
     ended = false;
   }
   else if(inChar == EOP){
     ended = true;
     break;
   }
   else{
     if(index < 79){
       inData[index] = inChar;
       index++;
       inData[index] = '\0';
       }
   }
  }

  if(started && ended){
    float current = atof(inData);
    Serial.println(current);
    delay(150);

    // Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';
  }
}

and all I'm getting is 3.00 so I tested just your code like this

Router:

int sensorPin = 14;
float val;

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

void loop(){
  val = analogRead(sensorPin);
  Serial.print("<");
  Serial.print(val);
  Serial.print(">");
}

Coordinator:

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

void setup()
{
   Serial.begin(9600);
   // Other stuff...
}

void loop()
{
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       inData[index] = '\0';
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
        inData[index] = '\0';
      }
    }
  }

  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    // The end of packet marker arrived. Process the packet
    float cake = atof(inData);
    Serial.println(cake);
    delay(150);
    // Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';
  }
}

here, I'm getting values ranging from 300-400 then 0 then 1000 then back to 300-400. Did I do something wrong?

  float current = (readCurrent(currentPin), 3);

What do you think that this statement is doing? It isn't, but how to fix this mess depends on what you think it is supposed to be doing.

What it looks like is that you took this statement:
Serial.print(readCurrent(currentPin), 3);
and simply replaced "Serial.print" with "float current = ".

That does NOT work.

I tried running your original code on the router side

float pi = 3.14159;

Serial.print("<");
Serial.print(pi);
Serial.print(">");

I sometimes receive 142.00, 3.00 and 0.00 instead of 3.14 all the way. Is this caused by jumbled bytes?

What do you think that this statement is doing? It isn't, but how to fix this mess depends on what you think it is supposed to be doing.

When I used "float current = (readCurrent(currentPin), 3);" I stored the returned rms value of the readCurrent() to 'current'? Why is it not the same as "float pi = 3.14"? I don't get it :frowning:

Sir PaulS, is the answer converting float to string? like using dtostrf()? because you used atof() in the receiver Xbee

Or could someone please post a link for 2 arduinos with xbee transferring analog data? I think that's my problem, the articles or posts I've read either uses 1 remote xbee and an arduino with xbee to transfer analog data or 2 arduino with xbee controlling potentiometer or LED.

Any help will be greatly appreciated :slight_smile:

When I used "float current = (readCurrent(currentPin), 3);" I stored the returned rms value of the readCurrent() to 'current'? Why is it not the same as "float pi = 3.14"? I don't get it

No, you don't. You get a value, using readCurrent(). Then, you apply the comma operator to that value and 3. The comma operator is not doing what you think it is. So, quit doing that shit.

float current = readCurrent(currentPin);

Oh, okay. Thank you, Sir PaulS!

I should do it like this:

Router

float current = readCurrent(currentPin);
Serial.print("<");
Serial.print(current, 4);
Serial.print(">");

Coordinator

float cake = atof(inData, 3);

Read it here --> http://forum.arduino.cc/index.php/topic,45279.0.html

Coordinator

NO! atof() does not take two arguments. Would you just forget the comma 3 shit?

Sorry, I mean.. when I print it

Coordinator

float current = atof(inData);
lcd.print(current, 4); lcd.print(" mA");

It works, I promise

What if I want to send multiple data? Can I do it like this?

Router

float apple = 0.75;
float pi = 3.14159;

Serial.print("<");
Serial.print(apple);
Serial.print(",");
Serial.print(pi);
Serial.print(">");

But how can I separate apple and pi in the coordinator?

strtok or strchr are useful C string handling functions for this kind of simple parsing.