//==================================================================
//=====							       =====
//=====              2 Meter Receiver Program                  =====
//=====              By: Steve Hageman 29Mar98                 =====
//=====		      www.sonic.net/~shageman		       ===== 	
//==================================================================
//=====          Copyright 1998 by Steven C. Hageman           =====
//=====                 All rights reserved                    ===== 
//==================================================================
//=====  Version: 0.0 - Initial Writing - 29Mar98              =====
//=====  Version: 1.0 - First release - 19Sep98                =====
//==================================================================


//-----< Include Files, setup fuses >-----
#include <16c73a.h>
#include <stdio.h>
#fuses HS, NOWDT, NOPROTECT, PUT, BROWNOUT


//-----< Compiler use statements >-----
// Tell compiler clock speed, baud, and Hardware UART pins
#use delay(clock=10000000) 			
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)


//-----< Port location defines >-----
#pragma byte port_a = 0x05
#pragma byte port_b = 0x06
#pragma byte port_c = 0x07


//-----< Pin defines >-----
#pragma bit AUDIO_PIN 	= port_a.2	// Audio override pin
#pragma bit A_SW 	= port_b.0	// Encoder A switch input         
#pragma bit B_SW     	= port_b.1	// Encoder B switch input
#pragma bit INC_SW	= port_b.2	// Freq increment select switch
#pragma bit SCAN_SW	= port_b.3	// Scan select switch
#pragma bit RS232_PIN	= port_c.7	// RS232 input pin


//-----< MC145170 PLL Chip Pins >-----
#pragma bit DIN 	= port_b.5
#pragma bit CLK 	= port_b.6
#pragma bit EN 		= port_b.7


//-----< General Program Defines >-----
#define TRUE		1
#define FALSE		0
#define DOOMSDAY	0	// Not here yet!
#define AUDIO_OFF 	1	// Set AUDIO OFF
#define AUDIO_ON	0	// Set AUDIO ON
#define PUSHED		0	// For switched closed sense
#define	RSSI		0	// Analog input for RSSI
#define SQUELCH		1	// Analog input for squelch pot
#define BAT		3	// Analog input for battery voltage
#define EOT		255	// End of RS232 transmission character


//-----< Global Variables >-----
unsigned long Channel;		// PLL channel number 0 - 799 for 5 kHz steps
int Increment;			// Frequency increment (in channel units)
int Changed;			// Frequency changed flag
int Rssi_val;			// RSSI A/D value
int Squelch_val;		// Squelch pot setting
int Bat_val;			// Battery voltage value
int Keep_scanning;		// Flag used during scanning


//-----< LCD Routines >----------------------------------------------

// LCD is connected to port c, definitions below

// LCD Connections - 
//   C5  C4    C3   C2   C1   C0 - PORT PINS
//   E   R/S   D7   D6   D5   D4 - LCD PINS


void blip_e_delay()
{
	// Set enable high for 2 cycles then low
	// Minimum E cycle time is 450 nS.
	#asm
		bsf port_c,5
		nop
		nop
		bcf port_c,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 2 cycles then low
	// Minimum E cycle time is 450 nS.
	#asm
		bsf port_c,5
		nop
		nop
		bcf port_c,5
	#endasm
	
	// Wait for shortest lcd routine to finish, takes time
	// but saves code in not checking for the busy flag.
	delay_us(50);
}

void first_line()
{
	// Set the LCD to the first display line
	// Send 0x80 for first line. 
	port_c = 0x08;
	blip_e();
	
	port_c = 0x00;
	blip_e();
}

void second_line()
{
	// Set the LCD to the second display line
	// Send 0xc0 for second line. 
	port_c = 0x0c;
	blip_e();
	
	port_c = 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_c = 0x03;
	blip_e_delay();
	
	port_c = 0x03;
	blip_e_delay();

	port_c = 0x03;
	blip_e_delay();

	// Send 0x02
	port_c = 0x02;
	blip_e_delay();
	
	// send 0x28
	port_c = 0x02;
	blip_e_delay();
	
	port_c = 0x08;
	blip_e_delay();
	
	// send 0x01
	port_c = 0x00;
	blip_e_delay();
	
	port_c = 0x01;
	blip_e_delay();

	// send 0x06
	port_c = 0x00;
	blip_e_delay();
	
	port_c = 0x06;
	blip_e_delay();

	// send 0x0c
	port_c = 0x00;
	blip_e_delay();
	
	port_c = 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_c = 0x00;
}

void write_lcd(byte ch)
{
	// writes the char ch to the lcd. HB then LB.
	// The OR sets RS high 
	port_c = (ch/0x10) | 0x10;          
	blip_e();
	port_c = (ch & 0x0f) | 0x10;
	blip_e();
	
	// Clean up port on exit
	port_c = 0x00;
}


void display_frequency(void)
{
unsigned long freq;

	// Calculate actual frequency
	freq = Channel*5 + 4000;
			
	// Write frequency to display
	first_line();
	write_lcd("14");
	write_lcd((int)(freq / 1000) + '0');
	write_lcd('.');
	write_lcd((int)((freq / 100) % 10) + '0');
	write_lcd((int)((freq / 10) % 10) + '0');
	write_lcd((int)(freq % 10) + '0');
	write_lcd(" MHz");
	
	// Write increment to display
	switch(Increment)
	{
		case 0:
			write_lcd(" Lock");
			break;
					
		case 1: 
			write_lcd("   5k");
			break;
			
		case 2: 
			write_lcd("  10k");
			break;
				
		case 20: 
			write_lcd(" 100k");
			break;
					
		case 200: 
			write_lcd("   1M");
			break;
	}
}


//-----< PLL Routines >----------------------------------------------
	
void setup_pll(void)
{

// PLL setup routine, only needs to be done once, at startup.
// Note: Until control register is set, the PIC clock will be running at
// 1.25 MHz.

	//----- Initilize PLL -----
	// EN High, Din Low, 4 clocks
	CLK = 0;
	DIN = 0;
	EN = 1;

	CLK = 1;
	CLK = 0;	

	CLK = 1;
	CLK = 0;	

	CLK = 1;
	CLK = 0;	

	CLK = 1;
	CLK = 0;	

	// EN Low, Din Low (except clock 4 = 1), 5 clocks
	DIN = 0;
	EN = 0;

	CLK = 1;
	CLK = 0;	

	CLK = 1;
	CLK = 0;	

	CLK = 1;
	CLK = 0;	

	DIN = 1;
	CLK = 1;
	CLK = 0;
	DIN = 0;	

	CLK = 1;
	CLK = 0;	

	EN = 1;
	// PLL is now reset
	
	//----- Setup C Reg with proper bits -----
	EN = 0;
	
	// Bit 7 - PD Polarity
	DIN = 0;
	CLK = 1;
	CLK = 0;
	
	// Bit 6 - PD Output
	DIN = 1;
	CLK = 1;
	CLK = 0;
	
	// Bit 5 - Lock Detect Off
	DIN = 0;
	CLK = 1;
	CLK = 0;
	
	// Bit 4, 3, 2 - Ref divider output (001 = /1)
	DIN = 0;
	CLK = 1;
	CLK = 0;
	
	DIN = 0;
	CLK = 1;
	CLK = 0;
	
	DIN = 1;
	CLK = 1;
	CLK = 0;

	// Bit 1 - FV off -- NOTE: Set this bit high to get
	// the N counter out on pin 10.
	DIN = 0;
	CLK = 1;
	CLK = 0;
	
	// Bit 0 - FR off -- NOTE: Set this bit high to get
	// the PD ref frequency on pin 9.
	DIN = 0;
	CLK = 1;
	CLK = 0;
	DIN = 0;
	// EN back high
	EN = 1;

	// Note: The PIC clock will now be 10 MHz
	
	//----- Setup Ref Divider -----
	// 10 MHz / 2000 = 5 KHz (2000 = 0x07d0)
	EN = 0;

	// BIT 14-12 = 000
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	// BIT 11-8 = 0111 	
	CLK = 1;
	CLK = 0;
	
	DIN = 1;
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	// BIT 7-4 = 1101
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	DIN = 0;
	CLK = 1;
	CLK = 0;
	
	DIN = 1;
	CLK = 1;
	CLK = 0;
	
	// BIT 4-0 = 0000
	DIN = 0;
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	CLK = 1;
	CLK = 0;
	
	// Enable Back High
	EN = 1;
}	


void tune_pll(void)
{
int ctr;
unsigned long n_reg;

	// Convert channel number to N counter value
	n_reg = Channel + 30940;

	// Set clock, enable low
	CLK = 0;
	EN = 0;
	
	// Shift in channel frequency to N reg (MSB first)
	for( ctr = 0 ; ctr <= 15 ; ctr++)
	{
		// Decompose the PLL N value into bits, set data pin
		DIN = shift_left(&n_reg, 2, 0);
		
		// Cycle the clock
		CLK = 1;
		CLK = 0;	
	}
	
	// Clean up and set enable high which 
	// transfers count to N reg
	DIN = 0;
	EN = 1;

}

//-----< Set Analog Globals >----------------------------------------
void update_analog(void)
{
	// Read all the analog channels and set their global variables
	set_adc_channel(RSSI);
	delay_us(20);
	Rssi_val = read_adc();
	
	// Bound the value, so it does not interfere with RS232 'EOT'.
	// As design was orginaly done, this won't ever hapen.
	// I put this here just in case something is changed.
	if( Rssi_val == 255 )
		Rssi_val = 254; 

	set_adc_channel(SQUELCH); // The /4 scales the squelch range
	delay_us(20);		  // to match the RSSI range
	Squelch_val = read_adc()/4;
	
	set_adc_channel(BAT);
	delay_us(20);

	Bat_val = read_adc();
	
	// Bound the value, so it does not interfere with RS232 'EOT'.
	if( Bat_val == 255 )
		Bat_val = 254; 

}


//-----< RS232 Control loop >-----------------------------------------
void rs232_mode(void)
{
char buf;
int hb, lb;

	// We just stay in this routine forever!
	
	// Update LCD display
	write_lcd("-- Hagtronics --");
	second_line();
	write_lcd("-- RS232 Mode --");
	
	while(!DOOMSDAY)
	{
		// Get command
		buf = getc();
			 
		// Process command
		switch(buf)
		{
			case 'A':	// Set Audio
			{
				// Say first char was OK
				putc(EOT);
				
				// Get mode
				buf = getc();
				
				switch(buf)
				{
					case 'N': // Set audio on
						AUDIO_PIN = AUDIO_ON; 
						putc(EOT);
						break;
					
					case 'F': // Set audio off
						AUDIO_PIN = AUDIO_OFF; 
						putc(EOT);
						break;
					
					default: // What did they want? 
						putc('?');
						putc(EOT);
						break;
				}
			break;
			}
			
			case 'C':	// Channel Set
			{
				// Say first char was OK
				putc(EOT);
				
				// Get channel bytes  HB, LB
				hb = getc();
				
				// Say HB was OK
				putc(EOT);
				
				// Get LB
				lb = getc();
				
				// Say it was good!
				putc(EOT);
				
				// Figure channel number
				Channel = hb * 256 + lb;

				// Set N Reg with frequency
				tune_pll();
			
				break;
			}
			
			case 'D': // Write data valid status out
			{
				//Send back current signal levels
				update_analog();	
				putc(Rssi_val);
				putc(Bat_val);
				putc(EOT);
				break;
			}

			default:  // Say char was not understood
			{
				putc('?');
				putc(EOT);
			}
							
		} // End of first char switch

	} // End of main while
}


void update_audio(void)
{
	// Decide if RSSI level is greater than the squelch level
	// This will determine if the audio needs to be turned on or not
	
	// Get current RSSI and squelch value
	update_analog();
	
	if(Rssi_val >= Squelch_val)
	{
		// Set audio on
		AUDIO_PIN = AUDIO_ON;
	}
	else
	{
		// Set audio off
		AUDIO_PIN = AUDIO_OFF;
	}
}

//-----< Scanning routine >------------------------------------------
// Note: Scan is also killed by turning the tune knob
void scan(void)
{
int ctr;

	// Turn off audio to start with
	AUDIO_PIN = AUDIO_OFF;
	
	// Initilize variable
	Keep_scanning = TRUE;
	
	// Scan till the cows come home
	while(Keep_scanning == TRUE)
	{
		
		// Increment channel 
		Channel += Increment;
				
		// Bound the channel values -- implements roll over
		if( Channel > 60000 )	// Catch unsigned underflow
			Channel = 800;
		if( Channel > 800 )	// Catch overflow
			Channel = 0;

		// Update PLL & wait a while
		tune_pll();
		delay_ms(20); // Enough time for a full scale step
					
		// Update display
		display_frequency();
		
		// Check if anything is on this channel
		update_analog();
				
		while((Rssi_val >= Squelch_val) && (Keep_scanning == TRUE))
		{
			// Yup got a signal, turn Audio on
			AUDIO_PIN = AUDIO_ON;
				
			// Delay a while (2 seconds) on signal, but check scan switch
			// This loop runs at about 10 times a second
			for(ctr = 0; ctr <= 19 ; ctr++)
			{
				// Hurry up and wait!
				delay_ms(100);
				
				// Is the scan switch depressed?
				if(SCAN_SW == PUSHED)
				{
					// Clean up display for the user, giving feedback
					second_line();
					write_lcd("                ");
					
					// Wait 'till switch is not pressed
					while(SCAN_SW == PUSHED);
					
					// Exit out of here
					Keep_scanning = FALSE;
				}
				
				// If audio drops during this time turn it off
				// but stay in loop (keep resetting loop counter)
				update_analog();
				if(Rssi_val >= Squelch_val)
				{	
					AUDIO_PIN = AUDIO_ON;
					ctr = 0;
				}		
				else
					AUDIO_PIN = AUDIO_OFF;

				// If something has set keep_scanning false, 
				// break out of loop
				if(keep_scanning == FALSE)
					break;	
		
			} // End of signal delay loop
			
		} // End of signal capture delay while loop
		
		// Check to see if scan is stopped
		if(SCAN_SW == PUSHED)
		{
			// Clean up display for the user, giving feedback
			second_line();
			write_lcd("                ");
			
			// wait till switch is not pushed
			while(SCAN_SW == PUSHED);
			
			// Now get outa town
			Keep_scanning = FALSE;
		}
	
	} // End of main loop					
	
	// Clean up display for the user, giving feedback
	second_line();
	write_lcd("                ");
	
	// Turn the audio back off, it will turn on later is there is
	// still a signal
	AUDIO_PIN = AUDIO_OFF;
}	


void change_increment(void)
{
// Change to next tuning increment value (Step size)

	switch(Increment)	
	{
		case 0: // 0 Channels = Locked 
			Increment = 1; 
			break;
		
		case 1: // 1 Channel = 5 kHz
			Increment = 2; 
			break;
							
		case 2: // 2 Channels = 10 kHz
			Increment = 20; 
			break;
			
		case 20: // 20 Channels = 100 kHz 
			Increment = 200; 
			break;
			
		case 200: // 200 Channels = 1 MHz
			Increment = 0; 
			break;

		default:  // Just in case, set 1 channel
			Increment = 1;
	}
}


void update_rssi_display(void)
{									
static int old_rssi; 		// Note: old_rssi is not initilized
int scaled_rssi, char_pos;

	// Displays the RSSI level of a signal on the LCD
	// Note: This one A/D read is used several placed below
	update_analog();
			
	scaled_rssi = Rssi_val/2;	// Divide by 2 to maximize range
		
	// Scale RSSI a/d value
	if(scaled_rssi > 8)
	{
		scaled_rssi -= 8; // The 8 offsets the display to zero
	}
	else
	{
		scaled_rssi = 0;  // Floor a/d value
	}	
			
	if(scaled_rssi > 16)	// Bound scaled a/d value
		scaled_rssi = 16;
			
	// Only update display if RSSI value changed
	if(scaled_rssi != old_rssi)
	{
		second_line();
		for(char_pos=1 ; char_pos <= 16 ; char_pos++)
		{
			if(char_pos <= scaled_rssi)
			{
				write_lcd(255);	
			}
			else
			{
				write_lcd(' ');
			}
		}
		old_rssi = scaled_rssi;
	}
}


//-----< Rotary Encoder routine >------------------------------------
#INT_EXT	// Change on falling edge of RB
		// Note: Must set int reg to get falling edge trigger
		
void encoder(void)
{
	// If here then the A encoder must have had a falling edge...
	
	// Read encoder B switch and check direction
	if( B_SW == 1 )
	{
		// CW Direction
		Channel += Increment;
	}
	else
	{
		// CCW Direction
		Channel -= Increment;
	}
	
	// Bound the channel values -- implements roll over
	if( Channel > 60000 )	// Catch unsigned underflow
		Channel = 800;

	if( Channel > 800 )	// Catch overflow
		Channel = 0;

	// Set the changed flag, cancel scanning (if we were scanning)
	Changed = TRUE;
	Keep_scanning = FALSE;

}


//-----< Main >------------------------------------------------------

// Note: At powerup the PIC clock will be 1.25 MHz. After PLL setup is
// done the clock will be 10 MHz. No RS232 can take place untill PLL
// is setup.

void main(void)
{
int ctr; // Local variables

	//----- Setup Ports / Unused pins are set to output
	set_tris_a(0b00001011);	// 

	set_tris_b(0b00001111);	// 

	set_tris_c(0b10000000);	// 
	
	port_b_pullups(TRUE);	// ON for the switches

	//----- Housekeeping -----
	// Initilize Pins
	EN = 1;
	AUDIO_PIN = AUDIO_OFF;
	
	// Initilize global/local variables
	Channel = 400;	// Set for 146.000 MHz
	Changed = TRUE; // Update display
	Increment = 2;	// Set for 10 kHz increment
	
	// Initilize interrupts
	disable_interrupts(GLOBAL);
	enable_interrupts(EXT_INT);
	ext_int_edge(H_TO_L);	
	
	// Initilize A/D
	setup_port_a(RA0_RA1_RA3_ANALOG);
	setup_adc(ADC_CLOCK_INTERNAL);
	
	// Wait for LCD powerup 
	// Note: Here clock is really 1.25 MHz, so delay is not
	// accurate (8 times more than value called out)
	delay_ms(100);

	// Initilize LCD
	init_lcd();

	// Display power on banner
	write_lcd("   Hagtronics   ");
	second_line();
	write_lcd("2 Meter Receiver");
		
	// Reset PLL, setup C, R regs
	setup_pll();
		
	// Setup PLL for initial turn on frequency of 146 MHz
	tune_pll();
		
	// Clean up display
	delay_ms(1000);
	init_lcd();

	//----- Determine if RS232 cable is hooked up
	// Read from RS232 receive pin, if it is high then
	// RS232 is connected.
	if(RS232_PIN == 1) 
	{
		// RS232 is connected, operate in the remote mode
		rs232_mode();		
	}
		
	//----- Main loop -----	
	// This outer loop operates about 4 times a second
	while(!DOOMSDAY)
	{
		disable_interrupts(GLOBAL);
		
		// Check battery level
		update_analog();
		if( Bat_val <= 141 )	// 141 gives about 1.1 volt per cell
		{
			// Low battery
			second_line();
			write_lcd("BAT");
		}
		
		
		// Update display / PLL if channel changed
		if( Changed )
		{
			display_frequency();
					
			// Update PLL
			tune_pll();
			
			// Reset the changed flag
			Changed = FALSE;
		}
		
		// Set the encoder interrupt on again
		enable_interrupts(GLOBAL);
		
		// Real time update loop 
		for( ctr = 0 ; ctr <= 24 ; ctr++ )
		{
		
			// Check if scan switch is pushed
			if(SCAN_SW == PUSHED)
			{
				// Give user feedback that switch was pushed
				second_line();
				write_lcd("----Scanning----");
				
				// Wait till scan is not pushed anymore
				while(SCAN_SW == PUSHED);

				// OK, Let's scan :)
				scan();
			}					
				
			// Check if Increment switch has been changed
			if(INC_SW == PUSHED)
			{
				// Increment tuning step size
				change_increment();
				
				// Give user feedback on button being pushed
				display_frequency();	
				
				// Wait till increment switch is not pushed anymore
				while(INC_SW == PUSHED);
			}
		
			// Update signal strength display
			update_rssi_display();
			
			// Check squelch level, open audio if needed
			update_audio();
			
			delay_ms(10); // Update loop about every 10 mSec

		} // End of 'real time' loop

	} // End of main while loop			

} //----- End of main -----
