/**********************************************************************
dtachc.c

This is a tachometer for a diesel engine in which there is no tach pulse
available because there is no spark ignition system.  Instead, a pulse
is taken off the alternator.  The number of poles in the alternator
and the crank-to-alternator pulley ratio must be factored into the code.

                   +5
                    |
                    14
                ----------
               |          |-12-- LCD D4
               |          |-3--- LCD D5
               |          |-2--- LCD D6
  ALT IN ----9-|          |-1--- LCD D7
               |  16F628  |-18-- LCD EN
               |          |-17-- LCD RS
               |          |
               |          |
  4MHz XTAL-15-|          |
       XTAL-16-|          |
                ----------
                     5
                     |
                    Gnd

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

#include < 16F628.h >
#include < jonsinc.h >

#fuses XT, NOPROTECT, PUT, NOWDT, BROWNOUT, MCLR, NOLVP

// LCD STUFF
#define LCD_D4      PIN_B6
#define LCD_D5      PIN_A4
#define LCD_D6      PIN_A3
#define LCD_D7      PIN_A2
#define LCD_EN      PIN_A1
#define LCD_RS      PIN_A0
#define FIRST_LINE  0x00
#define SECOND_LINE 0x40
#define CLEAR_DISP  0x01
#define CURS_ON     0x0e
#define CURS_OFF    0x0c

#define START_TACH  0
#define RUN_TACH    1
#define DONE_TACH   2
// Pulley factor is alternator pulley diameter divided by crank pulley diameter
#define PULLEY_FACTOR     1.0
// Obtain from manufacturers data sheet (6, 8, 10, etc.)
#define ALTERNATOR_POLES  10

#use delay ( clock=4000000 )
#use standard_io ( A )
#use standard_io ( B )

// proto statements
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 );

// Global variables
static char cTimerCount, cOneSecondFlag, cState;
static long iCount;

//************************************************************************
// INTERRUPT ROUTINES

#int_ccp1
void CCP1Interrupt ( void )
    {
    if ( cState == RUN_TACH ) // second edge
        {
        iCount = CCP_1;       // get capture value
        cState = DONE_TACH;   // prevent further processing during this interrupt
        }
    if ( cState == START_TACH )   // first edge
        {
        set_timer1 ( 0 );     // restart timer on this edge
        cState = RUN_TACH;
        }
    }

//*****************************************************************
// MAIN CODE

void main ( void )
    {
    char cCnt;
    float fRpm;
    int32 int32Count;

    // SETUP TIMER 1
    setup_timer_1 ( T1_INTERNAL );

    // SETUP CCP1
    setup_ccp1 ( CCP_CAPTURE_RE );          // capture every rising edge

    // INITIAL MESSAGE
    delay_ms ( 200 );       // wait enough time after Vdd rise
    LCD_Init();
    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( FIRST_LINE + 3 );
    printf ( LCD_PutChar, "TACHOMETER" );

    // ENABLE INTERRUPTS
    enable_interrupts ( INT_CCP1 );     // CCP1 interrupt
    enable_interrupts ( GLOBAL );       // enable all interrupts

    cState = DONE_TACH;
    while ( TRUE )
        {
        int32Count = 0;
        for ( cCnt = 0; cCnt < 20; cCnt++ )    // accumulate 20 readings
            {
            cState = START_TACH;                // allow interrupt to start
            while ( cState != DONE_TACH )       // wait for timing to complete
                {
                if ( get_timer1() > 60000 )  // timeout counter
                    {
                    iCount = 0;              // zero everything out
                    int32Count = 0;
                    break;                   // don't wait any longer
                    }
                }
            int32Count += iCount;        // otherwise accumulate
            }
        int32Count /= 20;                // get average of those 20 readings
        fRpm = 1 / ( float ) int32Count; // period in uS
        fRpm *= 1000000;                 // period in seconds
        fRpm *= 60;                      // period in minutes
        fRpm /= ALTERNATOR_POLES;        // adjust for number of poles in alternator
        fRpm *= PULLEY_FACTOR;           // adjust for pulley ratio
        LCD_SetPosition ( SECOND_LINE + 4 );
        printf ( LCD_PutChar, "%05.0f RPM    ", fRpm );
        }

    }

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

void LCD_Init ( void )
    {
    LCD_SetData ( 0x00 );
    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_D4, cX & 0x01 );
    output_bit ( LCD_D5, cX & 0x02 );
    output_bit ( LCD_D6, cX & 0x04 );
    output_bit ( LCD_D7, cX & 0x08 );
    }