/****************************************************************************
TACHTC.c

This program is a tachometer transmitter.
It transmits in Seatalk(tm) network protocol.

The following Seatalk protocol is extracted from Thomas Knauf's web site:
www.thomasknauf.de/seatalk.htm

Message protocol
* Each 4800 baud message contains between 3 and 18 characters:
* COMMAND byte (the only byte with the command-bit set)
  ATTRIBUTE byte, specifying the total length of the message in the least significant nibble:
      Most  significant 4 bits: 0 or part of a data value
      Least significant 4 bits: Number of additional bytes beyond the mandatory data byte
  DATA byte (mandatory, meaning than the smallest message is 3 bytes)
  DATA bytes (optional, up to 15 additional data bytes, meaning that longest messages is 18 bytes)

Serial Data Transmission
11 bits are transmitted for each byte:
  * 1  Start bit (0V)
  * 8  Data Bits (least significant bit transmitted first, bit ON = +12V)
  * 1  Command/Data bit (+12V if command byte, 0V if other)
  * 1  Stop bit (+12V)

Collision Management
Bus should be idle for at least 2mS (+12V for at least 10/4800 seconds).
Listens to it's own transmission and recognizes when its message has
been corrupted by a second talker. In this case it abandons the remaining
bytes in the message, waits for the bus to become free again, and then
retransmits the whole message.

CODES
------------------------------
FE 01 0x yy     Tank address x is yy percent full



                 +5   +5
                  |    |
                  14   4
                ----------
               |          |-17-- out to Seatalk (transistor driver)
               |          |-18-- in from Seatalk (transistor buffer)
               |          |
     IN  ----9-|          |
               |  16F628  |
               |          |
     ADR ---12-|A0        |
     ADR ---13-|A1        |
  4MHz XTAL-15-|        A2|-1--- LED
       XTAL-16-|          |
                ----------
                     5
                     |
                    Gnd

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

/* The following include should contain 16F84 or 16F628. */
#include < 16F628.h >
#include < jonsinc.h >

#fuses HS, NOPROTECT, PUT, NOWDT, BROWNOUT, NOMCLR, NOLVP

#use fast_io ( A )
#use standard_io ( B )
#use delay ( clock = 8000000, restart_wdt )

#byte PORT_A  = 5
#byte PORT_B  = 6
#bit TX_OUT   = PORT_A.0
#bit RX_IN    = PORT_A.1
#bit LED      = PORT_A.2

#define ADDR_0         PIN_B6
#define ADDR_1         PIN_B7
#define SEATALK_MSGNUM 0xFD
#define CMD            1
#define DATA           0
// TACHOMETER DEFINES ================
#define START_TACH  0
#define RUN_TACH    1
#define DONE_TACH   2
//    Pulley factor is alternator pulley diameter divided by crank pulley diameter
#define ENGINE_DIA      3.625
#define ALT_DIA         2.875
#define PULLEY_FACTOR   ALT_DIA/ENGINE_DIA
//    Obtain from manufacturers data sheet (6, 8, 10, etc.)
#define ALTERNATOR_POLES  10

void SendMsg ( int32 int32Data );
char SendByte ( char cError, char cCommand, char cData );
char SendBit ( cBit );
void CheckBus ( void );

static char cAddress;
static char cBuffer [ 10 ];

void main ( void )
        {
        char cX, cY, cLed;
        char cCnt;
        float fRpm;
        int32 int32Count;
        long iLevel;

        delay_ms ( 150 );       // wait for 75mS nom PUT
        TX_OUT = LOW;                       // allow output to float
        LED = LOW;
        set_tris_a ( 0b11111010 );          // A0, A2 are outputs, A1 is input
        output_low ( PROBE_POWER );         // default off
        setup_counters ( RTCC_INTERNAL, WDT_2304MS );   // 256 * 4uS = 1.024mS timer wrap
        port_b_pullups ( TRUE );
        cAddress = input_b();
        cAddress &= 0b11000000;       // mask on bits 6 and 7
        cAddress >>= 6;    // shift address to 0 bit index
        for ( cY = 0; cY < cAddress + 1; cY++ ) // blink the card address upon initialization
            {
            for ( cX = 0; cX < 5; cX++ )
                {
                LED = ON;
                delay_ms ( 18 );
                LED = OFF;
                delay_ms ( 18 );
                }
            delay_ms ( 200 );
            }
        for ( cX = 0; cX < 10; cX++ )
            {
            cBuffer [ cX ] = 0;             // initialize averaging buffer
            }
        for ( cX = 0; cX < cAddress; cX++ )
            {
            delay_ms ( 5 );                 // variable startup delay based on address
            }
        cLed = LOW;

        while ( TRUE )         // do forever
            {
            restart_wdt();
            enable_interrupts ( INT_CCP1 );     // CCP1 interrupt
            cTachState = DONE_TACH;
            if ( !cSkip )                           // do until button is pressed
                {
                int32Count = 0;
                for ( cCnt = 0; cCnt < 20; cCnt++ )    // accumulate 20 readings
                    {
                    cTachState = START_TACH;           // allow interrupt to start
                    while ( cTachState != DONE_TACH )  // wait for timing to complete
                        {
                        if ( get_timer1() > 60000 )  // timeout counter  //??? divide this by 8 since 8 prescaler is being used???
                            {
                            iTachCount = 0;          // zero everything out
                            int32Count = 0;
                            break;                   // don't wait any longer
                            }
                        }
                    int32Count += iTachCount;    // otherwise accumulate
                    }
                int32Count /= 20;                // get average of those 20 readings
                }
            disable_interrupts ( INT_CCP1 );     // CCP1 interrupt
            }
            SendMsg ( iLevel );
            LED = cLed;
            delay_ms ( 1000 );
            cLed ^= 1;
            }
        }

void SendMsg ( int32 int32Data )
    {
    char cData0, cData1, cData2, cData3, cError;

    cData0 = ( char ) ( int32Data & 0x000000ff );
    cData1 = ( char ) ( ( int32Data / 256 ) & 0x000000ff );
    cData2 = ( char ) ( ( int32Data / 65536 ) & 0x000000ff );
    cData3 = ( char ) ( ( int32Data / 167772216 ) & 0x000000ff );
    do {
        CheckBus();                                //wait for bus to be idle
        cError = SendByte ( NO, CMD, SEATALK_MSGNUM );  // command
        cError = SendByte ( cError, DATA, 0x04 );    // 4 extra data bytes (7 total)
        cError = SendByte ( cError, DATA, cAddress );   // card address
        cError = SendByte ( cError, DATA, cData0 );   // data
        cError = SendByte ( cError, DATA, cData1 );
        cError = SendByte ( cError, DATA, cData2 );
        cError = SendByte ( cError, DATA, cData3 );
        } while ( cError == YES );                 // repeat if message was corrupted
    }

char SendByte ( char cError, char cCommand, char cData )
    {
    char cX;

    if ( cError != YES )
        {
        cError = SendBit ( HIGH );           // start bit (0V)
        for ( cX = 0; cX < 8; cX++ )
            {
            cError = SendBit ( ~cData & 0x01 );  // LSB data bit
            cData >>= 1;                 // shift right
            }
        cError = SendBit ( cCommand ? LOW : HIGH );    // set if command byte, clear if data byte
        cError = SendBit ( LOW );           // stop bit (+12V)
        }
    return ( cError );
    }

char SendBit ( cBit )
    {
    char cX, cY;
    // this code adjusted to give 208uS bit times (4800 baud)
    TX_OUT = cBit;                  // send bit to output
    for ( cX = 0; cX < 8; cX++ )
        {
        delay_us ( 14 );
        if ( RX_IN == !cBit )       // check if output bit is corrupted by another talker
            {
            return ( HIGH );        // return collision error
            }
        }
    return ( LOW );                 // return no error
    }

void CheckBus ( void )
    {
    char cX;

    for ( cX = 0; cX < 255; cX++ )  // assumes output is floating to +12V for ~5mS
        {
        if ( RX_IN == HIGH )        // check if output bit is corrupted by another talker
            {
            cX = 0;                 // reset count to zero
            restart_wdt();          // CCS compiler doesn't put CLRWDT into short delay_us, apparently
            }
        delay_us ( 7 );
        }
    }