/*
    Title:    AVR-GCC program for transmiter in EE281 project
    Author:   Ioan Tudosa
    Date:     12/2001
    Purpose:  Transmits data through serial port, reads depth potentiometer and adjust the parameters of  
    			PID controller by reading 3 potentiometer and converting them , 
    needed
    Software: AVR-GCC to compile
    needed
    Hardware: AT90S8515 on STK500 board
    Note:     To contact me, mail to
                  itudosa@stanford.edu
              You might find more AVR related stuff at:
                  http://www.
*/

#include <io.h>
#include <stdlib.h>
#include <interrupt.h>
#include <sig-avr.h>
#include <string-avr.h>

typedef unsigned char  u08;
typedef          char  s08;
typedef unsigned short u16;
typedef          short s16;


u08 i,j,tmp,tmp1;											// some counters,temp variables
static volatile u08 buttons,oldbuttons,mode,bstate,changing;				// variable that stores the state of push buttons
static volatile u08 ADvalue_h,ADvalue_l;									// variables used by AD interrupt routine
static volatile u08 counter,*data,data_size; 								// variables used by UART interrupt routine
char data_string[26];				// UART data string transmitted ; 26 is just a set maximum number, to be adjusted if more data is transmitted
u08 string_size;

u08 depth_h,depth_l,P_h,P_l,I_h,I_l,D_h,D_l;					// value of the set depth; values of PID parameters
s08 PRange=0,IRange=0,DRange=0;			// Range of the Parameters P,I,D ; for example if PRange is 3 param P will be multiplied by 10^(-3)

void read_AD(u08* value_h,u08* value_l);

void delay(u08 delay)				// short delay function	
{
	u08 i,j;
	
		for (i=0; i<delay; i++) 
  		{   					// delay loop  
    		j++;
  		}
}


void long_delay(u08 del)		// long delay function
{
	u08 i;
  
  		for (i=0; i<del; i++) 
  		{   					// delay loop  
   			delay(255);
  		}
}



void read_buttons(void)		// buttons is return global variable 
{
	u08 tmp,tmp1;
	
		tmp=inp(PINC);
    	long_delay(25);						// delay and check the state again to get rid of the oscllations at the beginning of contact
    	tmp1=inp(PINC);
    	if	(tmp == tmp1) buttons=tmp;			//read debounced buttons at PortD 
    		
    	if (buttons!=oldbuttons) 
    	{
    		changing=buttons^oldbuttons;		// see which buttons are pressed
    		oldbuttons=buttons;
    		bstate=changing|~bstate;			// count by 2 to have only one change per pressing of a button;
    											// otherwise we would have a change for press and depress
    		
    		if (bstate == (u08)1) 			// mode button loop; button is at PINC 0
    		{
    			mode++;
    			if (mode==(u08)7) mode=0;	// 7 modes used
    		};
    		
    		if (bstate == (u08)2)			// increment button is PINC 1
    		{
    			switch (mode)
    			{
    				case 2:
    					PRange++;
    					break;
    				case 4:
    					IRange++;
    					break;
    				case 6:
    					DRange++;
    					break;
    				default:
    					break;	
    			}
    		};
			if (bstate == (u08)4)			// decrement button is PINC 2
    		{
    			switch (mode)
    			{
    				case 2:
    					PRange--;
    					break;
    				case 4:
    					IRange--;
    					break;
    				case 6:
    					DRange--;
    					break;
    				default:
    					break;
    			}
    		}
    		
    	};
    		
    	switch (mode)						// mode 0,1,3,5 are for reading values from potentiometers
    	{
    		case 0:
    			outp((1<<REFS0)|(0<<MUX1)|(0<<MUX0)|(0<<ADLAR),ADMUX);	// voltage ref is AVCC, channel 0, right adjusted
    			read_AD(&depth_h,&depth_l);
    			break;
    		case 1:
    			outp((1<<REFS0)|(0<<MUX1)|(1<<MUX0)|(0<<ADLAR),ADMUX);	// voltage ref is AVCC, channel 1, right adjusted
    			read_AD(&P_h,&P_l);
    			break;
    		case 3:
    			outp((1<<REFS0)|(1<<MUX1)|(0<<MUX0)|(0<<ADLAR),ADMUX);	// voltage ref is AVCC, channel 2, right adjusted
    			read_AD(&I_h,&I_l);
    			break;
    		case 5:
    			outp((1<<REFS0)|(1<<MUX1)|(1<<MUX0)|(0<<ADLAR),ADMUX);	// voltage ref is AVCC, channel 3, right adjusted
    			read_AD(&D_h,&D_l);
    			break;		
    	};
    	
    		
}



SIGNAL(SIG_ADC)				// interrupt routine reads A/D values when MCU wakes up at conversion complete interrupt 
{
		cli();					// disable interrupts 
		outp(0x00,MCUCR);		//disable sleep mode 
		ADvalue_l=inp(ADCL);		//read low byte of the 10 bit value of ADC register
    	ADvalue_h=inp(ADCH);	// read converted pressure value  		
		
}

void sample(void)	// ADvalue_h and ADvalue_l are return global variables written by the interrupt routine 
{
		outp((1<<ADEN)|(0<<ADSC)|(0<<ADFR)|(1<<ADIE)|(1<<ADPS0)|(1<<ADPS1),ADCSR);	//enable ADC single conversion, enable interrupt 
		sei();													//enable interrupts 
		outp((1<<SE)|(0<<SM1)|(1<<SM0),MCUCR);					//enable sleep mode ADC noise reduction 
		asm volatile("sleep"::);	// Start A/D, in noise reduction mode conversion, for pressure sensor 
		    						// the ADC interrupt will wake up the MCU
}

void read_AD(u08* value_h,u08* value_l)		// do an average over 64 samples to get rid of the noise 
{
	u08 i;
	u16 sum_h,sum_l;
	
		sum_h=0;sum_l=0;
    	for(i=0;i<64;i++)
    	{
    		sample();
    		sum_h+=ADvalue_h;
    		sum_l+=ADvalue_l;
    	};
    	ADvalue_h=((u08)(sum_h/64))&((u08)0x03);		// do average; high byte has only 2 bits
    	ADvalue_l=(u08)(sum_l/64);
    	*value_h=ADvalue_h;								
    	*value_l=ADvalue_l;
}


SIGNAL(SIG_UART_TRANS)
{
	if (counter<data_size) outp(data[counter++],UDR);	// transmit only after the previous byte was delivered
}

void transmit_uart(u08* data_string, u08 size)
{
		sbi(UCSRB,TXCIE);					// enable TXC interrupt and global I flag
		sei();
		counter=1;							// reset counter for number of data bytes
		data=data_string;					// data, variable used by interrupt routine is initialized with user's data string
		data_size=size;
		outp(data[0],UDR);					// transmits first byte; the UART transmission complete interrupt will take care of the rest of the bytes
		while(counter<data_size){long_delay(25);};			// wait to transmit the string, I don't want the uart interrupt to disturb some other processes
		cli();
		cbi(UCSRB,TXCIE);					// disable TXC interrupt and global I flag
}


void makestring(u08* data_string, u08* size)
{
	*size=6+11;
	data_string[0]='A';
	data_string[1]='A';
	data_string[2]='A';
	
	data_string[3]=depth_h;
	data_string[4]=depth_l;
	data_string[5]=P_h;
	data_string[6]=P_l;
	data_string[7]=PRange;
	data_string[8]=I_h;
	data_string[9]=I_l;
	data_string[10]=IRange;
	data_string[11]=D_h;
	data_string[12]=D_l;
	data_string[13]=DRange;
	
	data_string[14]='Z';
	data_string[15]='Z';
	data_string[16]='Z';
}


void LcdCommandWrite(u08 command)
{
	cbi(PORTD,4);				// set R/S high to access instruction register
	cbi(PORTD,3);				// set R/W low to write
	outp(command,PORTB);				// write data to port B
	sbi(PORTD,2);				// set Enale high to transfer data
	if (command >= 32) long_delay(50); else long_delay(10);	// wait long enough for commands that have the address over 20, usually the initialization ones
	cbi(PORTD,2);				// set Enable low to finish the cycle
}

void LcdDataWrite(u08 data)
{
	sbi(PORTD,4);			// set R/S high to access data register
	cbi(PORTD,3);			// set R/W low to write
	outp(data,PORTB);			// write data to port B
	sbi(PORTD,2);			// set Enale high to transfer data 
	long_delay(10);			// wait long enough to have the data transfered, experiment with the value!
	cbi(PORTD,2);			// set Enable low to finish the cycle
}

void LcdInit(void)
{
	long_delay(255);		// wait more than 15ms after power-on
	LcdCommandWrite(0x38);	// function set			8bit interface, 2 display lines, 5x7 font
    LcdCommandWrite(0x06);	// entry mode set		increment automatically, no display shift
    LcdCommandWrite(0x0C);	// display control		turn display on, cursor off, no blinking
    LcdCommandWrite(0x01);	// clear display			and set cursor position to zero 

}

void LcdWriteString(u08* string)
{
	while(*string!='\0') LcdDataWrite(*string++);
}

void LcdWriteU16(u08 value_h, u08 value_l)
{
u16 tmp;
u08 tmp_string[5]="     ";
	tmp=value_h;
	tmp<<=8;
	tmp+=value_l;
	itoa(tmp,tmp_string,10);
	LcdWriteString(tmp_string);
}

void LcdWriteS08(s08 value)
{
s08 tmp;
u08 tmp_string[5]="     ";
	tmp=value;
	itoa(tmp,tmp_string,10);
	LcdWriteString(tmp_string);
}

void LcdDisplay(void)
{	

	//LcdCommandWrite(0x01);					// clear display
	long_delay(255);
	switch (mode)
	{
		case 0:
			LcdCommandWrite(0x80);
			LcdWriteString("depth = ");
			LcdWriteU16(depth_h,depth_l);
			LcdWriteString("       ");
			
			LcdCommandWrite(0xC0);			// go to line 2 of display
			LcdWriteString("depth = ");
			LcdWriteU16(depth_h,depth_l);
			LcdWriteString("       ");
			break;
		case 1:
			LcdCommandWrite(0x80);
			LcdWriteString("P     = ");
			LcdWriteU16(P_h,P_l);
			LcdWriteString("       ");
			break;
		case 2:
			LcdCommandWrite(0x80);
			LcdWriteString("PRange= ");
			LcdWriteS08(PRange);
			LcdWriteString("  10^x");
			break;	
		case 3:
			LcdCommandWrite(0x80);
			LcdWriteString("I     = ");
			LcdWriteU16(I_h,I_l);
			LcdWriteString("       ");
			break;
		case 4:
			LcdCommandWrite(0x80);
			LcdWriteString("IRange= ");
			LcdWriteS08(IRange);
			LcdWriteString("  10^x");
			break;		
		case 5:
			LcdCommandWrite(0x80);
			LcdWriteString("D     = ");
			LcdWriteU16(D_h,D_l);
			LcdWriteString("       ");
			break;	
		case 6:
			LcdCommandWrite(0x80);
			LcdWriteString("DRange= ");
			LcdWriteS08(DRange);
			LcdWriteString("  10^x");
			break;			
		default:
			
			break;
	};
				
}

int main(void)
{
	outp(0xFF,DDRB);        // use all pins on PortB for output LCD  
	outp(0xFD,DDRD);		// use pins on PortD for control LCD; pins 0 and 1 used for serial communication
	outp(0x00,PORTB);
	outp(0x00,DDRC);        // use all pins on PortC for input pushbuttons
	outp(0xFF,PORTC);		// enable pull-up resistors
	outp(207,UBRR);						// set baud to 2400 for 8MHz
	outp(1<<TXEN,UCSRB);				// enable transmitter uart
	
	outp((1<<ADEN)|(1<<ADSC)|(1<<ADPS0)|(1<<ADPS1),ADCSR);			// start first conversion and discard it; scale ADC clock by 64  
	     															// don't enable interrupt for A/D, use it in the routine sample
    LcdInit();		// initialize LCD display

    for(;;)
    {
    	read_buttons();
		makestring(data_string,&string_size);
		transmit_uart(data_string,string_size);
		LcdDisplay();
	};
		return 0;
}

