	/*******************************************************
Project					: LiFePO4 Balancer
Chip type               : ATtiny412
Program type            : Application
AVR Core Clock frequency: 10,000000 MHz
File					: main.c
*******************************************************/

/*_____ I N C L U D E S ____________________________________________________*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>

#include "main.h"
#include "usart.h"

/*______D E C L A R A T I O N ______________________________________________*/

uint32_t		adc_val;

uint16_t		voltage[VOLTAGE_ENUM_COUNT];
uint8_t			voltage_sample_ttl;
int16_t			temperature;

t_Vrefs			VrefType;

// Constants
const uint8_t	temp_mul = 100;
uint16_t		temp_gain;
int16_t			temp_offset;

// Counters

t_tasks currentTask = TASK_ADC_TRIG_VOL;

/*----- Interrupts ---------------------------------------------------------*/

ISR( ADC0_RESRDY_vect ) 
{
	adc_val = ADC_RESULT;
	
	switch ( VrefType )
	{
		case VREF_2V50:	currentTask = TASK_BAL_CTRL_ADC_TRIG_TEMP;	break;
		case VREF_1V10: currentTask = TASK_MEAS_TEMP;				break;
		default:													break;
	}
}

ISR( TCA0_OVF_vect ) 
{
	CLK_TIM_OVF_CLR();
	currentTask = TASK_ADC_TRIG_VOL;
}

/*----- Functions ----------------------------------------------------------*/

void initCoefs()
{
	temp_gain	= SIGROW.TEMPSENSE0 * temp_mul;
	temp_offset = (int8_t)SIGROW.TEMPSENSE1;
	temp_offset *= 8;
}

void vrefSel( t_Vrefs vref )
{
	switch( vref )
	{
		case VREF_2V50: VREF_2V5_INIT();	break;
		case VREF_1V10: VREF_1V1_INIT();	break;
		default:							break;
	}
	
	VrefType = vref;
}

void set_pwm( uint8_t val ) 
{
	PWM_B_SET(val);
	PWM_A_SET(val);
	PWM_SYNC();
}


void balance( void )
{
	static uint8_t pwmVal;
	static uint8_t counter;
	
	if(++counter >= BALANCE_N_CYCLES)
	counter = 0;
	else
	return;
	
	// Decide what to do
	if((voltage[VOLTAGE_ACTUAL] > voltage[VOLTAGE_BALANCE]) && (pwmVal != 0xFF))
	{
		if(pwmVal == 0) PWM_TIM_ON();
		if(pwmVal < 0xFF) set_pwm(++pwmVal);
	}
	
	else if ( (voltage[VOLTAGE_ACTUAL] < voltage[VOLTAGE_BALANCE]) && (pwmVal != 0x00) )
	{
		if(pwmVal > 0x00) set_pwm(--pwmVal);
		if(pwmVal == 0) PWM_TIM_OFF();
	}
}


void measVol( void ) 
{
	static uint32_t adc_inc_sample_avg;
	uint32_t adc_sample_avg;
	uint32_t adc_inc_sample = adc_val << AVG_INC_FORMAT;	// Increase resolution for avg calculations
	
	adc_inc_sample_avg = ( adc_inc_sample_avg*AVG_COEF + adc_inc_sample*(AVG_ONE - AVG_COEF) ) >> AVG_FORMAT;
	adc_sample_avg = adc_inc_sample_avg >> AVG_INC_FORMAT;
	
	// Calculate voltage value
	voltage[VOLTAGE_AVG] = ( adc_sample_avg*ACD_MUL ) >> ADC_MUL_FORMAT;
	voltage[VOLTAGE_ACTUAL] = ( (uint32_t)adc_val*ACD_MUL ) >> ADC_MUL_FORMAT;
}

void measTemp( void ) 
{
	static int32_t temp_avg;
	
	int32_t temp = ( ( adc_val - temp_offset ) * temp_gain );
	temp >>= ADC_TEMP_FORMAT;
	temp >>= ADC_SAMPLES_FORMAT;
	temp -= 27315;	// Kelvin to degrees
	
	temp_avg = ( temp_avg*AVG_COEF + temp*(AVG_ONE - AVG_COEF) ) >> AVG_FORMAT;
	
	temperature = temp_avg;
}


void read_voltage( t_voltage vType, t_rxCmds cmd ) 
{
	const uint8_t datalen = 5;
	
	if((vType == VOLTAGE_AVG_SAMPLE) && !voltage_sample_ttl) 
	{
		vType = VOLTAGE_AVG;
	}
	
	tx_buffer[BUF_POS_ADDR] = device_id;
	tx_buffer[BUF_POS_DATALEN] = datalen+2;
	tx_buffer[BUF_POS_CMD] = cmd;
	tx_buffer[BUF_POS_ARGL] = _BYTEX(voltage[vType], 0);
	tx_buffer[BUF_POS_ARGH] = _BYTEX(voltage[vType], 1);
	
	usart_txStart(datalen);	// Send five bytes + CRC
}

void save_voltage_sample( void ) 
{
	voltage[VOLTAGE_AVG_SAMPLE] = voltage[VOLTAGE_AVG];
	voltage_sample_ttl = VOLTAGE_SAMPLE_TTL;
}

void set_bal_voltage( uint16_t reqVol ) 
{
	if( (reqVol >= BAL_VOL_MIN) && (reqVol <= BAL_VOL_MAX) ) 
	{
		voltage[VOLTAGE_BALANCE] = reqVol;
		cli();		
		eeprom_update_word(EEPROM_VOL_BAL, reqVol);
		sei();
	}
}

void read_temperature( t_rxCmds cmd )
{
	const uint8_t datalen = 5;
	
	tx_buffer[BUF_POS_ADDR] = device_id;
	tx_buffer[BUF_POS_DATALEN] = datalen+2;
	tx_buffer[BUF_POS_CMD] = cmd;
	tx_buffer[BUF_POS_ARGL] = _BYTEX(temperature, 0);
	tx_buffer[BUF_POS_ARGH] = _BYTEX(temperature, 1);
	
	usart_txStart(datalen);	// Send five bytes + CRC
}

void process_uart_data( void ) 
{
	uint8_t cmd;
	uint16_t arg;
		
	rx_flags = USART_FLG_NO_DATA;
	
	cmd = rx_buffer[BUF_POS_CMD];
	_MOVE_TO_BYTE1(arg, rx_buffer[BUF_POS_ARGH]);
	_MOVE_TO_BYTE0(arg, rx_buffer[BUF_POS_ARGL]);
	
	switch( cmd ) 
	{
		case CMD_SAMPLE_ACTUAL_VOLTAGE:	save_voltage_sample();					break;
		case CMD_READ_SAMPLED_VOLTAGE:	read_voltage(VOLTAGE_AVG_SAMPLE, cmd);	break;
		case CMD_READ_ACTUAL_VOLTAGE:	read_voltage(VOLTAGE_AVG, cmd);			break;
		case CMD_READ_BALANCE_VOLTAGE:	read_voltage(VOLTAGE_BALANCE, cmd);		break;
		case CMD_SET_BALANCE_VOLTAGE:	set_bal_voltage(arg);					break;
		case CMD_READ_TEMPERATURE:		read_temperature(cmd);					break;
	}	
}


/*----- Main ---------------------------------------------------------------*/

int main( void )
{
	// Startup
	CLK_INIT();
	SLEEP_INIT();
	PORTMUX_INIT();
	
	USART_INIT();
	USART_TIMOUT_TIM_INIT();
	USART_TIMOUT_RST();
	
	vrefSel( VREF_2V50 );
	initCoefs();
	
	ACD_INIT();
	
	PORTA_INIT();
	
	PWM_TIM_INIT();
	CLK_TIM_INIT();
	
	set_pwm( 1 );				// Set PWM to min value
	
	device_id =					eeprom_read_byte( EEPROM_ID );
	broadcast_id =				eeprom_read_byte( EEPROM_BROADCAST_ID );
	voltage[VOLTAGE_BALANCE] =	eeprom_read_word( EEPROM_VOL_BAL );
	
	WDT_INIT();
	
	sei();
	
	// Loop
    while ( 1 ) 
	{
		switch( currentTask )
		{
			case TASK_ADC_TRIG_VOL:
				ADC_START( ADC_VOL_CH );
				if( voltage_sample_ttl ) voltage_sample_ttl--;
				break;
			
			case TASK_BAL_CTRL_ADC_TRIG_TEMP:
				measVol();
				vrefSel( VREF_1V10 );
				balance();
				ADC_START( ADC_TEMP_CH );
				break;
			
			case TASK_MEAS_TEMP:
				measTemp();
				vrefSel( VREF_2V50 );
				break;
			
			default:
				break;
		}
		currentTask = TASK_NONE;
		
		if( rx_flags ) 
			process_uart_data();
		
		SLEEP();
		wdt_reset();
    }
}