Labels

Thursday, April 10, 2014

Using the XBee Library with Atmel Studio 6

Now that the library is coming together, I thought it would be useful to write up a quick tutorial on how to use the library, and elaborate a little more on the structure.

The test platform I initially used when developing this code is shown below:



Although an arduino board was used, the arduino bootloader was not. However, since the arduino compiler uses avr-gcc, the library can still be included with arduino code. The ISP programming pins are available on the arduino, and any ISP programmer can be used to upload c code to the Atmega328p microcontroller. I used the AVRISP MKII, which can be purchased from mouser for around 40 bucks.


I prefer the AVRISP MKII, although it is more expensive than 3rd party programmers, it is almost always guaranteed to work with Atmel studio software.

Now, I will create a new project in Atmel Studio 6:


Remember to select the proper AVR if you are using something other than an arduino uno. Since I'm using the uno, I've selected the Atmega328p. After the new project is created, I will copy the XbeeS2.c, XbeeS2.h, and XbeeHAL.h files into the same directory as the .c file with main(). After the files have been added, be sure to add the files to the project:



Once the files are added, be sure to #include "XbeeS2.h". Before compiling, the only file the user needs to edit to use the library is XbeeHAL.h. XbeeHAL contains all the hardware specific abstractions. At the time of writing this tutorial, the Atmega328p and Atmega2560 is supported. If you are also using the Atmega328p, you just have to make sure that "ATmega328P" is #defined. If you are using a different micro, there are a few functions to define that are necessary for the library to function properly.

  • XBEE_UDR - Xbee USART Data Register; This is macro'd to the Data Register Buffer of the USART that the microcontroller is using for interfacing with the Xbee.
  • TX_BUFFER_IS_FULL() - Normally macro'd to ((UCSR0A & (1 << RXC0)) == 0) for the ATmega328P, this is a simple function that is used to check if the UDR is ready to be written. Most USART modules on microcontroller include a bit in a configuration register that indicates when the register is clear, so just change it such that it is appropriate for your micro.
  • RX_BUFFER_IS_FULL() - Macro'd to ((UCSR0A & (1 << UDRE0)) == 0) for the ATmega328P, this is the same thing as TX_BUFFER_IS_FULL(), but indicates when new data is received on UDR rather than when UDR is available to be written.
  • TOGGLE_LED()/LED_OUTPUT() - A test led. This can be changed to whatever is appropriate for your setup, and may be removed from the library later. Not actually needed for proper functionality.
This covers the macros needed, but there are also 2 functions that need to be set in XbeeHAL.h.
  • ISR(USART_RX_vect) { rxISR(); } - This is strictly for AVR's, but the HAL needs to have access to your micro's USART ISR. All the ISR needs to do is call the rxISR() function from the Xbee.c file.
  • void USART_INIT(void) - This is a function just to set up the USART at whatever baud rate you want to specify. Just make sure it matches your Xbee's baud rate! ;)
That covers the hairy details. If you are using the Arduino UNO or ATmega328P, then all of those points can be ignored.


To use the library, the user must call XbeeUSART_init() first. Then, the user may call any function from the library, such at Zigbee_Transmit_Request() for sending messages. Also, a callback feature is available for when the user wants to perform some task when a new packet arrives. To do this, the user calls setNewPacketCB(), passing it the name of the function to be called when the new packet arrives. 




I hope this helps anyone who is looking to try out the library! I will post new revisions here as they come out! Remember, you should only have to modify the HAL, and all the functions available can be found in Xbee.h.

Thanks for reading!

XBee S2 HAL
XBeeS2.c
XBeeS2.h

UPDATE:
I have added more features and restructured some of the library's files. For the time being I will post a link here to the newest version, and later provide a tutorial and github link.
https://dl.dropboxusercontent.com/u/47687797/XBee%20Library.zip

Monday, April 7, 2014

Links to XBee Series 2 Code

Last post I had copied the XBee Series 2 Library code into the text itself, but now I have convenient links! They are still a work in progress, but feel free to give it a shot!

XBee S2 HAL
XBeeS2.c
XBeeS2.h

Monday, March 31, 2014

XBee Library Evolved

It has been quite some time since the last entry, and since then much has happened with the XBee C library and the progress on the ATmega-based sensor network.

The ATmega portion has forked off into its own separate project, which is in its prototype stage right now. The design itself has already won awards at IEEE Southeastcon for best poster/short paper, and we are expecting great progress with the project!

Now that we are working with a specific board, it was time to make the library much more versatile. The hardware-dependent functions have been moved into a hardware abstraction layer (HAL), and only raw XBee functionality remains in the c file. This way, we have no  microcontroller preference, and only the HAL has to be modified for use in the code.

In previous versions of the code, we had a function within the library for responding to new packets. This was a huge problem, as it required the user to go digging through the library itself, modifying the function inside, and exporting variables to the application code. This was a terrible design, and a solution had to be implemented. Now, the user calls a function, "setNewPacketCB()", which is passed a pointer to a function that is written in the application code. So, if you have a function, called, say, weiners(), you can set weiners to be called when a new packet arrives by calling "setNewPacketCB(weiners);".

Since the addition of the HAL, the user now has to #define which microcontroller he/she (who am I kidding?) he wants to use, and the HAL defines the appropriate ISR for the micro's USART, which USART is used, and what USART settings are configured. So, if the default settings aren't what you want, you only need to modify the contents of the HAL.

Originally, the micro being used was the AVR ATmega328P. We have now moved onto the mega2560, and soon to the mega1280 for low power capabilities. Introducing multiple USARTs have caused us to move the streaming target for printf() into the libraries for the development board, which we will discuss another day. Now, the print statements are safely wrapped in a #ifdefine statement, which will give the user feedback if DEBUG is #define'd. This also makes it easier for user to use this library with other micros sine the standard streaming target code won't need to be modified.

SO! Overall, the user now only needs to modify the HAL to use the library with any micro! More functions are still to come! But for now, here's what we have:

Xbee.h:
#include <stdio.h>
#include <stdlib.h>

#define RSSI_PIN PIND
#define RSSI_DDR DDRD
#define RSSI_PIN_NO PIND3

typedef struct{
int len;
char api_identifier;
long DH;
long DL;
int source_addr_16bit;
char options;
char frame_id;
char AT_com[2];
char status;
char data[20]; // also stores the "value" for at command response
int data_len; // stores the length of the data
char checksum;
} RxPacket;

void rxISR();
void receive_Msg(RxPacket*);
void send_Msg(char*, int);
void USART_vSendByte(char);
void ZigBee_TX_Request(char, long, long, int, char, char, char*, int);
void AT_Command(char, char, char, char*, int);
void Init_Timer0(void);
float getRSSIPWM(void);
char getRSSI(void);
int get16bitAddress(void);
void killTimer(void);
float getTime_us(void);
void setNewPacketCB(void (*funptr)(void));
RxPacket getPacket(void);
void XbeeUSART_init(void);

XBeeHAL.h:
#include "Xbee.h"

//#define ATmega328P
#define ATmega2560

// Register Names for selected microcontroller
#ifdef ATmega328P
#include <avr/io.h>
#include <avr/interrupt.h>

// USART Definitions
#ifdef F_CPU
#pragma message ("F_CPU already defined.")
#else
#define F_CPU 16000000 //be sure to adjust for various clock settings
#endif
#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

// Registers
#define XBEE_UDR UDR0

// Macros
#define RX_BUFFER_IS_FULL() ((UCSR0A & (1 << RXC0)) == 0)
#define TX_BUFFER_IS_FULL() ((UCSR0A & (1 << UDRE0)) == 0)
#define TOGGLE_LED() (PORTD ^= 0x80)
#define LED_OUTPUT() (DDRD |= 0x80)
// Functions
ISR(USART_RX_vect) { rxISR(); }

/* Code to initialize USART
*
* Eventually, this function should receive a USART number (e.g., 0-X, X being the number of USARTs available
* on the chip) and a baud rate. For now, we will use the #define variables to set this.
*/
void USART_INIT(void)
{
UBRR0L = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register
UBRR0H = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register

UCSR0C |= (1 << UCSZ00) | (1 << UCSZ01); // Use 8-bit character sizes
UCSR0B |= (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0);   // Turn on the transmission and reception circuitry
// Enable RX interrupt as well.
}

#endif

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

// USART Definitions
#ifdef F_CPU
#pragma message ("F_CPU already defined.")
#else
#define F_CPU 8000000 //be sure to adjust for various clock settings
#endif
#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

// Registers
#define USART_NO 3
#define XBEE_UDR UDR3

// Macros
#define RX_BUFFER_IS_FULL() ((UCSR3A & (1 << RXC3)) == 0)
#define TX_BUFFER_IS_FULL() ((UCSR3A & (1 << UDRE3)) == 0)
#define TOGGLE_LED() (PORTD ^= 0x80)
#define LED_OUTPUT() (DDRD |= 0x80)
// Functions
ISR(USART3_RX_vect) { rxISR(); }
/* Code to initialize USART
*
* Eventually, this function should receive a USART number (e.g., 0-X, X being the number of USARTs available
* on the chip) and a baud rate. For now, we will use the #define variables to set this.
*/
void USART_INIT(void)
{
UBRR3L = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register
UBRR3H = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register

UCSR3C |= (1 << UCSZ30) | (1 << UCSZ31); // Use 8-bit character sizes
UCSR3B |= (1 << RXCIE3) | (1 << RXEN3) | (1 << TXEN3);   // Turn on the transmission and reception circuitry
// Enable RX interrupt as well.
}
#endif

Xbee.c
/*
 * Xbee.c
 *
 * Created: 6/20/2013 1:29:02 PM
 *  Author: Waron
 */ 
#include "Xbee.h"
#include "XbeeHAL.h"

RxPacket rx_pkt;

// Global Variables
char RSSI;
int MY;
int cb; // callback flag -- set if a callback function for new packets has been specified
void (*cbFunPtr)(void); // callback function pointer

/* Routine to call USART initialization routine from the HAL
 *
 * This routine configures the USART that the XBee is attached to.
 * Configues for settings 8N1 and 9600 baud.
 */
void XbeeUSART_init()
{
USART_INIT();
}

/* Routine to send a byte through USART
 *
 * This routine polls the USART data buffer register full flag until it indicates it is ready
 * for a new byte to be transmitted, then loads the new byte into the USART data buffer
 * register for transmission.
 */
void USART_vSendByte(char Data)
{
// Wait if a byte is being transmitted
while (TX_BUFFER_IS_FULL()) {}; // Do nothing until UDR is ready for more data to be written to it
// Transmit data
XBEE_UDR = Data;
}

/* USART Receive Interrupt
 * 
 * Hardware interrupt routine inside the HAL calls this function. When a new byte is received, it is
 * checked for the start delimeter of a new packet. If it is, then the receive message function is called.
 * Otherwise, nothing happens. After a packet is received, the callback function is called to handle the
 * packet, if one has been configured.
 */
void rxISR()
{
cli();
TOGGLE_LED();
if(XBEE_UDR == 0x7E)
{
RxPacket pkt;
receive_Msg(&pkt);
rx_pkt = pkt;
// Call function for responding to new packet
if(cb)
(*cbFunPtr)();
}

TOGGLE_LED();
sei();
}
/* Function to return the 16 bit Address of the Xbee
 *
 * This function calls the MY AT command and waits until the response frame is 
 * received.
 */
int get16bitAddress()
{
RxPacket pkt;
int temp;

do 
{
AT_Command(0x01, 'M', 'Y', 0, 0); // Send DB AT Command
while (RX_BUFFER_IS_FULL()){};
temp = XBEE_UDR;
receive_Msg(&pkt);
} while (pkt.api_identifier != 0x88);

#ifdef DEBUG
printf("MY is %d\n", (pkt.data[0]<<8)|pkt.data[1]);
#endif

return (pkt.data[0]<<8)|pkt.data[1]; // Return MY
}

/* Send Message Function
 *
 * This function is passed a character array of an XBee API Packet, along with the
 * length of the packet. The checksum is calculated, then the start delimeter is transmitted,
 * followed by the length of the packet, then the data, and finally the checksum.
 */
void send_Msg(char *data, int len)
{
//Generate checksum
char checksum;
int counter = 0, sum = 0;
for(counter = 0; counter <= len - 1; counter++)
sum += data[counter];
//Checksum is calculated by adding the data values together, and subtracting the 
//last 8 bits from 0xFF.
checksum = 0xFF - (sum & 0x00FF); 
//Transmit data
USART_vSendByte(0x7E);  //Start delimiter
USART_vSendByte(8 >> len);  //Length MSB
USART_vSendByte(len); //Length LSB
for(counter = 0; counter <= len - 1; counter++)  //Transmit data
USART_vSendByte(data[counter]);
USART_vSendByte(checksum);  //Transmit checksum
}

void receive_Msg(RxPacket *rx_data)
{
int count;
char temp;
rx_data->data_len = 0;

while (RX_BUFFER_IS_FULL()) {}; // Do nothing until data have been received and is ready to be read from UDR
temp = XBEE_UDR; //next incoming byte is the MSB of the data size
while (RX_BUFFER_IS_FULL()) {}; // Do nothing until data have been received and is ready to be read from UDR

rx_data->len = (temp << 8) | XBEE_UDR; //merge LSB and MSB to obtain data length
while (RX_BUFFER_IS_FULL()) {}; // Do nothing until data have been received and is ready to be read from UDR
rx_data->api_identifier = XBEE_UDR;

switch(rx_data->api_identifier) // Select proper sequence for receiving various packet types
{
case 0x90: // Zigbee Receive Packet
for(count = 1; count < rx_data->len; count++)
{
while (RX_BUFFER_IS_FULL()) {}; // Do nothing until data have been received and is ready to be read from UDR
if(count == 1)
rx_data->DH = ((long)XBEE_UDR << 24);
else if(count == 2)
rx_data->DH |= ((long)XBEE_UDR << 16);
else if(count == 3)
rx_data->DH |= ((long)XBEE_UDR << 8);
else if(count == 4)
rx_data->DH |= (long)XBEE_UDR;
else if(count == 5)
rx_data->DL = ((long)XBEE_UDR << 24);
else if(count == 6)
rx_data->DL |= ((long)XBEE_UDR << 16);
else if(count == 7)
rx_data->DL |= ((long)XBEE_UDR << 8);
else if(count == 8)
rx_data->DL |= (long)XBEE_UDR;
else if(count == 9)
rx_data->source_addr_16bit = (XBEE_UDR << 8);
else if(count == 10)
rx_data->source_addr_16bit = XBEE_UDR;
else if(count == 11)
rx_data->options = XBEE_UDR;
else
{
rx_data->data[count - 12] = XBEE_UDR;
rx_data->data_len++;
}
}
while (RX_BUFFER_IS_FULL()) {}; // Do nothing until data have been received and is ready to be read from UDR
rx_data->checksum = XBEE_UDR; //store checksum
break;
case 0x88: // AT Command
for(count = 1; count < rx_data->len; count++)
{
while (RX_BUFFER_IS_FULL()) {}; // Do nothing until data have been received and is ready to be read from UDR
if(count == 1)
rx_data->frame_id = XBEE_UDR;
else if(count == 2)
rx_data->AT_com[0] = XBEE_UDR;
else if(count == 3)
rx_data->AT_com[1] = XBEE_UDR;
else if(count == 4)
rx_data->status = XBEE_UDR;
else
{
rx_data->data[count - 5] = XBEE_UDR;
rx_data->data_len++;
}
}
while (RX_BUFFER_IS_FULL()) {}; // Do nothing until data have been received and is ready to be read from UDR
rx_data->checksum = XBEE_UDR; //store checksum
break;
default:
break;
}

}

void ZigBee_TX_Request(char Frame_ID, long DH, long DL, int _16bitAddr, char Hops, char Options, char *RF_Data, int len )
{
int i; // counting variable
char buff[14 + len]; //temporary buffer for transmitting
// ZigBee Transmit Request API Identifier
buff[0] = 0x10;
// Identifies the UART data frame for the host to correlate with a 
// subsequent ACK (acknowledgment). Setting Frame ID to ‘0' will disable response frame.
buff[1] = Frame_ID;
// MSB first, LSB last. Broadcast = 0x000000000000FFFF
buff[2] = (DH >> 24);
buff[3] = (DH >> 16);
buff[4] = (DH >> 8);
buff[5] = DH;
buff[6] = (DL >> 24);
buff[7] = (DL >> 16);
buff[8] = (DL >> 8);
buff[9] = DL;
// 16 bit address
buff[10] = (_16bitAddr >> 8);
buff[11] = _16bitAddr;
// Number of hops for message to take
buff[12] = Hops;
// Options
buff[13] = Options;
for(i = 0; i < len; i++)
buff[14+i] = RF_Data[i];
send_Msg(buff, 14+len);
}

void AT_Command(char frameid, char command1, char command2, char *options, int len)
{
char buff[5]; //temporary buffer for transmitting
int count;
buff[0] = 0x08; // API ID for AT Commands
buff[1] = frameid; // Frame ID; set to 0 for no response
buff[2] = command1;
buff[3] = command2;
for(count = 1; count <= len; count++)
buff[3 + count] = options;
send_Msg(buff, 4 + len);
}

/* Function to get RSSI from the PWM pin on the XBee
 *
 * This function returns the high time in microseconds from the PWM signal with corresponds to
 * the RSSI of the last received message. This is accomplished by starting and stopping an 8-bit
 * timer.
 */
float getRSSIPWM(void)
{
char RSSI;
float time_us;

Init_Timer0();
while(bit_is_set(PIND, PORTD3) && (TCNT0 != 50));
killTimer();
while(bit_is_clear(PIND, PORTD3));
Init_Timer0();
while(bit_is_set(PIND, PORTD3) && (TCNT0 != 50));
#ifdef DEBUG
printf("TCNT0 is %d\n", TCNT0);
#endif
killTimer();

return time_us/200.0;
}

/* This function is under construction, and is not guaranteed to work for all uC's */
void Init_Timer0(void)
{
TCNT0 = 0; // clear count value
TCCR0B = 0x03; // Select clk/64
}

/* This function is under construction, and is not guaranteed to work for all uC's */
void killTimer(void)
{
TCCR0B = 0x00; // disable clk
TCNT0 = 0; // clear count value
}

/* This function is under construction, and is not guaranteed to work for all uC's */
float getTime_us(void)
{
return (float)TCNT0*(1.0/((float)F_CPU/64.0));

}

/* Function to get the RSSI value of the last received message in dB
 *
 * This function calls the DB AT command. It then waits for the returning packet.
 * if the packet is not the returned DB value, then it is discarded.
 * The returned value is the attenuation of the signal in -dB.
 */
char getRSSI(void)
{
RxPacket pkt;
int temp;

AT_Command(0x01, 'D', 'B', 0, 0); // Send DB AT Command
while (RX_BUFFER_IS_FULL()){};
temp = XBEE_UDR; // Probably should add some code to check for start delimeter
receive_Msg(&pkt);

if(pkt.api_identifier != 0x88)
{
#ifdef DEBUG
printf("Failed to retrieve RSSI\n");
#endif
// otherwise, handle the packet as usual
if(cb)
(*cbFunPtr)();
return 0xFF;
}
else
{
#ifdef DEBUG
printf("RSSI is %d\n", pkt.data[0]);
#endif
return pkt.data[0]; // Return RSSI value
}

}

/* Callback function for new packets
 *
 * The user passes the name of their designated callback function. The function's address is
 * then set to the callback fuction pointer, cbFunPtr. The callback flag, cb, is set.
 */
void setNewPacketCB(void (*funptr)())
{
cb = 1;
cbFunPtr = funptr;
}

/* Get Packet
 *
 * Returns the last received packet.
 */
RxPacket getPacket()
{
return rx_pkt;
}

Saturday, August 3, 2013

Xbee Series 2 RSSI Bug

For those of you who didn't read the last post, be sure to look at it here before reading this one, because
otherwise it doesn't make any sense!
http://stupidembeddedblog.blogspot.com/2013/07/getting-rssi-from-xbee-series-2.html

I have been wrestling with this stupid "bug" for the past week, and just before losing my last bit of sanity I discovered my problem and I feel it is appropriate to post it here. Last entry we had looked at how to retrieve the RSSI value from an Xbee series 2 module and some code to obtain it with as well.

Source
In my sensor network, I have each node periodically broadcast a message to all nodes within 1 hop, and the receiving nodes then obtain the sender's 16 bit address and the RSSI of the packet, then sends that back to the coordinator along with it's own 16 bit address. The coordinator is connected to a PC running MATLAB which then uses the information to localize the nodes using triangulation.

The problem I was having was after a brief period of time, when using the code from the previous post to obtain the RSSI, the code would get stuck after the DB AT command was issued. I couldn't figure out what was going on because most of the time the code worked, but it would lock up after at least 3 other nodes were on the network, constantly bombarding each other with packets. At first I was suspicious an interrupt was causing the problem, but that was not the case.

The Xbees store a message queue, and despite issuing the AT command and expecting the appropriate response frame back immediately, instead I was getting the Zigbee receive frame from a previously received message. This caused the code to get stuck waiting for certain parts of an expected frame that it wasn't going to received.

Here's the updated code!

char getRSSI(void)
{
RxPacket pkt;
int temp;

do 
{
AT_Command(0x01, 'D', 'B', 0, 0); // Send DB AT Command
while ((UCSR0A & (1 << RXC0)) == 0){};
temp = UDR0;
receive_Msg(&pkt);
} while (pkt.api_identifier != 0x88);

printf("RSSI is %d\n", pkt.data[0]);
return pkt.data[0]; // Return RSSI value
}

In this code we use the receive message command to obtain the packet, then check to see if the frame received was an AT response. If not, then we simply request it again. This may not be perfect, but for now, it works! Also, the new packet function was removed from the receive_Msg() function and added to the USART ISR which now looks like this:

ISR(USART_RX_vect) 
{   
PORTD ^= 0x80; // TEST LED
if(UDR0 == 0x7E)
{
receive_Msg(&rx_pkt);
newPacketRX(&rx_pkt); // Call function for responding to new packet
}

PORTD ^= 0x80; // TEST LED
}

So this is it for now. Subscribe for future updates!

Saturday, July 27, 2013

Getting RSSI from Xbee Series 2

This has been one of the most obnoxious endeavors I've taken on so far with Xbees. For those of you who might be familiar with the API mode of the Xbee series 1, each packet includes the RSSI (Received Signal Strength Indicator) value in it. In series 2 that is not the case. I'm not sure what the reasoning is behind excluding that byte from the packet, but I suspect that it has something to do with only reflecting the strength of the last hop, and in a multihop network that may be considered "useless", but whatever.

I scoured the internet looking for other solutions. Most sites I found mentioned that you can turn on the PWM (Pulse Width Modulation) output on pin6 to reflect the RSSI value. This was mostly designed with the intention of hooking up a cute little LED that glows brighter according to the signal strength. When the RSSI PWM is enabled, a signal is output on pin 6 which has a duty cycle that ranges from 24% to 100%.

According to the documentation:

 Zero percent means PWM output is inactive. One to 24% percent means the received RF signal is at or below the published sensitivity level of the module. The following table shows levels above sensitivity and PWM values.

dB above Sensitivity - Dutycycle 
10   -  41%
20   -  58%
30   -  75%


The total period of the PWM output is 64 μs. Because there are 445 steps in the PWM output, the
minimum step size is 144 ns.

A non-zero value defines the time that the PWM output will be active with the RSSI value of the
last received RF packet. After the set time when no RF packets are received, the PWM output will
be set low (0 percent PWM) until another RF packet is received. The PWM output will also be set
low at power-up until the first RF packet is received. A parameter value of 0xFF permanently
enables the PWM output and it will always reflect the value of the last received RF packet.

The next step was to find a legitimate conversion rate from this stupid PWM output to dB. I stumbled upon the following excerpt from some random forum:
DB parameter is used to read the received signal strength (in dBm) of the last RF packet received. Reported values are accurate between -40 dBm and the RF module's receiver sensitivity.

Parameter Range [read-only]: 0x17-0x5C (XBee), 0x24-0x64 (XBee-PRO)
Absolute values are reported. For example: 0x58 = -88 dBm (decimal). If no packets have been received (since last reset, power cycle or sleep event), “0” will be reported.

So I suppose I could map the sensitivity range to 24% to 100% duty cycle, but I came across a much better solution, the DB AT command!

Seems like a very obvious solution, but what I was reading at most websites was that it takes at least 2 seconds to retrieve the value. To enter "command mode" on most Xbees, you must wait 2 seconds, send "+++", the command to enter command mode, then send "ATDB\r" and wait for the response. I was used to working with the Xbee S1 modules in API mode, which issues AT commands the same way, by send the "+++" code and junk, so I assumed S2 is the same way and immediately dismissed that as a viable option. Who has 2 seconds to sit around and do nothing but miss packets!?

Turns out, if you are using Xbee series 2 API firmware, AT commands are not accessed by sending the "+++" command. You send an API frame that is structured for AT commands. This means you don't have to wait 2 seconds to receive the dB! However, this does mean I needed to seriously modify my code, because now we are using another type of API packet! The following function was written:

void AT_Command(char frameid, char command1, char command2, char *options, int len)
{
char buff[5]; //temporary buffer for transmitting
int count;
buff[0] = 0x08; // API ID for AT Commands
buff[1] = frameid; // Frame ID; set to 0 for no response
buff[2] = command1;
buff[3] = command2;
for(count = 1; count <= len; count++)
buff[3 + count] = options;
send_Msg(buff, 4 + len);
}

In this function you pass the frame ID, the two characters for the AT command (e.g., 'D' and 'B'),  and any options that may go along with that in the form of an array, and the length of that array. Those values are then sent to our send_Msg() function we discussed last post.

We also had to make a few modifications to receive the packets from AT commands if they return information such as the DB command. To hold this, our data structure for Xbee frames was modified:

typedef struct{
int len;
char api_identifier;
long DH;
long DL;
int source_addr_16bit;
char options;
char frame_id;
char AT_com[2];
char status;
char data[20]; // also stores the "value" for at command response
char checksum;
} RxPacket;

We've added the frame_id, AT_com[], status, and options for receiving AT returned frames. Inside the switch statement of our receive_Msg() function we've added the following case:

case 0x88: // AT Command
for(count = 1; count < rx_data->len; count++)
{
while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
if(count == 1)
rx_data->frame_id = UDR0;
else if(count == 2)
rx_data->AT_com[0] = UDR0;
else if(count == 3)
rx_data->AT_com[1] = UDR0;
else if(count == 4)
rx_data->status = UDR0;
else
rx_data->data[count - 5] = UDR0;
}
while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
rx_data->checksum = UDR0; //store checksum
break;

Now we can receive AT Commands! Now to look at the function I've written to specifically request the dB:

char getRSSI(void)
{
RxPacket pkt;

AT_Command(0x01, 'D', 'B', 0, 0); // Send DB AT Command
cli();

int count, len;
char temp, checksum;

while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
temp = UDR0;

while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
temp = UDR0; //next incoming byte is the MSB of the data size
while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR

pkt.len = (temp << 8) | UDR0; //merge LSB and MSB to obtain data length
while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
pkt.api_identifier = UDR0;

for(count = 1; count < pkt.len; count++)
{
while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
if(count == 1)
pkt.frame_id = UDR0;
else if(count == 2)
pkt.AT_com[0] = UDR0;
else if(count == 3)
pkt.AT_com[1] = UDR0;
else if(count == 4)
pkt.status = UDR0;
else
pkt.data[count - 5] = UDR0;
}
while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
pkt.checksum = UDR0; //store checksum

//printf("RSSI is %d\n", pkt.data[0]);
sei();

return pkt.data[0]; // Return RSSI value
}

I didn't actually end up using the receive_Msg() function for acquiring the dB, but the functionality is still there in case it's needed later. This getRSSI() function simply returns the dB value, just remember that the value is actually negative because it is the dB of the signal loss!