A Bar graph outside temperature thermometer for remote viewing with the Security camera system.

Updated 3/12/17

Key Search Words: PIC Microcontroller, CCS C Compiler, PIC Projects

 The goal of this project was simple - to be able to see the temperature outside at our home when at work or on vacation to monitor for subfreezing temperatures which can cause icy roads on the way home, and too warm temperatures for running early that evening. The display had to be big, easy to read and sharp on the security web cams we have in our house when viewed remotely on the internet. Big digits might have been fine, but I always wanted to build a bar graph type display and this was the perfect project!

We are using a LMC36 analog temperature sensor, which puts out 10mv / degrees C + 500mv. This can be read with the 10 bit A/D and converted to degrees F for the display. A large 40 pin processor was used, the PIC16F887 to address all 22 LEDs, and a fast 20MHz ceramic resonator was used for timing so to multiplex each LED on for only 250uS. This might seem quite fast - but this was the speed it took to make the display not flicker. The LEDs are quite bright and can be seen very nicely even with such a short on time. Here are some details of the construction of this instrument:

The prototype on the bench consisted of the processor, and LCD display to check the math, and all the tri color LEDs. Cold temps from 0 to 4 are blue, 5 - 7 are green, and 8 - 9 are red. By using standard tri color RGB LEDs, the brightness of each color at a given current is similar. Im driving all the LEDs with a single 470 ohm resistor. This works because there is ONLY one LED on at a time but since the cycling of the LEDs is so fast - it appears to the eye to be continuous.

With the prototype finished and all the C code finished, I started work on the display panel. it is a 1/4" thick panel of Ash that has been stained, polyurethened and drilled out to press fit in all the LEDs. Lots of wires! You can see the small circuit board I constructed at top. It is critical of course to use a precision voltage reference for the A/D converter. I am using a 2.500v ref to keep the readings deadly accurate.

On the front, you see three columns of lamps. The first is only 2 LEDs and is the 100's digit. The second is 9 LEDs and is the 10's and finally the third row is the 1's. So since the bottom row is zero - here is 61F. (actually it reads "061")

The temp probe had to reach through the wall and go past about 8 inches. To do this, the wires had to not pick up 60Hz hum, or noise and experiments indicated I could go about 4 feet using three parallel wires from ribbon cable and it worked great. Here a 2 foot length of ribbon is connected to the sensor for calibration tests.

The panel overlay was drawn up first in Autocad to get the dimensions perfect, then imported into Photoshop for the final graphics which were overlain on the Autocad drawing. Then the graphic was printed on a color laser printer at a print house locally. This was then laminated (by them) for the final overlay. I then cut it out, punched the holes (with a hammer and punch set from Harbor Freight) for the LEDs and mounted it over the panel using Silicone adhesive.

Final appearance with the laminated overlay in place. You can also see the sensor cable mounted inside a 3/8 vinyl tube with the end sealed in silicone adhesive for external mounting. It is totally waterproof!

Side view. I made a box frame up out of the same /4" ash, painted it black and glued it on the back over the LED wiring. The circuit board is mounted over that on a bracket. The back panel is a piece of 1/8" lexan with a notch cut in for clearing the sensor tubing. To power this with 12 volts, a small wall adapter also goes into the back panel seen here as the black box on the left. Some nice legs on the sides of the panel prop it up and are adjustable to tilt the panel as needed.

Final resting place is on a computer desk that faces the security camera. The vinyl tube can be seen going through a special panel we put in when we built the house for running cables and wires to the outside. (the panel is off here)

And on the outside you see this - the tube exits the plastic pipe going through the wall and is held up nice and level with a stake with a hole in it that fits the tubing. This is on the north side of the house and is not affected by the sun beating on it either. Seems very accurate so far and agrees very well with the other external thermometers we have near by.

In conclusion, this was a very fun project, giving me both good experience in keeping my skills up in programming in C, and ending up with a very accurate outside thermometer that is readable anywhere in the world over the internet. Now, what do I do with the other five sensors I bought??? :)

Schematic:

Left: schematic for the bar graph thermometer Click to enlarge to full size

Code with CCS C:

//****************************************************************************
//Chris Schur
//(Bargraph thermometer with LMC36 sensor - 16F887)
//Date: 12/23/16
//****************************************************************************

/* Description of this Program:

//I/O Designations ---------------------------------------------------
// RA0: (AN0) Analog input for Sensor
// RA1: (AN1)
// RA2: (AN2)
// RA3: (AN3) Input - analog ref for a/d
// RA4: (Open Collector output)
// RA5: (AN4)
// RA6: OSC OUT
// RA7: OSC IN

// RB0: Status LED output
// RB1: LCD output
// RB2: 1s - 9
// RB3: 1s - 8
// RB4: 1s - 7
// RB5: 1s - 6
// RB6: 1s - 5
// RB7: 1s - 4

// RC0: 1s - 3
// RC1: 1s - 2
// RC2: 1s - 1
// RC3: 1s - 0
// RC4: 10s - 9
// RC5: 10s - 8
// RC6: 10s - 7
// RC7: 10s - 6

// RD0: 10s - 5
// RD1: 10s - 4
// RD2: 10s - 3
// RD3: 10s - 2
// RD4: 10s - 1
// RD5: 10s - 0
// RD6: 100s - 1
// RD7: 100s - 0

// RE0: (AN5)
// RE1:
// RE2:
// RE3: MCLR input

//--------------------------------------------------------------------

//Include Files:
#include <16F887.h> //Normally chip, math, etc. used is here.

//Directives and Defines:
#device ADC=10 //Set ADC when used to 10 bit = 0 - 1023
#fuses NOPROTECT,HS,NOWDT //xtal is used
#use delay(crystal=20MHz) //xtal speed
#use fast_io(ALL) //must define tris below in main when using this

#define LED (Pin_B0)

//for new New Haven 16 x 2 Serial LCD:
#use rs232(baud=9600, xmit=Pin_B1, bits=8, parity=N, stream=SERIALNH)

 

//***Global Variables:********************************************************

int16 RESULT0; // AD0 conversion of sensor - 10 bit
float tcsum; //C temp averageing accumulator sum
float tc; //final averaged temp in degrees c with decimal
float tf; //final temp in F
float volts; //calculated measured voltage mv in from sensor
int8 n; //counting var
int8 tempFint; //integer part of temp in F
int8 l,m,h; //first, second, third part of int temp F digits 075
int16 dly; //delay in us between multiplexeing.

//***Functions/Subroutines, Prototypes:***************************************
void LCDCLR(); //Clears LCD Display:
void LCDLN2(); //Sets LCD to line 2 start point
void LCDOFF(); //Sets LCD backlight to OFF
void LCDON(); //Sets LCD backlight to ON
void CALCC(); //Calculates C from reading
void CALCF(); //calculates F
void HUNDAV(); //Takes 100 average
void CALCDIG(); //calculates bar graph digits
void SENDBAR1(); //SENDS data to bar graph leds 1'S
void SENDBAR10(); //SENDS data to bar graph leds 10's
void SENDBAR100(); //SENDS data to bar graph leds 100's

//****************************************************************************
//***-- Main Program*********************************************************

void main(void) {

// Set TRIS I/O directions, define analog inputs, compartors:
set_tris_A(0b10111111);
set_tris_B(0b00000000);
set_tris_C(0b00000000);
set_tris_D(0b00000000);
set_tris_E(0b1111);

//Initialize variables and Outputs: --------------------------------------
output_low(Pin_B0); //status LED off
output_b(0b11111111); //set all LEDs to off
output_c(0b11111111);
output_d(0b11111111);

delay_ms(2000); //LCD warmup time


//Set all PortA to ANALOG:
SETUP_ADC_PORTS(sAN0 | sAN1 | sAN2 | sAN3 | sAN4 | sAN5,VSS_VREF);
SETUP_ADC(ADC_CLOCK_DIV_32);

RESULT0 = 0; //meas voltage raw 10 bit


//----------------------------------------------------------------

//MAIN LOOP:

LCDCLR(); //initial splash screen
delay_ms(25);
fprintf(SERIALNH,"LMC36 Sensor");
delay_ms(2000); //Display time

dly = 500; //multiplexing spacing in us

while (true) {

output_high(LED); //Light Status LED at start of averaging set
HUNDAV(); //Take 100 readings and average
CALCF(); //calculate F from averaged C set
CALCDIG(); //calculate the digits for bargraph display
output_low(LED); //Off at end of calc and averaging set

//send value to single LEDs on bar graph:

SENDBAR1();
SENDBAR10();
SENDBAR100();

/*
//Send to display raw value
LCDCLR();
delay_ms(25);
//fprintf(SERIALNH,"Raw = %Lu",RESULT0); //0 - 1024
//fprintf(SERIALNH,"Volts = %3.1fv",volts); //send 3 total digits, 1 is decimal.
//fprintf(SERIALNH,"C= %3.1f",tc);
fprintf(SERIALNH,"F= %3.1f",tf);
delay_ms(25);
LCDLN2();
fprintf(SERIALNH,"%u ",tempFint);
fprintf(SERIALNH,"%u ",l);
fprintf(SERIALNH,"%u ",h);
fprintf(SERIALNH,"%u",m);

delay_ms(100); //Display time

*/

tcsum = 0; //reset accum and temp
tc = 0;

} //elihw

} //naim

//********* Functions which have prototypes at top of program ****************

//functions for NEW HAVEN LCD display:
//Clears LCD Display:
void LCDCLR() {
fputc(0xFE,SERIALNH); //Command Prefix
fputc(0x51,SERIALNH); //Clear screen
}

//Sets LCD to line 2 start point
void LCDLN2() {
fputc(0xFE,SERIALNH); //Command Prefix
fputc(0x45,SERIALNH); //set cursor command
fputc(0x40,SERIALNH); //Set cursor to next line, pos 40 = start line 2
}

void CALCC() {
//take reading on analog 1 input:
set_adc_channel(0); //Select channel
//delay_ms(10);
RESULT0 = read_adc(); //0 - 5v on input = 0 - 1024
//cal factor:
//RESULT0 += 1; //same as: RESULT0 = RESULT0 + 2

//Next calculate the actual voltage in millivolts:

//for 5v ref, we use 4.88mv/bit. For 2v ref we use 1.953mv/bit
// 2.5v ref - 2.441
volts = 2.441 * (float)RESULT0;
//Next we convert to C:
tc = (volts - 500) / 10;
}

void HUNDAV() { //Takes 100 average
//take av of 100 readings
for (n=0; n<=10; n++) {
CALCC();
tcsum = tcsum + tc; //100 av sum
}

tc = tcsum / 10; //100 av
}

void CALCF() { //calculates F from C
tf = (1.8 * tc) + 30; //adjust the 32 for cal factor!
}

void CALCDIG() {

//calculate digits of temp:
if(tf <=0)
tf = 0; //reads 0 if we go negative temps.

tempFint = tf; //convert to integer portion
l = tempFint/100;
h = tempFint/10;
m = tempFint - (h*10);

//because we can exeed 99F, the second digit used in the calc above must be
//changed to drive the bar graph. ie: 104F yields 10 for second digit above,
//change to 0 for bar graph display operation. Similarly, if we exceed 109F,
//then the second digit needs to be 1:

if(h>9)
h=0;

if(tempFint > 109)
h=1;
}

void SENDBAR1() {
//zero out all LEDs:
output_b(0b11111111); //set all LEDs to off = 1, on = 1
output_c(0b11111111);
output_d(0b11111111);

//1's:

if(m==0) {
output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);
}

if(m==1) {
output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_ms(5);
}

if(m==2) {
output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);
}

if(m==3) {
output_b(0b11111111);
output_c(0b11111110);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly); }

if(m==4) {
output_b(0b01111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111110);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);
}

if(m==5) {
output_b(0b10111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b01111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111110);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);}

if(m==6) {
output_b(0b11011111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b10111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b01111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111110);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);
}

if(m==7) {
output_b(0b11101111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11011111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b10111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b01111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111110);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);
}

if(m==8) {
output_b(0b11110111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11101111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11011111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b10111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b01111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111110);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);
}

if(m==9) {
output_b(0b11111011); //set all LEDs: off = 1, on = 0
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11110111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11101111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11011111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b10111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b01111111);
output_c(0b11111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111110);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111101);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111011);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11110111);
output_d(0b11111111);
delay_us(dly);
}
}

 

void SENDBAR10() {
//10's:

if(h==0) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

}

if(h==1) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);
}

if(h==2) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

}

if(h==3) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111011);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

}

if(h==4) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111101);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111011);
delay_us(dly);

}

if(h==5) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111110);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111011);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111101);
delay_us(dly);

}

if(h==6) {
output_b(0b11111111);
output_c(0b01111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111011);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111101);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111110);
delay_us(dly);

}

if(h==7) {
output_b(0b11111111);
output_c(0b10111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111011);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111101);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111110);
delay_us(dly);

output_b(0b11111111);
output_c(0b01111111);
output_d(0b11111111);
delay_us(dly);
}

if(h==8) {
output_b(0b11111111);
output_c(0b11011111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111011);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111101);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111110);
delay_us(dly);

output_b(0b11111111);
output_c(0b01111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b10111111);
output_d(0b11111111);
delay_us(dly);
}

if(h==9) {
output_b(0b11111111);
output_c(0b11101111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11011111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11101111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11110111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111011);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111101);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b11111110);
delay_us(dly);

output_b(0b11111111);
output_c(0b01111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b10111111);
output_d(0b11111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11011111);
output_d(0b11111111);
delay_us(dly);
}
}

void SENDBAR100() {

//100's:
if(l==0) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b01111111);
delay_us(dly);
}

if(l==1) {
output_b(0b11111111);
output_c(0b11111111);
output_d(0b10111111);
delay_us(dly);

output_b(0b11111111);
output_c(0b11111111);
output_d(0b01111111);
delay_us(dly);
}

//zero out all LEDs:
output_b(0b11111111); //set all LEDs to off = 1, on = 1
output_c(0b11111111);
output_d(0b11111111);
}

  HOME