//-----< WWVB.C >----------------------------------------------------
//
// Applied DC WWVB Time Receiver program
//
// By: S.C.Hageman - 7Dec97
//
// www.sonic.net/~shageman
//
//-------------------------------------------------------------------
//
// Version Control:
//	B.0 - Beta Release 0 - 8/28/94 - SCH
//	B.1 - Beta Release 1 - 8/28/94 - SCH - Added input averaging
//	1.0 - Release        - 7Dec97  - SCH - Released
//
//-------------------------------------------------------------------


//-----< Includes >--------------------------------------------------
#include <16c84.h>


//-----< PCM Use directives >----------------------------------------
#use delay(clock=4000000)


//-----< Port definitions >------------------------------------------
#pragma byte port_a 	= 5
#pragma byte port_b 	= 6


//-----< WWVB input code to this pin >-------------------------------
#pragma bit  CODE_IN 	= port_a.4


//-----< Program constants >-----------------------------------------
#define DOOMSDAY	0
#define ZERO		0
#define ONE		1


//-----< Program global variables >----------------------------------
int Minutes;
int Hours;
int Toggle;


//-----< LCD Routines >----------------------------------------------

// LCD is connected to port b, definitions below

// LCD Connections - 
//  RB5  RB4  RB3  RB2  RB1  RB0 - PORT PINS
//   E   R/S   D7   D6   D5   D4 - LCD PINS


//-------------------------------------------------------------------
void blip_e_delay()
{
	// Set enable high for 1 cycle then low
	// Designed for 4 MHz clock. Minimum E cycle time is 400 nS.
	#asm
		bsf port_b,5
		nop
		bcf port_b,5
	#endasm
	
	// Wait for longest lcd routine to finish, takes time
	// but saves code in not checking for the busy flag.
	delay_ms(5);
}

//-------------------------------------------------------------------
void blip_e()
{
	// Set enable high for 1 cycle then low
	// Designed for 4 MHz clock. Minimum E cycle time is 400 nS.
	#asm
		bsf port_b,5
		nop
		bcf port_b,5
	#endasm
	
	// Wait for shortest lcd routine to finish, takes time
	// but saves code in not checking for the busy flag.
	delay_ms(1);
}

//-------------------------------------------------------------------
void first_line()
{
	// Set the LCD to the first display line
	// Send 0xc0 for second line. Use 0x80 to move
	// to the first line.
	port_b = 0x08;
	blip_e();
	
	port_b = 0x00;
	blip_e();
}

//-------------------------------------------------------------------
void second_line()
{
	// Set the LCD to the second display line
	// Send 0xc0 for second line. Use 0x80 to move
	// to the first line.
	port_b = 0x0c;
	blip_e();
	
	port_b = 0x00;
	blip_e();
}

//-------------------------------------------------------------------
void init_lcd()
{
	// Clear and initilize LCD panel to 4 bit mode
	// two line mode, cursor off, display on.
	// RS needs to be low for all of these instructions.
	// -> Takes 60 mS to complete <-
		
	// Send 0x03 three times
	port_b = 0x03;
	blip_e_delay();
	
	port_b = 0x03;
	blip_e_delay();

	port_b = 0x03;
	blip_e_delay();

	// Send 0x02
	port_b = 0x02;
	blip_e_delay();
	
	// send 0x28
	port_b = 0x02;
	blip_e_delay();
	
	port_b = 0x08;
	blip_e_delay();
	
	// send 0x01
	port_b = 0x00;
	blip_e_delay();
	
	port_b = 0x01;
	blip_e_delay();

	// send 0x06
	port_b = 0x00;
	blip_e_delay();
	
	port_b = 0x06;
	blip_e_delay();

	// send 0x0c
	port_b = 0x00;
	blip_e_delay();
	
	port_b = 0x0c;
	blip_e_delay();

	// Send 0x0f to set cursor on with blink
	// Send 0x0e to set cursor on w/o blink

	// Clean up port on exit
	port_b = 0x00;
}

//-------------------------------------------------------------------
void write_lcd(byte ch)
{
	// writes the char ch to the lcd. HB then LB.
	// The OR sets RS high 
	port_b = (ch/0x10) | 0x10;          
	blip_e();
	port_b = (ch & 0x0f) | 0x10;
	blip_e();
	
	// Clean up port on exit
	port_b = 0x00;
}

//-------------------------------------------------------------------
void toggle_mark()
{
	// Toggles the pulse marker bit on the second line
	// on and off

	// Move to marker position
	port_b = 0x0c;
	blip_e();
	
	port_b = 0x07;
	blip_e();

	// Alternate marker on and off
	if(Toggle)
		write_lcd(':');
	else
		write_lcd(' ');

	// Toggle write
	Toggle = ~Toggle;
}
	
//-----< End of LCD routines >---------------------------------------  


//-----< LCD Messages >----------------------------------------------
msg_1()
{
	first_line();

	write_lcd(' '); // 1 
	write_lcd('A');   
	write_lcd('p');
	write_lcd('p');
	write_lcd('l');
	write_lcd('i');
	write_lcd('e');
	write_lcd('d');
	write_lcd('D');
	write_lcd('C');
	write_lcd(' ');
	write_lcd('W');
	write_lcd('W');
	write_lcd('V');
	write_lcd('B');
	write_lcd(' '); //16

	second_line();

	write_lcd(' '); // 1 
	write_lcd('T');   
	write_lcd('i');
	write_lcd('m');
	write_lcd('e');
	write_lcd(' ');
	write_lcd('R');
	write_lcd('e');
	write_lcd('c');
	write_lcd('e');
	write_lcd('i');
	write_lcd('v');
	write_lcd('e');
	write_lcd('r');
	write_lcd(' ');
	write_lcd(' '); //16
	
	delay_ms(2000);
}

//-------------------------------------------------------------------
msg_2()
{
	first_line();

	write_lcd('W'); // 1
	write_lcd('a');
	write_lcd('i');
	write_lcd('t');
	write_lcd('i');
	write_lcd('n');
	write_lcd('g');
	write_lcd(' ');
	write_lcd('F');
	write_lcd('o');
	write_lcd('r');
	write_lcd(' ');
	write_lcd('S');
	write_lcd('y');
	write_lcd('n'); 
	write_lcd('c'); // 16

	second_line();

	write_lcd('F'); // 1 
	write_lcd('r');   
	write_lcd('o');
	write_lcd('m');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' '); // 8
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd('W');
	write_lcd('W');
	write_lcd('V');
	write_lcd('B'); //16
	
}

//-------------------------------------------------------------------
msg_3()
{
	first_line();

	write_lcd('S'); // 1
	write_lcd('y');
	write_lcd('n');
	write_lcd('c');
	write_lcd('r');
	write_lcd('o');
	write_lcd('n');
	write_lcd('i');
	write_lcd('z');
	write_lcd('e');
	write_lcd('d');
	write_lcd('.');
	write_lcd('.');
	write_lcd('.');
	write_lcd('.'); 
	write_lcd('.');

	second_line();

	write_lcd(' '); // 1 
	write_lcd(' ');   
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' '); //16
	
}

//-------------------------------------------------------------------
void display_time()
{
//----- Display time message -----
int msb, digit;

	first_line();

	write_lcd('W'); // 1
	write_lcd('W');
	write_lcd('V');
	write_lcd('B');
	write_lcd(' ');
	write_lcd('T');
	write_lcd('i');
	write_lcd('m');
	write_lcd('e');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd('(');
	write_lcd('G');
	write_lcd('M');
	write_lcd('T'); 
	write_lcd(')'); // 16

	second_line();

	write_lcd(' '); // 1 
	write_lcd(' ');   
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');

	// Write time now
	msb = Hours / 10;
	digit = msb + '0';
	write_lcd(digit);
	
	digit = (Hours - (msb * 10)) + '0';
	write_lcd(digit);

	write_lcd(':'); // 8

	msb = Minutes / 10;
	digit = msb + '0';
	write_lcd(digit);
	
	digit = (Minutes - (msb * 10)) + '0';
	write_lcd(digit); // 10
	
	write_lcd(' '); // 11
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' ');
	write_lcd(' '); // 16

}

//-----< End of messages >-------------------------------------------


//-----< Time decode routines >--------------------------------------
int sample_input()
{
// Sample the code input pin every 5 mS, for 5 samples then
// return voted average to cancel noise.
int avg = 0;
int i;

	for( i = 0 ; i <= 4 ; i++ )
	{
		if(CODE_IN)
			avg++;
			
		delay_ms(5);
	}
	
	// Return average of digital input
	if( avg >= 3 )
		return(ONE);  // The code was a one
	else
		return(ZERO); // The code was a zero
}

//-------------------------------------------------------------------
int find_start()
{
// Look for a start pulse

	// --- Loop till negative edge ---
	while( sample_input() == 0 ); // Wait till input = 1
	while( sample_input() == 1);  // Wait till input = 0	

	// Toggle mark on and off
	toggle_mark();

	// Now I have found the falling edge, See if it was a start pulse
	delay_ms(650);
	
	// Check if input is still low, indicating a start pulse
	if( sample_input() == 0 )	
		return(TRUE); // Input is still low, then it is a start
	else
		return(FALSE); // Input was not low long enough	
}

//-------------------------------------------------------------------
int classify_pulse()
{
// Classify whether pulse is a one or zero

	// --- Loop till negative edge ---
	while( sample_input() == 0 ); // Wait till input = 1
	while( sample_input() == 1 );  // Wait till input = 0	

	// Toggle mark on and off
	toggle_mark();

	// Now I have found the falling edge, See what it is
	delay_ms(350);
	
	// Check if input is still low, indicating a one
	if( sample_input() == 0 )	
		return(ONE); // Input is still low, it is a one
	else
		return(ZERO); // Input was not low long enough	
}
	

//-----< Main Program >----------------------------------------------
main()
{
short first_pass = TRUE;

	//----- Set up port direction reg's -----
	#asm
		movlw 0		// Set port b to outputs
		tris port_b
		clrf port_b
	
		movlw 0xff	// Set port a to inputs
		tris port_a
	#endasm

	//----- Wait for powerup, Initilize LCD -----
	delay_ms(200);
	init_lcd();

	//----- Write a startup message -----
	msg_1();

	//----- Write status message -----
	msg_2();
	

	//----- Start of main loop -----			
	while(!DOOMSDAY)
	{

		// Find the double pulse start sequence 
		if( find_start() )
		{
			if( find_start() )
			{
				// If I got here then we found the start sequence.
				
				// Update status message, first pass only
				if(first_pass)
					msg_3();
				
				// Reset first pass
				first_pass = FALSE;
				
				// Decode minutes and hours information for display

				// Minutes first! 
				Minutes = 0;
	
				if( classify_pulse() )
					Minutes = 40;

				if ( classify_pulse() )
					Minutes += 20;
				
				if ( classify_pulse() )
					Minutes += 10;
	
				// Next comes an uncoded bit
				classify_pulse();	

				if ( classify_pulse() )
					Minutes += 8;

				if ( classify_pulse() )
					Minutes += 4;
		
				if ( classify_pulse() )
					Minutes += 2;

				if ( classify_pulse() )
					Minutes += 1;
		
				// Next comes a sync pulse, then two uncoded
				classify_pulse();
				classify_pulse();
				classify_pulse();

				// Now hours 
				Hours = 0;
	
				if( classify_pulse() )
					Hours = 20;
	
				if( classify_pulse() )
				Hours += 10;

				// Next comes an uncoded bit
				classify_pulse();
	
				if( classify_pulse() )
					Hours += 8;

				if( classify_pulse() )
					Hours += 4;

				if( classify_pulse() )
					Hours += 2;

				if( classify_pulse() )
					Hours += 1;
				
				// Update display with info
				display_time();
			
			} // End of 2nd 'if start pulse'
			
		} // End of 1st 'if start pulse'

	} // end of main while loop (forever loop)
}

/*-----< Fini WWVB.C >-----------------------------------------------*/
