/****************************************************************************
BCHAR18.C

This is a battery characterizer for NiMH and NiCD cells.
Four 5V ADC inputs (1.2V cells) and one 8V input (3.6 or 4.8V cells) collect samples.
Control and display is done via two pushbuttons and an LCD display.
Menus and results are viewed on the display.

WORKING CODE

Next:
    put in overvoltage sense

                   ---------
           +5--20-|Vdd      |
           +5---1-|Mclr     |
          Gnd---8-|Vss      |
          Gnd--19-|Vss      |
         4MHz--10-|Xtal     |
         4MHz---9-|Xtal     |
                  |         |
                  |         |
     SOCKET 1 --2-|AN0    B4|-24----load 0 FET gate
     SOCKET 2 --3-|AN1    B3|-24----load 1 FET gate
     SOCKET 3 --4-|AN2    B2|-23----load 2 FET gate
     SOCKET 4 --5-|AN3    B1|-22----load 3 FET gate
     SOCKET 5 --7-|AN4    B0|-21----load 4 FET gate
                  |         |
      SW 1 ----28-|B7       |
                  |         |
                  |         |         ---------
                  |       C2|-13--11-|D4       |
                  |       C3|-14--12-|D5       |
                  |       C5|-16--13-|D6       |
                  |       C4|-15--14-|D7       |
                  |         |        |         |-3--20K pot (contrast)
                  |       C1|-12---6-|EN       |
                  |       C0|-11---4-|RS       |
                  |         |        |         |
                  |         |   +5-2-|         |
                  |         |  Gnd-1-|         |
                  | 18F252  |  Gnd-5-|         |
                  |         |        | DISPLAY |
                   ---------          ---------

*/

#include < 18f252.h >
#device *=16 ADC=10    /* allow RAM addresses over 255 */
#include < jonsinc.h >
#fuses XT, NOPROTECT, NOOSCSEN, BROWNOUT, NOWDT, BORV20, PUT, NOSTVREN, NODEBUG, NOLVP, NOWRT, NOWRTD, NOWRTB

//=============================================================
// crystal frequency (Hz)
#define CRYSTAL_FREQ         4000000
// actual power supply voltage (volts)
#define VDD                  5.02
// cell nominal voltage (volts)
#define CELL_NOM_VOLTAGE     1.2
// voltage at which mAh accumlation stops (volts)
#define CUTOFF_VOLTAGE       1.0
// actual load resistances (ohms)
#define LOAD_0_OHMS          4.9
#define LOAD_1_OHMS          4.9
#define LOAD_2_OHMS          4.9
#define LOAD_3_OHMS          4.9
#define LOAD_4_OHMS          19.5
// actual FET RDS(on) resistance (ohms)
#define FET_RDS_OHMS         0.17
// actual scaling resistors R1 and R2 for channel 5 (ohms)
#define R1                   5350
#define R2                   10220
// display time for logo screens (normally 750 mS)
#define LOGO_TIME            750
// wait time for which voltage is displayed after a cell is inserted into a socket (seconds)
#define INITIAL_DELAY        2
// wait time after which display goes into rolling status mode (seconds)
#define STATUS_DELAY         10
// screen cycle time when in rolling status mode (seconds)
#define SCREEN_CYCLE_DELAY   2
// screen cycle time when in cell detail mode (mS)
#define DETAIL_CYCLE_DELAY   1500
// restart IRQ timer at adjusted value for 1-second accuracy (0-255, higher makes shorter IRQ tick)
#define IRQ_RESTART_TICK     3
// time at cutoff voltage required to end test (seconds, normally 60)
#define AUTOSTOP_TIME        60
// button time after which unit will display cell details (31mS interrupt counts, normally 16, ~= 0.5 second)
#define BUTTON_DETAIL_TIME   16
// button time after which unit will reset (31mS interrupt counts, normally 128, ~= 4 seconds)
#define BUTTON_RESET_TIME    128
// round to nearest 10's or 100's
#define ROUNDNUM             100
// socket definitions
#define SOCKET_1             0
#define SOCKET_5             4
// overvoltage definitions
#define CH_1_4_OVERVOLTAGE   2.0
#define CH_5_OVERVOLTAGE     6.0
//=============================================================

// LCD STUFF
#define LCD_D0      PIN_C2
#define LCD_D1      PIN_C3
#define LCD_D2      PIN_C5
#define LCD_D3      PIN_C4
#define LCD_EN      PIN_C1
#define LCD_RS      PIN_C0
#define LOAD_0      PIN_B4
#define LOAD_1      PIN_B3
#define LOAD_2      PIN_B2
#define LOAD_3      PIN_B1
#define LOAD_4      PIN_B0
#define BUTTON      PIN_B7
#define OHM_SYM     0xF4
#define FIRST_LINE  0
#define CLEAR_DISP  0x01
#define CELL_STATE_READY        'R'
#define CELL_STATE_TEST         'T'
#define CELL_STATE_DONE         'D'
#define FMULT                   ( float ) R1 / ( float ) ( R1 + R2 )
// sample interval is normally 60 seconds
#define SAMPLE_INTERVAL_SEC    60

#use delay ( clock=CRYSTAL_FREQ )
#use standard_io ( A )
#use standard_io ( B )
#use standard_io ( C )

separate void DelayMs ( long cDelay );    // prototype statements
separate long Round ( float fNum, int cNearest );
float ReadAdc ( unsigned int cChannel );
void LoadControl ( char cChannel, char cState );
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 );

static char cCellDetailWanted, cSwitchCount, cInitialDelayCount;
static char cInterruptCount1s, cInterruptScreenCycleCount;
static long iInterruptStatusCount, iRunMinutes [ 5 ];
static char cSocket, cSocketDisp, cY, cSkip, cStatusMode;
static char cLogging [ 5 ], cAutoStopCount [ 5 ], cCellCount [ 5 ];
static char cSampleCount [ 5 ], cCellPresent [ 5 ];
static char cSocketState [ 5 ], cSampleFlag [ 5 ], cIntResCount [ 5 ];
static float fIntRes [ 5 ], fLoadRes [ 5 ], fAccumMa [ 5 ];
static float fCutoffVoltage [ 5 ], fMultiplier [ 5 ], fOverVoltage [ 5 ];
static float fStartVoltageUnloaded [ 5 ], fStartVoltageLoaded [ 5 ];
static float fPresentVoltage [ 5 ];

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

#int_timer0
void TimerInterrupt ( void )      // 32.768mS tic @ 4MHz, ~31 interrupts per second
    {
    // ONE-SECOND TICK
    if ( cInterruptCount1s++ >= 31 )          // if one second yet
        {
        cInterruptCount1s = 0;
        for ( cY = SOCKET_1; cY <= SOCKET_5; cY++ )
            {
            cAutoStopCount [ cY ]++;
            if ( cLogging [ cY ] == YES )
                {
                if ( cSampleCount [ cY ]++ >= SAMPLE_INTERVAL_SEC - 1 )   // if sample time yet
                    {
                    iRunMinutes [ cY ]++;          // accumulate minutes
                    cSampleFlag [ cY ] = ON;       // signal time to sample
                    cSampleCount[ cY ] = 0;        // start count over
                    }
                }
            }
        }
    // INITIAL DELAY TIMER (delay during which voltage is displayed)
    if ( cInitialDelayCount < INITIAL_DELAY * 32 )  // hold at end of delay
        {
        cInitialDelayCount++;         // otherwise increment
        }
    // STATUS DELAY (time after which in rolling status display mode)
    if ( iInterruptStatusCount >= ( STATUS_DELAY * 32 ) )          // if ready to change screen yet
        {
        cStatusMode = YES;
        }
    else
        {
        cStatusMode = NO;
        iInterruptStatusCount++;
        }
    // SCREEN CYCLE (screen cycle rate once in status display mode)
    if ( cInterruptScreenCycleCount++ >= ( SCREEN_CYCLE_DELAY * 32 ) )           // if ready to change screen yet
        {
        if ( ( cStatusMode == YES ) && ( cCellDetailWanted == NO ) )     // if ready to change screen yet
            {
            cSocketDisp++;                            // point to next cell
            if ( cSocketDisp > SOCKET_5 )             // if last socket
                {
                cSocketDisp = SOCKET_1;               // wrap to first socket
                }
            }
        cInterruptScreenCycleCount = 0;
        }
    // SWITCH HANDLER (momentary cycles which cell, hold one second displays cell details, hold four seconds resets)
    if ( input ( BUTTON ) == LOW )   // if button is low
        {
        if ( cSwitchCount < 128 )  // hold at 128 (~4 seconds)
            {
            cSwitchCount++;         // otherwise increment
            }
        }
    else
        {
        if ( cSwitchCount > 2 )     // filter out glitches
            {
            // If button is held for a really long time, do cold reset
            if ( cSwitchCount == BUTTON_RESET_TIME )
                {
                reset_cpu();
                }
            // If button is held for shorter time
            if ( ( cSwitchCount > BUTTON_DETAIL_TIME ) && ( cSwitchCount < BUTTON_RESET_TIME ) )
                {
                cCellDetailWanted = YES;        // signal that switch was pressed
                cInterruptScreenCycleCount = 0;
                iInterruptStatusCount = 0;      // stop status cycling
                cSkip = NO;
                }
            // If button press is pressed only momentarily
            if ( cSwitchCount <= BUTTON_DETAIL_TIME )
                {
                cSocketDisp++;                                    // point to next cell
                if ( cSocketDisp > SOCKET_5 )              // if last socket
                    {
                    cSocketDisp = SOCKET_1;               // wrap to first socket
                    }
                cSkip = YES;
                cCellDetailWanted = NO;
                }
            cInterruptScreenCycleCount = 0;
            iInterruptStatusCount = 0;      // stop status cycling
            cSwitchCount = 0;   // reset
            }
        else
            {
            cSwitchCount = 0;             // switch was released prematurely, restart count
            }
        }
    // Adjust timing
    set_rtcc ( IRQ_RESTART_TICK );     // restart at adjusted value for 1-second accuracy (higher to make shorter tick)
    }

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

void main ( void )
    {
    char cX;
    long iX;
    float fV, fX, fVload, fLastV;

    cSwitchCount = 0;
    cSocket = SOCKET_1;
    cSocketDisp = SOCKET_1;
    setup_timer_0 ( RTCC_INTERNAL | RTCC_DIV_128 | RTCC_8_BIT );     // ~32mS timer wrap
    setup_adc_ports ( ALL_ANALOG );     // these three statements set up the ADC
    setup_adc ( ADC_CLOCK_INTERNAL );   // clock source
    enable_interrupts ( INT_RTCC );     // turn on timer interrupt
    enable_interrupts ( GLOBAL );       // enable interrupts

    output_float ( PIN_A4 );  // because Socket#5 was wrongly connected to Pin RA4 as well as Pin RA5 (AN4) in "bc01" circuit board
    cCellDetailWanted = NO;
    iInterruptStatusCount = 0;
    cInitialDelayCount = 0;
    cSkip = NO;
    for ( cX = SOCKET_1; cX <= SOCKET_5; cX++ )       // preset
        {
        cSocketState [ cX ] = CELL_STATE_READY;
        LoadControl ( cX, OFF );
        cLogging [ cX ] = NO;
        cSampleFlag [ cX ] = OFF;
        cCellPresent [ cX ] = NO;
        cIntResCount [ cX ] = 0;
        iRunMinutes [ cSocket ] = 0;
        }
    fLoadRes [ 0 ] = LOAD_0_OHMS + FET_RDS_OHMS;
    fLoadRes [ 1 ] = LOAD_1_OHMS + FET_RDS_OHMS;
    fLoadRes [ 2 ] = LOAD_2_OHMS + FET_RDS_OHMS;
    fLoadRes [ 3 ] = LOAD_3_OHMS + FET_RDS_OHMS;
    fLoadRes [ 4 ] = LOAD_4_OHMS + FET_RDS_OHMS;
    fOverVoltage [ 0 ] = CH_1_4_OVERVOLTAGE;
    fOverVoltage [ 1 ] = CH_1_4_OVERVOLTAGE;
    fOverVoltage [ 2 ] = CH_1_4_OVERVOLTAGE;
    fOverVoltage [ 3 ] = CH_1_4_OVERVOLTAGE;
    fOverVoltage [ 4 ] = CH_5_OVERVOLTAGE;
    fMultiplier [ 0 ] = 1.0;
    fMultiplier [ 1 ] = 1.0;
    fMultiplier [ 2 ] = 1.0;
    fMultiplier [ 3 ] = 1.0;
    fMultiplier [ 4 ] = FMULT;

    LCD_Init();
    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "  NiCd & NiMH  " );
    delay_ms ( LOGO_TIME );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "    BATTERY    " );
    delay_ms ( LOGO_TIME );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, " CHARACTERIZER " );
    delay_ms ( LOGO_TIME );

    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "Jon Fick 030405" );
    delay_ms ( LOGO_TIME );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "PressShort=next " );
    delay_ms ( LOGO_TIME * 2 );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "PressLong=detail" );
    delay_ms ( LOGO_TIME * 2 );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, " #1  #2  #3  #4 " );
    delay_ms ( LOGO_TIME * 2 );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "#5 is external  " );
    delay_ms ( LOGO_TIME * 2 );
    LCD_PutCmd ( CLEAR_DISP );

    while ( TRUE )                // do forever
        {
        cSkip = NO;   // reset
        // CALCULATION SECTION, USES CELL POINTER "cSocket" ///////////////////////////////
        for ( cSocket = SOCKET_1; cSocket <= SOCKET_5; cSocket++ )
            {
            // read cell voltage
            fV = ReadAdc( cSocket );            // read voltage at this cell
            //   if ( fVopen > fOverVoltage [ cSocket ] )
            //       {
            //       cCellPresent [ cSocket ] = NO;
            //       printf ( LCD_PutChar, "#%u Overvoltage!!", cSocketNum );
            //       }
            if ( fV <= 0.4 )     // check if this socket is empty
                {
                cCellPresent [ cSocket ] = NO;
                cSocketState [ cSocket ] = CELL_STATE_READY;
                }
            else                       // if socket is not empty
                {
                fPresentVoltage [ cSocket ] = fV;   // save present measurement
                switch ( cSocketState [ cSocket ] )
                    {
                    case CELL_STATE_READY:
                        {
                        if ( cCellPresent [ cSocket ] == NO )       // if socket was previously empty
                            {
                            // do all the preliminary stuff one time before changing to TEST state
                            // now that the voltage is greater than empty socket,
                            //   read cell again to ensure voltage is stable after a delay
                            fLastV = 0;     // restart
                            for ( cX = 0; cX < 10; cX++ )
                                {
                                fV = ReadAdc( cSocket );            // read voltage at this cell
                                if ( ( fV < fLastV - 0.010 ) || ( fV > fLastV + 0.010 ) )
                                    {
                                    cX = 0;    // restart count
                                    }
                                fLastV = fV;     // update
                                DelayMs ( 10 );
                                }
                            // determine number of cells in socket
                            fStartVoltageUnloaded [ cSocket ] = fV;    // save unloaded start voltage
                            cX = ( char ) ( fV / CELL_NOM_VOLTAGE );   // calculate number of cells
                            if ( cX < 1 )
                                {
                                cX = 1;     // one cell minimum, just in case so cutoff isn't ever zero volts
                                }
                            cCellCount [ cSocket ] = cX;   // save count
                            // calculate cutoff voltage
                            fCutoffVoltage [ cSocket ] = ( ( float ) ( cCellcount [ cSocket ] ) ) * CUTOFF_VOLTAGE;    // calculate cutoff voltage based on the number of cells
                            // measure internal resistance
                            LoadControl ( cSocket, ON );               // load cell
                            DelayMs ( 100 );                           // stabilize time
                            fVload = ReadAdc( cSocket );               // read ADC
                            LoadControl ( cSocket, OFF );              // unload cell
                            fStartVoltageLoaded [ cSocket ] = fVLoad;  // save loaded start voltage
                            fIntRes [ cSocket ] = ( fV - fVload ) / ( fVload / fLoadRes [ cSocket ] );   // calc R and store it
                            // initialize test
                            cSampleCount [ cSocket ] = 0;         // reset
                            fAccumMa [ cSocket ] = 0;             // reset accumulated mAh
                            cAutoStopCount [ cSocket ] = 0;       // reset
                            cLogging [ cSocket ] = YES;           // allow interrupt to flag accumulating
                            iInterruptStatusCount = 0;            // stop rolling status display for awhile
                            cSocketDisp = cSocket;                // display this cell immediately
                            cInitialDelayCount = 0;               // initialize delay for voltage display
                            iRunMinutes [ cSocket ] = 0;          // reset test duration
                            cSocketState [ cSocket ] = CELL_STATE_TEST; // put in TEST state
                            }
                        break;
                        }
                    case CELL_STATE_TEST:
                        {
                        if ( fV <= fCutoffVoltage [ cSocket ] )     // check if below cutoff voltage yet
                            {
                            // finish up, change state to DONE
                            if ( cAutoStopCount [ cSocket ] >= AUTOSTOP_TIME )   // need this many seconds in a row to end
                                {
                                cLogging [ cSocket ] = NO;      // stop interrupt from flagging accumulating
                                LoadControl ( cSocket, OFF );                     // unload cell
                                cSocketState [ cSocket ] = CELL_STATE_DONE;
                                }
                            }
                        else      // if still above autostop voltage
                            {
                            // still testing, reset stop count
                            cAutoStopCount [ cSocket ] = 0;                 // reset count
                            }
                        break;
                        }
                    }
                cCellPresent [ cSocket ] = YES;
                }
            // is it time to sample this particular cell?
            if ( cSampleFlag [ cSocket ] == ON )
                {
                cSampleFlag [ cSocket ] = OFF;     // reset flag
                fX = fV / fLoadRes [ cSocket ];    // calc mA for this sample
                fAccumMa [ cSocket ] += fX;        // accumulate mA
                }
            }     // end of calculation section
        // DISPLAY SECTION, USES CELL POINTER "cSocketDisp" /////////////////////////////////
        LCD_SetPosition ( FIRST_LINE + 0 );
        // check if any cells in any sockets at all
        if ( ( cCellPresent [ 0 ] == NO ) && ( cCellPresent [ 1 ] == NO ) && ( cCellPresent [ 2 ] == NO ) && ( cCellPresent [ 3 ] == NO ) && ( cCellPresent [ 4 ] == NO ) )
            {
            printf ( LCD_PutChar, " Sockets empty  " );
            for ( cX = SOCKET_1; cX <= SOCKET_5; cX++ )       // preset
                {
                LoadControl ( cX, OFF );
                }
            }
        else       // some cells are in sockets
            {
            for ( cX = 0; cX <= 5; cX++ )
                {
                if ( cCellPresent [ cSocketDisp ] == NO )      // find next socket that has a cell
                    {
                    cSocketDisp++;
                    if ( cSocketDisp > SOCKET_5 )              // if last socket
                        {
                        cSocketDisp = SOCKET_1;               // wrap to first socket
                        }
                    }
                else
                    {
                    break;          // found a cell, skip out
                    }
                }
            // check if detail screens are wanted
            if ( cCellDetailWanted == YES )
                {
                while ( TRUE )
                    {
                    if ( cSkip == YES ) { break; }
                    // display cell number and status
                    printf ( LCD_PutChar, "Cell #%u: ", cSocketDisp + 1 );
                    if ( cSocketState [ cSocketDisp ] == CELL_STATE_TEST )
                        {
                        printf ( LCD_PutChar, "in TEST" );
                        }
                    if ( cSocketState [ cSocketDisp ] == CELL_STATE_DONE )
                        {
                        printf ( LCD_PutChar, "is DONE" );
                        }
                    DelayMs ( DETAIL_CYCLE_DELAY );
                    // display number of cells
                    if ( cSkip == YES ) { break; }
                    LCD_SetPosition ( FIRST_LINE + 0 );
                    printf ( LCD_PutChar, "Num cells: %u    ", cCellCount [ cSocketDisp ] );
                    DelayMs ( DETAIL_CYCLE_DELAY );
                    // display initial unloaded voltage
                    if ( cSkip == YES ) { break; }
                    LCD_SetPosition ( FIRST_LINE + 0 );
                    printf ( LCD_PutChar, "Init Vopen: %01.2f", fStartVoltageUnloaded [ cSocketDisp ] );
                    DelayMs ( DETAIL_CYCLE_DELAY );
                    // display present or final loaded voltage
                    if ( cSkip == YES ) { break; }
                    if ( cSocketState [ cSocketDisp ] == CELL_STATE_TEST )
                        {
                        LCD_SetPosition ( FIRST_LINE + 0 );
                        printf ( LCD_PutChar, "Vloaded: %01.2f   ", fPresentVoltage [ cSocketDisp ] );
                        DelayMs ( DETAIL_CYCLE_DELAY );
                        LCD_SetPosition ( FIRST_LINE + 0 );
                        printf ( LCD_PutChar, "Int Res: %01.2f%c  ", fIntRes [ cSocketDisp ], OHM_SYM );
                        DelayMs ( DETAIL_CYCLE_DELAY );
                        }
                    // display average mA load
                    if ( cSkip == YES ) { break; }
                    LCD_SetPosition ( FIRST_LINE + 0 );
                    fX = ( fPresentVoltage [ cSocketDisp ] + ( fStartVoltageLoaded [ cSocketDisp ] - fPresentVoltage [ cSocketDisp ] ) / 2 );
                    if ( cSocketDisp == 0 )
                        {
                        fX /= LOAD_0_OHMS;
                        }
                    if ( cSocketDisp == 1 )
                        {
                        fX /= LOAD_1_OHMS;
                        }
                    if ( cSocketDisp == 2 )
                        {
                        fX /= LOAD_2_OHMS;
                        }
                    if ( cSocketDisp == 3 )
                        {
                        fX /= LOAD_3_OHMS;
                        }
                    if ( cSocketDisp == 4 )
                        {
                        fX /= LOAD_4_OHMS;
                        }
                    // convert to long
                    printf ( LCD_PutChar, "Avg Load: %04.0fmA   ", fX * 1000 );
                    DelayMs ( DETAIL_CYCLE_DELAY );
                    // display accumulated mAh
                    if ( cSkip == YES ) { break; }
                    LCD_SetPosition ( FIRST_LINE + 0 );
                    if ( cSocketState [ cSocketDisp ] == CELL_STATE_TEST )
                        {
                        printf ( LCD_PutChar, "Accum" );
                        }
                    if ( cSocketState [ cSocketDisp ] == CELL_STATE_DONE )
                        {
                        printf ( LCD_PutChar, "Final" );
                        }
                    iX = ( long ) ( fAccumMa [ cSocketDisp ] * 1000 / SAMPLE_INTERVAL_SEC );
                    printf ( LCD_PutChar, ": %lumAh     ", iX );
                    DelayMs ( DETAIL_CYCLE_DELAY );
                    // display test time
                    if ( cSkip == YES ) { break; }
                    LCD_SetPosition ( FIRST_LINE + 0 );
                    printf ( LCD_PutChar, "Time %02.1f hrs    ", ( float ) iRunMinutes [ cSocketDisp ] / 60 );
                    DelayMs ( DETAIL_CYCLE_DELAY );
                    iInterruptStatusCount = 0;      // stop status cycling
                    cCellDetailWanted = NO;
                    break;
                    }
                }
            else
                {
                switch ( cSocketState [ cSocketDisp ] )
                    {
                    case CELL_STATE_TEST:
                        {
                        if ( cInitialDelayCount < INITIAL_DELAY * 32 )  // if cell was just inserted in socket
                            {
                            // display initial unloaded voltage for a short time
                            fV = ReadAdc( cSocketDisp );           // read voltage at this cell
                            printf ( LCD_PutChar, "T#%u meas: %01.2fV  ", cSocketDisp + 1, fStartVoltageUnloaded [ cSocketDisp ] );
                            }
                        else
                            {
                            // display present mAh thereafter
                            LoadControl ( cSocketDisp, ON );          // turn load on
                            iX = ( long ) ( fAccumMa [ cSocketDisp ] * 1000 / SAMPLE_INTERVAL_SEC );
                            printf ( LCD_PutChar, "T#%u %lumAh        ", cSocketDisp + 1, iX );
                            }
                        break;
                        }
                    case CELL_STATE_DONE:
                        {
                        // display result
                        fX = fAccumMa [ cSocketDisp ] * 1000 / SAMPLE_INTERVAL_SEC;
                        iX = Round ( fX, ROUNDNUM );        // store final mAh value
                        printf ( LCD_PutChar, "D#%u %lumAh %01.1f%c    ", cSocketDisp + 1, iX, fIntRes [ cSocketDisp ], OHM_SYM );
                        break;
                        }
                    }
                } // end of display section
            }
        }
    }

separate void DelayMs ( long iDelay )
    {
    long iX;

    for ( iX = 0; iX < iDelay; iX++ )
        {
        delay_ms ( 1 );
        if ( cSkip == YES )
            {
            //cSkip = NO;   // reset flag
            break;
            }
        }
    }

separate long Round ( float fNum, int cNearest )
    {
    long iM, iX;

    iX = ( long ) fNum;
    if ( iX >= ( long ) cNearest )   // if greater than rounding factor, otherwise return actual number
        {
        iM = iX % cNearest;             // fractional part
        iX = iX / cNearest;
        if ( iM < ( cNearest / 2 ) )
            {
            iX = iX * cNearest;         // bump down
            }
        else
            {
            iX = ( iX + 1 ) * cNearest; // bump up
            }
        }
    return ( iX );
    }

float ReadAdc ( unsigned int cChannel )
    {
    long iAdcVal;
    char cCnt;

    set_adc_channel ( cChannel );
    delay_us ( 50 );
    iAdcVal = 0;
    for ( cCnt = 0; cCnt < 10; cCnt++ )     // average over 10 readings
        {
        iAdcVal += read_adc();
        DelayMs ( 5 );
        }
    // return average of ten readings, scaled to proper range
    return ( ( ( float ) iAdcVal ) / ( float ) 10 / ( float ) 1024 * ( float ) VDD / fMultiplier [ cChannel ] );
    }

void LoadControl ( char cChannel, char cState )
    {
    switch ( cChannel )
        {
        case 0:
            {
            output_bit ( LOAD_0, cState );
            break;
            }
        case 1:
            {
            output_bit ( LOAD_1, cState );
            break;
            }
        case 2:
            {
            output_bit ( LOAD_2, cState );
            break;
            }
        case 3:
            {
            output_bit ( LOAD_3, cState );
            break;
            }
        case 4:
            {
            output_bit ( LOAD_4, cState );
            break;
            }
        }
    }

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

void LCD_Init ( void )
    {
    LCD_SetData ( 0x00 );
    DelayMs ( 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 );
    DelayMs ( 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 );
    }