In the previous article, I described a simple wireless setup using a pair of Xbees connected to PIC microcontroller and serial port of a Linux machine. After finishing the article, I continued working on the project and that’s what I have found so far.
First, using router firmware in Xbee module is bad for your batteries. In this configuration power consumption is steady 40ma. In addition to that, sleep is not working. After loading end device firmware power consumption dropped to 5ma on average and I was able to use Sleep pin. Measured current in sleep mode is less than 1ua – not bad. Second, PIC18F4520 is not the best PIC for sensor applications. Newer K-series PIC18s with their internal 1.2V reference, such as PIC18F26K20, are much better. In addition, they can be clocked up to 64MHz, and they are cheaper. At the time of this writing PIC18F26K20 in DIP package sells for less than $4 in single quantities on Mouser.
I went ahead and modified the project to compile for PIC18F26K20. This was pretty easy – port and register names are the same so I only needed to modify names of some configuration fuses and change PIC name in MPLAB. New project files can be downloaded from downloads section and this is new connections schematic. Also, if you have older non-variable VPP debugger/programmer, like Pickit2 or ICD 2, you may need VPP limiter.
First of all, I wrote a piece of code which measures PICs power supply voltage. I’m planning to use all kinds of low power sources, such as small batteries, solar cells, super capacitors and such, and the best way to estimate the power source is load it and look at the discharge curve. PIC18F26K20 has internal 1.2V reference, which can be connected to PICs ADC, also internally. Having known voltage on ADC input we can then connect VDD to VREF+, VSS to VREF-, take a sample and calculate VDD by solving a simple proportion. That’s how:
We have unknown VDD. One step of ADC is VDD/1024 since VDD connected to VREF+ defines ADC full range. When we measure 1.2v we will get number of ADC steps which cover 1.2V and when we divide 1.2 by this result we should get the same step value. The proportion is VDD/1024 = 1.2/ADC_result. To get VDD we need to take a measure and divide 1228.8 by the measurement result. Easy.
The code to control ADC is spread to several functions. First of all, initialization which needs to happen only once goes to Board_init function in main module like that:
1 2 3 | /* A/D configuration */ ADCON2bits.ADFM = 1; //output format right-justified ADCON2 |= 0x3e; // conversion times ACQT 111, ADSC 110 |
Line 2 sets the output format. We want 8 lower bits in ADRESL and 2 high bits in ADRESH. Line 3 sets conversion times to a maximum. Power supply voltage changes very little and we have plenty of time.
The function which takes the measurement is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* Takes VDD measurement */ WORD VDD_measure( void ) { DWORD delay; WORD rcode; /* set measurement to FVR and turn ADC on */ ADCON0 = 0x3d; delay = uptime + 1; while( uptime < delay ); //wait 1 ms ADCON0bits.GO = 1; //start conversion while( ADCON0bits.GO ); //wait for conversion to complete rcode = ((( WORD )ADRESH )<<8 )|( ADRESL ); /* turn ADC off */ ADCON0bits.ADON = 0; return( rcode ); } |
Here, I set ADC input to internal reference on line 7, turn ADC on and wait 1ms. After that, on line 10 I start a conversion, take a result, convert it from two 8-bit register values to a single 16-bit value on line 12 , turn ADC off on line 14 and return.
Finally, the function which interprets results of what ADC has returned:
1 2 3 4 5 6 7 8 9 10 | /* Outputs VDD */ void VDD_print( void ) { WORD tmp; send_string("\rVDD is: "); tmp = VDD_measure(); send_decword((WORD)( 12280000/tmp )); send_string(" ADRESH/ADRESL = "); send_decword( tmp ); } |
There is not much to explain here, this function is straightforward. I use a 10,000 bigger number to convert ADC result to volts in order to see fractions of a volt. In addition, I output the result itself in decimal format without conversion just to see it.
This code can be used to measure other voltage sources as well. You will need to measure FVR first to get a known voltage for current VDD. Then you switch ADC input to other signal and measure it. Finally, knowing VDD, you can calculate measured signal value.
Now, when we have sensor functionality outlined, let’s continue talking about Xbee. From time to time, we may want to control our Xbee module with AT commands. In transparent mode it means we need to stop our normal transmission, switch to command mode, issue command or commands, and then switch back to transparent mode. If we talk to Xbee via it’s RF link, we won’t be able to see the output of AT command so we need a function which talks to Xbee, collects the results, ans then switches to transparent mode and sends the results back.
Functions for interacting with Xbee in command mode, are contained in xbee_at.c module. Let’s take a look at them.
The first one sets additional parametes of Xbee module and is usually called from the main initialization routine. Configuration can be done using X-CTU, however, it you initialize Xbee from application, replacing a module gets much easier – you just plug a new one and apply power. In addition, Xbee datasheet suggests using configuration writes “sparingly”; it’s good idea to refrain from saving configs during experiments. The function returns FALSE if any command fails. The only command that is set here is the one on line 6, which activates Sleep pin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | BOOL Xbee_init( void ) { if( Xbee_cmdmode_enter() == FALSE ) { //enter command mode return( FALSE ); } send_string("ATSM1\r"); if( Xbee_waitfor_OK() == FALSE ) { return( FALSE ); } if( Xbee_cmdmode_exit() == FALSE ) { return( FALSE ); } return( TRUE ); } |
As you can see, Xbee_init calls another Xbee functions. The Xbee_cmdmode_enter() function switches the module from transparent to command mode:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* switching Xbee to command mode */ /* returns TRUE if OK is received */ /* assumes DWORD uptime incrementing each millisecond */ BOOL Xbee_cmdmode_enter( void ) { DWORD delay; delay = uptime + 2000; while( uptime < delay ); //wait 1 sec guard time send_string(three_pluses); if( Xbee_waitfor_OK() ) { return( TRUE ); } return( FALSE ); } |
Next function is called when we need to exit command mode. Another way to achieve the same result is simply wait for 5 seconds, but that could be too long in many cases.
1 2 3 4 5 6 7 8 9 10 11 | /* command mode assumed */ /* returns TRUE if OK received */ BOOL Xbee_cmdmode_exit( void ) { //DWORD delay; send_string( at_cmdmode_exit ); if( Xbee_waitfor_OK() ) { return( TRUE ); } return( FALSE ); } |
Finally, the function called from previous three checks if Xbee module responds with “OK”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* reads serial stream expecting OK */ /* returns TRUE if received */ /* assumes 'uptime' incrementing each millisecond */ BOOL Xbee_waitfor_OK( void ) { BOOL rcode = FALSE; char buf[ 4 ] = { 0 }; BYTE i = 0; DWORD timeout; rom char *resp_ok = "OK\r"; timeout = uptime + 2000; //timeout 2 sec while( uptime < timeout ) { if( CharInQueue()) { buf[ i ] = recvchar(); i++; } if( strcmppgm2ram( buf, (const far rom char *)"OK\r" ) == 0 ) { rcode = TRUE; break; } } return( rcode ); } |
In my next article I will show how to use command mode form the application to inspect register contents of a module. The code to do this is already posted; take a look and should you need an explanation, come back in a week or so.
See you soon,
Oleg.
Hi,
When I am sending three plus signs from XCTU my XBEE S1 is entering into command mode. But the same if i am sending from microcontroller (PIC16F877A) XBEE is not entering into command mode. i have tried to send with 1200 and 9600 bps. First i sent plus signs without any delay. then later i tried by introducing different delays from 10 ms to 1 sec. But in all cases i XBee is not entered in AT mode. please help me to solve this problem.
thanks in advance,
R.Pandiaraj