Wireless Temperature Display

Wireless temperature display with LCD HD44780 Hitachi controller

Along with the wireless temperature sensors I have been playing with I wanted to have a small display that would show the temperatures in various locations.  I wanted this display device to be small, easy to use, and be semi portable.

The wireless temperature display is based on an LCD with an HD44780 Hitachi controller.  These are plentiful and can be found almost any where.  I purchased a few 24×2 green displays.  They were about $4 each which makes them practical and very affordable.  If you do some searching on EBay I’m sure you can even beat the price I got.

Well enough with the background lets get down to business.  I based my display on the USB receiver I developed in another article.  If you refer to that article I talk about the receiver and how to implement it.  There were a lot of unused pins on the PIC24FJ64GB002.  With those pins I hooked up my 24×2 HD44780 display.  This was pretty straight forward to do.  See the following schematic:

Wireless LCD Receiver

Wireless LCD Receiver

You can see from the schematic that the display hooks right up to the microcontroller.  I’m using the 4 bit bus mode in the HD44780 which is why data bits D0-D3 are tied to ground.  They are not used in this mode.  We still have the receiver and USB port as well as the programming header.  One thing that was added is a button.  I’ll explain what I use the button for in a minute.

The user interface to the device is very simple.  There is just one button and when you push that button it rotates through the different temperatures read and displays this selected temperature on the second line of the display.  This temperature will remain on the second line of the display all the times so, if you want to monitor a certain temperature this is the way to do it.  If you want to go through and see all the temperatures at your own pace you can do that with the button.  The first line of the display is used to show the current temperature received from the receiver.  This can change rapidly sometimes if the temperature sensors are almost in sync.  That is why I added the second line of the display and the button.

I had some issues with the display.  There are traces on the display which very close to the holes that I had to enlarge to get my screws to fit.  I used some metal screws and found that I was shorting out the power and ground.  USB doesn’t like when you do this.  The USB port kept enumerating as the USB hub would turn off power to the device as it was drawing excessive current due to the short.  I was thinking that maybe the hub I was using couldn’t supply enough current to the device I tried a USB power supply and that poor little supply gave up the ghost.  It fried a transistor in the device.  That is when I new there was a problem with the device itself.  Too bad it cost me a perfectly good USB power supply.  The way I solved this problem was to order some nylon nuts and screws.  Now there is no more shorting and everything is working great.

The code to support interface to the LCD is in the latest library.  Make sure you look at the release notes for the library to make sure that it has the LCD routines in that library.  I try and explain all the features in the library through the readme.

The box is a little big.  It is the only enclosure I had that would accommodate the display.  The display is kind of big being 24×2.  It is long a lot longer than the 16×2 displays.

Open Case (Wireless Temperature Display)

Open Case (Wireless Temperature Display)

Here you can see inside the case.  There are a few wires involved in hooking up the LCD.  I used some female to female jumpers to hook the LCD to the receiver board.  The receiver board has some .1″ center male connectors hot glued to the board.  If you look close you can see the variable resistor for the contrast setting of the LCD.  The long green wire that loops around the case is the antenna for the receiver.

Wireless USB Temperature Display Working

Wireless USB Temperature Display Working

In the above photo you can see the wireless display in action.  It is showing the temperature in Fahrenheit in my garage on the bottom line and the top line is showing the temperature of some sensor I haven’t programmed into the system yet so, it is marked as unknown.  The three temperatures on each line is from left to right the current temperature, the low since the unit was reset, and the high since the unit was reset.  You can reset the high’s and low’s using the button and holding it for 2 seconds.  If you press the button for less than 2 seconds you can toggle through the temperatures displayed on the bottom line.  You can also see the power cord in the top of the unit.  This is simply a USB cable currently hooked up to a powered USB hub and nothing else.  If you look closely to the metal screw in the upper right hand corner you can see that it is defective.  It wasn’t cast properly and the Philips head pattern in the screw is filled in.  I had to use some needle nose pliers to get it in as far as I did.  That is the only complaint I have against the enclosure.  You can also see the nylon screws I purchased.  They work great.  They hold the display solidly and don’t short out the power and ground on the display.

The unit works as well if not better than the receiver I have hooked up to a Raspberry Pi.  That is another story.

Back of 24x2 LCD (Wireless Temperature Display)

Back of 24×2 LCD (Wireless Temperature Display)

Here you can see the back of the LCD I used in the project.

Front of 24x2 LCD (Wireless Temperature Display)

Front of 24×2 LCD (Wireless Temperature Display)

Here you can see the front of the LCD.  You can also see the camera I used to take the picture reflected in the LCD display.  The display gives some extra characters over a traditional 16×2 display but, it is bigger and more expensive.  I have seen 16×2 displays for $2 US where these 24×2 cost me about $4 US so twice as much.  It just depends on what you need.  I had these on hand so didn’t need to order anything to complete the display.  It was a fun project and if you have any questions don’t hesitate to ask.

This is the code.  You can see how to use the LCD library.  Remember that the latest library supports the following code.

#include "./USB/usb.h"
#include "./USB/usb_function_cdc.h"

#include "HardwareProfile.h"
#include "rtos.h"
#include "pins.h"
#include "uart1.h"
#include "uart1Pins.h"
#include "lcd.h"
#include "initializeSystem.h"

#define NUMSENSORS 32
//#define LCD

extern int numBytesRead;         // USB variable
extern char USB_In_Buffer[256];  // USB in buffer
extern char USB_Out_Buffer[64];  // USB out buffer
extern unsigned char NextUSBOut; // USB number of bytes to write
char curlstr[25];    // variable to creat line for display
char tstr[NUMSENSORS][32];   // save all old temp sensor line outputs

char debug[16];

// minimum temperature by sensor
float min[NUMSENSORS] = {
   99.0,99.0,99.0,99.0,99.0,99.0,99.0,99.0,
   99.0,99.0,99.0,99.0,99.0,99.0,99.0,99.0,
   99.0,99.0,99.0,99.0,99.0,99.0,99.0,99.0,
   99.0,99.0,99.0,99.0,99.0,99.0,99.0,99.0};
// maximum temperature by sensor
float max[NUMSENSORS] = {
   -9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0,
   -9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0,
   -9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0,
   -9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0,-9.0};

unsigned int lastGoodID;
unsigned int displayID = 0;

unsigned int swState = 0;   // button handler state variable
unsigned int pState = 0;    // uart handler state variable

unsigned char lsNibble;    // used to decode manchester
unsigned char msNibble;    // used to decode manchester
unsigned int packCnt;      // used to count the number of chars in a packet

unsigned char packet[32];  // space to receive a packet
unsigned int packPtr;      // pointer into the above packet
unsigned int sum;          // This is to verify the sum of the packet

unsigned int buttonDown;

/** CONFIGURATION **************************************************/
_CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & ICS_PGx1 & FWDTEN_OFF & WINDIS_OFF & FWPSA_PR32 & WDTPS_PS8192)
_CONFIG2(IESO_OFF & FNOSC_FRCPLL & OSCIOFNC_ON & POSCMOD_NONE & PLL96MHZ_ON & PLLDIV_DIV2 & FCKSM_CSDCMD & IOL1WAY_OFF)
_CONFIG3(WPFP_WPFP0 & SOSCSEL_IO & WUTSEL_FST & WPDIS_WPDIS & WPCFG_WPCFGDIS & WPEND_WPENDMEM)
_CONFIG4(DSWDTPS_DSWDTPS3 & DSWDTOSC_LPRC & RTCOSC_LPRC & DSBOREN_OFF & DSWDTEN_OFF)

int charToSend;
int charSending;
int bitToSend;
char vers[NUMSENSORS][16];
char volts[NUMSENSORS][16];

void handUSB(void);
void delayLEDOff(void);

void
defaultMinMax(void)
{
   int i;

   for (i=0; i<NUMSENSORS; i++)
   {
      min[i] = 99.9;
      max[i] = -30.0;
   }
}

void
handUSB(void)
{
#if 0
   if (numBytesRead > 0)
   {
      if (USB_In_Buffer[0] == 'B')
      {
         strcpy(USB_Out_Buffer,"This is a B\r\n");
         NextUSBOut = strlen(USB_Out_Buffer);
      }
      else
      {
      strncpy(USB_Out_Buffer,USB_In_Buffer,numBytesRead);
      NextUSBOut = numBytesRead;
      }
      numBytesRead = 0;
   }
#endif
   if (numBytesRead > 0)
   {
     charToSend = USB_In_Buffer[0];
     //USB_Out_Buffer[0] = charToSend;
     //USB_Out_Buffer[1] = 0;
     //strcat(USB_Out_Buffer," = ");
     strcpy(USB_Out_Buffer,tstr[displayID]);
     NextUSBOut = strlen(USB_Out_Buffer);
     numBytesRead = 0;
   }
   queueUSB(handUSB);
}

unsigned char DecodeData(unsigned char encoded) 
{
   unsigned char i,dec,enc,pattern; 
   enc = encoded; 
   if (enc == 0xf0) // start/end condition encountered 
      return 0xf0; 
   dec = 0; 
   for (i=0; i<4; i++) 
   { 
      dec >>=1; 
      pattern = enc & 0b11; 
      if (pattern == 0b01) // 1 
         //bit_set(dec,3); 
         dec |= 0x08;
      else if (pattern == 0b10) 
         //bit_clear(dec,3); // 0 
         dec &= 0xF7;
      else 
         return 0xff; // illegal code 
      enc >>=2; 
   }
   return dec; 
}

char pPack[64];

void
packPrint(char *pack)
{
#ifdef LCD
   lcdGoto(1,1);
   lcdWrite(pack);
#endif
}

void
packPrintProt(char *pack)
{
   strcpy(USB_Out_Buffer,pPack);
   strcat(USB_Out_Buffer,"\r\n");
   NextUSBOut = strlen(USB_Out_Buffer);
}

void
packPrint2(char *pack)
{
#ifdef LCD
   lcdGoto(2,1);
   lcdWrite(pack);
#endif
}

void
packHandler(unsigned char *pack)
{
   float temperature;
   char tChar[32];
   int i;

   pPack[0] = 0;
   for (i=0; i<pack[1]+3; i++)
   {
      sprintf(tChar,"0x%02X ",pack[i]);
      strcat(pPack,tChar);
   }
   packPrintProt(pPack);

   switch (pack[2])
   {
      case 11:
         sprintf(curlstr,"Shop   ");
         break;
      case 1:
         sprintf(curlstr,"BkFnce ");
         break;
      case 2:
         sprintf(curlstr,"Poting ");
         break;
      case 4:
         sprintf(curlstr,"Garage ");
         break;
      case 5:
         sprintf(curlstr,"BkPrch ");
         break;
      case 6:
         sprintf(curlstr,"FdStrg ");
         break;
      case 7:
         sprintf(curlstr,"Office ");
         break;
      case 8:
         sprintf(curlstr,"FtPrch ");
         break;
      case 9:
         sprintf(curlstr,"Traler ");
         break;
      default:
         sprintf(curlstr,"Unknwn ");
         break;
   }

   switch(pack[1])
   {
      case 5:
         temperature = (float)((signed char)pack[5]);
         if (pack[6] > 0)
            temperature += .5;
         temperature = (temperature * 9.0) / 5.0 + 32.0;
         if (temperature < min[pack[2]])
            min[pack[2]] = temperature;
         if (temperature > max[pack[2]])
            max[pack[2]] = temperature;
         sprintf(tChar,"%.2f %.2f %.2f",temperature,min[pack[2]],max[pack[2]]);
         strcat(curlstr,tChar);
//     strcpy(USB_Out_Buffer,curlstr);
//     NextUSBOut = strlen(USB_Out_Buffer);
         packPrint(curlstr);
         strcpy(tstr[pack[2]],curlstr);
         //sprintf(debug,"pack[2] = 0x%02X",pack[2]);
         //packPrint2(debug);
         if (displayID == pack[2])
         {
            packPrint2(curlstr);
         }
         break;
      case 4:
         switch (pack[3])
         {
            case 0:
               // VCC
               //sprintf(curlstr,"VCC 0x%02X 0x%02X\n",pack[4],pack[5]);
               sprintf(volts[pack[2]]," VCC 0x%02X 0x%02X",pack[4],pack[5]);
               break;
            case 1:
               temperature = (float)((signed char)pack[4]);
               if (pack[5] > 0)
                  temperature += .5;
               temperature = (temperature * 9) / 5 + 32;
               if (temperature < min[pack[2]])
                  min[pack[2]] = temperature;
               if (temperature > max[pack[2]])
                  max[pack[2]] = temperature;
               sprintf(tChar,"%.2f %.2f %.2f\n",temperature,min[pack[2]],max[pack[2]]);
               strcat(curlstr,tChar);
               //if (strlen(volts[pack[2]]) > 0)
               //{
               //   sprintf(curlstr,"%s",volts[pack[2]]);
               //}
               //if (strlen(vers[pack[2]]) > 0)
               //{
               //   sprintf(curlstr,"%s",vers[pack[2]]);
               //}
               //sprintf(curlstr,"\n");
               packPrint(curlstr);
               strcpy(tstr[pack[2]],curlstr);
               //sprintf(debug,"pack[2] = 0x%02X",pack[2]);
               //packPrint2(debug);
               if (displayID == pack[2])
               {
                  packPrint2(curlstr);
               }
               break;
            case 5:
               // Version
               //sprintf(curlstr,"Version 0x%02X 0x%02X\n",pack[4],pack[5]);
               sprintf(vers[pack[2]]," Ver 0x%02X 0x%02X",pack[4],pack[5]);
               break;
            default:
               //packPrint(pack);
               break;
         }
         break;
   }
}

/******************************************************************************
 * Function:  void handUart1(void)
 *
 * Input:     None
 *
 * Output:    None
 *
 * Overview:  This routine handles all the incoming data from the Linx
 *   receiver.  It decodes the data coming from the Linx and decodes the
 *   manchester encoding that the transmitter does.  It is run by a state
 *   machine.
 *
 * Note:      None
 *****************************************************************************/
void
handUart1(void)
{
  unsigned char tVal;
  //unsigned int i;
  //unsigned int j;
  //int temperature;

  while (uart1IsChar())
  {
    tVal = uart1GetChar();
    switch (pState)
    {
      case 0:
        // wait till we have a start of communication character
        if (tVal == 0x55)
        {
          pState = 1;
        }
        break;
      case 1:
        // wait till the start of communication character stops
        if (tVal != 0x55)
        {
          packPtr = 0;
          lsNibble = DecodeData(tVal);
          if (lsNibble != 0xFF)
          {
            pState = 2;
          }
          else
          {
            pState = 0;
          }
        }
        else
        {
        }
        break;
      case 2:
        // decode data
        msNibble = DecodeData(tVal);
        // if no error
        if (msNibble != 0xff)
        {
          msNibble <<= 4;
          lsNibble |= msNibble;
          // rebuild byte and see if it is
          // a start of packet if so next state
          // and init variables
          if (lsNibble == 0x7E)
          {
            //printf("0x7E ");
            //packPrint("Got 0x7E");
            sum = 0;
            packPtr = 0;
            packet[packPtr++] = 0x7E;
            pState = 3;
          }
        }
        else
        {
          pState = 0;
        }
        break;
      case 3:
        lsNibble = DecodeData(tVal);
        if (lsNibble != 0xff)
          pState = 4;
        else
          pState = 0;
        break;
      case 4:
        msNibble = DecodeData(tVal);
        if (msNibble != 0xff)
        {
          msNibble <<= 4;
          lsNibble |= msNibble;
          sum += lsNibble;
          packet[packPtr++] = lsNibble;
          //printf(" 0x%02X ",lsNibble);
          //sprintf(debug,"count = 0x%02X",lsNibble);
          //packPrint(debug);
          packCnt = lsNibble;
          pState = 5;
        }
        else
        {
          pState = 0;
        }
        break;
      case 5:
        lsNibble = DecodeData(tVal);
        if (lsNibble != 0xff)
          pState = 6;
        else
          pState = 0;
        break;
      case 6:
        msNibble = DecodeData(tVal);
        if (msNibble != 0xff)
        {
          msNibble <<= 4;
          lsNibble |= msNibble;
          //printf("0x%02X ",lsNibble);
          //sprintf(debug,"got data 0x%02X",lsNibble);
          //packPrint(debug);
          sum += lsNibble;
          packet[packPtr++] = lsNibble;
          packCnt--;
          if (packCnt == 0)
          {
            pState = 7;
          }
          else
          {
            pState = 5;
          }
        }
        else
        {
          pState = 0;
        }
        break;
      case 7:
        lsNibble = DecodeData(tVal);
        if (lsNibble != 0xff)
          pState = 8;
        else
          pState = 0;
        break;
      case 8:
        msNibble = DecodeData(tVal);
        if (msNibble != 0xff)
        {
          msNibble <<= 4;
          lsNibble |= msNibble;
          //printf("0x%02X\n",lsNibble);
          packet[packPtr++] = lsNibble;
          //sprintf(debug,"0x%02X 0x%04X",lsNibble,sum);
          //packPrint(debug);
          if ((sum & 0xFF) == lsNibble)
          {
            //packPrint("Got good packet");
            //printf("Got good packet\n");
            //packPrint(packet);
            lastGoodID = packet[2];
            packHandler(packet);
          }
          else
          {
          }
          pState = 0;
        }
        else
        {
          pState = 0;
        }
        break;
    }
  }
  queueSerial1(handUart1);
}

/******************************************************************************
 * Function:  void buttonHandler(void)
 *
 * Input:     None
 *
 * Output:    None
 *
 * Overview:  This routine handles button presses and moves to the next
 *   sensor data.  If you have come to the end of the sensor data it moves to
 *   the beginning of the list.
 *
 * Note:      None
 *****************************************************************************/
void
buttonHandler(void)
{
  // the button handler is based on a state machine.
  // The first state waits for a button press.  A button press is signified
  // by a low value coming from the input pin attached to the button.  The
  // next thing it does is wait some time to debounce the switch and make sure
  // that it is really down.  If it is really down it records this sensor
  // id for the persistant display.  If it detects that the button is still
  // up or is being noisy it goes back to state 0 and starts again.
  // On to state 2.  This state waits till the button has been released.
  // This is signified by the input pin going high when the button is released.
  // Again the state after this (3) is used to debounce the switch going
  // high.  This button routine works well.
  switch (swState)
  {
    case 0:
      // wait for button press
      if (digitalRead(12) == 0)
      {
        swState = 1;
      }
      break;
    case 1:
      // debounce button press if not pressed
      // return to state 0 else record sensor data
      if (digitalRead(12) == 0)
      {
         buttonDown = 0;
         //displayID = lastGoodID;
         displayID++;
         if (displayID >= NUMSENSORS)
         {
            displayID = 0;
         }
         while (tstr[displayID][0] == 0)
         {
            displayID++;
            if (displayID >= NUMSENSORS)
            {
               displayID = 0;
            }
         }
         //sprintf(debug,"0x%02X",displayID);
         //packPrint2(debug);
         packPrint2(tstr[displayID]);
         swState = 2;
      }
      else
      {
        swState = 0;
      }
      break;
      // wait for button to be released
    case 2:
      if (digitalRead(12))
      {
        swState = 3;
      }
      buttonDown++;
      if (buttonDown > 200)
      {
         defaultMinMax();
         lcdGoto(2,1);
         lcdWrite("Reset Min and Max       ");
         swState = 4;
      }
      break;
    case 3:
      // debounce button release if not released
      // go back to state 2
      if (digitalRead(12))
      {
        swState = 0;
      }
      else
      {
        swState = 2;
      }
      break;
    case 4:
      if (digitalRead(12))
      {
        swState = 3;
      }
      break;
    default:
      swState = 0;
      break;
  }
  // reschedule this routine.  You have to do
  // this or the buttonHandler won't be run again!
  delayMS(buttonHandler,10);
}

/******************************************************************************
 * Function:        void main(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        Main program entry point.
 *
 * Note:            None
 *****************************************************************************/
int main(void)
{   
   //timer1Init();
   //timer2Init();
   //RTOSInit();
   initializeSystem();
   // TODO need to fix these names not good.  They are too close.
   InitializeSystem();
   int i;

   for (i=0; i<NUMSENSORS; i++)
      tstr[i][0] = 0;

   //uart1Pins(24,11);
   uart1Pins(24,10);
   uart1Init(9600);

   PORTB = 0;
   TRISB = 0xFC43;

#ifdef LCD
   lcd_initialize(11,7,6,18,17,16,14);
#endif

   //lcdGoto(1,1);
   //lcdWrite("Temperature Display v0.1");

   queueUSB(handUSB);

   pinMode(PIN6,OUTPUT);

   pinMode(PIN26,OUTPUT);
   digitalWrite(PIN26,HIGH);

   queueSerial1(handUart1);

#ifdef LCD
   delayMS(buttonHandler,10);
#endif

   pinMode(12,INPUT);   

   while(1)
   {
      runNext();
   }//end while
}//end main