//*********************************************************************
//*********************************************************************
//
//  NIXIE THERMOMETER
//  AVR Atmega88 
//
//  File:	thermometer.c
//  Author:	Colin Mann
//  Date:	January 14, 2008
//
//
//	D.0 -> D.7		Output
//  B.3 -> B.4		Input
//  C.1 -> C.3 		Output
//	C.0 = ADC input from thermo-amplifier AD595
//
//  Ports C.1 -> C.3 are 100's digit (1,2,or 3)
//  Ports D.0 -> D.3 are 1's digit		0xF#
//  Ports D.4 -> D.7 are 10's digit		0x#F
//

//			



#include <avr/io.h>
#include <stdlib.h>
#include <avr/sfr_defs.h>
#include <math.h>
#include <avr/interrupt.h>
#define F_CPU 1000000UL  // 1 MHz
#include <util/delay.h>

#define ADC_CHANNEL 0
// NTC temperature constant "B"-value: 3977K +/- 1%
#define NTC_Bi 3977.0		//Was 3977.0
// 4.7 kOhm at 25 degree celsius
#define NTC_TNi 25.0  
// 4.7 kOhm +/- 10%, change for calibration:
#define NTC_RNi 4000.0

#define CTC_VALUE		15625
#define DISPLAY_ON		1
#define DISPLAY_OFF		0
#define CELSIUS			2
#define FAHRENHEIT		1
#define KELVIN			0
#define DISPLAY			0
#define MODE			1
#define DEBOUNCE_TIME	25
#define BLANK			9999	
#define TIMEOUT			360


void output(int);
int convertanalog(unsigned char channel);
void whichTemperature(double celsius);
double adc_2_ohm_avcc(int adc_value);
double r2temperature(double ohm);
void checkButtons(void);

volatile int time = 0;
volatile int display = DISPLAY_ON;
volatile int mode = FAHRENHEIT;
volatile int buttonState[2] = {0,0};
volatile double temperature=0.0;
volatile double adc_value=0.0;


void delay_ms(unsigned int ms)
/* delay for a minimum of <ms> */
{
	// we use a calibrated macro. This is more
	// accurate and not so much compiler dependent
	// as self made code.
	while(ms){
		_delay_ms(0.96);
		ms--;
	}
}


int main(void)
{
	/*****************Timer Enable*****************/
	TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
	TIMSK1 |= (1 << OCIE1A); // Enable CTC interrupt
	sei(); //  Enable global interrupts
	OCR1A   = CTC_VALUE; // Set CTC compare value to ~1Hz at 1MHz AVR clock
	TCCR1B |= (1 << CS11) | (1<<CS10);//Prescaler set to 64
	/**********************************************/		



	//Initializes registers for input/output and sets pull-up resistors
	//Having all inputs set high to start will blank the nixie tubes, if using the appropriate 74141A chip
	//If not, setting all inputs to zero may be more appropriate, either way, the number will only be present for milliseconds
	DDRD = 0xFF;	//  Sets all pins in port D to high (output)
	DDRC |= (1<<PC1) | (1<<PC2) | (1<<PC3);
	PORTB |= (1<<PB3) | (1<<PB4);		//Set internal pullups for buttons.

	adc_value = convertanalog(ADC_CHANNEL);
	temperature=r2temperature(adc_2_ohm_avcc(adc_value));
	whichTemperature(temperature);
	
	while(1)
		checkButtons();
	return 0;
}


ISR(TIMER1_COMPA_vect)
{
	
	time++;
	if(time%3 == 1)
	{
		adc_value = convertanalog(ADC_CHANNEL);
		temperature=r2temperature(adc_2_ohm_avcc(adc_value));
	}
	if(display == DISPLAY_ON)
			whichTemperature(temperature);
	else
			whichTemperature(BLANK);
	if(time==TIMEOUT || display == DISPLAY_OFF)
	{
		time=0;
		display = DISPLAY_OFF;
	}
}



//Based on B.0 and B.1 corresponding to the temperature switch.
//Outputs the corresponding temperature, Celsius, Kelvin, or Fahrenheit
//Switch operates as grounded inputs, when in the middle position, the output is left to float, but with pull-up resistors, so it is high.
//When in the up or down position, that particular output is grounded:
void whichTemperature(double celsius)
{
	
	if(display == DISPLAY_ON)
	{
		switch(mode)
		{
			case KELVIN:
				output((int)(celsius + 273.15));
				break;
			case FAHRENHEIT:
				output((int)(celsius*1.8+32));
				break;
			case CELSIUS:
				output((int)celsius);
				break;
		}
	}
	else
		output(BLANK);
}


void output(int temp)
{
	if(temp != BLANK)
	{
		PORTD = ( (((temp%100)/10)<<4) | (temp%10) );
		if( ((temp/100)>0) && (temp/100)<4 )
			PORTC = (1<<(temp/100));
		if( (temp/100) == 0)
			PORTC = 0;
	}
	else
	{
		PORTD = 0xFF;
		PORTC = 0x00;
	}
}

/* convert a ntc resistance value given in kohm to
 * temperature in celsius. The NTC follows a exponential
 * characteristic. */
double r2temperature(double ohm)
{
	double tcelsius=0.0;
	double tmp=0.0;
	tmp= ((1.0/NTC_Bi)*(log(ohm/NTC_RNi))) + (1/(NTC_TNi+273.15));
	tcelsius=(1.0/tmp) - 273.15;
	return(tcelsius);
}

int convertanalog(unsigned char channel) 
{
	unsigned char adlow,adhigh;
	ADMUX |= (1<<REFS0) | (channel & 0x0f);
	ADCSRA |= (1<<ADEN)|(1<<ADPS2);	//16 Prescaler
	//  start conversion 
	ADCSRA |= (1<<ADSC);
	while(bit_is_set(ADCSRA,ADSC)); // wait for result 
	adlow=ADCL; // read low first !! 
	adhigh=ADCH;
	
	return((adhigh<<8) | (adlow & 0xFF));
}

/* this function converts the ADC value into an Ohm value of the NTC
 * here AVcc=5V is used as reference voltage 
 */
double adc_2_ohm_avcc(int adc_value){
	return( 9800.0*1024.0/adc_value - 9800.0 );
}

void checkButtons()
{
	if(bit_is_clear(PINB, PB4))
	{
		delay_ms(DEBOUNCE_TIME);
		if(bit_is_clear(PINB, PB4) && (buttonState[DISPLAY] == 0))
		{
			display = DISPLAY_ON;
			buttonState[DISPLAY] = 1;
			time = 0;
			adc_value = convertanalog(ADC_CHANNEL);
			temperature=r2temperature(adc_2_ohm_avcc(adc_value));
			whichTemperature(temperature);
		}
	}
	else
		buttonState[DISPLAY] = 0;
	
	if(bit_is_clear(PINB, PB3))
	{
		delay_ms(DEBOUNCE_TIME);
		if(bit_is_clear(PINB, PB3) && (buttonState[MODE] == 0))
		{
			if(mode == 2)
				mode = 0;
			else
				mode++;
			buttonState[MODE] = 1;
			adc_value = convertanalog(ADC_CHANNEL);
			temperature=r2temperature(adc_2_ohm_avcc(adc_value));
			whichTemperature(temperature);
		}
	}
	else
		buttonState[MODE] = 0;

}




