 
/*

Even Better LC Meter, an ATTINY861 based inductanace andCapacitance Meter 

Version  LCM071125I

Copyright 2007 Richard Cappels

The entire project, including schematic can be found at:
www.projects.cappels.org 

LCD library copyright  Peter Fleury ( pfleury@gmx.ch  
http://homepage.hispeed.ch/peterfleury/) 

Mr. Fleury's libraries are located at
http://homepage.hispeed.ch/peterfleury/avr-software.html

The LCD library is included in the distribution of the Even Better LC Meter
project with Mr. Fleury's permission.

Compiler: 4.1.2 (WinAVR 20070525)
The compiler can be downloaded from http://sourceforge.net/

Here is how to connect it:
Use an Attiny861, 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 Hitachi 2 line x 16 character display using the HD44780 controller to 
Port A as described in the port pin assignments below. Displays using the KS0073
controller can be accomodated with a #define statement. accoding to the documetation
supplied with Mr. Flery's library, at the URL noted above. 

A comparitor based LC oscilator drives the T0 input on the ATTINY861.
The output of a comparitor based LC oscillator oscillates at the resonant freuency
of the LC parallel combiantion. For inductance measurement, a capacitor of .01 uf
is used and the resonant frequency is used to calcualte the inductance. When measuring
capacitance, an approximate 1 mH inductor in parallel with a precise 1000 pf capacitor
is used in parallel with the unknown capacitor and again the capacitance is calculated from
the resonant frequency. 

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.

Many thanks to Marshall Schaffer for his c tutorial and for his assistance in cleaning some compiler errors 
in the original projec.

*/

#include <avr/interrupt.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "lcd.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	counter0_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.
char numstring[16];										// Array for strings to be displayed.	
double component_value =0;                             		 // This corresponds to an inductor of 1.00 mh to get 1 pf ls digit for capacitance measurement.
double	raw_component_value;					// Results of component calculation before offset is applied.
double reference_inductance;		// Value of inductance used to calculate capacitance.
double ref_reactance =   1000;					//  Capacitance of .0100 uf capacitor reads 100 nh ls digit for inductance measurement. 
double numerator = 1e11;							// For proper scaling of results in 100 nh/1pf resolution.
double frequency =0;			
double	c_calcap;											// Value of capacitor used to measure the inductor that is used to measure DUT capacitance.
double l_refcap;												// Value of capacitor used to measure inductance.

char numstring[16];										// Array for strings to be displayed.
char	calflag;													// Used to signal that calibration button has been pushes -when value >0;
double coffset;												// Offset when making capacitance measurements.
double loffset;												// Offset when making inductance measurements.

void ioinit (void)											// Initialize  I/O.
{																			//Initialize output port
	/*
	I/O Pin assignments:

	B0	Unused.  Input with pullup.
	B1	Taken low (grounded) to indicate capacitance is being measured.  Input with pullup.
	B2	Taken low (grounded) to zero meter. Input with pullup.
	B3	Measuring indicator LED. Output.
	B4	Pin used for Crystal.
	B5	Pin used for Crystal and Clock Out.
	B6	Timer 0 input from LC Oscillator circuit.
	B7	Reset - always reads zero. 
	
	A0 - A6 Are used for the LCD.
	A0	Data 4	Display pin 11	Output 
	A1	Data 5	Display pin 12	Output 	
	A2	Data 6	Display pin 13	Output 
	A3	Data 7 	Display pin 14	Output 
	A4	RS			Display pin 4		Output 
	A5	R/W		Display pin 5		Output 
	A6	E				Display pin 6		Output 
	A7	Unused								Input with pullup
	
	*/
	
	PORTA = 0b10000000;
	DDRA =    0b01111111;
	PORTB =0b00000111;
	DDRB =  0b00001000;
	
	lcd_init(LCD_DISP_ON );
	lcd_clrscr();	
}
	
	
ISR (SIG_OVERFLOW1)							// TIMER1 Interrupt Service
{  
		if  (counter ==0 )							
			Data_Ready = 0xFF;
		else;
			{counter --;}
}


ISR (SIG_OVERFLOW0)							// Counter0 Interrupt Service
{
	counter0_overflow++;							
}

void led_on (void)									//Take PORTB, bit 3 high to light LED.
{
 PORTB =0b00001111;      
}
	
	
void  led_off  (void)									//Take PORTB, bit 3 low to turn LED off.
{
PORTB =0b00000111;       
}	


void EEPROM_write(unsigned char ucAddress, unsigned char ucData)  //EEPROM write routine from Atmel ATTINY861 datasheet
{
	/* Wait for completion of previous write */
	while(EECR & (1<<EEPE));
	/* Set Programming mode */
	EECR = (0<<EEPM1)|(0<<EEPM0);
	/* Set up address and data registers */
	EEAR = ucAddress;
	EEDR = ucData;
	/* Write logical one to EEMPE */
	EECR |= (1<<EEMPE);
	/* Start eeprom write by setting EEPE */
	EECR |= (1<<EEPE);
}


unsigned char EEPROM_read(unsigned char ucAddress)			//EEPROM read routine from Atmel ATTINY861 datasheet
{
	/* Wait for completion of previous write */
	while(EECR & (1<<EEPE));
	/* Set up address register */
	EEAR = ucAddress;
	/* Start eeprom read by writing EERE */
	EECR |= (1<<EERE);
	/* Return data from data register */
	return EEDR;
}


double measure_frequency (void)		//Returns incoming frequency in Hz.
{
	double 	frequency = 0;  						//Hz	

	TCCR0B = 0b00011000;						// Hold Timer0 in reset.
	TCCR0A = 0b10000000;						// Set Timer0 to 16 bit counter mode.
	TCNT0H = 0;											// Clear Timer/Counter0 low and high bytes.
	TCNT0L = 0;

	TCCR1B = 0b01000000;						//Reset and stop Timer1 prescaler.
	TC1H = 0;
	TCNT1 = 0;												// Clear Timer/Counter1 low and high bytes
	
	TCCR1B = 0b00001101;						//Run Timer1 prescaler, divide peripheral clock by 4096. Pclock should be same as CPU clock.
	TCCR0B = 0b00000110;						// Run Timer0, clocked by falling edge of T0 (external) input.
	TIMSK = TIMSK |  0b00000110;		// Enable Timer0 and Timer1 interrupts.
	
	counter0_overflow = 0;  						// Count of Timer1 overflows -bits 16 through 23 of raw frequency.
	counter = 3;												//Used by timer0 to count number of  interrupts.
	Data_Ready =0;										// Flag from Timer0 interrupt to let this routine know when time is up.
	
	sei ();															// Enable global interrupts.	
	while (!Data_Ready) 							//Wait for timer0 to count to 0, Light LED on B6.
		{
			if (!(PINB & 0b00000100))		//If B2 is low, set zero meter routine flag.
				{
					led_off();
					calflag =255;
				}
		}
	cli();															// Stop global interrupts then compute and display results.

	TCCR0B = 0b00011000;						// Hold Timer0 prescaler in reset.
	TCCR1B = 0b01000000;						// Hold Timer1 prescaler in reset.
	TIMSK = TIMSK &  0b11111001;		// Disable Timer0 and Timer1 interrupts.         				 

	unsigned int i;										// Get the counter contents into a single variable and
	i = TCNT0L;												// normalize the number so it comes out in Hz.
	i |= ((unsigned int)TCNT0H << 8);
	frequency = 1000* (  i+ ( counter0_overflow* 65536))/1049;
	 return frequency;
}
	



 void winkdelay (void)
{
	counter = 0;
	TCCR1B = 0b01000000;							//Reset and stop Timer1 prescaler.
	TC1H = 0;
	TCNT1 = 200;												// Clear Timer/Counter1 low and high bytes
	TCCR1B = 0b00001101;							//Run Timer1 prescaler, divide peripheral clock by 4096. Pclock should be same as CPU clock.
	TIMSK = TIMSK |  0b00000100;			// Enable Timer0 and Timer1 interrupts.
	Data_Ready =0;											// Flag from Timer0 interrupt to let this routine know when time is up.
	sei ();																// Enable global interrupts.
	while (!Data_Ready) {};							//Wait for timer0 to count to 0, Light LED on B6.
	cli();																// Stop global interrupts then compute and display results.
	TCCR1B = 0b01000000;							// Hold Timer1 prescaler in reset.
	TIMSK = TIMSK &  0b11111011;			// Disable Timer0 and Timer1 interrupts.
}

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.
{
	int  i = 0;
	int  thru =0;												//Not zero after first pass.
	char c =0;
	char d= 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++]);	
			if ( thru !=0)	{lcd_putc (c);}	
			c=d;
			thru = 1;												//Set flag to show that loop has executed at least once.
		}
	if (add_decimal_point)  lcd_putc('.');		// Send a decimal point.
	lcd_putc(d);
}






	void zerometer(void)
	// If measuring inductance, take the current component value and use it as an zero offset.
	// If measuring capacitance, first measure value of the 1 mh inductor used in the measurement, then
	// that the current component value as the zero offset.
	{
			lcd_clrscr();	
			add_decimal_point =0;												//The default is to not add a decimal point.
			if (!(PINB & 0b00000010))									// If capacitance is being measured.
			{																						// Measure inductance assuming capacitance is "c_calcap".
					lcd_puts("CAPACITANCE CAL\n");
					reference_inductance = (((numerator/(628*frequency))*(numerator/(628*frequency)))/c_calcap) ; 
					ultoa( (unsigned long) ((double) c_calcap), numstring, 10);  // unsigned long to string
					sendstring(numstring);	
					lcd_puts("pf  ");	
					ultoa( (unsigned long) ((double) ref_reactance), numstring, 10);  // unsigned long to string
					sendstring(numstring);
					lcd_puts("uH\n");				
					coffset = raw_component_value;
			}
			else																				//If measuring inductance.
			{
					lcd_puts("INDUCTANCE ZERO\n");
					ultoa( (unsigned long) ((double) (10*ref_reactance)), numstring, 10);  // unsigned long to string
					sendstring(numstring);
					lcd_puts("pf ref cap \n");
					loffset = raw_component_value;
			}
	}




void measure_and_display_component_value (void)
{					
	if (!(PINB & 0b00000010))								// Make choices depending upon whether capacitance or inductance is being measured.
		{
				ref_reactance = reference_inductance;	
				add_decimal_point =0;								//Do not add a decimal point.
				ioffset = coffset;	
		}
	else
		{
				add_decimal_point =1;								// Set a flag so inductance will be displayed with one digit to right of decimal point.
				ioffset = loffset;
				ref_reactance = l_refcap ;	
		}

		frequency = measure_frequency();				//Measure the incoming frequency.	
		raw_component_value = (((numerator/(628*frequency))*(numerator/(628*frequency)))/ref_reactance) ; //The main event.
		component_value = raw_component_value - ioffset;	// Compensate for the offset.				
		lcd_clrscr();			
		ultoa( (unsigned long) ((double) component_value), numstring, 10);  // unsigned long to string
	
		if (!(PINB & 0b00000010))							// Make choices depending upon whether capacitance or inductance is being measured.
			{
				lcd_puts("C(pf)= ");	
			}
		else
			{
				lcd_puts("L(uH)= ");
				add_decimal_point =1;								// Set a flag so inductance will be displayed with one digit to right of decimal point.
			}
					
			sendstring(numstring);
			lcd_puts("\n");
			lcd_puts("F(Hz)= ");
			ultoa( (unsigned long) ((double) frequency), numstring, 10); 	// unsigned long to string.
			lcd_puts(numstring);	
			
		if ( (unsigned long) ((double) component_value) > 1000000000)	// Error message if inductance is over 100H or 100uF.		
			{
				lcd_clrscr();	
				lcd_puts("OUT OF RANGE\n");
			}
}
	



void startup_calibration_for_capacitance_mode (void)
{	
	if (!(PINB & 0b00000010))									// If capacitance is being measured, perform calibration at power-up.
		{
			lcd_puts("Stabilizing\n"); 
			measure_and_display_component_value ();
			zerometer();
			measure_and_display_component_value ();
			zerometer();
			measure_and_display_component_value ();
		}
}


void Adjust_reference_capacitors (void)
{
		
			double 	bport = 6;	
			double c_calcapoffset = 0;									//The offset used to adjust the C calibration capacitor.
			double l_refcapoffset = 0;									//The offset used to adjust the L calibration capacitor.
			
			c_calcap = 990;														// Load the lowest value for the C calibration capacitor.
			l_refcap =990;															// Load the lowest value for the L calibration capacitor.
			add_decimal_point =0;											// Make sure the decimal point is off since this will only display capacitance values.		
			if  (!(PINB & 0b00000010))							
				{																						// If  in capacitance  mode adjust c_calcap
				while  (!(PINB & 0b00000010))						// While in capacitance  mode adjust c_calcap
					{
						lcd_puts(" Ref cap adj\n"); 
						lcd_puts(" cap="); 	
						ultoa( (unsigned long) ((double) ( c_calcap+c_calcapoffset)), numstring, 10); // unsigned long to string
						sendstring(numstring);
						lcd_puts("pf \n");
						while (!(PINB & 0b00000100)) {winkdelay();};	// Wait until zero button is released.						
						bport = PINB & 0b00000110;
						while ((bport == 4)) {									// Wait until zero button is pressed or S1 goes out of C mode.	
						winkdelay();
						bport = PINB & 0b00000110;
						}	 				
						if (!(PINB & 0b0000010)) {c_calcapoffset = c_calcapoffset + 1;}	// If in inductance  mode, adjust c_calcap value.
						}		
						EEPROM_write(90,c_calcapoffset);			//Write the offset to the nominal value to the EEPROM.	
					}
				
				else
				{																					// If in the inductance mode.
					while  (PINB & 0b00000010)						// While in inductance  mode adjust l_refcap   .
					{
						lcd_puts(" L ref cap  adj\n"); 
						lcd_puts(" cap="); 	
						ultoa( (unsigned long) ((double)(10*( l_refcap+l_refcapoffset))), numstring, 10);  // uUnsigned long to string.
						sendstring(numstring);
						lcd_puts("pf \n");					
						while (!(PINB & 0b00000100)) {winkdelay();};		// Wait until zero button is released.				
						bport = PINB & 0b00000110;
						while ((bport == 6)) {									// Wait until zero button is pressed or S1 goes out of C mode.	
						winkdelay();
						bport = PINB & 0b00000110;
						}	 				
						if ((PINB & 0b0000010)) {l_refcapoffset = l_refcapoffset + 1;}		// If in capacitance  mode, adjust c_calcap value.
						}							
						EEPROM_write(93,l_refcapoffset);			//Write the offset to the nominal value to the EEPROM.	
				}
	}


void check_cal_flag (void) 													// If cal flag was set (during frequency measurement) perform calibration.
{
if (calflag)																					// If calflag is set, run calaibration routines;
	{
			measure_and_display_component_value ();		// I think two cycles about right.
			zerometer();																// The additional cycle allows the analog circuits to settle.
			measure_and_display_component_value ();		
			zerometer();
			calflag = 0;																	// Done with calibration cycle, so clear flag.
		}
}


int	main (void)		
{	
	ioinit ();																						// Initialize the I/O and the LCD.							 				
	calflag =0;																					// Clear calibration flag.	
	led_on ();																					// Turn the LED on so if the LCD isn't working, you have a clue as to whether the battery is the problem.
	winkdelay();																			// Give a fraction of a second for things to settle before doing anything.
	
	if (!(PINB & 0b00000100)) 												//If zero button is down, go to the adjust reference capacitors routine.
	Adjust_reference_capacitors();

	c_calcap = 990 + EEPROM_read(90);								// Get the value  of the capacitor used to calibrate the inductor used to measure C.
	l_refcap = 990 + EEPROM_read(93);								// Get the value  of the capacitor used to measure L.

	lcd_puts("LCM071125I\n"); 													//Display the LC meter firmware version and an advertisement.
	lcd_puts("www.cappels.org "); 											
	winkdelay();	
	startup_calibration_for_capacitance_mode();					//If the meter is in the C meter mode, perform calibration and zeroing. 
	
	while (1)																					//The main loop.
	{	 															
	led_on ();
	measure_and_display_component_value ();					// Measure the device under test.
	led_off();																					// Wink the LED off to indicate that a measurement was just made.
	winkdelay();
	check_cal_flag();																	// Check to see of the calibration flag was set during frequency measurement.
	}
}


