/*****************************************************
Project : HV_ConverterR2b (Rewrited R2a)
Version : 1.0
Date    : 30.09.2017
Author  : Sergey Abovyan

Chip type               : ATmega48
AVR Core Clock frequency: 12.000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 128
*****************************************************/

#include <mega48.h>

// Standard Input/Output functions
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <math.h>
#include <delay.h>

typedef unsigned char u8;
typedef unsigned int  u16;
typedef unsigned long u32;

#define PWM_EN	PORTD.5

/***************************************************************************************/
eeprom u8 MyAddress = 1;

const u16 PWM_MAX = 2500;
const u16 PWM_MIN = 0;
const u16 HV_MAX = 5000;
const u16 HV_MIN = 900;

eeprom u16 HV = HV_MIN;

//default calibration values
eeprom u16 HV_PHYS_MAX = 2591;
eeprom u16 HV_PHYS_MIN = 861;
eeprom float VOLTAGE_TO_PWM = (float)(PWM_MAX - PWM_MIN) / (float)(2591 - 861);
eeprom float ADC_TO_VOLTAGE = 3.2827;

/***************************************************************************************/
// Declare your global variables here
#define _ALTERNATE_PUTCHAR_
void putch(char ch)
{
	PORTD.3 = 1;
  	UDR0 = ch;
  	while (!(UCSR0A & (1 << 6)));
  	UCSR0A |= (1 << 6);
  	PORTD.3 = 0;
}

void putchar(char c)
{
	if (c == '\r') {putch('\r'); putch('\n');}
	putch(c);
}

char LastChar;
u16 GetWORD()
{
  	u16 word = 0;
  	while(1)
	{
    	LastChar = getchar();
    	if (isdigit(LastChar))
		{
			#asm("wdr")
      		word *= 10;
      		word += LastChar - '0';
    	}
    	else break;
  	};

  	return word;
}

void init(void)
{
	// Input/Output Ports initialization
	// Port B initialization
	PORTB=0x00;
	DDRB=0x04;

	// Port C initialization
	PORTC=0x00;
	DDRC=0x00;

	// Port D initialization
	PORTD=0x20;
	DDRD=0x2A;

	// Timer/Counter 0 initialization
	// Clock source: System Clock
	// Clock value: Timer 0 Stopped
	// Mode: Normal top=0xFF
	// OC0A output: Disconnected
	// OC0B output: Disconnected
	TCCR0A=0x00;
	TCCR0B=0x00;
	TCNT0=0x00;
	OCR0A=0x00;
	OCR0B=0x00;

	// Timer/Counter 1 initialization
	// Clock source: System Clock
	// Clock value: 12000.000 kHz
	// Mode: Fast PWM top=OCR1A
	// OC1A output: Discon.
	// OC1B output: Discon.
	// Noise Canceler: Off
	// Input Capture on Falling Edge
	// Timer1 Overflow Interrupt: Off
	// Input Capture Interrupt: Off
	// Compare A Match Interrupt: Off
	// Compare B Match Interrupt: Off
	TCCR1A=0x33;    //Set OC1B on compare match, clear OC1B at TOP. WG11=1 WG10=1
	TCCR1B=0x19;    //No prescaling, WG13=1 WG12=1
	TCNT1H=0x00;
	TCNT1L=0x00;
	ICR1H=0x00;
	ICR1L=0x00;
	OCR1AH=(u8)(PWM_MAX >> 8);
	OCR1AL=(u8)PWM_MAX;
	OCR1BH=0;
	OCR1BL=0;
	//4.8kHz PWM 50%

	// Timer/Counter 2 initialization
	// Clock source: System Clock
	// Clock value: Timer2 Stopped
	// Mode: Normal top=0xFF
	// OC2A output: Disconnected
	// OC2B output: Disconnected
	ASSR=0x00;
	TCCR2A=0x00;
	TCCR2B=0x00;
	TCNT2=0x00;
	OCR2A=0x00;
	OCR2B=0x00;

	// External Interrupt(s) initialization
	// INT0: Off
	// INT1: Off
	// Interrupt on any change on pins PCINT0-7: Off
	// Interrupt on any change on pins PCINT8-14: Off
	// Interrupt on any change on pins PCINT16-23: Off
	EICRA=0x00;
	EIMSK=0x00;
	PCICR=0x00;

	// Timer/Counter 0 Interrupt(s) initialization
	TIMSK0=0x00;

	// Timer/Counter 1 Interrupt(s) initialization
	TIMSK1=0x00;

	// Timer/Counter 2 Interrupt(s) initialization
	TIMSK2=0x00;

	// USART initialization
	// Communication Parameters: 8 Data, 1 Stop, No Parity
	// USART Receiver: On
	// USART Transmitter: On
	// USART0 Mode: Asynchronous
	// USART Baud Rate: 9600
	UCSR0A=0x00;
	UCSR0B=0x18;
	UCSR0C=0x06;
	UBRR0H=0x00;
	UBRR0L=0x4D;

	// Analog Comparator initialization
	// Analog Comparator: Off
	// Analog Comparator Input Capture by Timer/Counter 1: Off
	ACSR=0x80;
	ADCSRB=0x00;
	DIDR1=0x00;

	// ADC initialization
	// Digital buffer disabled on ADC1
	DIDR0=0x02;
	// ADC Enabled, Auto Trigger disabled, ADC Interrupt enabled, Prescaler 128 - 97kHz
	ADCSRA=0x8F;
	// Internal 1.1V Voltage Reference with external capacitor at AREF pin
	// ADC1 selected at MUX
	ADMUX= 0xC1;

	// SPI initialization
	// SPI disabled
	SPCR=0x00;

	// TWI initialization
	// TWI disabled
	TWCR=0x00;

	// Watchdog Timer initialization
	// Watchdog Timer Prescaler: OSC/2k
	// Watchdog Timer interrupt: Off
	#pragma optsize-
	#asm("wdr")
	WDTCSR=0x38;
	WDTCSR=0x28;
	#ifdef _OPTIMIZE_SIZE_
	#pragma optsize+
	#endif
}

/***************************************************************************************/
volatile u32 adc_averaging_index = 0;
volatile u32 adc_sum = 0;
volatile u16 adc_averaged = 0;
volatile u8  adc_completed = 0;
volatile u8  calibration = 0;
//7211 Hz
#define AVERAGING 721 //0.1sec

/***************************************************************************************/
void SetOCR1B(u16 OCR1B)
{
	OCR1BH=(u8)(OCR1B >> 8);
	OCR1BL=(u8)(OCR1B);
}
/***************************************************************************************/
void SetHV(u16 V)
{
	u16 OCR1B = (u16)((float)(V - HV_PHYS_MIN) * VOLTAGE_TO_PWM);
    SetOCR1B(OCR1B);
	#asm("cli")
	adc_sum = 0;
	adc_averaging_index = 0;
	adc_averaged = 0;
	adc_completed = 0;
	#asm("sei")
}
/***************************************************************************************/
u16 PerformADC()
{
	#asm("cli")
	adc_sum = 0;
	adc_averaging_index = 0;
	adc_averaged = 0;
	adc_completed = 0;
	#asm("sei")
	while(!adc_completed);
	return adc_averaged;
}
/***************************************************************************************/
interrupt [ADC_INT] void adc_isr(void)
{
	u16 HV_measured;
	u16 OCR1B;
	u16 adc_data = ADCW;
	adc_sum += adc_data;

	if(++adc_averaging_index >= AVERAGING)
	{
		adc_averaged = adc_sum / AVERAGING;
		adc_sum = 0;
		adc_averaging_index = 0;
		adc_completed = 1;

		if(!calibration)
		{
			HV_measured = (u16)((float)adc_averaged * ADC_TO_VOLTAGE);
       		OCR1B = (((u16)OCR1BH) << 8) + OCR1BL;

			if(HV > HV_measured && OCR1B < PWM_MAX) OCR1B++;
			else if(HV < HV_measured && OCR1B > 0) OCR1B--;
			SetOCR1B(OCR1B);
			//printf("\rADC = %d  V = %d  OCR1B = %d", adc_averaged, HV_measured, OCR1B);
		}
	}
  	ADCSRA |= 0x40;
}
/***************************************************************************************/
void main(void)
{
	char cmd;
	u16 temp;
	u16 hv_min, hv_max, hv_med, adc;

   	init();
    delay_ms(2000);

	printf("\rHV_Converter: %d", MyAddress);
	printf("\rV_TO_PWM * 10^3 = %d", (u16)(VOLTAGE_TO_PWM * (float)1000));
	printf("\rADC_TO_V * 10^3 = %d\r", (u16)(ADC_TO_VOLTAGE * (float)1000));

	SetHV(HV);
	PWM_EN = 1;

    #asm("sei")
    ADCSRA |= 0x40;

	while (1)
	{
		#asm("wdr");

		if(UCSR0A & 0x80)
		{
			cmd = toupper(getchar());
			if(cmd != 'C' && cmd != 'A' && cmd != 'V' && cmd != 'H' && cmd != 'S' && cmd != 'G' && cmd != 'P')
			{
				printf("\rERR: Incorrect command\r");
				continue;
			}

			temp = GetWORD();
			if(temp != MyAddress && temp != 253)
			{
				printf("\rERR: Incorrect address\r");
				continue;
			}

			switch(cmd)
			{
				case 'C': //Calibrate
					calibration = 1;
					SetOCR1B(PWM_MIN);
					printf("Measure MIN voltage ");
					#asm("wdr")
					hv_min = GetWORD();
					if(!hv_min || (hv_min > 5000))
					{
					 	printf("\rERR: Incorrect voltage\r");
						calibration = 0;
						continue;
					}

					SetOCR1B(PWM_MAX);
					printf("Measure MAX voltage ");
					#asm("wdr")
					hv_max = GetWORD();
					if(!hv_max || (hv_max >= 5000))
					{
					 	printf("\rERR: Incorrect voltage\r");
						calibration = 0;
						continue;
					}

					SetOCR1B((PWM_MAX + PWM_MIN)/2);
					printf("Measure MEDIUM voltage ");
					#asm("wdr")
					hv_med = GetWORD();
					if(!hv_med || (hv_med < hv_min) || (hv_med > hv_max))
					{
					 	printf("\rERR: Incorrect voltage\r");
						calibration = 0;
						continue;
					}
					#asm("wdr")
                    adc = PerformADC();
					printf("ADC = %d", adc);

					HV_PHYS_MIN = hv_min;
					HV_PHYS_MAX = hv_max;
                    VOLTAGE_TO_PWM = (float)(PWM_MAX - PWM_MIN) / (float)(hv_max - hv_min);
					printf("\rV_TO_PWM * 10^3 = %d", (u16)(VOLTAGE_TO_PWM * (float)1000));

					ADC_TO_VOLTAGE = (float)hv_med / (float)adc;
                    printf("\rADC_TO_V * 10^3 = %d", (u16)(ADC_TO_VOLTAGE * (float)1000));

					calibration = 0;
					SetHV(HV);
					printf("\r");
					break;

				case 'A':
					if(LastChar == ' ')
					{
						temp = GetWORD();
						if(temp && temp < 253) MyAddress = temp;
					}

                    printf("ADDR = %d\r", MyAddress);
					break;

				case 'V':
           			temp = GetWORD();
                    if(temp > HV_MAX) temp = HV_MAX;
					if(temp < HV_MIN) temp = HV_MIN;
    				HV = temp;
					SetHV(temp);
					printf("HV=%d\r", HV);
					break;

				case 'H':
					printf("HV=%d\r", HV);
					break;

				case 'S':
					PWM_EN = 0;
					printf("HV OFF\r");
					break;

				case 'G':
					PWM_EN = 1;
					printf("HV ON\r");
					break;

				case 'P': //Set PWM
					calibration = 1;
					temp = GetWORD();
					SetOCR1B(temp);
					printf("OCR1B = %d\r", temp);
					break;

			}
		}
	}
}
