/*
 * MPPT_firmware.c
 *
 * Created: 3.4.2020 21:25:49
 * Author : Martin Košút
 */ 

#define F_CPU 8000000UL

#include <stdio.h>
//#include <stdlib.h>         // itoa() function
#include <stdbool.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "uart.h"
#include "twi.h"
#include "tc74.h"
#include "sm72442.h"

#define UART_BAUD_RATE			38400

#define REVERSE_CURRENT_DISABLE PB0
#define MASTER_RESET			PB1
#define BOOST_STARTUP			PB2
#define LED_RED					PB6
#define LED_GREEN				PB7
#define OUT_OV_SENSE			PD2
#define RESET_SENSE				PD3

#define MIN_INPUT_VOLTAGE				9.3
#define MAX_INPUT_VOLTAGE				17.5
#define MIN_BATTERY_VOLTAGE				13
#define BATTERY_START_CHARGING_VOLTAGE	16.5
#define MAX_BATTERY_VOLTAGE				16.8
#define MIN_OUTPUT_CURRENT				0.2
#define MAX_OUTPUT_CURRENT				6
#define TEMPERATURE_THRESHOLD			85
#define MAX_TEMPERATURE					100

void initialization(void);
uint8_t check_parameters(void);
uint8_t startup(void);

typedef enum
{
	INITIALIZATION_STATE = 1,
	STARTUP_STATE,
	PARAMETERS_CHECK_STATE,
	RESET_STATE,
	IDLE_STATE,
	
}state_t;

uint8_t uart_string[80];

uint16_t i = 0;

float V_in = 0, V_out = 0, I_out = 0;
int8_t Temp;
uint16_t ADC_value;
uint8_t status = 1;
uint8_t sample_count = 0;
uint8_t mcusr_copy, reset_cause;
bool ADC_done = false;
bool TIMER_done = false;
bool first_reset = false;


state_t current_state = INITIALIZATION_STATE;

int main(void)
{
	mcusr_copy = MCUSR;			//copy MCU status register
	MCUSR = 0;					//clear all reset flags
	cli();						//disable global interrupt
	wdt_disable();				//disable watchdog timer
	
	while (1)
	{
		switch(current_state)
		{
			case INITIALIZATION_STATE:
				initialization();
				current_state = STARTUP_STATE;
				break;
				
			case STARTUP_STATE:
				status = startup();
				if (status == 0)					
				{									//startup successful
					PORTB &= ~(1<<LED_RED);			//green LED
					PORTB |= (1<<LED_GREEN);
					OCR1A = 782;					//set timer to 25ms (parameter check every 25ms)
					current_state = IDLE_STATE;					
				}
				else
				{									//startup failed, not passed voltage or temperature check -> RESET
					OCR1A = 62500;					//set timer to 2s (try startup every 2s)
					first_reset = true;
					current_state = RESET_STATE;	
				}
				TCNT1 = 0;							//timer to zero value
				TIFR1 |= (1<<OCF1A);				//clear timer compare interrupt flag
				TIMSK1 |= (1<<OCIE1A);				//enable timer compare interrupt
				break;
				
			case PARAMETERS_CHECK_STATE:
				status = check_parameters();
				if (status == 0)					//if parameter check passed
				{
					sample_count++;
					if (sample_count == 40)			//send parameters via UART every 40th sample (1 second)
					{
						sprintf(uart_string, "V_in=%.3fV, I_in=%.3fA, V_out=%.3fV, I_out=%.3fA, Temp: %d*C\r\n", MPPT.V_in, MPPT.I_in, MPPT.V_out, MPPT.I_out, Temp);
						uart_puts(uart_string);
						_delay_ms(20);
						sample_count = 0;
					}
					current_state = IDLE_STATE;
				}
				else
				{									//parameter check failed -> RESET
					OCR1A = 62500;					//set timer to 2s (try startup every 2s)
					TCNT1 = 0;						//timer to zero value
					TIFR1 |= (1<<OCF1A);			//clear timer compare interrupt flag
					TIMSK1 |= (1<<OCIE1A);			//enable timer compare interrupt	
					first_reset = true;
					current_state = RESET_STATE;					
				}
				break;
				
			case RESET_STATE:
				PORTB |= (1<<MASTER_RESET);			//activate master reset
				PORTB &= ~(1<<LED_GREEN);			//red LED
				PORTB |= (1<<LED_RED);
				wdt_reset();						//reset watchdog timer
				if (first_reset == true)			//send RESET via UART
				{
					first_reset = false;
					uart_puts("->RESET\r\n");
					_delay_ms(10);
				}
				if (TIMER_done == true)				//if timer interrupt occurred -> try STARTUP
				{
					TIMER_done = false;
					TCNT1 = 0;						//timer to zero value
					TIFR1 |= (1<<OCF1A);			//clear timer compare interrupt flag
					current_state = STARTUP_STATE;
				}
				break;
				
			case IDLE_STATE:
				if (TIMER_done == true)				//if timer interrupt occurred -> PARAMETER CHECK
				{
					TIMER_done = false;
					TIFR1 |= (1<<OCF1A);			//clear interrupt flag
					TCNT1 = 0;						//timer to zero value
					current_state = PARAMETERS_CHECK_STATE;
				}
				wdt_reset();						//reset watchdog timer
				break;
				
		}
	}
}

void initialization(void)
{
	DDRB |= ((1<<REVERSE_CURRENT_DISABLE) | (1<<MASTER_RESET) | (1<<BOOST_STARTUP) | (1<<LED_GREEN) | (1<<LED_RED));	//output pins
	DDRD &= ~((1<<OUT_OV_SENSE) | (1<<RESET_SENSE));				//input pins
	
	PORTB &= ~((1<<BOOST_STARTUP) | (1<<LED_GREEN) | (1<<LED_RED));
	PORTB |= (1<<MASTER_RESET);										//activate master reset
	
	wdt_enable(WDTO_2S);											//watchdog enable, set watchdog timeout to 2s
	wdt_reset();													//reset watchdog timer
	
	ADMUX &= ~(1<<REFS1);											//set external analog reference voltage for ADC
	ADMUX |= (1<<REFS0);
	ADCSRA |= ((1<<ADEN) | (1<<ADPS2) | (1<<ADPS1));				//ADC enable, fcpu(8MHz) clock divider to 64 = 125kHz
	ADCSRA &= ~(1<<ADPS0);
	
	ACSR |= (1<<ACD);												//disable analog comparator(not used) 
	
	EICRA |= ((1<<ISC11) | (1<<ISC01));								//external interrupt on falling edge RESET and falling edge OUTPUT OVERVOLTAGE(OVP)
	EICRA &= ~((1<<ISC10) | (1<<ISC00));
	EIMSK |= ((1<<INT1) | (1<<INT0));								//enable external interrupt
	 
	TCCR1B |= (1<<CS12);											//prescaler 256 - increment take 32us (8MHz fcpu)
	TCCR1B &= ~((1<<CS11) | (1<<CS10));
	 
	uart_init(UART_BAUD_SELECT(UART_BAUD_RATE, F_CPU));				//uart initialization
	twi_init();														//i2c initialization
		
																	//Determine internal reset cause
	if (mcusr_copy & 1)												//Power-on Reset
	{
		reset_cause = 1;
	}
	else if (mcusr_copy & 2)										//External Reset
	{
		reset_cause = 2;
	}
	else if (mcusr_copy & 4)										//Brown-Out Reset
	{
		reset_cause = 3;
	}
	else if (mcusr_copy & 8)										//Watchdog Reset
	{
		reset_cause = 4;
	}
	else
	{
		reset_cause = 9;											//undetermined (code runaway)
	}
	
	sei();															//global interrupt enable
	
	if (reset_cause == 1)											//send reason of MCU internal RESET via uart
	{
		uart_puts("\r\nPowerON internal RESET\r\n");
		_delay_ms(10);
	}
	else if (reset_cause == 2)
	{
		uart_puts("\r\nExternal internal RESET\r\n");
		_delay_ms(10);
	}
	else if (reset_cause == 3)
	{
		uart_puts("\r\nBrown-out internal RESET\r\n");
		_delay_ms(10);
	}
	else if (reset_cause == 4)
	{
		uart_puts("\r\nWatchdog internal RESET\r\n");
		_delay_ms(10);
	}
	else if (reset_cause == 9)
	{
		uart_puts("\r\nCode runaway internal RESET\r\n");
		_delay_ms(10);
	}
	
	PORTB |= (1<<LED_RED);											//blink red(500ms)
	_delay_ms(500);
	PORTB &= ~(1<<LED_RED);
	PORTB |= (1<<LED_GREEN);										//blink green(500ms)
	_delay_ms(500);
	PORTB &= ~(1<<LED_GREEN);
	 
	uart_puts("\r\nMPPT solar charger\r\nHW & FW Designed by Martin Kosut\r\n");
	_delay_ms(30);
}

uint8_t check_parameters(void)						//check if operating parameters are within range (V_in, V_out, I_in, I_out, Temp)
{
	Temp = read_temperature(TEMP_SENZOR_address);	//read temperature from temp sensor
	_delay_us(50);	
	status = MPPT_read_register(MPPT_reg1);			//read voltages and currents from MPPT
	_delay_ms(1);
	
	if (status == 0)								//if check not pass -> return 1 from function and send via UART if any parameter is out of recommended value
	{
		if (MPPT.V_in > MAX_INPUT_VOLTAGE)			//check V_in OVERVOLTAGE
		{
			uart_puts("\r\nV_in: OVERVOLTAGE->RESET\r\n");
			_delay_ms(10);
			return 1;
		}
		if (MPPT.V_in < MIN_INPUT_VOLTAGE)			//check V_in UNDERVOLTAGE
		{
			uart_puts("\r\nV_in: UNDERVOLTAGE->RESET\r\n");
			_delay_ms(10);
			return 1;
		}
		if (MPPT.V_out > MAX_BATTERY_VOLTAGE)		//check V_out OVERVOLTAGE
		{
			uart_puts("\r\nV_out: OVERVOLTAGE->RESET\r\n");
			_delay_ms(10);
			return 1;
		}
		if (MPPT.V_out < MIN_BATTERY_VOLTAGE)		//check V_out UNDERVOLTAGE
		{
			uart_puts("\r\nV_out: UNDERVOLTAGE->RESET\r\n");
			_delay_ms(10);
			return 1;
		}
		if (MPPT.I_out > MAX_OUTPUT_CURRENT)		//check I_out OVERCURRENT
		{
			uart_puts("\r\nI_out: OVERCURRENT->RESET\r\n");
			_delay_ms(10);
			return 1;
		}
		if (MPPT.I_out < MIN_OUTPUT_CURRENT)		//check I_out UNDERCURRENT
		{
			uart_puts("\r\nI_out: UNDERCURRENT->RESET\r\n");
			_delay_ms(10);
			return 1;
		}							
		if (Temp > MAX_TEMPERATURE)					//check H-bridge OVERTEMPERATURE
		{
			uart_puts("\r\nTEMP: OVERTEMP->RESET\r\n");
			_delay_ms(10);
			return 1;
		}
		if (Temp == -100)							//if Temp equals to initialization value -> sensor FAILED to send any data
		{
			uart_puts("\r\nTEMP: TEMP SENSOR FAILED !\r\n");
			_delay_ms(10);
			return 1;
		}
		
	}
	return 0;		//return 0 if all checks passed
}

uint8_t startup(void)
{
	uint8_t count = 0;

	ADC_done = false;
	ADMUX &= ~((1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0));		//ADC0 (input voltage)
	ADCSRA |= ((1<<ADIE) | (1<<ADIF) | (1<<ADSC));					//start ADC conversion, enable ADC interrupt and clear interrupt flag
	while(ADC_done == false)										//wait until ADC conversion
	{
		_delay_us(1);
	}
	_delay_ms(1);			//safety delay (without delay it cannot read ADC register (I don't know why))
	V_in = ADC_value * ((float)V_REF / 1023);
	V_in = (V_in * ((float)R_IN2 + (float)R_IN1)) / (float)R_IN2;
	ADC_done = false;

	ADMUX &= ~(1<<MUX3);											//ADC7 (output voltage)
	ADMUX |= ((1<<MUX2) | (1<<MUX1) | (1<<MUX0));
	ADCSRA |= ((1<<ADIF) | (1<<ADSC));								//start ADC conversion and clear interrupt flag
	while(ADC_done == false)										//wait until ADC conversion
	{
		_delay_us(1);
	}
	_delay_ms(1);													//safety delay
	V_out = ADC_value * ((float)V_REF / 1023);
	V_out = (V_out * ((float)R_OUT2 + (float)R_OUT1)) / (float)R_OUT2;
	ADC_done = false;

	Temp = read_temperature(TEMP_SENZOR_address);					//read temperature from TC74 sensor
	_delay_ms(1);

	if (V_in <= MAX_INPUT_VOLTAGE && V_in >= MIN_INPUT_VOLTAGE)		//check input voltage
	{
		sprintf(uart_string, "\r\nV_in: OK, %.3fV\r\n", V_in);
		uart_puts(uart_string);
		_delay_ms(15);
	}
	else
	{
		if (V_in > MAX_INPUT_VOLTAGE)
		{
			sprintf(uart_string, "\r\nV_in: OVERVOLTAGE, %.3fV\r\n", V_in);
		}
		if (V_in < MIN_INPUT_VOLTAGE)
		{
			sprintf(uart_string, "\r\nV_in: UNDERVOLTAGE, %.3fV\r\n", V_in);
		}
		uart_puts(uart_string);
		_delay_ms(15);
		return 1;													//return from startup if input OVER/UNDER voltage
	}

	if (V_out <= MAX_BATTERY_VOLTAGE && V_out >= MIN_BATTERY_VOLTAGE)	//check battery voltage
	{
		if (V_out >= BATTERY_START_CHARGING_VOLTAGE)				//if battery voltage is over 16.5V -> not start
		{
			sprintf(uart_string, "V_out: over 16.5V, not start, %.3fV\r\n", V_out);
			uart_puts(uart_string);
			_delay_ms(15);
			return 1;												//return from startup if connected battery is OVER threshold voltage
		}
		else
		{
			sprintf(uart_string, "V_out: OK, %.3fV\r\n", V_out);
			uart_puts(uart_string);
			_delay_ms(15);
		}
	}
	else
	{
		if (V_out > MAX_BATTERY_VOLTAGE)
		{
			sprintf(uart_string, "V_out: OVERVOLTAGE, %.3fV\r\n", V_out);
		}
		if (V_out < MIN_BATTERY_VOLTAGE)
		{
			sprintf(uart_string, "V_out: UNDERVOLTAGE, %.3fV\r\n", V_out);
		}
		uart_puts(uart_string);
		_delay_ms(15);
		return 1;													//return from startup if connected battery OVER/UNDER voltage
	}
	
	if (Temp == -100)												//if Temp equals to initialization value -> sensor FAILED to send any data
	{
		uart_puts("TEMP: TEMP SENSOR FAILED !\r\n");
		_delay_ms(10);
		return 1;
	}
	if (Temp < TEMPERATURE_THRESHOLD)								//check temperature
	{
		sprintf(uart_string, "TEMP: OK, %d*C\r\n", Temp);
		uart_puts(uart_string);
		_delay_ms(10);
	}
	else
	{																
		if (Temp >= TEMPERATURE_THRESHOLD)							
		{
			sprintf(uart_string, "TEMP: OVER THRESHOLD, %d*C\r\n", Temp);
		}
		if (Temp >= MAX_TEMPERATURE)
		{
			sprintf(uart_string, "TEMP: OVERTEMP, %d*C\r\n", Temp);
		}
		uart_puts(uart_string);
		_delay_ms(10);
		return 1;													//return from startup if power stage is over temp threshold or over maximum temperature
	}
	
	ADMUX &= ~((1<<MUX3)  | (1<<MUX0));								//ADC6 (output current)
	ADMUX |= ((1<<MUX2) | (1<<MUX1));
	
	PORTB &= ~(1<<MASTER_RESET);									//release master reset
	while((PIND & (1<<RESET_SENSE)) == 0)							//wait until RESET is released by reset IC (~200ms)
	{
		_delay_us(1);
	} 
	_delay_ms(1);													//safety delay
	
	PORTB |= (1<<BOOST_STARTUP);									//enable startup circuit
	
	int i = 0;
	do																//sampling I_out with ~1ms period until 4 samples in row is more than output current threshold
	{
		ADCSRA |= ((1<<ADIF) | (1<<ADSC));							//start ADC conversion and clear interrupt flag
		while(ADC_done == false)									//wait until ADC measure voltage
		{
			_delay_us(1); 
		}
		I_out = ADC_value * ((float)V_REF / 1023);					//calculate value measured on pin from measured digital 10bit value
		I_out = (I_out * (float)R_SIB) / ((float)R_IOUT * (float)R_SH); //calculate I_out value from measured voltage
		if (I_out > MIN_OUTPUT_CURRENT)								//count I_out samples with higher current then minimum output current
		{
			count++;
		}
		else
		{
			count = 0;
		}
		ADC_done = false;
		
		if (i == 1000)		//if BOOST startup is activated more than 1s -> break the cycle of measuring and stop STARTUP
		{
			break;
		}
		i++;
		
		_delay_us(810);
	} while (count < 4);
	
	PORTB &= ~(1<<BOOST_STARTUP);									//disable startup circuit
	uart_puts("STARTUP: OK\r\n\r\n");
	_delay_ms(10);

	return 0;
}


ISR(INT0_vect)			//falling edge of OVP signal from driver (output overvoltage)
{
	OCR1A = 62500;						//set timer to 2s (try startup every 2s)
	TCNT1 = 0;							//timer to zero value
	TIFR1 |= (1<<OCF1A);				//clear timer compare interrupt flag
	first_reset = true;
	current_state = RESET_STATE;
}

ISR(INT1_vect)			//falling edge of RESET or PGOOD from H-bridge driver
{
	OCR1A = 62500;						//set timer to 2s (try startup every 2s)
	TCNT1 = 0;							//timer to zero value
	TIFR1 |= (1<<OCF1A);				//clear timer compare interrupt flag
	first_reset = true;
	current_state = RESET_STATE;
}

ISR(TIMER1_COMPA_vect)
{
	TIMER_done = true;
}

ISR(ADC_vect)
{
	ADC_value = ADC;
	ADC_done = true;
}
