/*******************************************************
Project					: Li Current Meter
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 ______________________________________________*/

uint16_t adc_val;

int16_t current;	// [mA/10]
int16_t curOfset;

// Constants

uint16_t adc_mul;

// Counters

t_tasks currentTask = TASK_ADC_TRIGGER;

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

ISR( ADC0_RESRDY_vect ) 
{
	adc_val = ADC_RESULT;
	currentTask = TASK_MEAS_CURRENT;
}

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

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

void measCur( void ) 
{
	static uint32_t adc_inc_sample_avg;
	int32_t adc_sample_avg;
	int32_t adc_inc_sample = (int32_t)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 current value
	current = ( (adc_sample_avg-curOfset) * adc_mul ) >> ADC_MUL_FORMAT;
}

void readCurOfset( void ) 
{
	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_READ_OFSET;
	tx_buffer[BUF_POS_ARGH] = _BYTEX(curOfset, 1);
	tx_buffer[BUF_POS_ARGL] = _BYTEX(curOfset, 0);
	
	usart_txStart( datalen );	// Send five bytes + CRC
}

void read_current( void ) 
{
	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_READ_ACTUAL_CURRENT;
	tx_buffer[BUF_POS_ARGH] = _BYTEX(current, 1);
	tx_buffer[BUF_POS_ARGL] = _BYTEX(current, 0);
	
	usart_txStart( datalen );	// Send five bytes + CRC
}

void curOfsetCalib( void )
{
	uint8_t i;
	uint16_t buf = 0;
	
	CLK_TIM_STOP();
	for ( i=0; i < CALIB_SAMPLES; i++ )
	{
		ADC_START(ADC_CUR_CH);
		SLEEP();
		buf += adc_val;
	}
	cli();
	curOfset = (buf / CALIB_SAMPLES);
	eeprom_update_word(EEPROM_CUR_OFSET, curOfset);
	sei();
	
	CLK_TIM_START();
}

void blink( void ) 
{
	int16_t cur_abs = current;
	const uint16_t unit = 25000;

	uint8_t led_active;
	uint8_t led_inactive;
	
	uint16_t on_time;
	const uint8_t off_time = 2;			// 8ms
	
	static uint16_t on_counter;
	static uint8_t off_counter;
	
	// Discharging
	if( cur_abs < 0 ) 
	{
		led_active = LED_DISCHG_bm;
		led_inactive = LED_CHG_bm;
		cur_abs *= -1;
	}
	// Charging
	else if( cur_abs > 0 ) 
	{
		led_active = LED_CHG_bm;
		led_inactive = LED_DISCHG_bm;
	}
	// No current flow
	else 
	{
		LED_OFF( LED_DISCHG_bm | LED_CHG_bm );
		return;
	}
	
	
	LED_OFF( led_inactive );
	
	if( off_counter ) 
	{
		if( !(--off_counter) )
			LED_OFF(led_active);	
		return;
	}
	
	on_time = (unit / cur_abs) - off_time;
	
	if( ++on_counter > on_time ) 
	{
		LED_ON(led_active);
		on_counter = 0;
		off_counter = off_time;
	}
}

void process_uart_data( void ) 
{
	uint8_t cmd;
	
	rx_flags = USART_FLG_NO_DATA;
	cmd = rx_buffer[BUF_POS_CMD];
	
	switch( cmd ) 
	{
		case CMD_READ_ACTUAL_CURRENT:	read_current();		break;
		case CMD_READ_OFSET:			readCurOfset();		break;
		case CMD_CALIB_OFSET:			curOfsetCalib();	break;
	}
}

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

int main( void )
{
	// Startup
	CLK_INIT();
	SLEEP_INIT();
	PORTMUX_INIT();
	
	USART_INIT();
	USART_TIMOUT_TIM_INIT();
	USART_TIMOUT_RST();
	
	VREF_INIT();
	ACD_INIT();
	
	PORTA_INIT();
	
	CLK_TIM_INIT();
	
	device_id = eeprom_read_byte( EEPROM_ID );
	curOfset = eeprom_read_word( EEPROM_CUR_OFSET );
	adc_mul = eeprom_read_word( EEPROM_ADC_MUL );
	// 40mV/A	5380  = 1.3135 >> 12  [mA/10]
	// 20mV/A	10761 = 2.6271 >> 12  [mA/10]
	
	WDT_INIT();
	
	sei();
	
	// Loop
	while ( 1 ) 
	{
		// Task Manager
		switch( currentTask ) 
		{
			case TASK_ADC_TRIGGER: 
				ADC_START(ADC_CUR_CH);
				blink();
				break;
			
			case TASK_MEAS_CURRENT: 
				measCur();
				break;
				
			default: 
				break;
		}
		currentTask = TASK_NONE;
		
		if( rx_flags ) 
		{
			process_uart_data();
		}
		
		SLEEP();
		wdt_reset();
    }
}