 
/*At90S2313 LC Meter Version  2313LCmeter_070217A	

Copyright 2007 Richard Cappels
www.projects.cappels.org

Complied with 	avr-gcc (GCC) 3.4.6
The compiler can be downloaded from http://sourceforge.net/

Here is how to connect it:
Use an AT90S21313 or an ATTINY2313, operating with a 4 MHz clock. A crystal is recommended since
the clock timing can affect accuracy, and this is designed for a 4.00 MHz clock.

Connect an inverting RS-232 buffer to the TXD pin. This is the output fo the circuit and it will drive a dumb terminal
application or a serial input LCD controller. The output is 9600 baud, no parity. A carriage return and a linefeed
are sent to indicate that a new line is to be displayed. Since this LC meter displays both the measured L or C value and
the oscillation frequency, a two line display is required. 

Alternatively, UART Transmit output can be connected wtihout an inverting buffer to the UART Receive input to an AN90S2313 
or ATTINY2313 operating from the same power supply voltage, and on the same circuit board. 

The output of a comparitor based LC oscillator, that is used to measure the unknown L and C connects to the input of 
counter/timer1. 

In the case of inductance measurement, the expected value for the capacitor is 0.01 uf.
In the case of capacitance measurement, the expected value for the inductor is 1.00 mh.

Using an LM393 comparitor, readings below 20 uH should be suspect.
Using an LM399 comparitor, no problems have been observed below 1 uh.
Using an LM393 comparitor, readings below about 220 pf should be suspect.
Using an LM399 comparitor, no problems have been observed down to 1 pf.

This code pretty much fills the flash on the ATTINY2313.

*/

#include <avr/interrupt.h>
#include <stdlib.h>

					//Global Variables
volatile char  Data_Ready =0;					// Flag for timer0 interrupt to tell freq. measurement when time is up.
volatile int counter =0;						// Counts the number of timer0 interrupts.
volatile double	counter1_overflow = 0;  		// Counts the number of timer1 overflow events.
volatile double ioffset;						// The measurement offset value.
volatile char add_decimal_point;				// Flag to tell sendstring routine when to add a decimal point to the display.

	
void ioinit (void)								// Initialize  I/O.
{		
												//Initialize output ports
	PORTB = 0b00000000;	
	DDRB = 0xFF;
	PORTD = 0b01111100;
	DDRD = 0b01000000;
	
	/*
	I/O Pin assignments for AT90S2313/ATTINY2313:
	
	DO	Reserved for UART RXD.
	D1	UART TXD.
	D2	Not used -AVAIABLE.
	D3	Taken low (grounded) to indicate capacitance is being measured.
	D4	Taken low (grounded) to zero meter.
	D5	Uses as Timer 1 clock input from LC oscillator.
	D6	Measuring indicator LED.
	D7	Does not exist as a physical port on AT90S2313/ATTINY2313.
	
	B0 - B6 Are not used.
	
	*/
}

void UARTinit (void) 
{
	UBRR = 25;										// 9600 Baud with 4 MHz crystal.
	UCR = (1<<TXEN)|(1<<RXEN);						// 8 Databits, receive and transmit enabled, receive and transmit complete interrupt enabled
}	


void uart_send(char data)
{
	while(! (USR & 0b00100000) ) {}
	UDR = data;
}


void (crlf) (void)									//Send a carriage return and a linefeed via the UART.
{
	uart_send  (0x0A);
	uart_send  (0x0D);
}	
	
	
void sendstring(char s[])							// Send a string up to 16 characters long. If add_decimal_point is not zero,
													//place a decimal point to the left of the least significant digit.
{
	char c =0;
	char d= 0;
	int i = 0;
	while(i < 16) 									// don't get stuck if it is a bad string
	{
		if( s[i] == '\0' ) break; // quit on string terminator
			d= (s[i++]);	
			uart_send(c);
			c=d;
	}
		if (add_decimal_point) uart_send(46); 		// Send a decimal point.
		uart_send(d);
		add_decimal_point =0;
}


ISR (SIG_OVERFLOW0)									// TIMER0 Interrupt Service
{  
		if  (counter ==16 )							//  Count N+1 number of 65.536 ms interrupts. 17 Iterations is 1.114 seconds.
			Data_Ready = 0xFF;
		else;
{			counter++;
	}
}

ISR (SIG_OVERFLOW1)									// Counter1 Interrupt Service
{
	counter1_overflow++;							// Only keep track of the quantity of counter1 overflows.
}


void Timers_Setup (void)
{
	// Initialize Timers.
	TCCR0	 = 0b00000101;							//Set up Timer0.
	TCCR1B =  0b00000110; 							//Enable External T1 input (pin 9 on AT90S2313/TINY2313).
	TIMSK =   0b10000010;							//Counter/Timer Interrupt Mask -enable these two interrupts.	
}

void Timers_stop (void)								//Set the prescalers so the timers stop.
{
	TCCR0	 = 0b00000000;
	TCCR1B =  0b00000000; //       
}


double measure_frequency (void)						//Returns incoming frequency in Hz.
{
	double 	frequency = 0;  						//Hz	
	TCNT1 = 0;										// Set up registers for the next measurement.
	frequency = 0;									
	counter = 0;									//Used by timer0 to count number of 65.536 ms interrupts.
	 Data_Ready =0;									// Flag from Timer0 interrupt to let this routine know when time is up.
	TCNT0=0;										// Clear timero data register.
	counter1_overflow=0;							// Clear the count of timer1 overflow events.
	Timers_Setup ();								// Restart clocks to the timers.
	sei ();											// Re-enable interrupts.
	while (!Data_Ready) {PORTD =0b01011100;};		//Wait for timer0 to count to 0, Light LED on B6.
	cli();											// Stop interrupts then compute and display results.
	Timers_stop();									//Stop clocks to the timers.
	PORTD=0b00011100;								//Turn off LED on B6.
	frequency = (1000 * (frequency + TCNT1 + (counter1_overflow  * 65536)))/1114; //Calculate the frequency for 1.1114 second sampling.
	 return frequency;
}
	
	
void measure_and_display_inductance (void)
/* The same formula is used to display inductance in tenths of microhenries and capacitance in picofarads when
a 0.0100 uf capacitor is used to measure inductance and a 1.00 milihenry inductor is used to measure capacitance. 
The formula below calculates "inductance" and displays it as inductance when measuring inductance and as capacitance 
when measuring capacitance. When displaying inductance, a decimal point is written to the left of the last digit so the
display can be read directly in microhenries and thenths of microhenries. The capacitance display does not have a 
decimal point and it reads directly in picofarags. 

When PORTD, bit 4 is found to be low during a measurement cycle, indicating that the "Zero" button has been pressed,
the result of the calculation, whether it is inductance or capacitance is stored as the offset value and then the offset
value is subtracted from subsequent readings.
*/

{
	double capacitance =   1000;					//  Capacitance of .0100 uf capacitor reads 100 nh ls digit for inductance measurement. 
	double inductance =0;                              //  This corresponds to an inductor of 1.00 mh to get 1 pf ls digit for capacitance measurement.
	double numerator = 1e11;						// For proper scaling of results in 100 nh/1pf resolution.
	double frequency =0;							
	char numstring[16];									// Array for strings to be displayed.
	
	frequency = measure_frequency();				//Measure the incoming frequency.	
	inductance = (((numerator/(628*frequency))*(numerator/(628*frequency)))/capacitance) ; //The main event.
	
	if ((!(PIND & 0b00010000)))	//If D4 is low, store inductance reading as offset instead of displaying it.
			{
			ioffset = inductance;
			add_decimal_point =0;
			crlf();
			sendstring("ZERO SET");
			}
		else
	
			{		inductance = inductance - ioffset;							// Compensate for the offset.				
				if ( (unsigned long) ((double) inductance) > 1000000000) 		// Error message if inductance is over 100H or 100uF.		
			{
				crlf();
				sendstring("RANGE");
				}
			else
			{
					ultoa( (unsigned long) ((double) inductance), numstring, 10);  // unsigned long to string
					crlf();
					crlf();
					if (!(PIND & 0b00001000))										// Display capacitance or inductance depending on D3.
						sendstring("C(pf)= ");
						else
						{
						sendstring("L(uH)= ");
						add_decimal_point =1;										// Set a flag so inductance will be displayed with one digit to right of decimal point.
						}
					sendstring(numstring);											// Send the inductance value.
					crlf();
					sendstring("F(Hz)= ");
					ultoa( (unsigned long) ((double) frequency), numstring, 10);  	// unsigned long to string.
					sendstring(numstring);
						
					}
			}

}

int	main (void)		
{
	ioinit ();																// Initialize the I/O, the UART, and variables.
	UARTinit();
	ioffset = 0;
	measure_frequency();													//This only done for a 1 second delay while the LCD identifies its firmware verion.
	sendstring("LCmeter070217A");											//Identify the LC meter firmware version.
	while (1)
	{	
	measure_and_display_inductance();										//Go through measurement cycles over and over again.
	}	
    return (0);
}



