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!