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);
}
|