/**************************************************************************** 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 ); }