
My most recently completed project is a remote temperature and humidity sensor which wirelessly sends the readings to a base station to display and log the data.
For the base station I used an old iPaq Pocket PC which was past its useful life. The remote sensor is controlled by a PIC18F1220.
The temperature and humidity sensor used is an Sensirion SHT15 which I picked-up from Futurelec. The datasheet claims accuracy of ±2.0% for relative humidity measurements and ±0.4°C for temperature measurements.
The SHT15 gives 14bit temperature and 12bit humidity readings via a 2-wire serial interface. Unfortunately the interface is not I2C compatible so I had to write a Swordfish module to handle communications between the PIC and the sensor - SHT Module
XBee RF modules are used to provide the wireless link between the sensor and the base station. The XBee modules are really easy to use – they have an integrated antenna and are ready to go straight out the box. Apply power, feed serial data into the UART of one and it comes out the other without any setting-up or tweaking.
Multiple XBee modules can be linked together into a Personal Area Network (PAN). Each module in the PAN has its own address and messages can either be broadcast to all modules or addressed to a specific one. The modules will ignore any messages which don’t come from within the PAN.
The PAN ID, module address and other settings are configured using AT commands sent through the module’s UART. I’ve written a small Swordfish module to handle putting the XBee into AT Command Mode and setting / reading the control parameters - XBee Module
The pins on the XBee modules are 2mm pitch so they can’t be plugged directly into breadboard for prototyping. Also, the modules are 3.3V parts which was a problem for me as I’ve only got 5V PICs to hand. To get around these problems I used a Droids breakout board. The board brings the pins out to a 2.54mm pitch and also includes a 3.3V regulator to power the XBee from a 5V supply.
The XBee’s DataIn and DataOut pins connect to the TX and RX pins of the PIC’s USART. As the XBee is a 3.3V device, a voltage divider brings the 5V output of the PIC’s TX pin down to a level which the XBee can handle. The RX pin on the PIC is connected directly to the DataOut of the XBee. The only other connection to the PIC is the XBee’s Sleep pin which is used to put it into a low power mode between transmissions.

The sensor is powered by 2 x AA NiMH batteries. A MAX756 switching step-up regulator generates the 5V needed to power the PIC, SHT15 and XBee. A solar panel charges the batteries during the day and the voltages of the batteries and the solar panel are monitored using the PIC’s ADC.

The PIC used is an 18F1220. To conserve battery power, the PIC and XBee module spend most of the time in Sleep mode. The PIC’s Watchdog Timer is set to wake the PIC every 2.18 minutes (the maximum period possible). Each time the PIC wakes-up it gets three temperature readings and three humidity readings from the SHT15 and stores the median value of each.
Taking three readings and only storing the median value is done so that an anomalous reading caused by a sensor glitch will be ignored. For example, if the three temperature readings are 25°C, 1000°C, and 24°C then the 1000°C reading will be ignored and the value stored will be 25°C.
After 3 sleep / wake cycles (approx. 7minutes) the PIC wakes the XBee module and sends the average of the last three temperature and humidity readings, along with the voltage readings of the battery and solar panel, to the base station.
The complete Swordfish code is listed at the bottom of the page.

The base station is a recycled iPaq Pocket PC running WinCE. It doesn’t have any wireless capability but it does has an RS232 port which I connected to an XBee module via a MAX233 driver. Same as in the remote sensor, a voltage divider is used on the XBee’s DataIn line so the XBee doesn’t see the full 5V output of the MAX233.
The code for the Pocket PC application was written in eMbedded Visual Basic 3.0. As well as displaying the current temperature and humidity, the application stores all the readings from the remote sensor with the date and time in a text log file. A menu option copies the log file to an SD Card so I can get the data off the device and onto a PC.

{ ******************************************************************************** * Name : Remote Temperature Humidity Sensor * * Author : AndyO * * Notice : Copyright (c) 2010 AndyO * * : All Rights Reserved * * Date : 30 January 2010 * * Version : 1.0 * * Notes : Uses watchdog timer to wake up every 2.18mins and take temperature* * : and humidity reading. Every three readings, average of last three* * : readings is sent to Base Station via XBee RF module * * : * * : Data packets sent to Base Station in following format: * * : [#2 "DP" t h s c #3] where * * : * * : #2 (ASCII 2) = Start Of Text * * : "DP" = Data Packet * * : t = Average of last three temperature readings as 5 digit number * * : h = Average of last three humidity readings as 5 digit number * * : s = A/D reading of solar panel voltage as 3 digit no. (255 = 5V) * * : c = A/D reading of battery voltage as 3 digit number (255 = 5V) * * : #3 (ASCII 3) = End Of Text * * : * * : * * : Message packets sent to Base Station in following format: * * : [#2 xx #3] where * * : * * : #2 (ASCII 2) = Start Of Text * * : #3 (ASCII 3) = End Of Text * * : xx = 2 character code for message type: * * : CC = Comms Check (Base Station responds with "CO" (Comms OK)) * * : SP = Sensor self-test Passed * * : SF = Sensor self-test Failed * * : SE = Sensor Error * * : SO = Sensor OK * * : CS = Clear to Send (Will wait 2s for Base Station to send) * * : * * : * * : Message packets sent from Base Station in following format: * * : [#2 xx #3] where * * : * * : #2 (ASCII 2) = Start Of Text * * : #3 (ASCII 3) = End Of Text * * : xx = 2 character code for message type: * * : CO = Comms OK (Sent in reponse to "CC" (Comms Check)) * * : ST = Carry out Sensor Self-Test * * : * * : * * : * ******************************************************************************** } '=============================================================================== 'Device, Clock and Config directives '------------------------------------------------------------------------------- Device = 18F1220 Clock = 4 '---Set-up Watchdog timer Config WDTPS = 32768 'WDT period = 4mS x 32,768 = 131.072S Config WDT = ON '=============================================================================== 'Module options & Includes '------------------------------------------------------------------------------- '---Options #option SHT_Data_Pin = PORTA.0 'Note: Data pin tied to Vdd via 10k #option SHT_Clock_Pin = PORTA.1 ' resistor '---Includes Include "InternalOscillator.bas" 'Configures internal oscillator Include "Utils.bas" Include "USART.bas" Include "Convert.bas" Include "SHT.bas" Include "XBee.bas" '=============================================================================== 'Variable and Const Declarations '------------------------------------------------------------------------------- Dim TemperatureReading(3) As Word 'Array to hold temperature readings Dim HumidityReading(3) As Word 'Array to hold humidity readings Dim ReadingNumber As Byte 'Holds how many readings have been taken Dim TemperatureString As String(6) '}Temperature and humidity readings are Dim HumidityString As String(6) '}sent as 5 digit strings Dim SensorErrorFlag As Boolean 'TRUE if sensor has a problem Dim PanelVoltage As Byte 'Stores solar panel voltage reading Dim CellVoltage As Byte 'Stores battery voltage reading Dim PanelVoltageString As String(4) '}Solar panel and battery voltage Dim CellVoltageString As String(4) '}readings sent as 3 digit strings Dim XBeeSleep As PORTB.Booleans(5) 'TRUE puts XBee into Sleep Mode '---Const strings used for comms with Base Station 'All packets start with StartOfText (ASCII 2) and end with EndOfText (ASCII 3) Const msgCommsCheck = #2 + "CC" + #3 Const msgCommsOK = #2 + "CO" + #3 Const msgSensorOK = #2 + "SO" + #3 Const msgSensorError = #2 + "SE" + #3 Const msgSelfTestPassed = #2 + "SP" + #3 Const msgSelfTestFailed = #2 + "SF" + #3 Const msgClearToSend = #2 + "CS" + #3 Const msgSensorTest = #2 + "ST" + #3 '=============================================================================== 'Subs and Functions '------------------------------------------------------------------------------- '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'Name: FindMedian(pOne as Word, pTwo as Word, pThree as Word) as Word 'Purpose: Finds median of three word values by sorting them into numerical ' order and returning the middle one ' 'Returns: Median of three values '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Function FindMedian(pOne As Word, pTwo As Word, pThree As Word) As Word Dim TempStore As Word If pOne > pTwo Then TempStore = pTwo '}If first value is bigger than second pTwo = pOne '}then swap them pOne = TempStore '} End If If pTwo > pThree Then TempStore = pThree '}If second value is bigger than third pThree = pTwo '}then swap them pTwo = TempStore '} End If If pOne > pTwo Then TempStore = pTwo '}If first value is bigger than second pTwo = pOne '}then swap them pOne = TempStore '} End If Result = pTwo 'Return the middle value End Function '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'Name: Average(pOne as Word, pTwo as Word, pThree as Word) as Word 'Purpose: Finds average of three word values ' 'Returns: Average of three values '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Function Average(pOne As Word, pTwo As Word, pThree As Word) As Word Dim TempStore As LongWord TempStore = pOne + pTwo + pThree TempStore = TempStore / 3 Result = TempStore.Word0 End Function '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'Name: PowerOnSelfTest() as Boolean 'Purpose: Performs power-on initialisation and tests. ' ' Sets-up XBee RF module and does comms check with Base Station ' ' Checks comms with sensor and checks sensor performance by turning on ' heater and checking for temperature rise + humidity fall. ' 'Returns: TRUE if all tests OK, FALSE otherwise '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Function PowerOnSelfTest() As Boolean Dim TempCounter As Byte DelayMS(10000) 'Let XBee module settle If XBee.EnterCommandMode = True Then 'If XBee successfully enters AT Command Mode then: ' '1) Set it to be an End Device which will only associate with a ' Coordinator with the same PAN ID (Base Station's XBee module is ' set-up to be the Coordinator); ' '2) Set the XBee's address to "2" and the destination address to "1" ' ("1" is address of the Base Station's XBee module); ' '3) Set the Sleep Mode and TX power level; ' '4) Save settings and exit AT command mode. ' 'If XBee fails to acknowledge any of the commands then Result = FALSE Result = XBee.SendATCommand(xbNodeIdentifier, "WWRemoteSensor") Result = Result And XBee.SendATCommand(xbPAN_ID, "5757") Result = Result And XBee.SendATCommand(xbCoordinatorEnable, "0") Result = Result And XBee.SendATCommand(xbEndDeviceAssociation, "6") Result = Result And XBee.SendATCommand(xbSourceAddress, "2") Result = Result And XBee.SendATCommand(xbDestinationLow, "1") Result = Result And XBee.SendATCommand(xbSleepMode, "2") Result = Result And XBee.SendATCommand(xbPowerLevel, "4") Result = Result And XBee.SendATCommand(xbSaveParameters, "") XBee.SendATCommand(xbExitCommandMode, "") DelayMS(1500) 'Let XBee module settle If XBee.EnterCommandMode = True Then 'If XBee successfully enters AT Command Mode then poll XBee once 'per second to see if it has Associated with the Coordinator. 'Continue polling for 60 seconds or until XBee associates. TempCounter = 0 Repeat DelayMS(1000) Inc(TempCounter) Until XBee.GetParameterValue(xbAssociationIndication) = "00" Or TempCounter = 60 If TempCounter < 60 Then 'XBee has associated with coordinator 'Exit AT Command Mode XBee.SendATCommand(xbExitCommandMode, "") DelayMS(1000) 'Send "CommsCheck" message to Base Station and wait 2sec for 'reply. If no reply recieved in time then Result = False USART.Write(msgCommsCheck) Result = Result And USART.WaitForStrTimeout(msgCommsOK, 2000) Else 'XBee failed to associate Result = False XBee.SendATCommand(xbExitCommandMode, "") End If Else 'XBee failed to enter AT Command Mode after initial set-up Result = False End If Else 'XBee failed to enter AT Command Mode to do initial set-up Result = False End If '---XBee module checks completed If Result = True Then 'XBee module checks OK, now check SHT15 temperature and humidity sensor SHT.Initialise If SHT.CommsCheck = True Then 'If comms with sensor ok then send message to Base Station USART.Write(msgSensorOK) If SHT.SensorCheck = True Then 'If sensor self-test is okay then send message to Base Station USART.Write(msgSelfTestPassed) Result = True Else 'Sensor did not pass self-test Result = False End If Else 'No comms with sensor Result = False End If End If End Function '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'Name: GetTemperature() as Word 'Purpose: Takes three temperature readings from sensor and returns the median ' reading ' 'Returns: Word value for temperature. By default sensor returns 14bit value ' see datasheet for formula to convert value to centigrade measurement ' If sensor didn't respond, function returns value 65,535 '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Function GetTemperature() As Word Dim Reading(3) As Word Dim TempCounter As Byte For TempCounter = 0 To 2 Reading(TempCounter) = SHT.ReadTemperature If Reading(TempCounter) = 65535 Then '} '}If sensor doesn't respond then SHT.Initialise '}re-initialise sensor before '}taking next reading End If '} DelayMS(500) Next Result = FindMedian(Reading(0), Reading(1), Reading(2)) End Function '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'Name: GetHumidity() as Word 'Purpose: Takes three humidity readings from sensor and returns the median ' reading ' 'Returns: Word value for humidity. By default sensor returns 12bit value ' see datasheet for formula to convert value to percentage measurement ' If sensor didn't respond, function returns value 65,535 '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Function GetHumidity() As Word Dim Reading(3) As Word Dim TempCounter As Byte For TempCounter = 0 To 2 Reading(TempCounter) = SHT.ReadHumidity If Reading(TempCounter) = 65535 Then '} '}If sensor doesn't respond then SHT.Initialise '}re-initialise sensor before '}taking next reading End If '} DelayMS(500) Next Result = FindMedian(Reading(0), Reading(1), Reading(2)) End Function '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'Name: GetVoltages 'Purpose: Reads Solar Panel and Battery Voltage and stores results in ' PanelVoltage and CellVoltage byte variables ' 'Notes: Solar Panel output connected to AN2 (PortA.2, Pin 6). Battery ' output connected to AN3 (PortA.3, Pin 7). 8 bit result (0 = 0V, ' 255 = 5V) '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sub GetVoltages() ADCON0.0 = 1 'Turn on A/D module ADCON0.4 = 0 '} ADCON0.3 = 1 '}Set A/D channel to AN2 (Solar Panel) ADCON0.2 = 0 '} ADCON0.1 = 1 'Start conversion While ADCON0.1 = 1 '}Wait for conversion to finish Wend '} PanelVoltage = ADRESH 'Store result (8 MSBs only) ADCON0.4 = 0 '} ADCON0.3 = 1 '}Set A/D channel to AN3 (Battery) ADCON0.2 = 1 '} ADCON0.1 = 1 'Start conversion While ADCON0.1 = 1 '}Wait for conversion to finish Wend '} CellVoltage = ADRESH 'Store result (8 MSBs only) ADCON0.0 = 0 'Turn off A/D module End Sub '=============================================================================== 'Main Program '------------------------------------------------------------------------------- '---Set-up analog to digital converter ADCON0.7 = 0 '}Set A/D Vref+ and Vref- to Vdd and Vss ADCON0.6 = 0 '} ADCON1 = %01110011 'AN2 + AN3 set to analog inputs ADCON2.7 = 0 'A/D result left justified ADCON2.5 = 1 '} ADCON2.4 = 0 '}Set A/D Aquisition time to 12 Tad ADCON2.3 = 1 '} ADCON2.2 = 0 '}Set A/D Converstion Clock to Fosc / 8 ADCON2.1 = 0 '}This is the minimum possible with a ADCON2.0 = 1 '}clock speed of 4MHz (Tad = 2uS) '---Enable sleep mode by clearing SCS1:SCS0 of OSCCON OSCCON = OSCCON And %11111100 '---Initialise vars ReadingNumber = 0 SensorErrorFlag = False '---Program Start Output(PORTB.5) 'Set XBee sleep pin to output XBeeSleep = False 'Wake XBee If PowerOnSelfTest = False Then '} '} ASM '}Do Power-On-Self-Test. If it fails reset '}then reset PIC End ASM '} '} End If '} XBeeSleep = True DelayMS(10000) 'Delay to allow sensor to cool down to 'ambient temperature after self-test '---Get initial temperature, humidity and voltage readings and transmit them GetVoltages 'Get battery and solar voltages PanelVoltageString = DecToStr(PanelVoltage, 3) '}Convert voltage readings to CellVoltageString = DecToStr(CellVoltage, 3) '}strings to send TemperatureString = DecToStr(GetTemperature, 5) '}Get temp and humidity readings HumidityString = DecToStr(GetHumidity, 5) '}and convert to strings to send XBeeSleep = False 'Wake XBee DelayMS(20) 'Let XBee wake-up 'Send readings USART.Write(2, "DP ", TemperatureString, " ", HumidityString, " ", PanelVoltageString, " ", CellVoltageString, 3) DelayMS(10) 'Let TX finish XBeeSleep = True 'Put XBee to sleep ASM '}Put PIC to sleep. Will wake-up sleep '}and resume code execution on End ASM '}Watchdog Timer time-out (2.18mins) '---Main Program Loop While True Repeat 'Loop to take 3 temperature and humidity readings, sleeping between readings 'Get a temperature and humidity reading and store in array TemperatureReading(ReadingNumber) = GetTemperature HumidityReading(ReadingNumber) = GetHumidity If TemperatureReading(ReadingNumber) = 65535 Or HumidityReading(ReadingNumber) = 65535 Then 'If there was a problem getting a reading then set SensorErrorFlag SensorErrorFlag = True End If Inc(ReadingNumber) If ReadingNumber = 3 Then ReadingNumber = 0 Else ASM '}Put PIC to sleep. Will wake-up sleep '}and resume code execution on End ASM '}Watchdog Timer time-out (2.18mins) End If Until ReadingNumber = 0 '---Three readngs have been taken - send data XBeeSleep = False 'Wake XBee DelayMS(20) 'Let XBee wake-up GetVoltages 'Read solar panel and battery voltages If SensorErrorFlag = False Then '3 readings were taken without a problem. Average readings and convert 'to strings TemperatureString = DecToStr(Average(TemperatureReading(0), TemperatureReading(1), TemperatureReading(2)), 5) HumidityString = DecToStr(Average(HumidityReading(0), HumidityReading(1), HumidityReading(2)), 5) 'Convert voltage readings to strings PanelVoltageString = DecToStr(PanelVoltage, 3) CellVoltageString = DecToStr(CellVoltage, 3) 'Send readings USART.Write(2, "DP ", TemperatureString, " ", HumidityString, " ", PanelVoltageString, " ", CellVoltageString, 3) Else 'Problem getting readings - send error message to Base Station USART.Write(msgSensorError) End If USART.Write(msgClearToSend) 'Request any commands from Base Station If USART.WaitForStrTimeout(msgSensorTest, 1000) = True Then 'If Base Station requests Sensor Test then do test If SHT.SensorCheck = True Then 'Sensor test ok, send result to Base Station and clear error flag USART.Write(msgSelfTestPassed) SensorErrorFlag = False Else 'Sensor test failed, send result to Base Station and set error flag USART.Write(msgSelfTestFailed) SensorErrorFlag = True End If End If DelayMS(10) 'Let TX finish XBeeSleep = True 'Put XBee to sleep ASM '}Put PIC to sleep. Will wake-up sleep '}and resume code execution on End ASM '}Watchdog Timer time-out (2.18mins) Wend