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