/*********************************************************************************
DATALOG5.C

With interrupt button on dump routine (both buttons pressed.)

This is a data logger.  It has a 5V and 14V ADC input and collects (with
the 24LC128 EEPROM memory) 8192 samples.  Control and display is done
via two pushbuttons and and LCD display.  Data dump can be viewed on
the display or sent to a terminal via 9600 baud RS232.

WORKING CODE

                       ---------              ---------
               +5--20-|Vdd    B0|-----21->-6-|SCL*     |    *Pullups required
               +5---1-|Mclr   B1|-----22->-5-|SDA*     |     on SCL and SDA.
              Gnd---8-|Vss      |            | 24LC128 |
              Gnd--19-|Vss      |       +5-8-|Vdd      |
             4MHz--10-|Xtal     |      Gnd-4-|Vss      |           --------
                 ---9-|Xtal     |      Gnd-7-|Wp       |   Gnd-20-|EN      |
                      |         |      Gnd-1-|A0       |   Gnd-21-|SD      |
                      | 16F876  |      Gnd-2-|A1       |   Gnd-11-|Vss     |
                      |         |      Gnd-3-|A2       |    +5-12-|Vcc     |
              POT     |         |             ---------           |        |
            ©-10K--13-|C2       |                                 |        |
    In--10K-*-------2-|A0     B3|-24-->----9600 baud------------7-|IN   OUT|-4-->-Tx
                      |         |                                 |        |
       -Menu/Dec --11-|C0       |                                 | MAX235 |
     -Select/Inc --12-|C1       |                                  --------
                      |         |             ---------
                      |       C3|-----14--11-|D4       |
                      |       C4|-----15--12-|D5       |
                      |       C5|-----16--13-|D6       |
                      |       C6|-----17--14-|D7       |
                      |         |            |         |-3--20K pot (contrast)
                      |       B5|-----26---6-|EN       |
                      |       B6|-----27---4-|RS       |
                      |         |            |         |
                      |         |       +5-2-|         |
                      |         |      Gnd-1-|         |
                      |         |      Gnd-5-|         |
                      |         |            | DISPLAY |
                       ---------              ---------

*********************************************************************************/

#include < 16F876.H >
#device ADC=10
#include < jonsinc.h >

#fuses XT, NOPROTECT, NOPUT, NOWDT, NOBROWNOUT, NOLVP, NOCPD, NOWRT

// ADC
#define VDD                 5.00

// INTERNAL EEPROM ASSIGNMENTS
#define SAMPLE_INTERVAL_HI 0
#define SAMPLE_INTERVAL_LO 1
#define SAMPLE_COUNT_HI    2
#define SAMPLE_COUNT_LO    3
#define LOGGING_STATE      4
#define RANGE              5

// EXTERNAL EEPROM ASSIGNMENTS 128Kbit EEPROM is 16384 bytes
#define EEPROM_BYTE_SIZE   16384
#define EEPROM_SCL         PIN_B0
#define EEPROM_SDA         PIN_B1

// LCD STUFF
#define LCD_D0  PIN_C3
#define LCD_D1  PIN_C4
#define LCD_D2  PIN_C5
#define LCD_D3  PIN_C6
#define LCD_EN  PIN_B5
#define LCD_RS  PIN_B6
#define LINE_1  0x00
#define LINE_2  0x40
#define CLEAR_DISP  0x01

#define MENU_DEC_SWITCH        PIN_C0
#define SELECT_INC_SWITCH      PIN_C1
#define RANGE_SHUNT            PIN_C2
#define SEL0                   PIN_B2
#define SEL1                   PIN_B4

#define MINIMUM_INTERVAL   1
#define STATE_START        0
#define STATE_STOP         1
#define STATE_STATUS       2
#define STATE_RESET        3
#define STATE_RANGE        4
#define STATE_INTERVAL     5
#define STATE_VIEW         6
#define STATE_DUMP         7
#define MAX_MENU_STATE     7

#define hi(x)  (*(&x+1))

#use delay ( clock=4000000 )
#use standard_io ( A )
#use standard_io ( B )
#use standard_io ( C )
#use rs232 ( baud=9600, xmit=PIN_B3 )
#use i2c ( master, scl=EEPROM_SCL, sda=EEPROM_SDA )

void PrintMenu ( void );       // protos
void init_ext_eeprom ( void );
void write_ext_eeprom ( long int lngAddress, BYTE intData );
BYTE read_ext_eeprom ( long int lngAddress );
void SetTime ( void );
void CheckSample ( void );
void CheckSwitches ( void );
char GetEchoNumChar ( void );
void LCD_Init ( void );
void LCD_SetPosition ( unsigned int cX );
void LCD_PutChar ( unsigned int cX );
void LCD_PutCmd ( unsigned int cX );
void LCD_PulseEnable ( void );
void LCD_SetData ( unsigned int cX );
void DisplayVolts ( long iAdcValue, char cLoc );
float ScaleAdc ( long iValue );
void SetRange ( BYTE cDisplay );

static long iIntervalCount, iIntervalTrigger, iSampleCount;
static char cLogging, cSampleFlag, cLedCount;
static char cLoggingIndicatorFlag, cAdcFlag, cToggleFlag;
static char cInterruptCount, cViewing;
static char cMenuState, cSelectFlag, cRange;
static char cMenuDecSwitchOn, cMenuSwitchCount;
static char cSelIncSwitchOn, cSelectSwitchCount;

//****************************************************************************

#int_rtcc
void TimerInterrupt ( void )      // 32.768mS tic, ~30 interrupts per second
    {
    if ( cInterruptCount++ == 30 )      // if one second yet
        {
        cAdcFlag = ON;              // allow write to display
        cInterruptCount = 0;
        if ( cLogging == YES )
            {
            cLoggingIndicatorFlag = ON;    // time to toggle "running" indicator on display
            }
        if ( ( iIntervalCount++ == iIntervalTrigger - 1 ) && ( cLogging == YES ) )  // if sample time yet
                {
                cSampleFlag = ON;                       // signal time to sample
                iIntervalCount = 0;                     // start count over
                }
           }
    if ( input ( MENU_DEC_SWITCH ) == LOW )
        {
        if ( cMenuSwitchCount++ == 1 )     // debounce for 30mS, (was 2)
            {
            cMenuDecSwitchOn = YES;        // signal that switch was pressed
            cMenuSwitchCount = cViewing ? 252 : 240;       // set up for auto repeat (faster if viewing)
            }
        }
    else
        {
        cMenuSwitchCount = 0;             // switch up, restart
        }
    if ( input ( SELECT_INC_SWITCH ) == LOW )
        {
        if ( cSelectSwitchCount++ == 1 )  // debounce for 30mS (was 2)
            {
            cSelIncSwitchOn = YES;        // signal that switch was pressed
            cSelectSwitchCount = cViewing ? 252 : 240;       // set up for auto repeat (faster if viewing)
            }
        }
    else
        {
        cSelectSwitchCount = 0;             // switch is up, restart count
        }
    set_rtcc ( 4 );     // restart at adjusted value for 1-second accuracy
    }

//*****************************************************************************

void main ( void )
    {
    delay_ms ( 200 );           // wait enough time after VDD rise
    output_float ( RANGE_SHUNT );
    init_ext_eeprom();                  // set SDA and SCL to float
    setup_counters ( RTCC_INTERNAL, RTCC_DIV_128 );       // 31mS roll

    LCD_Init();
    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( LINE_1 + 2 );
    printf ( LCD_PutChar, "DATA LOGGER" );
    LCD_SetPosition ( LINE_2 + 2 );
    printf ( LCD_PutChar, "%4lu samples", EEPROM_BYTE_SIZE / 2 );
    delay_ms ( 1000 );
    LCD_PutCmd ( CLEAR_DISP );    LCD_Init();
    LCD_SetPosition ( LINE_1 + 4 );
    printf ( LCD_PutChar, "Jon Fick" );
    LCD_SetPosition ( LINE_2 + 4 );
    printf ( LCD_PutChar, "01/12/07" );
    delay_ms ( 1000 );
    LCD_PutCmd ( CLEAR_DISP );

    // SETUP
    setup_ccp1 ( CCP_OFF );
    setup_ccp2 ( CCP_OFF );
    setup_adc_ports ( RA0_ANALOG );     // these three statements set up the ADC
    setup_adc ( ADC_CLOCK_INTERNAL );   // clock source
    set_adc_channel ( 0 );              // select channel
    enable_interrupts ( INT_RTCC );     // turn on timer interrupt
    enable_interrupts ( GLOBAL );       // enable interrupts

    // RESTORE PREVIOUS STATE
    cRange = read_eeprom ( RANGE );
    if ( cRange >= 2 )
        {
        cRange = 0;    // reset to default
        }
    SetRange ( NO );         // set range according to cRange, don't display
    iSampleCount = ( 256 * read_eeprom ( SAMPLE_COUNT_HI ) ) + read_eeprom ( SAMPLE_COUNT_LO );
    cLogging = read_eeprom ( LOGGING_STATE );   // get existing state
    iIntervalTrigger = ( 256 * read_eeprom ( SAMPLE_INTERVAL_HI ) ) + read_eeprom ( SAMPLE_INTERVAL_LO );
    if ( iIntervalTrigger == 0 )
        {
        iIntervalTrigger = 1;     // preset to at least 1 second sample interval
        }

    // INITIALIZE VARIABLES
    cSelectFlag = OFF;
    cToggleFlag = 0;
    cMenuDecSwitchOn = OFF;
    cSelIncSwitchOn = OFF;
    cMenuSwitchCount = 0;
    cSelectSwitchCount = 0;
    cMenuState = ( cLogging == YES ) ? STATE_STOP : STATE_START;  // set first menu

    while ( TRUE )              // do forever
        {
        PrintMenu();            // display screens and voltage
        CheckSwitches();        // check and do any switch activity
        CheckSample();          // check if it's time to sample and store ADC
        }
    }

//****************************************************************************

void PrintMenu ( void )
    {
    // ACTIVITY INDICATOR
    LCD_SetPosition ( LINE_1 + 15 );
    if ( cLogging == NO )       // if not logging at this time
        {
        printf ( LCD_PutChar, " " );    // blank symbol for activity indicator
        }
    else                    // if presently logging
        {
        if ( cLoggingIndicatorFlag == ON )  // turned on once per second by interrupt
            {
            cToggleFlag ^= 1;              // toggle the activity indicator symbol
            if ( cToggleFlag == 1 )
                {
                printf ( LCD_PutChar, "%c", 255 );  // 255 symbol
                }
            else
                {
                printf ( LCD_PutChar, " " );        // blank symbol
                }
            cLoggingIndicatorFlag = OFF;
            }
        }

    // PRINT LOWER LINE OF MENU
    LCD_SetPosition ( LINE_2 + 0 );
    switch ( cMenuState )
        {
        case STATE_START:
            {
            if ( cLogging == YES )      // don't display while logging
                {
                cMenuState++;   // point to next menu
                break;
                }
            printf ( LCD_PutChar, "Next       START" );
            break;
            }
        case STATE_STOP:
            {
            if ( cLogging == NO )      // don't display if not logging
                {
                cMenuState++;   // point to next menu
                break;
                }
            printf ( LCD_PutChar, "#%04lu", iSampleCount );
            LCD_SetPosition ( LINE_2 + 11 );
            printf ( LCD_PutChar, " STOP" );
            break;
            }
        case STATE_INTERVAL:
            {
            if ( cLogging == YES )      // prevent changing while logging
                {
                cMenuState++;   // point to next menu
                break;
                }
            printf ( LCD_PutChar, "Next    INTERVAL" );
            break;
            }
        case STATE_STATUS:
            {
            printf ( LCD_PutChar, "Next      STATUS" );
            break;
            }
        case STATE_VIEW:
            {
            printf ( LCD_PutChar, "Next        VIEW" );
            break;
            }
        case STATE_DUMP:
            {
            printf ( LCD_PutChar, "Next      UPLOAD" );
            break;
            }
        case STATE_RESET:
            {
            if ( cLogging == YES )      // prevent changing while logging
                {
                cMenuState++;   // point to next menu
                break;
                }
            printf ( LCD_PutChar, "Next       RESET" );
            break;
            }
        case STATE_RANGE:
            {
            if ( cLogging == YES )      // prevent changing while logging
                {
                cMenuState++;   // point to next menu
                break;
                }
            printf ( LCD_PutChar, "Next       RANGE" );
            break;
            }
        }
    // DISPLAY VOLTS
    if ( cAdcFlag == ON )                    // if interrupt signalled an ADC reading
        {
        DisplayVolts ( read_adc(), 5 );      // read ADC, send raw data to display at location 5
        cAdcFlag = OFF;
        }
    }

void CheckSwitches ( void )
    {
    char cX, cDigit, cDigitPointer, cDone;
    long iX, iY, iVal, iPlace;

    // INCREMENT/DECREMENT
    if ( cMenuDecSwitchOn == YES )      // if interrupt caught the switch press
        {
        if ( cMenuState++ >= MAX_MENU_STATE )      // if at maximum
            {
            cMenuState = 0;            // roll
            }
        cMenuDecSwitchOn = NO;     // turn back off
        }
    if ( cSelIncSwitchOn == YES )      // if interrupt caught the switch press
        {
        cSelectFlag = ON;
        cSelIncSwitchOn = NO;     // turn back off
        }

    // PRINT MENU (upper line and sometimes overwrite lower line)
    switch ( cMenuState )
        {
        case ( STATE_START ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                if ( iSampleCount >= ( EEPROM_BYTE_SIZE / 2 ) )  // already at end of memory
                    {
                    LCD_PutCmd ( CLEAR_DISP );
                    LCD_SetPosition ( LINE_1 + 2);
                    printf ( LCD_PutChar, "MEMORY FULL" );
                    LCD_SetPosition ( LINE_2 + 2 );
                    printf ( LCD_PutChar, "%4lu samples", iSampleCount );
                    delay_ms ( 1000 );
                    LCD_PutCmd ( CLEAR_DISP );
                    cMenuDecSwitchOn = NO;
                    cSelIncSwitchOn = NO;
                    cMenuState = STATE_START;    // menu displays "START"
                    }
                else                        // if OK to start
                    {
                    cLogging = YES;
                    write_eeprom ( LOGGING_STATE, YES );
                    write_eeprom ( RANGE, cRange );
                    cSampleFlag = ON;                       // immediate sample
                    cInterruptCount = 0;    // synchronize interrupt timing from here
                    iIntervalCount = 0;         // synchronize
                    cMenuState = STATE_STOP;    // menu displays "STOP"
                    break;
                    }
                }
            }
        case ( STATE_STOP ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                cLogging = NO;
                write_eeprom ( LOGGING_STATE, NO );
                cMenuState = STATE_START;    // menu displays "START"
                break;
                }
            }
         case ( STATE_RESET ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                write_eeprom ( SAMPLE_COUNT_HI, 0 );
                write_eeprom ( SAMPLE_COUNT_LO, 0 );
                iSampleCount = 0;
                cLogging = NO;
                LCD_PutCmd ( CLEAR_DISP );
                LCD_SetPosition ( LINE_1 + 0 );
                printf ( LCD_PutChar, "Reset..." );
                delay_ms ( 1000 );
                LCD_SetPosition ( LINE_1 + 8 );
                printf ( LCD_PutChar, "complete" );
                delay_ms ( 1000 );
                LCD_PutCmd ( CLEAR_DISP );
                cMenuDecSwitchOn = NO;
                cSelIncSwitchOn = NO;
                cMenuState = STATE_START;    // menu displays "START"
                break;
                }
            }
         case ( STATE_STATUS ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                LCD_PutCmd ( CLEAR_DISP );
                LCD_SetPosition ( LINE_1 );
                printf ( LCD_PutChar, "Interval: %lus", 256 * read_eeprom ( SAMPLE_INTERVAL_HI ) + read_eeprom ( SAMPLE_INTERVAL_LO ) );
                LCD_SetPosition ( LINE_2 );
                printf ( LCD_PutChar, "Samples:  %lu", 256 * read_eeprom ( SAMPLE_COUNT_HI ) + read_eeprom ( SAMPLE_COUNT_LO ) );
                delay_ms ( 2000 );
                LCD_PutCmd ( CLEAR_DISP );
                cMenuDecSwitchOn = NO;
                cSelIncSwitchOn = NO;
                cMenuState = STATE_START;    // menu displays "LOG"
                break;
                }
            }
        case ( STATE_RANGE ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                LCD_PutCmd ( CLEAR_DISP );
                LCD_SetPosition ( LINE_2 + 0 );
                printf ( LCD_PutChar, "CHANGE    Return" );
                SetRange ( YES );         // set and display present range according to cRange
                while ( TRUE )
                    {
                    if ( cSelIncSwitchOn == YES )      // if RETURN button is pressed
                        {
                        cSelIncSwitchOn = NO;
                        break;                         // done selecting
                        }
                    if ( cMenuDecSwitchOn == YES )     // if CHANGE button is pressed
                        {
                        cMenuDecSwitchOn = NO;
                        if ( ++cRange >= 2 )       // increment and wrap
                            {
                            cRange = 0;
                            }
                        LCD_SetPosition ( LINE_1 + 7 );
                        SetRange ( YES );         // set range, display
                        }
                    }
                write_eeprom ( RANGE, cRange );    // save final selected range
                LCD_PutCmd ( CLEAR_DISP );
                cMenuState = STATE_START;          // menu displays "START"
                break;
                }
            }
        case ( STATE_INTERVAL ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                LCD_PutCmd ( CLEAR_DISP );
                LCD_SetPosition ( LINE_1 );
                printf ( LCD_PutChar, "Presently %05lus", iIntervalTrigger );
                LCD_SetPosition ( LINE_2 + 0 );
                printf ( LCD_PutChar, "DIGIT 00000s INC" );
                cX = LINE_2 + 6;        // point to beginning of zeros
                LCD_SetPosition ( cX );
                LCD_PutCmd ( 0x0E );    // display ON, cursor on, no blink
                cDigit = 0;
                cDigitPointer = 0;
                iX = 0;
                iPlace = 10000;
                while ( TRUE )
                    {
                    if ( cSelIncSwitchOn == YES )
                        {
                        if ( ++cDigit > 9 )       // increment digit
                            {
                            cDigit = 0;     // roll
                            }
                        cSelIncSwitchOn = NO;
                        cSelIncSwitchOn = NO;
                        LCD_SetPosition ( cX + cDigitPointer );   // set cursor to this digit
                        printf ( LCD_PutChar, "%u", cDigit );     // display the digit
                        LCD_SetPosition ( cX + cDigitPointer );   // set cursor back to this digit
                        }
                    if ( cMenuDecSwitchOn == YES )
                        {
                        cMenuDecSwitchOn = NO;
                        iX += cDigit * iPlace;   // add in to total
                        iPlace /= 10;         // point to next place value down
                        cDigit = 0;      // zero digit again
                        if ( ++cDigitPointer == 5 ) // point to next digit
                            {
                            break;
                            }
                        LCD_SetPosition ( cX + cDigitPointer );
                        }
                    }
                if ( iX != 0 )         // if number was updated
                   {
                   write_eeprom ( SAMPLE_INTERVAL_HI, iX / 256 );  // store high byte
                   write_eeprom ( SAMPLE_INTERVAL_LO, iX % 256 );  // store low byte
                   iIntervalTrigger = iX;       // update interval
                   }
                LCD_PutCmd ( 0x0E );    // display ON, cursor off, no blink
                LCD_PutCmd ( CLEAR_DISP );
                cMenuState = STATE_START;    // menu displays "LOG"
                break;
                }
            }
        case ( STATE_VIEW ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                iX = 0;        // zero sample number
                iY = ( read_eeprom ( SAMPLE_COUNT_HI ) * 256 ) + read_eeprom ( SAMPLE_COUNT_LO );
                cRange =  read_eeprom ( RANGE );    // used stored range
                LCD_PutCmd ( CLEAR_DISP );
                cDone = NO;
                if ( iY != 0 )           // if any samples at all
                    {
                    LCD_SetPosition ( LINE_1 );
                    printf ( LCD_PutChar, "#" );
                    LCD_SetPosition ( LINE_2 );
                    printf ( LCD_PutChar, "DEC BothDone INC" );
                    while ( cDone == NO )
                        {
                        LCD_SetPosition ( LINE_1 + 1 );
                        printf ( LCD_PutChar, "%04lu", iX );   // display sample number
                        iPlace = ( read_ext_eeprom ( iX * 2 ) * 256 ) + read_ext_eeprom ( ( iX * 2 ) + 1 );    // retrieve data from EEPROM
                        DisplayVolts ( iPlace, 10 );      // display data at location 10
                        while ( TRUE )
                            {
                            cViewing = ON;
                            if ( ( input ( MENU_DEC_SWITCH ) == LOW ) && ( input ( SELECT_INC_SWITCH ) == LOW ) )
                                {
                                cDone = YES;
                                break;
                                }
                            if ( cMenuDecSwitchOn == YES )
                                {
                                cMenuDecSwitchOn = NO;
                                if ( iX-- == 0 )
                                    {
                                    iX = iY - 1;     // roll negative
                                    }
                                    break;
                                }
                            if ( cSelIncSwitchOn == YES )
                                {
                                cSelIncSwitchOn = NO;
                                if ( iX++ >= ( iY - 1 ) )
                                    {
                                    iX = 0;        // roll positive
                                    }
                                break;
                                }
                            }
                        cViewing = OFF;
                        }
                    }
                else
                    {
                    LCD_SetPosition ( LINE_1 );
                    printf ( LCD_PutChar, "No samples yet!" );
                    delay_ms ( 1000 );
                    }
                LCD_PutCmd ( CLEAR_DISP );
                cMenuState = STATE_START;    // menu displays "LOG"
                break;
                }
            }
        case ( STATE_DUMP ):
            {
            if ( cSelectFlag == ON )    // if switch is pressed
                {
                cSelectFlag = OFF;         // turn flag off
                iY = ( read_eeprom ( SAMPLE_COUNT_HI ) * 256 ) + read_eeprom ( SAMPLE_COUNT_LO );     // get number of samples
                cRange =  read_eeprom ( RANGE );    // used stored range
                LCD_PutCmd ( CLEAR_DISP );
                cDone = NO;
                if ( iY != 0 )           // if any samples at all
                    {
                    LCD_SetPosition ( LINE_1 + 0 );
                    printf ( LCD_PutChar, "Dump 9600-8-N-1 " );
                    LCD_SetPosition ( LINE_2 );
                    printf ( LCD_PutChar, "   Both=Stop    " );
                    disable_interrupts ( GLOBAL );  // turn off during async
                    printf ( "\r\n\r\n   Sample interval -- %lu seconds", 256 * read_eeprom ( SAMPLE_INTERVAL_HI ) + read_eeprom ( SAMPLE_INTERVAL_LO ) );
                    printf ( "\r\n   Number of samples- %lu", iY );
                    printf ( "\r\n\r\nSample\tVolts" );
                    printf ( "\r\n------\t-----\r\n" );
                    for ( iX = 0; iX < iY; iX++ )
                        {
                        if ( ( input ( MENU_DEC_SWITCH ) == LOW ) && ( input ( SELECT_INC_SWITCH ) == LOW ) )
                            {
                            cDone = YES;
                            break;
                            }
                        iVal = ( read_ext_eeprom ( iX * 2 ) * 256 ) + read_ext_eeprom ( ( iX * 2 ) + 1 );  // get sample data
                        if ( iVal == 0x3FF )
                            {
                            printf ( "%lu\tO/L\r\n", iX ); // out-of-range
                            }
                        else
                            {
                            printf ( "%lu\t%1.2f\r\n", iX, ScaleAdc ( iVal ) ); // send data sample
                            }
                        delay_ms ( 1 );     // avoids text glitches in output stream
                        }
                    enable_interrupts ( GLOBAL );  // turn on again
                    }
                else
                    {
                    LCD_SetPosition ( LINE_1 );
                    printf ( LCD_PutChar, "No samples yet!" );
                    delay_ms ( 1000 );
                    }
                delay_ms ( 1000 );      // allow time to view 9600 baud msg, etc.
                LCD_PutCmd ( CLEAR_DISP );
                cMenuState = STATE_START;    // menu displays "LOG"
                break;
                }
            }
        }
    }

void SetRange ( BYTE cDisplay )
    {
    LCD_SetPosition ( LINE_1 + 3 );
    switch ( cRange )
        {
        case ( 0 ):         // if 5V range
            {
            output_float ( RANGE_SHUNT );      // let shunt resistor float
            if ( cDisplay == YES )
                {
                printf ( LCD_PutChar, "Range: 5V " );     // 5V scale
                }
            break;
            }
        case ( 1 ):         // if 14V range
            {
            output_low ( RANGE_SHUNT );        // pull shunt resistor down
            if ( cDisplay == YES )
                {
                printf ( LCD_PutChar, "Range: 14V" );     // 14V scale
                }
            break;
            }
        }
    }

void CheckSample ( void )
    {
    long iVal;

    if ( cSampleFlag == ON )     // if time to sample
        {
        if ( iSampleCount >= ( EEPROM_BYTE_SIZE / 2 ) )  // if at end of memory
            {
            cLogging = NO;       // stop any further logging
            write_eeprom ( LOGGING_STATE, NO );
            cMenuState = STATE_STATUS;     // display status menu next
            }
        else                                              // if not at end of memory
            {
            iVal = read_adc();
            write_ext_eeprom ( iSampleCount * 2, iVal / 256 );   // write high data to external EEPROM
            write_ext_eeprom ( ( iSampleCount * 2 ) + 1, iVal % 256 );   // write low data
            iSampleCount++;             // point to next memory location
            write_eeprom ( SAMPLE_COUNT_HI, ( char ) ( iSampleCount / 256 ) );    // save sample count to internal EEPROM
            write_eeprom ( SAMPLE_COUNT_LO, ( char ) ( iSampleCount % 256 ) );
            }
        cSampleFlag = OFF;       // reset flag, interrupt turns on again later
        }
    }

void init_ext_eeprom ( void )
    {
    output_float ( EEPROM_SCL );
    output_float ( EEPROM_SDA );
    }

void write_ext_eeprom ( long int lngAddress, BYTE intData )
    {
    i2c_start();
    i2c_write ( 0xa0 );  // set up for writing address and data, chip address 000
    i2c_write ( hi ( lngAddress ) );      // write high address
    i2c_write ( lngAddress );      // write low address
    i2c_write ( intData );                        // write data
    i2c_stop();
    delay_ms ( 11 );
    }

BYTE read_ext_eeprom ( long int lngAddress )
    {
    BYTE intData;

    i2c_start();
    i2c_write ( 0xa0 );   // set up for writing address, chip address 000
    i2c_write ( hi ( lngAddress ) );      // write high address
    i2c_write ( lngAddress );      // write low address
    i2c_start();
    i2c_write ( 0xa1 );   // set up for reading data, chip address 000
    intData = i2c_read ( 0 );                     // read data
    i2c_stop();
    return ( intData );
    }

char GetEchoNumChar ( void )
    {
    char cX;

    while ( TRUE )
        {
       // cX = getc();                    // wait for character
       cX = getc();
        if ( ( cX >= '0' ) && ( cX <= '9' ) )   // check bounds
            {
            break;
            }
        }
    putc ( cX );                                // echo to screen
    return ( cX - 0x30 );                       // adjust to numeric
    }

void DisplayVolts ( long iAdcValue, char cLoc )
    {
    float fX;

    LCD_SetPosition ( LINE_1 + cLoc );
    if ( iAdcValue == 0x3FF )
        {
        printf ( LCD_PutChar, " O/L     " );    // out of range
        }
    else
        {
        fX = ScaleAdc ( iAdcValue );
        printf ( LCD_PutChar, "%2.2fV ", fX ); // display data sample
        }
    }

float ScaleAdc ( long iValue )
    {
    float fScale, fX;

    switch ( cRange )
        {
        case ( 0 ):
            {
            fScale = VDD;     // 5V scale
            break;
            }
        case ( 1 ):
            {
            fScale = 14;     // 14V scale
            break;
            }
        }
    return ( ( ( float ) iValue ) / 1023 * fScale );   // scale to proper range, 1023 leaves room for out-of-range
    }

// LCD FUNCTIONS =================================

void LCD_Init ( void )
    {
    LCD_SetData ( 0x00 );
    delay_ms ( 200 );       // wait enough time after Vdd rise
    output_low ( LCD_RS );
    LCD_SetData ( 0x03 );   // init with specific nibbles to start 4-bit mode
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_SetData ( 0x02 );   // set 4-bit interface
    LCD_PulseEnable();      // send dual nibbles hereafter, MSN first
    LCD_PutCmd ( 0x2C );    // function set (all lines, 5x7 characters)
    LCD_PutCmd ( 0x0C );    // display ON, cursor off, no blink
    LCD_PutCmd ( 0x01 );    // clear display
    LCD_PutCmd ( 0x06 );    // entry mode set, increment
    }

void LCD_SetPosition ( unsigned int cX )
    {
    // this subroutine works specifically for 4-bit Port A
    LCD_SetData ( swap ( cX ) | 0x08 );
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );
    LCD_PulseEnable();
    }

void LCD_PutChar ( unsigned int cX )
    {
    // this subroutine works specifically for 4-bit Port A
    output_high ( LCD_RS );
    LCD_SetData ( swap ( cX ) );     // send high nibble
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );     // send low nibble
    LCD_PulseEnable();
    output_low ( LCD_RS );
    }

void LCD_PutCmd ( unsigned int cX )
    {
    // this subroutine works specifically for 4-bit Port A
    LCD_SetData ( swap ( cX ) );     // send high nibble
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );     // send low nibble
    LCD_PulseEnable();
    }

void LCD_PulseEnable ( void )
    {
    output_high ( LCD_EN );
    delay_us ( 10 );
    output_low ( LCD_EN );
    delay_ms ( 5 );
    }

void LCD_SetData ( unsigned int cX )
    {
    output_bit ( LCD_D0, cX & 0x01 );
    output_bit ( LCD_D1, cX & 0x02 );
    output_bit ( LCD_D2, cX & 0x04 );
    output_bit ( LCD_D3, cX & 0x08 );
    }