/*

	Program:		freq_cnt.c
	
	Function:		Frequency Counter
	
	Hardware:		ATMega 16 running on +5V with a 2x16 LCD plus external counter,
					counting gate and gate time generator
					
	Crystal:		4.096 MHz
	
		
	
	History:

	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
					
					
*/



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

// --- 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_1;	// default mode is: no prescaler


// --- Constant Data that is kept in the flash memory ---
											
unsigned char PROGMEM scr_greeting[]=      "FrequencycounterVer 1.1   DL5NEG";
unsigned char PROGMEM scr_counting[]=      "  xx.xxx.xx MHz :1        DL5NEG";

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

	
	
	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.

		// ---- 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);
				
		// ----- 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();
		
		// 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[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_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[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];
		
	}

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

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

