/*

	Program:		freq_cnt_12ghz.c
	
	Function:		Frequency Counter
	
	Hardware:		ATMega 16 running on +5V with a 2x16 LCD plus external counter,
					counting gate and gate time generator. Uses as fixed divide by 1000
					prescaler to get a frequency coverage of 12GHz (typically 15GHz).
					
	Crystal:		4.096 MHz
	
		
	
	History:

	----- Predevelopment of the counter itself -----
	
	10.05.2005		LCD works in 8bit mode

	11.05.2005		4 Bit mode implemented tested for LCD
	
					Counting works for internal 16bit counter
					
	12.05.2005		Counting works for all 24 bit (external + internal counter)
			
	14.05.2005		Mode selection for different prescalers implemented and tested				
					-> Version 1.0 released
					
	28.05.2005 		Test functions implemented for showing the counting
					results in as hex figure in the LCD when PD7 is pulled low

	29.05.2005 		Calculation formulas cleaned up and simplified, this 
					solves minor display errors, especially for :64/:128 prescaler
					tested, works fine for all prescaler mode
					-> Version 1.1 released
			
	12.04.2007		Frequency drift calculation and showing in 2nd line of 
					LCD implemented and tested ok
					-> Version 2.0 released
					
	----- Adaptation and refinement for the usage with the GHz counter -----
	
	22.03.2009		The circuit is now build into a housing, the push button is not
					accessable any more, hence there is only one mode. There is a 
					fixed divide by 1000 prescaler build into the housing in front
					of the scaler that is specified up to 12GHz (typically 15GHz).
					- Default mode (which will never change is this application)
					  set to prescaler 1000
					- Default screen set to GHz units rather than MHz
					- Debugged and tested ok
					-> Version 3.0 released
					  					
					
*/



// --- Includefiles --- 

#include <avr/io.h> 	 				// Universal .h for AVR, does automatically load the device-specific
										// .h file, the device type is read from the makefile. 
#include <avr/pgmspace.h> 				// enables reading of data from the flash-memory 
#include <string.h>						// for usign string functions
#include <stdio.h>						// standard input output functions



// --- Prototypes for functions --- 

void init_lcd_4bit_interface(void);
void update_lcd_4bit_interface(void);
void char2lcd_4bit_interface(unsigned char lcdchar);

void put_result_to_lcd(unsigned long cnt_result);

void switch_to_next_mode(void);

unsigned char asc2lcd(unsigned char asc_char);

void pause(unsigned int n);

void hex_output(unsigned long int number);
unsigned char hex2asc(unsigned char hexbyte);

void put_drift_to_lcd(long int drift);
void process_drift_moving_avg( long int new_value );

// --- Macros for better code readability

#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

// --- modes for different prescalers are ---
#define PRESCALE_FACTOR_1		0		// 0 = no prescaler
#define PRESCALE_FACTOR_64		1		// 1 = divide by   64 prescaler
#define PRESCALE_FACTOR_128		2		// 2 = divide by  128 prescaler
#define PRESCALE_FACTOR_640		3		// 3 = divide by  640 prescaler
#define PRESCALE_FACTOR_1000	4		// 4 = divide by 1000 prescaler
#define MAX_PRESCALE_MODE 4				


// --- Global varialbles ---

unsigned char videoram[33];				// 32 bytes for the lcd videoram (+1 for \n) as global variable
unsigned char mode=PRESCALE_FACTOR_1000;// default mode is: divided by 1000 by prescaler

unsigned long drift_mv_avg;				// the latest smoothed drift value calculated from moving avg table
unsigned long mv_avg_table[16];			// table of 16 entries for a smooth moving average display
										// of the drift

// --- Constant Data that is kept in the flash memory ---
											
unsigned char PROGMEM scr_greeting[]=      "FrequencycounterVer 3.0   DL5NEG";
unsigned char PROGMEM scr_counting[]=      "f= xx.xxx.xx GHzdf/dt=     ppm/s";

  
// --- Port Definitions ---  

#define LCD_CMD_PORT 	PORTD			// LCD command lines are connected to Port D
#define LCD_CMD_DIR  	DDRD
#define LCD_STR 		_BV(5)			// PD5 is LCD strobe
#define LCD_RS  		_BV(6)			// PD6 is LCD register select

#define LCD_DATA_PORT	PORTB			// LCD data lines are connected to PORT B
#define LCD_DATA_DIR	DDRB	

#define CNTR_PORT		PORTD			// Control port for the external counter is Port D
#define CNTR_DIR		DDRD
#define CNTR_IN			PIND
#define CNTR_GATE		_BV(4)			// PD4 is gate signal input
#define CNTR_RESET		_BV(2)			// PD2 is reset output 

#define CNT_DATA_IN		PINC			// Parallel output pins from external counter 
										// are connected to Port C

#define CNT_T1_IN		PINB			// PB1 is the physical pin for T1
#define CNT_T1_BIT		1			

#define KEY_PORT		PORTD			// User keys are connected to PORTD
#define KEY_IN			PIND			
#define KEY_MODE_SEL	_BV(3)			// PD3 is mode select key 
#define KEY_DEBUG		_BV(7)			// PD7 to be pulled low for debug mode



//;------------------------ here we go --------------------------

int main(void)
{
  
	unsigned long int cnt_result;
	unsigned char ls_byte;
	unsigned int tmp_cntlb;
	unsigned int tmp_cnthb;
	unsigned long int former_cnt_result=0;
	long int cnt_difference;
	double drift;
	
	
	TCCR1B = _BV(CS12) | _BV(CS11) ; 				// 16bit counter on, input is T1, falling edges, no prescaling
	
	CNTR_DIR	|= CNTR_RESET;						// set the reset pin to the external counter as output
	
	LCD_DATA_DIR = 0xF0; 							// the 4 higher pins as output for the LCD data bits 
	LCD_CMD_DIR	|= ( LCD_STR | LCD_RS );			// set LCD reg-sel. and strobe as output
	
	KEY_PORT |= KEY_MODE_SEL;						// switch on the internal pull up resistor for the mode select key
	KEY_PORT |= KEY_DEBUG;							// switch on the internal pull up resistor for the debug select pin


	init_lcd_4bit_interface();						// initialize the 2x16 char display
	
	strcpy_P( videoram, scr_greeting );				// copy the switch-on message to the videoram
		
	update_lcd_4bit_interface();					// transfer data from video ram to lcd display
	
	pause(2000);									// show switch-on message for 2 seconds

	strcpy_P( videoram, scr_counting );				// copy the normal counting screen to the videoram
		
	update_lcd_4bit_interface();					// transfer data from video ram to lcd display
	
	
	
	
	for(;;)
	{
		
		// now keep counting until the gate closes 
		// we detect the falling edge of the gateing-signal
					
		while( !(CNTR_IN & CNTR_GATE) ); // wait until gate is open
		while(   CNTR_IN & CNTR_GATE  ); // wait until gate closes
		
		// 1ms pause to make sure all signals are stable 
		// (we have 128ms until the gate opens again)
		pause(1);
		
		// ----- read out the external 8bit counter value -----
		// the MSB is directly connected to the T1 pin and must be mapped together
		// with the lower 7 bits which are connected to an input port
		ls_byte = ( CNT_T1_IN & _BV(CNT_T1_BIT) ) << (7-CNT_T1_BIT);  	// take T1 shifted to MSB
		ls_byte |=  CNT_DATA_IN & 0x7F; 								// add the other 7 bits
		
		// ----- read the value of the internal 16bit counter -----
		// (it is important to overtake the values into UNSIGNEDinteger
		// variables, otherwise the leftshift will give wrong results)
		tmp_cntlb = TCNT1L;
		tmp_cnthb = TCNT1H;
		
		// put together the 3 counter bytes (2 from the internal 16bit counter and
		// one from the external counter) to one 24 bit number

		cnt_result = tmp_cnthb;	 	  		// value must be stored in a long variable before 
		cnt_result <<= 16;		 	  		//   the 16bit shift can be done
		
		cnt_result += (tmp_cntlb << 8);  	// now add the lower byte of the internal cnt.
		
		cnt_result += ls_byte;	 	  		// and add the value of the external cnt.


//*************** debug test vectors *****************
//former_cnt_result = 0x000200;
//cnt_result        = 0x200050;
//****************************************************


		// ---- if debug pin is pulled low, update counting result as hex figure in LCD ----

		if( (KEY_IN & KEY_DEBUG)==0 )
			hex_output(cnt_result);	

		// ----- calculate frequency with selected prescaler and put result to LCD -----
	
		put_result_to_lcd(cnt_result);
		
		// ----- calculate the drift from former and current counting result -----
		
		/*		
		if( cnt_result >= former_cnt_result )			// calc difference but avoid negative drift numbers
			drift = cnt_result - former_cnt_result;
		else
			drift = former_cnt_result - cnt_result;
		*/
		
		cnt_difference = former_cnt_result - cnt_result;// calculate difference to former count result
														// including the sign of the difference, not only
														// the absolute value
		/* 
		if(cnt_difference==0)
			videoram[0]='0';
			
		if(cnt_difference>0)
			videoram[0]='+';
			
		if(cnt_difference<0)
			videoram[0]='-';
		*/		
		
		if( cnt_result == 0 ) cnt_result=1;				// avoid division by zero	
		
		drift = ( (float)cnt_difference / (float)cnt_result ) * 1.0e6; // calc change in ppm compared to last cycle
		
		drift = (float)drift *1000 /128 ;				// calc change in ppm per second (with 128ms cycle time)
				
		if(drift>1e5) drift=1e5;						// avoid numbers too big for handling 
														// (will simply show overflow)
		
		process_drift_moving_avg(round(drift));			// add the latest value to the moving average
														// that smoothes the displayed valed
						
		put_drift_to_lcd( drift_mv_avg );				// put calculated number to screen
				
		// ----- reset the external counter -----
		
		CNTR_PORT |= CNTR_RESET;
		CNTR_PORT &= ~CNTR_RESET;
		

		// It is important that the external counter is reset first, before
		// the internal counter is reset. Otherwise it can happen (if the
		// MSB of the external counter was set before the reset) that a falling
		// edge occurs for the internal counter and the result becomes one
		// internal counter bit to high!!!
	
	
		// ----- reset the internal counter -----
	
		// set counter (low and higbyte) back to zero for the next gate-cycle
 		// (highbyte must be written first, then the lowbyte must be written)
		TCNT1H = 0;
		TCNT1L = 0;

		
		// ----- switch to next prescaler mode if key was pressed -----
		
		if( (KEY_IN & KEY_MODE_SEL)==0 )
			switch_to_next_mode();
		
		// ----- remember this counting result to compare it with the next one -----
		
		former_cnt_result = cnt_result;
		
		// now the counter is ready for the next gate-cycle, lets go up again				
			
	}


	return(0);								// to avoid a compiler warning
	
}





// ------------------------ init LCD ---------------------------
void init_lcd_4bit_interface(void)
{

	unsigned char initcnt;

	pause(15); 										// wait, datasheet req. at least 15ms
		
	LCD_CMD_PORT &= ~( LCD_STR | LCD_RS ); 			// strobe and reg-sel to low
	
		
	// according to datasheet the init-byte must be clocked in 3 times
	// (Register Select must stay low to indicate an instruction)
	for( initcnt=1 ; initcnt<=3 ; initcnt++ )
	{
		char2lcd_4bit_interface(48);				// value given in datasheet for init
		pause(5);									// wait 5ms
	}
		
	char2lcd_4bit_interface( _BV(5) );				// switch to 4bit mode	
	pause(5);										

	char2lcd_4bit_interface( _BV(5) );				// set 2 lines, font 5x7
	char2lcd_4bit_interface( _BV(7) );
	pause(5);

	char2lcd_4bit_interface( 0 );					// display clear
	char2lcd_4bit_interface( _BV(4) );
	pause(5);
	
	char2lcd_4bit_interface( 0 );					// entry mode incremental
	char2lcd_4bit_interface( _BV(6) | _BV(5) );
	pause(5);
	
	char2lcd_4bit_interface( 0 );					// display on 
	char2lcd_4bit_interface( _BV(7) | _BV(6) );
	pause(5);
	

}


// ----------------------- update LCD ---------------------------
void update_lcd_4bit_interface()
{

	unsigned char lcdpos;

	LCD_CMD_PORT &= ~LCD_RS;						// reg-sel low -> instr.
	
	
	char2lcd_4bit_interface(0x80);					//set cursor to line1 (coloum1)
	char2lcd_4bit_interface(0x00);
		
	pause(15);										//cursor setting requires 15ms pause
		
	LCD_CMD_PORT |= LCD_RS; 						// set reg sel high to indicate data, not instruction
		
	for( lcdpos = 0 ; lcdpos<16 ; lcdpos++ )		// write the 16 characters of line 1	
	{
		char2lcd_4bit_interface( ( (videoram[lcdpos]   ) & 0xF0 ) );	// higher nibble first
		char2lcd_4bit_interface( ( (videoram[lcdpos]<<4) & 0xF0 ) );	// lower nibble last

	}
	
	// to come to line 2 we have to use and instruction to set the cursor to coloum 1 in line 2
	LCD_CMD_PORT &= ~LCD_RS;						// reg-sel low -> instr.
	
	char2lcd_4bit_interface(0xC0);					//set cursor to line2 (coloum1)
	char2lcd_4bit_interface(0x00);
	
	pause(15);										//cursor setting requires 15ms pause
	
	LCD_CMD_PORT |= LCD_RS; 						// set the reg select back to low to indicate data to come
	
	for( lcdpos = 16 ; lcdpos<32 ; lcdpos++ )		// write the 16 characters of line 2
	{
		char2lcd_4bit_interface( ( (videoram[lcdpos]   ) & 0xF0 ) );	// higher nibble first
		char2lcd_4bit_interface( ( (videoram[lcdpos]<<4) & 0xF0 ) );	// lower nibble last

	}
		
	
}


// ----------------------- character to LCD --------------------
// sends one byte to the LCD and takes the strobe high shortly
// (depending on the reg-sel line this is recognized as character
// or as instruction by the LCD. For some instructions additional
// pauses may be necessary and must be cared for externally)
void char2lcd_4bit_interface(unsigned char lcdchar)
{
	
	LCD_DATA_PORT &= ~0xF0;							// clear bits 4-7
	LCD_DATA_PORT |= (lcdchar & 0xF0);				// put data bits 4-7 to LCD port
	
	LCD_CMD_PORT |= LCD_STR;						// send one strobe pulse to takeover the data
	LCD_CMD_PORT &= ~LCD_STR;
		
	pause(1);										// do 1ms pause for the LCD

}


// --------- calculate the frequency from the counting result and put it to the LCD --------
// calculations are based on a gate-time of 128ms
void put_result_to_lcd(unsigned long int cnt_result)
{

	unsigned char res_string[20];

	if( mode==PRESCALE_FACTOR_1 )					// for usage without prescaler
	{
	
		cnt_result *= 100;							// calculate *100 :128 for
		cnt_result >>= 7;							// 100Hz resolution at 128ms gate-time
	
		sprintf( res_string, "%8ld", cnt_result);	// convert to decimal digits
		
		videoram[2]  = res_string[0];				// put decimal digits to videoram
		videoram[3]  = res_string[1];
		videoram[4]  = res_string[2];

		videoram[6]  = res_string[3];
		videoram[7]  = res_string[4];
		videoram[8]  = res_string[5];
		
		videoram[10] = res_string[6];
		videoram[11] = res_string[7];
	
	}

	if( mode==PRESCALE_FACTOR_64 )					// for usage with :64 prescaler
	{

		// very little calculation necessary, prescaler of 128 would fit perfectly to a gate-time of 128ms,
		// for a prescaler of 64 we just have to do a divide by 2 for a resolution of 100kHz at 128ms gate-time

		cnt_result >>= 1;							// :2

		sprintf( res_string, "%8ld", cnt_result);	// convert to decimal digits
		
		videoram[2]  = res_string[1];				// put decimal digits to videoram
		
		videoram[4]  = res_string[2];
		videoram[5]  = res_string[3];
		videoram[6]  = res_string[4];
		
		videoram[8]  = res_string[5];
		videoram[9]  = res_string[6];
		videoram[10] = res_string[7];
		
	}

	if( mode==PRESCALE_FACTOR_128 )					// for usage with :128 prescaler
	{
			
		// no calculation necessary, prescaler of 128 fits perfectly to a gate-time of 128ms
		// we get a resolution of 100kHz at 128ms gate-time

		sprintf( res_string, "%8ld", cnt_result);	// convert to decimal digits
		
		videoram[2]  = res_string[1];				// put decimal digits to videoram
		
		videoram[4]  = res_string[2];
		videoram[5]  = res_string[3];
		videoram[6]  = res_string[4];
		
		videoram[8]  = res_string[5];
		videoram[9]  = res_string[6];
		videoram[10] = res_string[7];
			
	}

	if( mode==PRESCALE_FACTOR_640 )					// for usage with :640 prescaler
	{
		
		// for a prescaler of 640 we act like it was a prescaler of 64 (see above) and simply
		// arange the decimal digites shifted by one digit around the decimal point
		
		cnt_result >>= 1;							// :2 

		sprintf( res_string, "%8ld", cnt_result);	// convert to decimal digits
	
		videoram[1]  = res_string[0];				// put decimal digits to videoram
		videoram[2]  = res_string[1];
		videoram[3]  = res_string[2];

		videoram[5]  = res_string[3];
		videoram[6]  = res_string[4];
		videoram[7]  = res_string[5];
		
		videoram[9]  = res_string[6];
		videoram[10] = res_string[7];
		
	}
	
	if( mode==PRESCALE_FACTOR_1000 )				// for usage with :1000 prescaler
	{

		// for a prescaler of 1000 we act just like it was no prescaler at all and simply change
		// the displayed unit from MHz to GHz

		cnt_result *= 100;							// *100 :128 
		cnt_result >>= 7;

		sprintf( res_string, "%8ld", cnt_result);	// convert to decimal digits
		
		videoram[2]  = res_string[0];				// put decimal digits to videoram
		videoram[3]  = res_string[1];
		videoram[4]  = res_string[2];

		videoram[6]  = res_string[3];
		videoram[7]  = res_string[4];
		videoram[8]  = res_string[5];
		
		videoram[10] = res_string[6];
		videoram[11] = res_string[7];
		
	}

	
	update_lcd_4bit_interface();					// transfer data from video ram to lcd display
				
}


//-----------------------------------------------------------------------------
void process_drift_moving_avg( long int new_value )
{

	int entry;
	long int mv_avg_sum;

	for( entry=15 ; entry>=1 ; entry-- )			// first shift table by one position to make room for the new value 
		mv_avg_table[entry] = mv_avg_table[entry-1];// and drop out the oldest value
		
	mv_avg_table[0] = new_value;					// add the new value to the table
		
	mv_avg_sum=0;									// add up all values in the table
	for( entry = 0 ; entry<= 15 ; entry++ )
		mv_avg_sum += mv_avg_table[entry];
		
	drift_mv_avg = 	mv_avg_sum >> 4;				// divide by 16 to calculate the new moving average value
		
	
}


//-----------------------------------------------------------------------------
void put_drift_to_lcd(long int drift)
{

	
	unsigned char res_string[20];
		
	sprintf( res_string, "%8ld", drift);			// convert to decimal digits

	if( drift<=99999 )
	{
		
	//	videoram[19]  = res_string[0];					// put decimal digits to videoram
	//	videoram[20]  = res_string[1];
	//	videoram[21]  = res_string[2];
		videoram[22]  = res_string[3];
		videoram[23]  = res_string[4];
		videoram[24]  = res_string[5];
		videoram[25]  = res_string[6];
		videoram[26]  = res_string[7];
		videoram[27]  = 'p';
		videoram[28]  = 'p';
		videoram[29]  = 'm';
		videoram[30]  = '/';
		videoram[31]  = 's';
	
	
	}

	else
	{
		videoram[22]  = 'o';
		videoram[23]  = 'v';
		videoram[24]  = 'e';
		videoram[25]  = 'r';
		videoram[26]  = 'f';
		videoram[27]  = 'l';
		videoram[28]  = 'o';
		videoram[29]  = 'w';
		videoram[30]  = ' ';
		videoram[31]  = ' ';
	
	}
	
	
}

//---------------------- switch to next mode ----------------------------------
// go to next prescaler mode and update the LCD screen mask acoordingly
void switch_to_next_mode(void)
{

	mode++;											// switch to next prescale mode

	if( mode > MAX_PRESCALE_MODE )					// if last mode reached..
		mode=0;										// start with first mode again
		
		
	if( mode==PRESCALE_FACTOR_1 )					// for usage without prescaler
	{
	
		videoram[17]='1';							// write prescalefactor 1 to LCD
		videoram[18]=' ';
		videoram[19]=' ';
		videoram[20]=' ';

		videoram[12]='M';							// write M for MHz to frequency unit

		videoram[4]='.';							// set the 3-digit separation points
		videoram[8]='.';			
		
	}

	if( mode==PRESCALE_FACTOR_64 )					// for usage with :64 prescaler
	{
	
		videoram[17]='6';							// write prescalefactor 64 to LCD
		videoram[18]='4';
		videoram[19]=' ';
		videoram[20]=' ';

		videoram[12]='G';							// write G for GHz to frequency unit

		videoram[3]='.';							// set the 3-digit separation points
		videoram[7]='.';			
		
	}

	if( mode==PRESCALE_FACTOR_128 )					// for usage with :128 prescaler
	{
	
		videoram[17]='1';							// write prescalefactor 128 to LCD
		videoram[18]='2';
		videoram[19]='8';
		videoram[20]=' ';

		videoram[12]='G';							// write G for MHz to frequency unit
		
		videoram[3]='.';							// set the 3-digit separation points
		videoram[7]='.';			
		
	}
	
	if( mode==PRESCALE_FACTOR_640 )					// for usage with :640 prescaler
	{
	
		videoram[17]='6';							// write prescalefactor 640 to LCD
		videoram[18]='4';
		videoram[19]='0';
		videoram[20]=' ';

		videoram[12]='G';							// write G for MHz to frequency unit

		videoram[4]='.';							// set the 3-digit separation points
		videoram[8]='.';			
		
	}
		
	if( mode==PRESCALE_FACTOR_1000 )				// for usage with :1000 prescaler
	{
	
		videoram[17]='1';							// write prescalefactor 1000 to LCD
		videoram[18]='0';
		videoram[19]='0';
		videoram[20]='0';

		videoram[12]='G';							// write G for MHz to frequency unit

		videoram[4]='.';							// set the 3-digit separation points
		videoram[8]='.';			
		
	}
	
	
	pause(200);										// pause to avoid unintended key repetitions
	
}
		
//-------------------------------------------- pause -----------------------------------------------------------
// waits for n times 1 ms (adapted for 4MHz crystal)
void pause(unsigned int n)
{

	unsigned int t;

	TCCR0 = 0x03;				 					// set timer0 prescaler to 64

	for( t=0; t<n; t++)								// wait n times 1ms
	{

		TCNT0 = 198;				 				// reset timer 0
		TIFR |= _BV(TOV0);						 	// reset overflow-bit
		while( !(TIFR & _BV(TOV0)) );	 			// wait for first overflow of timer0

	}	
	
}


// --------------------------- hex output -------------------------------------
// show the counting result as hex figure in the LCD
void hex_output(unsigned long int number)
{
	
	videoram[24]= hex2asc( number>>28 & 0x0000000F );
	videoram[25]= hex2asc( number>>24 & 0x0000000F );
	videoram[26]= hex2asc( number>>20 & 0x0000000F );
	videoram[27]= hex2asc( number>>16 & 0x0000000F );
	
	videoram[28]= hex2asc( number>>12 & 0x0000000F );
	videoram[29]= hex2asc( number>>8  & 0x0000000F );
	videoram[30]= hex2asc( number>>4  & 0x0000000F );
	videoram[31]= hex2asc( number     & 0x0000000F );
	
}

//--------------------------- hex to asc --------------------------------------
// converts a hex value (00..0F) into the ASCII-character '0'..'F'
unsigned char hex2asc(unsigned char hexbyte)
{
	
	hexbyte += 48;			// ASCII code for 0-9 is 48-57
	
	if(hexbyte>=58)			// ASCII code for A-F is 65-70 -> in that case add 7
		hexbyte += 7;
	
	return hexbyte;
}


