PIC16F690 Measure Temperature and Store Readings
Use the TMP36 temperature sensor and the 16F690’s analog-to-digital converter (ADC) to measure temperature. We will also store the measurements in the 16F690’s on-board EEPROM.
The software implements a command interpreter. To use this you must have a USB-to-TTL converter like the FTDI 232 3.3v or a USB cable with this chip built in. Without this the only way to see the readings taken by the software will be to add a readout to the breadboard. Eight LEDs could do the job.
Below is the schematic. It adds the TMP36 to lab4. The components labeled *-sbb go on the solderless breadboard.
Note on the FTDI adapter board
If you use the adapter board, the pins are labeled from the viewpoint of the terminal/PC. Facing the component side of the board, pin 1 (gnd) is on the right, Pin 4 is labeled Tx and must be connected to the Rx pin of the 16F690 (per the schematic below). Similarly, pin 5 is labeled Rx and must be connected to the Tx pin of the 16F690. You can plug the adapter directly into the solderless breadboard. You need a USB cable with A-connector at the PC end and a mini-B plug for the adapter board.
Note: version 2 source code (Lab5_v2.c)
Version 2 corrects several defects found in the previous version. All features now work correctly.
Show text file suitable for cut-and-paste.
/*
* File: lab5_v2.c
* Author: Brian
*
* DESCRIPTION: take temperature readings from a TMP36 device. Record each
* measurement (in raw form) in the PIC16F690's on-board EEPROM.
* If a terminal is connected, ongoing results are printed plus
* you can request a dump of all results.
* VERSION: 2 defects in averaging logic:
* initialize averageCount = N
* set accum=0 for completed average
* re-implement measurement task as a switch() statement.
* defect: timer-driven items did not work:
* enable interrupt system! GIE=1
* defect: dump() runs forever. Failed to increment getIdx
* defect: saveMode could not be disabled.
* Missing break in switch()
*/
#include <xc.h>
#include <stdint.h>
#include <stdio.h> /* for printf() */
#include /* for toupper() */
// CONFIG
#pragma config FOSC = INTRCIO // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = OFF // Brown-out Reset Selection bits (BOR disabled)
#pragma config IESO = OFF // Internal External Switchover bit (Internal External Switchover mode is disabled)
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled)
// function prototypes
void interrupt ISR(void);
void initTimer0(void);
void initUsart(void);
void putch(char c);
void dump(void);
void help(void);
void cmdInterpreter(void);
void initAdcAndIo(void);
int readAdc(void);
void write2Eeprom(uint8_t value);
uint8_t readEeprom(uint8_t idx);
#define TMR0_PRELOAD 58 // see "Timer 0 Configuration"
#define N_X_SYSCLOCK_COARSE 50 /* number of sysClocks per coarse
* 0.2 msec * 50 = 10 msec
*/
#define N_X_COARSE_PER_SECOND 100 /* number of coarse ticks per 1 second
* 10 msec * 100 = 1000 msec = 1 sec
*/
#define N_X_SECONDS_PER_MINUTE 60 /* 60 seconds per minute */
#define NUM_2_AVERAGE 10
/* measurement modes for temperature */
enum mode {
OFF, // nothing pending
ONCE, // one-shot reading
FAST, // read once per second
SLOW, // read once per minute
} ;
int measMode = OFF;
/* operating modes for save */
enum sMode {
NOSAVE,
SAVE,
} ;
int saveMode = NOSAVE;
enum aMode {
SINGLE,
AVERAGE, // average 10 readings
} ;
int averageMode = SINGLE;
/* ISR modifies these events: each is a message between actors */
volatile bit eventCoarseTick = 0; // TMR0 ISR => task loop
uint8_t eepromWriteIdx = 0; // keep track of next spot
static bit enableDebug = 0;
void main(void) {
uint16_t reading;
uint8_t tickClock = 0;
uint8_t minuteClock = 0;
uint8_t averageCount = NUM_2_AVERAGE;
uint16_t accum = 0;
static bit armFast = 0;
static bit armSlow = 0;
static bit armSave = 0;
initTimer0();
initAdcAndIo();
initUsart();
printf("Init complete!\n\r");
// enable interrupt system
GIE = 1;
while (1) {
/***********************
* REMOTE COMMANDS
***********************/
cmdInterpreter();
/***********************
* TIMER
***********************/
armSlow = 0;
armFast = 0;
if (eventCoarseTick) {
// occurs every 10 msec
eventCoarseTick = 0;
tickClock += 1;
if (tickClock == N_X_COARSE_PER_SECOND) {
// occurs every 1 sec
tickClock = 0;
armFast = 1;
if (enableDebug) {
printf("Tick 1 second\n\r");
}
//
minuteClock += 1;
if (minuteClock == N_X_SECONDS_PER_MINUTE) {
// occurs every 1 minute
minuteClock = 0;
armSlow = 1;
if (enableDebug) {
printf("Tick 1 minute\n\r");
}
//
}
}
}
/***************************
* MEASURE
***************************/
switch (measMode) {
case OFF:
// do nothing
break;
case ONCE:
if (averageMode == AVERAGE) {
if (averageCount) {
averageCount -= 1;
accum += readAdc();
} else { // averageCount == 0
// averaging complete. clean up
reading = accum / NUM_2_AVERAGE;
measMode = OFF;
averageCount = NUM_2_AVERAGE;
accum = 0;
}
} else if (averageMode == SINGLE) {
reading = readAdc();
measMode = OFF;
}
break;
case FAST:
if (armFast) {
armFast = 0;
if (averageMode == AVERAGE) {
if (averageCount) {
averageCount -= 1;
accum += readAdc();
} else { // averageCount == 0
// averaging complete. clean up
reading = accum / NUM_2_AVERAGE;
armSave = 1;
averageCount = NUM_2_AVERAGE;
accum = 0;
}
} else if (averageMode == SINGLE) {
reading = readAdc();
armSave = 1;
}
}
break;
case SLOW:
if (armSlow) {
armSlow = 0;
if (averageMode == AVERAGE) {
if (averageCount) {
averageCount -= 1;
accum += readAdc();
if (enableDebug) {
printf("AVG# %d\n\r", averageCount);
}
} else { // averageCount == 0
// averaging complete. clean up
reading = accum / NUM_2_AVERAGE;
armSave = 1;
averageCount = NUM_2_AVERAGE;
accum = 0;
}
} else if (averageMode == SINGLE) {
reading = readAdc();
armSave = 1;
}
}
break;
default: // do nothing
break;
}
/*********************
* RECORD
*********************/
if ((armSave) && (saveMode == SAVE)) {
armSave = 0;
// record last result to non-volatile memory
uint8_t result = (reading >> 2);
if (enableDebug) {
printf("SAVING\n\r");
}
write2Eeprom(result);
}
armSave = 0;
}
}
void interrupt ISR(void) {
static uint8_t isrCoarseCounter = 0;
/**********************
* TIMER 0
**********************/
if (T0IE && T0IF) // TMR0 overflows every 200 microseconds
{
T0IF = 0; // Clear the TMR0 interrupt flag
TMR0 = TMR0_PRELOAD; // use this value consistently
isrCoarseCounter += 1;
if (isrCoarseCounter == N_X_SYSCLOCK_COARSE) {
isrCoarseCounter = 0;
eventCoarseTick = 1; // fire event
}
}
}
void write2Eeprom(uint8_t value) {
// write a byte to the on-board EEPROM.
// 256 bytes are available
// the index will wrap, causing new data to overwrite older data
EEADR = eepromWriteIdx++; // bump pointer
EEDAT = value;
EECON1bits.EEPGD = 0; // data memory
EECON1bits.WREN = 1; // write enable
/* CRITICAL SECTION START */
/* see Data Sheet sec 10.1.3 */
di();
EECON2 = 0x55;
EECON2 = 0xAA;
EECON1bits.WR = 1;
ei();
/* CRITICAL SECTION END */
while (EECON1bits.WR) {
; // wait for write to complete
}
EECON1bits.WREN = 0; // disable writes
}
uint8_t readEeprom(uint8_t idx) {
EEADR = idx;
EECON1bits.EEPGD = 0; // data memory
EECON1bits.RD = 1;
return EEDAT;
}
void initUsart(void) {
/*
* set up for 9600 baud, asynchronous
* see Table 12-5, pp.165-168
* section 4: SYNC=0 BRGH=1 BRG16=0
* Fosc=4 MHz: SPRG=25
* use RB5 for Rx and RB7 for Tx
*/
TXSTAbits.TXEN = 1; // enable transmitter
TXSTAbits.BRGH = 1; // set up baud rate according to Table 12-5, 4th section
BAUDCTLbits.BRG16 = 0; // use the 8-bit counter
SPBRG = 25; // baud rate generator
TXSTAbits.SYNC = 0; // asynchronous mode
RCSTAbits.CREN = 1; // continuous receive
ANSELHbits.ANS11 = 0; // disable AN11 input (on pin RB5)
TRISBbits.TRISB5 = 1; // RX (RB5) is an input
TRISBbits.TRISB7 = 1; /* TX (RB7) is an input
* automatically configured as output by TXEN
*/
PIE1bits.RCIE = 0; // no Rx interrupts
RCSTAbits.SPEN = 1; // automatically configures RX (RB5) as an input
}
void putch(char data) {
/*
* this is used by printf()
*/
while (!PIR1bits.TXIF) {
;
/* wait until TXIF indicates TXREG is available */
/* alternatively, TXSTAbits.TRMT is set once a pending character has
* been pumped out on the line.
*/
}
TXREG = data;
}
void dump(void) {
// since EEPROM could wrap, we could start at eepromWriteIdx
// for now, read 0..eepromWriteIdx-1
uint8_t getIdx = 0;
printf("Raw Measurement Data [ADC]\n\r");
while (getIdx < eepromWriteIdx) {
printf("%3d %3d\n\r", getIdx, readEeprom(getIdx));
getIdx += 1;
}
}
void help(void) {
printf("HELP\n\r");
printf("A = average 10 readings\n\r");
printf("D = dump saved results\n\r");
printf("E = no averaging\n\r");
printf("F = measure every 1 second\n\r");
printf("G = take a single measurement without save\n\r");
printf("H = help (this is it)\n\r");
printf("M = measure every 1 minute\n\r");
printf("N = do not save\n\r");
printf("S = save results in non-volatile memory\n\r");
printf("Z = toggle debug output\n\r");
}
void cmdInterpreter(void) {
static unsigned char t;
/*
* service serial port input
*/
if (PIR1bits.RCIF) {
t = RCREG;
/*
* parse a command character
*/
t = toupper(t);
switch (t) {
case 'G':
printf("go 1 time. no save\n\r");
measMode = ONCE;
break;
case 'A':
printf("average 10 measurements, no rate change\n\r");
averageMode = AVERAGE;
break;
case 'E':
printf("no averaging, no rate change\n\r");
averageMode = SINGLE;
break;
case 'H':
help();
break;
case 'D':
dump();
break;
case 'F':
printf("measure 1 per second\n\r");
measMode = FAST;
break;
case 'M':
printf("measure 1 per minute\n\r");
measMode = SLOW;
break;
case 'N':
printf("do NOT save results\n\r");
saveMode = NOSAVE;
break;
case 'S':
printf("save results in non-volatile memory\n\r");
saveMode = SAVE;
break;
case 'Z':
enableDebug = !enableDebug;
if (enableDebug) {
printf("DEBUG ON\n\r");
} else {
printf("DEBUG OFF\n\r");
}
default:
printf("%c unrecognized\n\r", t);
help();
break;
}
}
}
void initAdcAndIo(void) {
/*
* the ADC is configured to read AN7/RC3/pin7
*/
OSCCONbits.IRCF = 0b110; // 4 MHz (default)
TRISCbits.TRISC3 = 1; // RC3 = input
ANSELbits.ANS7 = 1; // enable analog (default=1)
ADCON0bits.CHS = 0b0111; // ADCCON select ANS7 as the ADC input source
ADCON0bits.VCFG = 0; // select Vdd as ADC Vref. Alt: RA1/AN1/pin 18.
ANSELbits.ANS1 = 1; // enable analog (default=1)
ADCON1bits.ADCS = 0b001; // ADC clock Fosc/8 = 500 kHz
ADCON0bits.ADFM = 1; // get 16-bit result
ADCON0bits.ADON = 1; // enable ADC, must wait 5 microseconds
}
int readAdc(void) {
/*
* take a single reading, return 10-bit result
*/
int reading;
ADCON0bits.GO = 1; // start conversion
while (ADCON0bits.GO_nDONE) {
;
} // wait for ADC to say done
reading = (ADRESH & 0x03) << 8; // get the result
reading |= ADRESL;
printf("ADC=%d\n\r", reading);
return reading;
}
void initTimer0(void) {
/*
* Timer 0 Configuration
*
* TMR0 will interrupt (on overflow) 5000 times per second (T0i = 200 usec).
* system clock = Fosc/4 = 1 MHz
* period T = 1 microsecond
* want T0i = 0.0002 sec
* prescale must be power of 2. The smaller the prescale, the closer we
* can hit our target. The other constraint is the size of TMR0, 8-bits
* We can preload TMR0 with 0 through 255.
* So, with a prescale of 1, we need TMR0 to count
* N = 0.0002 / 0.000001 = 200
* Per data sheet 5.1.1, we must compensate for a 2 cycle delay
* TMR0_PRELOAD = 256 - (200 - 2) = 58
*
* Resulting TMR0 interrupt interval:
* T0i = 200 * 0.000001 = 0.0002 second
*/
OPTION_REGbits.PS = 0b111; // XXX Don't need prescaler
OPTION_REGbits.PSA = 1; // Assign prescaler to WDT
OPTION_REGbits.T0CS = 0; // Select FOSC/4 as Timer0 clock source
OPTION_REGbits.T0SE = 0; // X don't care
/* lines above may be achieved by a single statement:
* OPTION_REG = 0b00001111;
*/
T0IF = 0; // Clear the TMR0 interrupt flag
TMR0 = TMR0_PRELOAD; // use this value consistently
T0IE = 1; // Enable TMR0 interrupts
}

very good