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