Monday, January 21, 2013

Sending wireless signals with XBee & Arduino

We recently needed to make a system by which we can send sensor data from a freely moving animal wirelessly.  One simple way to do this is by using XBee modules from Digi.  These modules are fairly small (about the size of a quarter) and are incredibly easy to use in a new project.  We received a pair from SparkFun to play with.

Here is the information on XBee:
Product: https://www.sparkfun.com/products/11215 ($22.95)
Datasheet: http://www.sparkfun.com/datasheets/Wireless/Zigbee/XBee-Datasheet.pdf

We also got accessories:
XBee Shield: https://www.sparkfun.com/products/10854 ($24.95)
XBee Explorer Dongle: https://www.sparkfun.com/products/9819 ($24.95)

Also, I downloaded the X-CTU software from Digi: http://ftp1.digi.com/support/documentation/90001003_A.pdf

My plan was to use the on-board AD on the XBee to sample an analog signal (max sampling rate 1 kHz) and to send it to a receiving XBee and then to Arduino (an old Demilanove) for processing and output using an I2C DAC.

For DAC, I got MCP4725 breakout from Sparkfun:
https://www.sparkfun.com/products/8736 ($4.95)

Overall, this project cost: $100.75 not counting the Arduino, which I already had on hand.

First, I configured the two XBee modules to talk to each other using X-CTU and XBee Explorer Dongle.

Transmit Module Settings: 
DL = 1234  //Destination Address Low
MY = 4321 //Source Address
BD = 7 //Interface Data Rate: 115200
D0 = 2 //Digital input 0 is ADC
IT = 4 //Samples before transmitting = 4
IR = 1 //Sampling rate = 1 (1kHz)

Receive Module Settings:
DL = 4321 //Destination Address Low
MY = 1234 //Source Address
BD = 7 //Interface Data Rate: 115200
AP = 1 // API Enable
IA = 4321 // I/O Input Address

Next, I put the Transmit module on the dongle on a breadboard hooked up to a signal source (in this case a pressure sensor).  I put the Receive module on the Arduino Shield and also hooked up the DAC on a separate breadboard.  The setup looks like so:

Transmit Module with pressure sensor input.
Receive Module with Arduino and DAC.

To communicate with I2C, I am using the Wire library for Arduino.  To read packets from the XBee, I wrote a really simple little script:



#include <Wire.h>

#define BUFFER_LEN 64

// constants
#define START_DELIMITER 0x7E
#define API_ID          0x83

#define PACKET_LEN      0xA //0x10
#define N_SAMPLES       0x01

#define MCP4725_ID      0b01100000  

int i;

byte buffer[BUFFER_LEN];
byte L12[2];
byte dac_bits[2];

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(115200);
  Wire.begin();
  
  for (i=0;i<10;i++)
    pinMode(i+2,OUTPUT);
  
  dac_bits[0] = 0;
  dac_bits[1] = 0;
}

// the loop routine runs over and over again forever:
void loop() {
  
  int len;
  unsigned char firstByte;
    
  int value;
  word adc_val;

  if (Serial.available()) {
  
    firstByte = Serial.read();

    if (firstByte==START_DELIMITER) // start of a packet
    {
      Serial.readBytes((char*)L12,2);
      len = word(L12[0],L12[1]);

      if (len==PACKET_LEN) { // check for API packet of right length
        
        Serial.readBytes((char*)buffer,len);  
        
        // add 4 samples together
        value = 0;
        for (i=0;i<N_SAMPLES;i++)
          value+=word(buffer[(i*2)+8],buffer[(i*2)+9]);

        adc_val = value * (4 / N_SAMPLES);
        
        // DAC Communication
        Wire.beginTransmission(96);
        Wire.write(0b01000000); // write dac register command [c2 c1 c0 x x pd1 pd0 x]  for write dac c2 = 0, c1 = 1, c0 = 0
        for (i=0;i<8;i++) bitWrite(dac_bits[0],i,bitRead(adc_val,i+4)); // shift bits around for making i2c compatible
        for (i=0;i<4;i++) bitWrite(dac_bits[1],i+4,bitRead(adc_val,i));
        Wire.write(dac_bits[0]);
        Wire.write(dac_bits[1]);
        Wire.endTransmission();
        // END DAC Communication  
      }          
    }
  }
  
}
The Arduino is plenty fast to handle all the data from the XBee.  However, I was not pleased to see that the real-world transmission rate for the XBee capped out at 1 packet every 5 msec or 200 Hz.  This means that if you are sampling and transmitting as fast as possible, you take only one sample every 5 msec in the end.  This leads to a minimum 5msec lag in the system.  If you acquire more samples as in my case (4).  You get a 10 msec lag and a sampling rate of 100 Hz.

Averaging 4 samples let's me effectively improve the ADC resolution to 12 bits fro the 10 bit native on the XBee.

It works overall.  Here is a 10 Hz sine curve passed at 4 samples per packet compared to the original:


If I do not do any averaging and just send one sample at maximum settings data looks like:


Feeding this signal into a proper ADC like one from National Instruments sampling at 1 kHz, it should be fine to resolve and time-stamp individual samples.

NOTE: Reading the data using Arduino and then passing it to DAC is better than filtering the native PWM of the XBee.  This is because filters that can cut out the PWM frequency of the XBee (which runs at 15.6 kHz) will yield significant amount of distortion for signals near the cutoff.  For example, Digi suggests using the following RC combination to filter: 850 Ohm, 1 uF.  Fc = 187 Hz.  However, using this filter with signals at 50 Hz, you will get a lot of phase distortion.  This does not happen with a DAC.  Further, the XBee actually receives the data as digital values.  It makes no sense to convert those to PWM unless you absolutely can't afford to have the Arduino/DAC.

In conclusion, however, for faster signals, we will need a different radio.  Perhaps a bluetooth one.