#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdlib.h>
#include <stddef.h>
#include "yaMBSiavr.h"

#define AVRG 1024

uint16_t adc_values[AVRG];

void init_clk(void)
{
    // ========= System Clock configuration =========
    // Set to external 16Mhz crystal, using the PLL at *2
    // set it to be a 12-16Mhz crystal with a slow start-up time.
    OSC.XOSCCTRL = OSC_FRQRANGE_2TO9_gc | OSC_XOSCSEL_XTAL_16KCLK_gc ;
    OSC.CTRL |= OSC_XOSCEN_bm ; // enable it
    while( (OSC.STATUS & OSC_XOSCRDY_bm) == 0 ){} // wait until it's stable

    // The external crystal is now running and stable.
    // (Note that it's not yet selected as the clock source)
    // Now configure the PLL to be eXternal oscillator * 2
    OSC.PLLCTRL = OSC_PLLSRC_XOSC_gc | 2;

    // now enable the PLL...
    OSC.CTRL |= OSC_PLLEN_bm ; // enable the PLL...
    while( (OSC.STATUS & OSC_PLLRDY_bm) == 0 ){} // wait until it's stable

    // And now, *finally*, we can switch from the internal 2Mhz clock to the PLL
    CCP = CCP_IOREG_gc;	// protected write follows   
    CLK.CTRL = CLK_SCLKSEL_PLL_gc;	// The System clock is now  PLL (16Mhz * 2)
    // ==============================================
}

void init_timer_100us(){
    TCC0.CTRLA |= TC_CLKSEL_DIV256_gc;
    TCC0.PER = 13; // ==> 9615Hz
    TCC0.INTCTRLA |= TC_OVFINTLVL_MED_gc;
}

void adc_init(void){
    ADCA.CTRLB = ADC_FREERUN_bm;
    ADCA.REFCTRL = ADC_REFSEL_VCC_gc;
    ADCA.PRESCALER = ADC_PRESCALER_DIV512_gc;

    uint8_t CalibrationByteL;
    uint8_t CalibrationByteH;
    NVM_CMD = NVM_CMD_READ_CALIB_ROW_gc;
    CalibrationByteL = pgm_read_byte(offsetof(NVM_PROD_SIGNATURES_t, ADCACAL0));
    CalibrationByteH = pgm_read_byte(offsetof(NVM_PROD_SIGNATURES_t, ADCACAL1));
    NVM_CMD = NVM_CMD_NO_OPERATION_gc;
    ADCA.CALL = CalibrationByteL;
    ADCA.CALH = CalibrationByteH;

    ADCA.CTRLA = ADC_ENABLE_bm;

    ADCA.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
    ADCA.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN0_gc;
}

uint16_t adc_read(void){
    ADCA.CH0.CTRL |= ADC_CH_START_bm;
    while(!(ADCA.CH0.INTFLAGS & ADC_CH_CHIF_bm));
    ADCA.CH0.INTFLAGS = ADC_CH_CHIF_bm;
    return ADCA.CH0.RES;
}

float avrg(uint16_t * values, uint16_t length){
    uint64_t res = 0;
    for(uint16_t i=0; i < length; i++){
        res += values[i];
    }
    return (float)(res)/length;
}

void dma_init(void){
    DMA.CTRL = DMA_ENABLE_bm;

    //DMA.CH0.CTRLB = DMA_CH_TRNINTLVL_LO_gc;

    DMA.CH0.TRFCNT = AVRG * 2;
    DMA.CH0.CTRLA = DMA_CH_BURSTLEN_2BYTE_gc | DMA_CH_SINGLE_bm;
    DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_ADCA_CH0_gc;

    //repeat endless
    DMA.CH0.CTRLA |= DMA_CH_REPEAT_bm;
    DMA.CH0.REPCNT = 0;

    DMA.CH0.SRCADDR0 = ((uintptr_t)&ADCA.CH0.RES) & 0xFF;
    DMA.CH0.SRCADDR1 = (((uintptr_t)&ADCA.CH0.RES) >> 0x08) & 0xFF;
    DMA.CH0.SRCADDR2 = (((uintptr_t)&ADCA.CH0.RES) >> 0x0F) & 0xFF;

    DMA.CH0.DESTADDR0 = ((uintptr_t)adc_values) & 0xFF;
    DMA.CH0.DESTADDR1 = ((uintptr_t)adc_values >> 0x08) & 0xFF;
    DMA.CH0.DESTADDR2 = ((uintptr_t)adc_values >> 0x0F) & 0xFF;

    DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_BURST_gc | DMA_CH_DESTRELOAD_TRANSACTION_gc | DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTDIR_INC_gc;

    DMA.CH0.CTRLA |= DMA_CH_ENABLE_bm;
}

void modbusGet(void) {
	if (modbusGetBusState() & (1<<ReceiveCompleted))
    {
        switch(rxbuffer[1]) {
            case fcReadHoldingRegisters: ;
                float tmp[4];
                tmp[0] = avrg(adc_values, AVRG) - 170;
                tmp[1] = (tmp[0])/ 3926 * 1.95;
                tmp[2] = (tmp[1]*980)/(3.3-tmp[1]);
                tmp[3] = (tmp[2]-100)/0.3927;
                modbusExchangeRegisters(tmp, 0, 8);
                break;
            default:
                modbusSendException(ecIllegalFunction);
                break;
        }
    }
}

int main(void){
    init_clk();
    PORTD.DIRSET = 1 << 5;
    PORTD.OUTTGL = 1 << 5;
    _delay_ms(500);
    PORTD.OUTTGL = 1 << 5;

    adc_init();
    dma_init();

    PORTCFG.MPCMASK = 0xFF;
    PORTC.PIN1CTRL = PORT_OPC_PULLUP_gc;

    init_timer_100us();

    PMIC.CTRL |= PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm; 
    sei();

    modbusSetAddress(1);
    modbusInit();

    for(;;){
        modbusGet();
    }
}

ISR(TCC0_OVF_vect){
    modbusTickTimer();
}
