/****************************************************************************
LEVEL_TX.C

This program is level sensor.
It transmits in Seatalk(tm) network protocol.

WORKING CODE

Initial diagnostic sequence:
* Flashes the card address 1-4 flashes.
* One second pause.
* Without powering the probes, flashes shorted FETs in physical order starting from the LED side of the card
* One second pause.
* Powering the probes, flashes FETs in the on-state to show liquid level.
* One second pause.
* Normal operation thereafter.

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
                ----------
     PP  ----6-|          |-17-- out to Seatalk (transistor driver)
     P0  ----7-|          |-18-- in from Seatalk (transistor buffer)
     P1  ----8-|          |
     P2  ----9-|          |
     P3  ---10-|  16F628  |
     P4  ---11-|          |
     A0  ---12-|          |
     A1  ---13-|          |         A0   A1
  4MHz XTAL-15-|          |       0  f   f
       XTAL-16-|          |       1  0   f
                ----------        2  f   0
                     5            3  0   0
                     |
                    Gnd


                --------------------------
               |                          |
               |     BOARD LAYOUT         |
               |                          |
          100% | O                        |
           80% | O                        |
           20% | O                        |
           60% | O                      O | +12V
           40% | O                      O | Data
   Probe Power | O                      O | Gnd
                --------------------------

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

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

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

#use fast_io ( A )
#use standard_io ( B )
#use delay ( clock = 6000000, 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 PROBE_POWER    PIN_B0
#define PROBE_20       PIN_B1
#define PROBE_40       PIN_B2
#define PROBE_60       PIN_B3
#define PROBE_80       PIN_B4
#define PROBE_100      PIN_B5
#define ADDR_0         PIN_B6
#define ADDR_1         PIN_B7
#define SEATALK_MSGNUM 0xFE
#define CMD            1
#define DATA           0
// determine unused seatalk message number

void MeasureLevel ( void );
void Blink ( char cFlag );
void SendMsg ( char cData );
char SendByte ( char cError, char cCommand, char cData );
char SendBit ( char cBit, char cError );
void CheckBus ( void );

static char cAddress, cLevel_20, cLevel_40, cLevel_60, cLevel_80, cLevel_100;
static char cBuffer [ 10 ];
static long iLevel;

void main ( void )
        {
        char cX, cY;

        delay_ms ( 150 );                   // wait for 75mS 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;       // invert bits 6 and 7
        cAddress &= 0b11000000;       // mask on bits 6 and 7
        cAddress >>= 6;    // shift address to 0 bit index
        // blink the card address upon initialization (1, 2, 3, or 4 blinks)
        for ( cY = 0; cY < cAddress + 1; cY++ )
            {
            Blink ( 0 );
            }
        // FET SHORTS CHECK
        // diag:  blink short if FET is off; long if on
        // blink them in order of 60-40-20-80-100%, RB3-RB2-RB1-RB4-RB5, the physical
        // order on the board of the FETS starting from the LED end, works whether or
        // not the probes are in water because the probe power is low.
        delay_ms ( 1000 );
        output_low ( PROBE_POWER );        // turn probe power off
        MeasureLevel();     // do measures (without turning on probe power) to check for shorted FETs
        Blink ( !cLevel_60 );
        Blink ( !cLevel_40 );
        Blink ( !cLevel_20 );
        Blink ( !cLevel_80 );
        Blink ( !cLevel_100 );
        // ACTUAL PROBE READING
        // diag:  blink short if FET is off; long if on
        // blink them in order of 20-40-60-80-100%, RB1-RB2-RB3-RB4-RB5, the logical order
        delay_ms ( 1000 );
        output_high ( PROBE_POWER );        // turn probe power on to do actual level sensing
        MeasureLevel();     // do measures (without turning on probe power) to check for shorted FETs
        Blink ( cLevel_20 );
        Blink ( cLevel_40 );
        Blink ( cLevel_60 );
        Blink ( cLevel_80 );
        Blink ( cLevel_100 );
        // initialize averaging buffer
        for ( cX = 0; cX < 10; cX++ )
            {
            cBuffer [ cX ] = 0;
            }
        for ( cX = 0; cX < cAddress; cX++ )
            {
            delay_ms ( 300 );                // variable startup delay based on address
            }
        delay_ms ( 2000 );
        // main loop
        while ( TRUE )         // do forever
            {
            restart_wdt();
            // get the reading
            output_high ( PROBE_POWER );        // turn probe power on
            MeasureLevel();
            // insert the reading into the buffer
            for ( cX = 0; cX < 9; cX++ )
                {
                cBuffer [ cX ] = cBuffer [ cX + 1 ]; // move value down one location
                cBuffer [ 9 ] = iLevel;        // insert new reading
                }
            // average the readings
            for ( cX = 0; cX < 9; cX++ )
                {
                iLevel += cBuffer [ cX ];        // accumulate readings
                }
            iLevel = iLevel / 10;               // take average
            // send the message
            SendMsg ( iLevel );
            LED = ON;
            delay_ms ( 50 );
            LED = OFF;
            delay_ms ( 950 );
            for ( cX = 0; cX < cAddress; cX++ )
                {
                delay_us ( 1 );         // variable adder delay based on address
                }
            }
        }

void MeasureLevel ( void )
    {
    iLevel = 0;                         // init at zero
    cLevel_20 = OFF;                    // init at zero
    cLevel_40 = OFF;                    // init at zero
    cLevel_60 = OFF;                    // init at zero
    cLevel_80 = OFF;                    // init at zero
    cLevel_100 = OFF;                   // init at zero
    delay_ms ( 2 );                     // delay to allow RC of 1meg resistor to rise
    // each probe simply adds 20% to the total
    if ( input ( PROBE_20 ) == HIGH )   // if 20 probe is immersed
        {
        cLevel_20 = ON;
        iLevel += 20;
        }
    if ( input ( PROBE_40 ) == HIGH )   // if 40 probe is immersed
        {
        cLevel_40 = ON;
        iLevel += 20;
        }
    if ( input ( PROBE_60 ) == HIGH )   // if 60 probe is immersed
        {
        cLevel_60 = ON;
        iLevel += 20;
        }
    if ( input ( PROBE_80 ) == HIGH )   // if 80 probe is immersed
        {
        cLevel_80 = ON;
        iLevel += 20;
        }
    if ( input ( PROBE_100 ) == HIGH )  // if 100 probe is immersed
        {
        cLevel_100 = ON;
        iLevel += 20;
        }
    output_low ( PROBE_POWER );         // turn probe power off
    }

void Blink ( char cLength )
    {
    LED = ON;
    if ( cLength == ON )
        {
        delay_ms ( 800 );
        }
    else
        {
        delay_ms ( 50 );
        }
    LED = OFF;
    delay_ms ( 300 );
    }

void SendMsg ( char cData )
    {
    char cError, cX;

    do {
        CheckBus();                                //wait for bus to be idle
        cError = SendByte ( NO, CMD, SEATALK_MSGNUM );  // command
        cError = SendByte ( cError, DATA, 0x01 );    // 1 extra data byte (4 total)
        cError = SendByte ( cError, DATA, cAddress );   // card address
        cError = SendByte ( cError, DATA, cData );   // level data
        if ( cError == YES )                        // if bit error occured
            {
            for ( cX = 0; cX < 55; cX++ )           // flash LED dimmly for two seconds
                {
                LED = ON;
                delay_ms ( 9 );
                LED = OFF;
                delay_ms ( 27 );
                }
            }
        } while ( cError == YES );                 // repeat if message was corrupted
    }

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

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

char SendBit ( char cBit, char cError )
    {
    char cX, cY;
    // depending on crystal, this code adjusted to give 208uS bit times (4800 baud)
    if ( cError != YES )                // if no incoming error
        {
        TX_OUT = cBit;                  // send bit to output
        for ( cX = 0; cX < 8; cX++ )
            {
            delay_us ( 10 );
            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 );
        }
    }