Lab5_v2 Temperature Sensor

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.

lab5-sch-B

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
}

One thought on “Lab5_v2 Temperature Sensor”

Leave a Reply

Your email address will not be published. Required fields are marked *

Tools, techniques, design approaches and fun!