/*-------------------------------------------------------------------------------------------------

		Program:	Double Sensor Power Meter (3rd approach): dspm03.c
		
		Author:		Herbert Dingfelder, DL5NEG
		
		Function: 	A small handheld RF powermeter. Is able to read DC-voltages
					from two internal sensors (diode sensor, thermal senosr), 
					convert the voltage to a dBm value using internal lookup-
					tables. The result is displayed on a LCD.
												
		Features:	- Displays dBm for three different sensors
					- Battery voltage displayed in 1/10th of Volts
					- Displayment of values relative and absolute
					- Can display raw values of the ADCs in Hex-Format
					- Uses two measurments ranges for the diode-sensor, the
					  range can be manually selected or autorange can be switched on
															
		Disclaimer: This software, just like the hardware description, are
					provided "as is", without any guarantee of correct functionality.
							
					I hope this provides many radio amateurs and other hobbyists with
					a means to measure RF power in homemade projects. Using my IP
					for commercial projects however, is not approved by me.
							
					For compilation I have used the free GNU C-compiler as it is
					distributed by AVRFREAKS, together with Atmels free development
					environment AVR-Studio 3.54.
							
		Hardware: 	An Atmel AT90LS4433, running on 5Volts with a 3.68 MHz crystal.
					The reference voltage for the ADCs is set to 3.2 Volts.
					For a detailed hardware description see:
							www.dl5neg.de/powermeter/powermeter.html
													
		Pinning:	(Pin numbers reference to the PDIP28 package)
							
							Pin	Function	Used for
							1	Reset		Reset on ISP
							2	PD0			LCD databit 0
							3	PD1			LCD databit 1
							4	PD2			LCD databit 2
							5	PD3			LCD databit 3
							6	PD4			LCD databit 4
							7	Vcc			Vcc +5 Volts
							8	GND			GND
							9	XTAL1		crystal pin 1
							10	XTAL2		crystal pin 2
							11	PD5			LCD databit 5
							12	PD6			LCD databit 6
							13	PD7			LCD databit 7
							14	PB0			User key "mode select"
							15	PB1			PWM output for zero-adj. of the thermo sens.
							16	PB2			LCD Strobe
							17	PB3			MOSI on ISP, User key "Relative-Reset" (former abs/rel)
							18	PB4			MISO on ISP, User key "ADC/freq select"
							19	PB5			SCK on ISP, User key "Zero adjust"
							20	AVcc		Vcc for PORTC
							21	ARef		ADC reference set to +3.2 Volts
							22	AGND		GND for PORTC
							23	PC0			ADC 0 for thermal sensor input 1
							24	PC1			ADC 1 for diode sensor input 1
							25	PC2			ADC 2 for diode sensor input 2
							26	PC3			ADC 3 for monitoring the battery voltage
							27	PC4			ADC 0 for thermal sensor input 2
							28	PC5			LCD Register select
	

				Development based on umd_gcc_4x16.c
				(initial version made for 4x16 LCD)
																	
				
				Old Conversion History for umd_gcc_4x16.c:
				
				18.08.2002		Conversion starts based on the version of umd_gcc	
								that is running well on the first (3x12 LCD) hardware
											
				19.08.2002		Software seems to work on new hardware (only rough
								tests because ADCs are not yet connected)		
										
				23.08.2002		Complete hardware tests with Diode- and LogAmp
								Sensor successful, everything works fine
											
				24.08.2002		Resistive power divider is now 1:4 for an max.
								measureable Vbat of 12.8Volts. Software adapted
								and implemented the leading 1 for voltages > 9.9V.
											
				14.09.2002		Adapted to new hardware version that features
								an automatic zero adjust for the thermo sensor.
								Changes:
								User key "ADC/freq select" is now connected to PB4.
								Additional user key "zero adjust" is connected to
								PB5. Both keys share a pin with the ISP-connector
								now. This is not a problem as long as the keys are
								not pressed during software download.
								
								
				And here starts the new history for the dual-sensor power meter dspm03	
							
								
				01.03.2003		- This comments-header adapter for dspm03
								- Mode 4 (log-amp sensor) completly removed from the old code
								- update_lcd change from displaying 3x12 to 4x16 characters 
								- display masks rearranged for 4x16 LCD
								- permanent displaying of absolute and relative value implemented
								
				02.03.2003		- made the response to user keys a lot faster: delay in smoothkey()
								  reduced from 400 to 100ms, lcd_delay implemented
								- Pin C0 (ADC0) set as input for thermal sensor. This was necessary
								  since we have two sensor connected at the same time now. The ADCs 
								  1 and 2 are only for the diode sensor from now on.
								- lookup-table for thermal sensor adapted to new sensor hardware
								
				03.03.2003		- hardware was changed:
								  user key "relative reset" now shares PB3 with SPI-MOSI
								  -> that frees PB2 which is now LCD-Strobe
								  -> that frees PC4 which is now input for thermals sensor 2
								  software adapted for LCD-Strobe and user key 
								  
				08.03.2003		- [compiler version changed to WinAVR AVR-GCC 3.3]
								- raw ADC-screen changed to display ADC4 also
								- bug fixed: PC4 was not changed to I/O-input => ADC did not work
								
				09.03.2003		- PWM for zero-adjustment of thermal sensor integrated
								- first functionality for user key "zero adjust" implemented for 
								  testing the PWM hardware with the sensor		
								  
				14.03.2003		- makefile changed to extended version from winavr/samples/	
								- code-cleanup: tabs in before all code-lines corrected	
							
				15.03.2003		- automatic zero adjust for thermal sensor implemented										
								- second sensor for thermal reading (fine) implemented
								- bug fixed (from UMD-times)
									in key2 (ADC select) is pressed the diode-sensor0 (which never
									existed) was choosen together with autorange. Since ADC0 wasn't
									used in the UMD, it accidently worked. Now autorange did not work.
									Key-actions totally rewritten, now it works fine.
								- thermo_zero (valid for both ranges) set to 0x10 instead of 0x40,
									that is more than enough margin for thermal drift to lower values
									
								- all functions seem to work now (look-up tables are only roughly
									correct, must be adjusted after hardware measurements)
									-> Version 1.0 released
									
				16.03.2003		- thermal sensors calibrated, lookup tables updated
									-> Version 1.1 released
									
								- bug fixed: overload message was partly overwritten by zero-indicator
								  bar in the thermal sensor screen, variable "overload" implemented for
								  function interpol to indicate when overl-screen was switched on
								  	-> Version 1.2 released
								  
				16.04.2003		- new lookup-table for diode sensor inserted after hardware measurement
									(data measured at 100MHz->at 500MHz to 2.5GHz shows around 0.25dB
									less power than is actually applied)
									
				01.06.2004		- A new screen is implemented to allow the user to manually set a fixed
									preattenuation that is added to the power value shown in the display
									for every measurement. Mode 4 is used which was not used for a while, 
									it was used for the log-amp sensor originally. A fuzzy logic is 
									provided for setting the value for the fixed preattenuation.
									Positive attenuation -> a resistive attenuation, a cable, a filter, etc.
									with a certain attenuation is put before the sensor input. The set
									value will be added to the measured value before it is shown on the
									LCD. 
									Negative attenuation is for special cases when gain blocks (amplifiers)
									are put between the signal source and the power sensor.
				02.06.2004		- Fuzzy logic improved to hold 1dB steps a while after it was switched on once					
									-> Version 1.3 released
				03.06.2004		- Letter 'A' shown in measurment screens whenever the chosen pre-attenuation
									is different from 0dB. 
								- Complete Pre-attenuation functionality tested, works.
									-> Version 1.4 released
				
																	
		Contact:				WWW.DL5NEG.DE 
									
------------------------------------------------------------------------------------------------------*/

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


/* --- Prototypes for functions --- */
void i2c_start(void);
void i2c_send(unsigned char sendbyte);
void init_lcd(void);
void update_lcd(void);
unsigned char asc2lcd(unsigned char asc_char);
void pause(unsigned char n);
unsigned int read_adc(unsigned char adc_number);
unsigned char hex2asc(unsigned char hexbyte);
void preset_videoram(unsigned char screen);
void smoothkey(void);
void int2lcd(int param);
void parameter2videoram(void);
void interpol(int adc);
unsigned char bit(unsigned char param);
unsigned int PRG_RDI(unsigned int *prgmem);
void char2lcd(unsigned char lcdchar);
int lcd_delay(int loops);


/* --- Define names for special registers and bits --- */

/* Number of ADC readings that are averaged before an value is returned, 
   interpretation is in 2^N, possible is 0-6 i.e. 1-64 cycles. The limitation
   to 2^6 comes from the fact that the ADC delivers 10bits, so 6 bits
   are left in an integer variable (2bytes=16bits).
   A good value is 4, 5 gives an even better averaging but the key-response 
   gets a bit slow since the averaging takes too much time. */
#define average 4

// 36 bytes for the lcd videoram (+1 for \n) as global variable
unsigned char videoram[64];

unsigned char mode=1;        		// mode determines the screen that is currently
				 			 		// used (rawadc, diodesensor, thermal sensor, etc.)
				 			 
unsigned char diode_adc_select=1;  	// number of ADC which is currently used for diode-sens reading (0/4)
unsigned char therm_adc_select=0;  	// number of ADC which is currently used for therm-sens reading (1/2)

#define ON			1				//   turned on
#define OFF			0				//   turned off
unsigned char auto_select=ON;		// ON if ADC is autorange, OFF = manual ADC sel. (only used for diode sensor)

int rel_value;				 		// memory for reference power for relative displaying
							 		// the value interpredation is in 1/100 dBm

int pre_attenuation=0;				// memory for the fixed preattenuation set by the user
							 		// the value interpredation is in 1/100 dBm
							 
int cur_abs_value;			 		// global memory for the latest shown absolute power		

int zero_adj;						// memory for storing the current PWM-value that
									// does the zero-adjusting for the thermal sensor
									
signed char zadj_status=OFF;		// stores if automatic zeroadjusting for thermal sensor is 
									//   turned off (OFF is already defined above)
#define UPWARDS 	1				//   working upwards
#define DOWNWARDS  -1				//   or working downwards

unsigned char overload;				// for storing if the overload-screen is on; set in interpol() to
									// indicate within thermal-sensor mode that the zero-indicator bar
									// must not be drawn in line 4 of the LCD

unsigned char fuzzy_logic_counter=0;// counts the number of consequtive key-pushings during pre-attenuation setting
unsigned int fuzzy_full_db=0;

#define FUZZY_TIME 50
											
//this data is kept in the flash, it is not copied to the data memory 
//this line is only for numbering the coloums on the LCD  012345678901234*012345678901234*012345678901234*012345678901234*
unsigned char __attribute__ ((progmem)) greeting[]=      "  Dual-Sensor    RF Powermeter  DSPM03  Vers 1.4(c) 2004  DL5NEG";
unsigned char __attribute__ ((progmem)) adc_screen[]=    "  ADC-rawvalue  for calibration AD0 AD1 AD2 AD4 --- --- --- --- ";
unsigned char __attribute__ ((progmem)) diode_screen[]=  "Diode-Sensor    abs: :xx.xx dBm rel: :xx.xx dB   xxxx  sensor - ";
unsigned char __attribute__ ((progmem)) thermo_screen[]= "Thermal-Sens.x  abs: :xx.xx dBm rel: :xx.xx dB                  ";
unsigned char __attribute__ ((progmem)) battery_screen[]="Battery Voltage     -.- Volts     Status: -oo-                  ";
unsigned char __attribute__ ((progmem)) overld_screen[]= "WARNING OVERLOAD  reduce input   power to avoid damage of sensor";
unsigned char __attribute__ ((progmem)) preatt_screen[]= "Pre-Attenuation set: :xx.xx dB  use Rel-Reset &  Zero-Adj to set";
  
/* Here start the lookup tables for the different sensors. Stated is the ADC-value for each dBm-value.
   The first hex-values represents the dBm value that is stated below the array in start_...
   Each following value is one dB more. Since the sensors have different dynamic ranges, the
   arrays have different lengths. Each string is terminated by the value FFFF hex.
   For the diodesensor the autorange-switching-limits for both tables (range1 and range2)
   are also stated. */ 
  
// look-up-table for the diode sensor adc1
// these adc values represent the dbm values 
// termination is marked by the value $FFFF
unsigned int __attribute__ ((progmem)) lut_ds1[]= { 4, 6, 7, 10, 12, 16, 19, 24, 29, 35, 42, 51, 60,
71, 83, 98, 114, 133, 155, 179, 208, 240, 276, 318, 365, 418, 480, 548, 639, 812, 0xFFFF};
#define start_ds1 -14
#define lut1_lowerlimit 35

// look-up-table for the diode sensor adc2
// these adc values represent the dbm values 
// termination is marked by the value $FFFF
unsigned int __attribute__ ((progmem)) lut_ds2[]= { 3, 4, 5, 6, 7, 9, 11, 14, 18, 22, 28, 35, 44, 55,
68, 85, 105, 129, 158, 194, 236, 286, 346, 415, 497, 592, 704, 833, 983, 0xFFFF};
#define start_ds2 -30
#define lut2_upperlimit 983

/* define the adc-value that is defined as zero for the thermo-head */
// (a value >0 is choosen to have margin to ADC=0 for sensor drifts)
// this value is valid for both thermal sensor lookup-tables
#define thermo_zero 0x10 

// look-up-table for the thermo sensor 1
// these adc values represent the dbm values 
// termination is marked by the value $FFFF
unsigned int __attribute__ ((progmem)) lut_th1[]= { 21, 22, 24, 26, 28, 31, 35, 40, 46, 54, 
64, 76, 92, 111, 136, 166, 204, 251, 310, 383, 472, 581, 713, 871, 0xFFFF };
#define start_th1 -3

// look-up-table for the thermo sensor 2
// these adc values represent the dbm values 
// termination is marked by the value $FFFF
unsigned int __attribute__ ((progmem)) lut_th2[]= { 22, 23, 25, 27, 30, 34, 39, 45, 52, 61, 
73, 88, 106, 130, 159, 196, 243, 302, 376, 469, 586, 734, 919, 0xFFFF };
#define start_th2 -10

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

int main(void)
{
  
	unsigned int adc_value;
	unsigned char keystatus,dash;
	int marker_pos;

	char z,e;
	int k;
			
	// all 8 pins of PORTD as output for the LCD data bits 
	outp(0xFF, DDRD); 
	
	// set C5 as output for LCD reg-sel.
	outp(0x20, DDRC);

	// set PB1 as output for the PWM zero adjust for the thermo sensor
	// and PB2 as output for LCD-Strobe
	outp(0x06, DDRB);
				
	/* set PB0,3,4,5 to high to activate the internal pull-ups */
	outp(0x39, PORTB);  
	
	// --- switch on the PWM mode for the thermal sensor zero adjust ---
	outp( 0x83, TCCR1A);  	// non inverting PWM with 10bit resolution
	outp( 0x01, TCCR1B);  	// counter clock = 1x system clock
	
	zero_adj = 0x0200;	 	// set 10 bit PWM value to 1/2 of full value
	outp( zero_adj>>8 & 0xFF, OCR1H);   
	outp( zero_adj    & 0xFF, OCR1L); 
	  			
	// initialize the 4x16 char display 			
	init_lcd();
			
	/* load the greeting message into the video ram */
	preset_videoram(1);
		
	/* transver data from video ram to lcd display*/
	update_lcd();
	
	/* wait 3 seconds to present the greeting message*/	
	pause(0xFF);
	pause(0xFF);
	pause(0xFF);
	
	mode=3; // set the start mode, 2=rawadc, 3=diode sensor
	preset_videoram(mode);
					
	while(1) // +++++++++++++++++++++++ main loop +++++++++++++++++++++++++++++++++++++++ 
	{
							
		/* transver data from video ram to lcd display*/
		update_lcd();

		if( zadj_status != OFF )	// if automatic zero-adjusting is currently working
		{
			adc_value = read_adc(therm_adc_select);
			
			if( (zadj_status == UPWARDS)   && ( adc_value >= thermo_zero) )
				zadj_status = OFF;	
			
			if( (zadj_status == DOWNWARDS) && ( adc_value <= thermo_zero) )
				zadj_status = OFF;	
			
			zero_adj += zadj_status;
			
			outp( zero_adj>>8 & 0xFF, OCR1H);  // set 10 bit PWM to new value 
			outp( zero_adj    & 0xFF, OCR1L); 
					
		}

		keystatus = PINB; 						// read the status of the input keys
		
		
		if( (keystatus&8) && (keystatus&32) )  	// reset off fuzzy logic key-counter if neither Rel-Reset
		{										// nor Zero-Adj was pressed
			fuzzy_logic_counter=0;				
			
			if(fuzzy_full_db>0) 				// count down the time that the full-db (fast) mode for
				fuzzy_full_db--;				// the pre-att. setting is switched on
		}	
			

		//-------------------------------------------------------------------------------
		if( !(keystatus & 1) ) 			//if key1 (mode select) is pressed
		{
			mode++;						//switch to next screen mode
		 		 			 		
			if( mode > 6 ) mode = 2; 	//after the last mode we start again with mode 2
									 	//(mode 1 is only the startup-screen shown at switch-on)
			
			smoothkey(); 				//update the LCD with all current datas
									 	//and wait some time to avoid key repetition
		}

		//-------------------------------------------------------------------------------
		if( !(keystatus & 16) ) //if key2 (ADC select) is pressed
		{
			// in mode 3 (diodesensor) key2 is used to switch the used
			// ADC (man ACD1, man ADC2, Autoselect)
		 	if( mode==3 ) 
			{
				 		
				if( auto_select == ON )
				{
					auto_select = OFF;
					diode_adc_select=1;	
				}
				 		
				else
				{
					if( diode_adc_select==1 )
						diode_adc_select++;
					else
					{
						diode_adc_select=1;	
						auto_select = ON;
					}
				} 		
												
			}//end if mode=3
				
			// in mode 5 (thermal sensor) key2 is used to switch the used
			// ADC between sensor 0 (coarse reading) and sensor 4 (fine reading)
		 	if( mode==5 ) 
			{
				 			
				if( therm_adc_select != 0 )
					therm_adc_select = 0;
				else
					therm_adc_select = 4;
																
			}//end if mode=5	
			smoothkey(); //update the LCD with all current datas
						 //and wait some time to avoid key repetition
				
		}

		//-------------------------------------------------------------------------------
		if( !(keystatus & 8) ) 						//if key3 (relative-reset) is pressed
		{
			
			if(mode==4) 							// if we are in set-preattenuation mode
			{
				if( pre_attenuation<6000 )			// allow max. 60dB of preattenuation
				{
				
					pre_attenuation +=1;			// increase the pre_att value
					
					fuzzy_logic_counter++;			// increase the number of consequtive increases
					if(fuzzy_logic_counter>=10)  	// if key is pushed long enough without release
					{
												
						fuzzy_logic_counter--;		// avoid overflow of fuzzy logic counter
						
						fuzzy_full_db = FUZZY_TIME;	// set marker to full-db steps
											
					}

					if( fuzzy_full_db > 0 )
					{
						while( pre_attenuation%100 != 0 )	// in fuzzy-mode add till next full 100th is reached
							pre_attenuation++;	 			// -> this gives full dB steps in the display
					
						fuzzy_full_db = FUZZY_TIME;	// keep fuzzy mode on
					
					}
					
					
					pause(30);						// slow down the increase for user convenience

				}

			}
			else
			{
				//store the current value as reference for future relative readings
				rel_value = cur_abs_value;
			
				smoothkey(); //update the LCD with all current datas
							 //and wait some time to avoid key repetition
			
			} 						
			
			
		}
		
		//-------------------------------------------------------------------------------
		if( !(keystatus & 32) ) 							// if key4 (zero adjust) is pressed
		{		
										
			if	(mode==2 || mode==5 )						// and mode is raw-adc or thermal sensor
			{
										
				if( zadj_status == OFF )
				{
					
					adc_value = read_adc(therm_adc_select);
							
					if( adc_value > thermo_zero )
						zadj_status	= DOWNWARDS;
					
					if( adc_value < thermo_zero )
						zadj_status = UPWARDS; 
					
				}	
				
			}
		
			if( mode==4 )									// if we are in set-preattenuation mode
			{
				if( pre_attenuation>-6000 )					// allow min. -60dB of preattenuation
				{
					
					pre_attenuation -=1;					// decrease the pre_att value
					
					fuzzy_logic_counter++;					// increase the number of consequtive increases
					if(fuzzy_logic_counter>=10)  			// if key is pushed long enough without release
					{
												
						fuzzy_logic_counter--;				// avoid overflow of fuzzy logic counter
						
						fuzzy_full_db = FUZZY_TIME;			// set marker to full-db steps
											
					}
					
					if( fuzzy_full_db > 0 )
					{
						while( pre_attenuation%100 != 0 )	// in fuzzy-mode sub till previous full 100th is reached
							pre_attenuation--;	 			// -> this gives full dB steps in the display
					
						fuzzy_full_db = FUZZY_TIME;			// keep fuzzy mode on
					
					}
					
					pause(30);
				}
				
			}
		
		}
		
									
		// ################################################################################
		//  here comes the real action - reading the adc and diplaying the
		//                      the results in dBm
		
		switch(mode)
		{
			case 2: // mode 2= raw adc values are displayed
			{
				
				// show status of zero-adjusting if switched on
				parameter2videoram();
										
				/* read ADC channel 0 (thermal sensor 1) */
				adc_value = read_adc(0);
				
				/* write the adc_value to the videoram */
				videoram[50] = hex2asc(  adc_value     & 0x0F );
				videoram[49] = hex2asc( (adc_value>>4) & 0x0F );
				videoram[48] = hex2asc( (adc_value>>8) & 0x0F );
				
				/* read ADC channel 1 (diode sensor 1) */
				adc_value = read_adc(1);
				
				/* write the adc_value to the videoram */
				videoram[54] = hex2asc(  adc_value     & 0x0F );
				videoram[53] = hex2asc( (adc_value>>4) & 0x0F );
				videoram[52] = hex2asc( (adc_value>>8) & 0x0F );
				
				/* read ADC channel 2 (diode sensor 2) */
				adc_value = read_adc(2);
				
				/* write the adc_value to the videoram */
				videoram[58] = hex2asc(  adc_value     & 0x0F );
				videoram[57] = hex2asc( (adc_value>>4) & 0x0F );
				videoram[56] = hex2asc( (adc_value>>8) & 0x0F );
				
				/* read ADC channel 4 (thermal sensor 2) */
				adc_value = read_adc(4);
				
				/* write the adc_value to the videoram */
				videoram[62] = hex2asc(  adc_value     & 0x0F );
				videoram[61] = hex2asc( (adc_value>>4) & 0x0F );
				videoram[60] = hex2asc( (adc_value>>8) & 0x0F );

			}break;
						
						
			case 3: // mode 3= diode sensor values are displayed in dBm
			{
				/* read ADC on selected channel */
				adc_value = read_adc(diode_adc_select);
					 
				if(auto_select) // automatically select the best sensor setting
				{
					 	 
					//if adc_value is to low switch to adc2 (if adc2 was already
					//used it simply stays select and nothing can be done anyhow)
					if(adc_value < lut1_lowerlimit)
					 	diode_adc_select=2;
					 
					//if adc_value is to high switch to adc1 (if adc1 was already
					//used it simply stays select and nothing can be done anyhow)
					if(adc_value > lut2_upperlimit)
					 	diode_adc_select=1;
			 
			 		/* now read ADC on automatically selected channel */
				   	adc_value = read_adc(diode_adc_select);
				 			 
				 }
				 						
				 /* interpolate to calculate the dBm value and write that to LCD */				
				 interpol(adc_value);
			
			}break;

			case 4: // mode 4= let the user set the pre-attenuation value in dB
			{

				int2lcd(0);		// call int2lcd to display the current value of the preattenuation
			
			}break;

		
			case 5: // mode 5= thermo sensor values are displayed in dBm
			{
				/* read selected ADC channel for thermo-sensor */
				adc_value = read_adc(therm_adc_select);
				
				/* interpolate to calculate the dBm value and write that to LCD */				
				interpol(adc_value);
				
				/* if function interpol indicates that there is an sensor overload
				   we must not draw the zeroindicator bar; just leave this block right now */
				if( overload == ON )
					break;
										
				/* refresh the zero adjust indicatior in the 4th line of the LCD*/
				/* to have some margin for drift towards lower values */
	
				if(adc_value == thermo_zero) 	//if the read adc-value is exactly
				{							  	//the defined zero-value				 			 
					videoram[55] = '<';  
					videoram[56] = '>';			//write '<>' to middle of 4th line
				}
				
				else
				{
				 			 	 
  			 		if(adc_value > thermo_zero) //if the value is higher than the
					{										//defined zero-value
						marker_pos=adc_value-thermo_zero+55; //calc positon of marker in line
						 	 
						if(marker_pos>63) marker_pos=63; //limit marker postion to 63
						 	
						videoram[marker_pos] = '>';	//put '>' on calcultate position in line 4
						 	 
						for( dash=55; dash<marker_pos; dash++ ) //draw line to marker
							videoram[dash]='-';
					}
					
					else												 //if it is smaller than the
					{													 //defined zero-value
						marker_pos=56+adc_value-thermo_zero; //calc postion of marker in line 4
						 	
						if(marker_pos<48) marker_pos=48; //limit marker postion to 48
						 	 
						videoram[marker_pos] = '<';	//put '<' on calcultate position in line 4
					      
					    for( dash=56; dash>marker_pos; dash-- ) //draw line to marker
							videoram[dash]='-';
					      
					}	 
				}		
														 			
			}break;
			
			case 6:  // mode 6= Battery voltage in Volts is displayed
			{
			
				/* read ADC channel 3 */
				adc_value = read_adc(3);
							
				if( adc_value > 640 ) 			// if Vbatt >= 8.0 Volts
				{
				 	videoram[45]='d';		      // write 'good'
			    	videoram[42]='g';					// the double 'oo' is always in the LCD
				}
				else
				{
				 	videoram[45]='r';		      // write 'poor'
			    	videoram[42]='p';					// the double 'oo' is always in the LCD
				}
	
				//battery voltage is divided by 4 by a resistive divider before
				//it enters ADC3; Vref=3.2Volts -> ADC3=$3FF means Vbatt=12.8 Volts
				// -> ADC3/8= Vbatt in 1/10 Volts		 
				adc_value >>= 3; // 3 times shift right to perform the /8
							
				k = 0;		 			 // find the tenth-number
			  
				for(z=0; k<=adc_value ; z++) //go in loop until the tenth-number
					k += 10;									 //is found
				
			  	z--;	 								
			  	adc_value = adc_value-k+10;	// substract the already found digits
				
				k = 0;
	
			  	e=(char)adc_value;					// one's number remains
			  
			  	//since voltage up to 12.8V are possible, we have to check if 
			  	//the tenth number is >9
			  	if( z>9 )
			  	{
			  		z -= 10; 									// substract 10 from the one's number
			  		videoram[19]='1';					// write 1 before the tenth number on the LCD
			  	}
			   	else
			  		videoram[19]=' ';					// else: clear the 1 before tenth number on LCD
			 	 
			  	videoram[20]=z+48;		      // put the result to the
			  	videoram[22]=e+48;					// videomemory (converted to ASCII with +48)
			
			}break;
			
		} // end of switch block
		
	} //+++++++++++++++++++++++++++++++ end of main loop ++++++++++++++++++++++++++++++++++
	
	return(0);	// to avoid a compiler warning
	
}

/*
;-------------------- subroutines from here on ------------------
;================================================================
*/



//------------------------------- read adc -------------------------------
// reads the selected adc (does some averaging to stabilize the value) 
unsigned int read_adc(unsigned char adc_number)
{
	unsigned char adlow, adhigh, counter;
	unsigned int adsum; 
	
	// status for ADC: enable adc, start 1st conversion, free run,
	// clock prescaled with factor 64 ->57kHz clock with fq=3.686MHz
	outp( 0xE6, ADCSR); // ADCSR = 0b11100110;
		
	// select ADC, i.e. switch the MUX to the selected ADC
	outp( adc_number, ADMUX); //ADMUX = adc_number;

	// reset the averaging sum
	adsum = 0;
	
	for(counter=0; counter<bit(average); counter++ )
	{

		// wait N times 4.4ms in subroutine pause (here N=1)
		// for AD-conversion to be performed
		pause(1);
	
		// read adc-value (low byte must be read first !!!
		adlow  = inp(ADCL);
		adhigh = inp(ADCH);

		// add the read value to the averaging sum	
		adsum += (adhigh<<8) + adlow;
		
	}
	
	// divide the averaging sum by the number of values
	adsum >>= average;
	
	return adsum;
	
}
		
//----------------------------- pause --------------------------------------
// waits for n times 4.4 ms
void pause(unsigned char n)
{
	// this function had to be rewritten to use the 8bit counter
	// instead of the 16bit counter since the 16bit counter is
	// now used for the PWM output for the zero adjust of the
	// thermal sensor

	unsigned char pcnt;

	// set 8bit counter back to zero
	outp( 0x00, TCNT0 );
	
	// switch the counter on with prescaler set to 64
	// -> 8bit overflow occurs every 4.4ms
	outp( 0x03, TCCR0 );

	// reset counting variable
	pcnt = 0;

	// count the numbers of overflows until parameter n is reached
	while( pcnt < n )
	{
		if( inp(TIFR) & bit(TOV0) )					// if overflow
		{
			pcnt++;									// increment counting variable
			outp( inp(TIFR) | bit(TOV0), TIFR );	// reset overflow flag	
		}
	}
	
}

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

//--------------------------- ascii to lcd ------------------------------
// converts a ascii character into the lcd-character
// works for A..Z, a..z, 0..9, space and spec. char /()!%=
// other characters must be handeled with if-conditions
unsigned char asc2lcd(unsigned char asc_char)
{
	
	if(asc_char == '$') /* LCD code for $ is 130 (=2+128) */
		asc_char = 2;
	
	asc_char += 128;		/* all normal characters have ASC+128 as LCD cod */
			
	return asc_char;
}		 
	
	
//--------------------------- preset videoram ---------------------------
// presets the videoram with the different mask
void preset_videoram(unsigned char screen)
{

	unsigned char counter;

	//copy all 64 characters from the selected string to the videoram
	for(counter=0; counter<64; counter++ )
	{
 				
	switch(screen)
	{
		
		case 1:
			videoram[counter]= PRG_RDB(&greeting[counter]);
			break;
				
		case 2:
			videoram[counter]= PRG_RDB(&adc_screen[counter]);
			break;
				
		case 3:
			videoram[counter]= PRG_RDB(&diode_screen[counter]);
			break;
			
		case 4:
			videoram[counter]= PRG_RDB(&preatt_screen[counter]);
			break;
					
		case 5:
			videoram[counter]= PRG_RDB(&thermo_screen[counter]);
			break;
			
		case 6:
			videoram[counter]= PRG_RDB(&battery_screen[counter]);
			break;
				
		case 7:
			videoram[counter]= PRG_RDB(&overld_screen[counter]);
			break;
		
		} //end switch(screen)
		
	}

}		 

// ---------------------------------------------------------------	

// typical action after key-pushings have been worked out
// fill the screen, refresh it and avoid contact jitter from the keys
void smoothkey(void)
{
	
	preset_videoram(mode);		//copy the permanent characters of the current
														//screen to the videoram

	update_lcd();							//copy the videoram to the physical LCD display

	pause(25);								//100ms short delay 
	
}


// ---------------------------------------------------------------	
	
// int2lcd, displays a integer number on the LCD 
// x is -9999..+9999, interpredation is -99.99dBm to +99.99dBm
// the separation to BCD reprensentation can be also be calcualated 
// mathematically but the methode used below is very memory efficient
// and still very fast (max. 40 for()-cycles)

// This function was written to display the measured power in dBm
// to the LCD. From Version 1.3 on it is also missused for showing
// the value of the fixed preattenuation in mode 4 (setting the
// preattenuation by the user).

void int2lcd(int param)
{
	char t,h,z,e,abs_rel,sign;
	int k,x;
		
	
	param += pre_attenuation;	// before we do anything else, we correct the fixed 
								// preattenuation that was set by the user
	
				
	cur_abs_value=param; //remember the recent value, if the user presses the relative reset
						 //key in the next cycle, this value will be used as reference

		
	// do the value-displaying twice, once for absolute, once for the relative value
	for( abs_rel=0 ; abs_rel<=1 ; abs_rel++ )
	{
					
		x=param;	// use the sent parameter as value for displaying
		
		if(mode==4) 
		{
			x=pre_attenuation; 	// here we missuse the function for displaying
								// the preattenuation in mode 4
		}
		else
		{			
			if( abs_rel )		// for relative displaying substract the reference value
				x -= rel_value;
		}
					
		//now we start to process the value	
										
		if( x<0 ) // determin the sign of the shown value
		{
			x = -x;	//build the positve value if negative
			sign='-';
		}
		else
			sign='+';
							 
		k = 0;						 		// find the thausand-number
		for(t=0; k<=x ; t++)
			k += 1000;
		
		t--;	 							
		x=x-k+1000;						// substract the already found digits
		
		k = 0;		 						// find the hundred-number
		for(h=0; k<=x ; h++)
			k += 100;
		
		h--;	 		 						
		x=x-k+100;						// substract the already found digits
		
		k = 0;		 						// find the tenth-number
		for(z=0; k<=x ; z++)
			k += 10;
					
		z--;	 								
		x=x-k+10;							// substract the already found digits
		k = 0;
		
		e=(char)x;						// one's number remains

		
	
		if((abs_rel==0) || (mode==4))	//display the number as absolute value or the 
		{								// of the preattenuation value at the same LCD position
			videoram[21]=sign;	
					 	 
			videoram[22]=t+48;		// conversion of all digits from a BCD-number
			videoram[23]=h+48;		// to a ASCII character and writing to 
			videoram[25]=z+48;		// videomemory directly
			videoram[26]=e+48;
		
			if(fuzzy_full_db>0)		// if fuzzy logic has switched to dB-steps
			{
				videoram[25]='_';	// overwrite the numbers right of the comma with
				videoram[26]='_';	// underscores to indicate that full-dB-steps are switched on
			}
		
		}
		else						// display the number as relative value
		{
			videoram[37]=sign;	
					 	 
			videoram[38]=t+48;		// conversion of all digits from a BCD-number
			videoram[39]=h+48;		// to a ASCII character and writing to 
			videoram[41]=z+48;		// videomemory directly
			videoram[42]=e+48;
		}

		

	} //end of for(abs_rel...)
		
}


// ---------------------------------------------------------------	


void interpol(int adc)
{

	int d,k,x;
	unsigned char counter=0;
	int lut_c0=0, lut_cm1=0;
	int startvalue=0;

	preset_videoram(mode);   // write screen new to overwrite old overload message

	parameter2videoram();	 // screen if totally overwritten, parameters must
							 // must be written again to the bottom line of LCD
							 
	overload = OFF;			 // reset old overload flag
		
	// depending on the currently selected look-up-table the dBm values
	// directly above and below the measured value are searched
		
	if(mode == 3) //if diode-sensor is selected -> use the diode sensor
	{					 		//look-up-tables values according to the selected adc
									
  		if(diode_adc_select==1)
  		{								 
			for( counter=0 ; PRG_RDI(&lut_ds1[counter]) <= adc ; counter++ );
			startvalue=start_ds1;
  			lut_c0  = PRG_RDI(&lut_ds1[counter]);
	  		lut_cm1 =	PRG_RDI(&lut_ds1[counter-1]);	
		}

		else
		{								 
			for( counter=0 ; PRG_RDI(&lut_ds2[counter]) <= adc ; counter++ );
			startvalue=start_ds2;
			lut_c0  = PRG_RDI(&lut_ds2[counter]);
			lut_cm1 =	PRG_RDI(&lut_ds2[counter-1]);	
		}

	}
		
						
	if(mode == 5) //if thermo-sensor is selected -> use the thermo-senor
    {					 		//look-up-table values
    
    	if(therm_adc_select==0)
    	{
			for( counter=0 ; PRG_RDI(&lut_th1[counter]) <= adc ; counter++ );
			startvalue=start_th1;
	  		lut_c0  = PRG_RDI(&lut_th1[counter]);
		  	lut_cm1 = PRG_RDI(&lut_th1[counter-1]);	
		}

    	else
    	{
			for( counter=0 ; PRG_RDI(&lut_th2[counter]) <= adc ; counter++ );
			startvalue=start_th2;
	  		lut_c0  = PRG_RDI(&lut_th2[counter]);
		  	lut_cm1 = PRG_RDI(&lut_th2[counter-1]);	
		}
	
	}
		
	// values are fetched out of the valid look-up-table, now the
	// processing of the data can begin independent of the mode adc etc.		
							
	if( counter == 0 )			// if adc_value is lower as lowest table value
	{
		videoram[20]='<';		// put '<' before numbers to inidicate low signal
		videoram[36]='<';
		int2lcd(100*startvalue); // write the lowest table value meaning to scr.
		return;
	}
			
	if( lut_c0 == 0xFFFF )// if adc_value is higher as highest table v.
	{
		preset_videoram(7); // write overload message to screen
		overload = ON;		// remember that the overload-screen is active
		return;				// leave this subroutine immediately
	}
			
	// calculate the difference between prev. and next known value
	d = lut_c0 - lut_cm1;
			
	// calculate the difference between adc-value and prev. known value
	k = adc - lut_cm1;
			
	// multiply with 100 since the result shall be in 1/100 of a dBm
	k = k *100;
						
	// do the interpolation and add the meaning of the prev. value
	x = k / d + (startvalue + counter-1)*100; 

	int2lcd(x);		 // output the value dezimal on the lcd display
					 // paramters unit is 1/100 of dBm
						
}


// ---------------------------------------------------------------	


// put the current parameters to the videoram
void parameter2videoram()
{
 		 
	if( mode==3 ) //if the diode sensor is selected 
	{
						
		if( auto_select ) // if auto-select is switched on write "auto"
		{
			videoram[49] = 'a';
			videoram[50] = 'u';
			videoram[51] = 't';
			videoram[52] = 'o';
		}
		else             // otherwise write "man "
		{
			videoram[49] = 'm';
			videoram[50] = 'a';
			videoram[51] = 'n';
			videoram[52] = ' ';
		}	 		
	
		// write the number of the current ADC to the videoram (in ASCII)					
		videoram[62] = diode_adc_select +48;
							
	}
	
	if( mode==5 ) // if thermal sensor is selected
	{
		// write the number of the current range to the videoram 			
		// (analog to the diode sensor 1 means coarse, 2 means fine)
		if( therm_adc_select==0 )
			videoram[13] = '1';
		else
			videoram[13] = '2';
	}

	videoram[15] = ' ';				// right upper corner in the display is the
									// place for status informations
									// -> reset it to ' ' first

	// if one of the dBm measurement screens is shown ( mode 3 or 5 ) 
	// indicate any preattenuation different form 0dB by an 'A' in the
	// right upper corner of the screen
	if( ( mode==3 || mode==5 ) && pre_attenuation!=0 )
		videoram[15] = 'A';

	// if one of the thermal-sensor-relevant screens is shown (raw-ADC or thermal sensor screen)
	// and automatic zeoring is currently turned on, write Z to upper right corner of the display
	if( ( mode==2 || mode==5 ) && zadj_status != OFF )		
		videoram[15] = 'Z';		
	
}


// ---------------------------------------------------------------	


// calculates the value of a bit (e.g. bit(5) -> 16, i.e. 2^x)
unsigned char bit( unsigned char param )
{

	return ( 1 << param );

}


// ---------------------------------------------------------------	

// reads an integer (2 bytes) from the flash (program memory)
// parameter is the address of the data, i.e. a call of
// this function is like: a=PRG_RDI(&table[i]); with table[]
// being an array of integers

unsigned int PRG_RDI( unsigned int *prgmem )
{
		
  return (PRG_RDB(((unsigned char*)prgmem)+1)<<8) + PRG_RDB(prgmem);
      
}



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

	unsigned char initcnt;

	pause(10); // wait 4x4.4ms (datasheet requires at least 15ms)
	
	outp( inp(PORTC) & ~0x30, PORTC ); // 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(48);	// value given in datasheet for init
		pause(1);	// wait 4.4ms
	}
		
	char2lcd(56); // datalines =8, one-line-mode =off, Font= 5x7
	pause(1);	// wait 4.4ms
		
	char2lcd(12); // display on, cursor off
	pause(1);		// wait 4.4ms	
		
	char2lcd(1); // display clear
	pause(4);		// wait 4x4.4ms
		
	char2lcd(2); // cursor home
	pause(4);		// wait 4x4.4ms
	
}

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

	unsigned char lcdpos;
	
	outp( inp(PORTC) & ~0x20, PORTC );	// reg-sel low -> instr.
	char2lcd(0x80);	//set cursor to line1 (coloum1)
	pause(4);	//cursor setting requires 15ms pause
	
	// set reg sel high to indicate data, not instruction
	outp( inp(PORTC) |  0x20, PORTC );	
		
	// write the 16 characters of line 1	
	for( lcdpos = 0 ; lcdpos<16 ; lcdpos++ )
		char2lcd( videoram[lcdpos] );
	
	// memory mapping of the LCD is line 3
	// follows line 1, therefore we are now in line 3
	
	// write the 16 characters of line 3		
	for( lcdpos = 32 ; lcdpos<48 ; lcdpos++ )
		char2lcd( videoram[lcdpos] );

	// to come to line 2 we have to use and instruction to
	// set the cursor to coloum 1 in line 2
	outp( inp(PORTC) & ~0x20, PORTC );	// reg-sel low -> instr.
	char2lcd(0xC0);	//set cursor to line2 (coloum1)
	pause(4);	//cursor setting requires 15ms pause
	
	// set the reg select back to low to indicate data to come
	outp( inp(PORTC) |  0x20, PORTC );	
	
	// write the 16 characters of line 2
	for( lcdpos = 16 ; lcdpos<32 ; lcdpos++ )
		char2lcd( videoram[lcdpos] );
	
	// memory mapping of the LCD is line 4
	// follows line 2, therefore we are now in line 4

	// write the 16 characters of line 4
	for( lcdpos = 48 ; lcdpos<64 ; lcdpos++ )
		char2lcd( videoram[lcdpos] );
	
}


// ----------------------- 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(unsigned char lcdchar)
{

	outp( lcdchar, PORTD);	// write the byte to Port D
	
	outp( inp(PORTB) |  0x04, PORTB );	// strobe to high
	outp( inp(PORTB) & ~0x04, PORTB ); 	// strobe to low
	
	//this 4ms pause makes the update to slow, the LCD
	//requires 37us according to datasheet
	lcd_delay(50);

}

// delay loops for microsecond-delays (for LCD etc.)
// one loop is roughly 1 microsecond with a 3.686MHz crystal
int lcd_delay(int loops)
{
	
	while(loops) loops--; //simply count down to zero
	
	return loops; 	//just to avoid that the compiler is ignoring the whole function
					//because it has no "real" effect

}
