/*************************************************************************
LEVELR.C

This program is level sensor.
It transmits in a variant of 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
                    |
                    14
                ----------
               |          |
               |          |-6--- in from Seatalk (transistor buffer)
               |          |
               |          |-13-- LCD D4
               |  16F628  |-12-- LCD D3
               |          |-11-- LCD D2
               |          |-10-- LCD D1
               |          |-9--- LCD EN
  8MHz XTAL-15-|          |-8--- LCD RS
       XTAL-16-|          |
                ----------
                     5
                     |
                    Gnd

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

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

#fuses HS, NOPROTECT, NOPUT, NOWDT, BROWNOUT, MCLR, NOLVP

// LCD STUFF
#define LCD_D7      PIN_B7
#define LCD_D6      PIN_B6
#define LCD_D5      PIN_B5
#define LCD_D4      PIN_B4
#define LCD_EN      PIN_B3
#define LCD_RS      PIN_B2
#define FIRST_LINE  0x00
#define SECOND_LINE 0x40
#define THIRD_LINE  0x10
#define FOURTH_LINE 0x50
#define CLEAR_DISP  0x01
#define CURS_ON     0x0e
#define CURS_OFF    0x0c
#define NINTH_BIT   7
#define SEATALK_MSGNUM 0xFE
#define NUL         0

#use delay ( clock = 8000000 )
#use standard_io ( B )
#use standard_io ( A )
#use rs232 ( BAUD = 4800, RCV = PIN_B0, BITS = 9, ERRORS )

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

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

void main ( void )
    {
    char cCmd, cCnt, cAdr, cLevel, cError;

    // INITIAL MESSAGE
    delay_ms ( 200 );       // wait enough time after Vdd rise
    LCD_Init();
    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( FIRST_LINE + 0 );
    printf ( LCD_PutChar, "TANK LEVELS" );
    LCD_SetPosition ( SECOND_LINE + 0 );
    printf ( LCD_PutChar, "Water 1" );
    LCD_SetPosition ( THIRD_LINE + 0 );
    printf ( LCD_PutChar, "Water 2" );
    LCD_SetPosition ( FOURTH_LINE + 0 );
    printf ( LCD_PutChar, "Waste" );

    while ( TRUE )
        {
        cCmd = NUL;
        while ( cCmd != SEATALK_MSGNUM )
            {
            cCmd = getc();
            }

        if ( ( bit_test ( rs232_errors, NINTH_BIT ) == HIGH ) )
            {
            cCnt = getc();      // get count bit
            if ( ( bit_test ( rs232_errors, NINTH_BIT ) == HIGH ) )
                {
                cError = YES;    // should not have received a command byte here
                }
            if ( cCnt != 0x01 )
                {
                cError = YES;    // count should have been 01
                }
            cAdr = getc();      // get address bit
            if ( ( bit_test ( rs232_errors, NINTH_BIT ) == HIGH ) )
                {
                cError = YES;    // should not have received a command byte here
                }
            cLevel = getc();    // get level bit
            if ( ( bit_test ( rs232_errors, NINTH_BIT ) == HIGH ) )
                {
                cError = YES;    // should not have received a command byte here
                }
            switch ( cAdr & 0x03 )  // mask on address bits 0 & 1
                {
                case 0x00:
                    {
                    LCD_SetPosition ( SECOND_LINE + 8 );
                    break;
                    }
                case 0x01:
                    {
                    LCD_SetPosition ( THIRD_LINE + 8 );
                    break;
                    }
                case 0x02:
                    {
                    LCD_SetPosition ( FOURTH_LINE + 8 );
                    break;
                    }
                case 0x03:
                    {
                    // error
                    }
                }
            printf ( LCD_PutChar, "%3u%%", cLevel );
            }
        }
    }

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

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