/*
 * ADBMS_LL_Driver.c
 *
 *  Created on: 05.06.2022
 *      Author: max
 */
#include "ADBMS_LL_Driver.h"


#define INITAL_PEC 0x0010
#define ADBMS_SPI_TIMEOUT 1000 //Timeout in ms

SPI_HandleTypeDef* adbmsspi;

uint8 adbmsDriverInit(SPI_HandleTypeDef* hspi)
{
	mcuAdbmsCSLow();
	HAL_Delay(1);
	mcuAdbmsCSHigh();
	adbmsspi = hspi;
	return 0;
}

uint8 calculatePEC(uint8_t* data, uint8_t datalen)
{
    uint16 currentpec = INITAL_PEC;
    if(datalen >= 3)
    {
        for(int i = 0; i < (datalen-2); i++)
        {
            for(int n = 0; n < 8;n++)
            {
                uint8 din = data[i] << (n);
                currentpec = updatePEC(currentpec, din);
            }
        }

        data[datalen-2] = (currentpec>>7) & 0xFF;
        data[datalen-1] = (currentpec<<1) & 0xFF;
        return 0;
    }

    else
    {
        return 1;
    }
}

uint8 checkPEC(uint8* data, uint8 datalen)
{
	if(datalen <= 3)
	{
		return 255;
	}

	uint16 currentpec = INITAL_PEC;

	for(int i = 0; i < (datalen-2); i++)
	{
	    for(int n = 0; n < 8;n++)
	    {
	        uint8 din = data[i] << (n);
	        currentpec = updatePEC(currentpec, din);
	    }
	}

	uint8 pechigh = (currentpec>>7) & 0xFF;
	uint8 peclow = (currentpec<<1) & 0xFF;

	if((pechigh == data[datalen-2]) && (peclow == data[datalen-1]))
	{
		return 0;
	}

	return 1;

}

uint16 updatePEC(uint16 currentPEC, uint8 din)
{
    din = (din>>7) & 0x01;
    uint8 in0 = din ^ ((currentPEC >> 14) &0x01);
    uint8 in3 = in0 ^ ((currentPEC >> 2) &0x01);
    uint8 in4 = in0 ^ ((currentPEC >> 3) &0x01);
    uint8 in7 = in0 ^ ((currentPEC >> 6) &0x01);
    uint8 in8 = in0 ^ ((currentPEC >> 7) &0x01);
    uint8 in10 = in0 ^ ((currentPEC >> 9) &0x01);
    uint8 in14 = in0 ^ ((currentPEC >> 13) &0x01);

    uint16 newPEC = 0;

    newPEC |= in14<<14;
    newPEC |= (currentPEC & (0x01<<12))<<1;
    newPEC |= (currentPEC & (0x01<<11))<<1;
    newPEC |= (currentPEC & (0x01<<10))<<1;
    newPEC |= in10<<10;
    newPEC |= (currentPEC & (0x01<<8))<<1;
    newPEC |= in8<<8;
    newPEC |= in7<<7;
    newPEC |= (currentPEC & (0x01<<5))<<1;
    newPEC |= (currentPEC & (0x01<<4))<<1;
    newPEC |= in4<<4;
    newPEC |= in3<<3;
    newPEC |= (currentPEC & (0x01<<1))<<1;
    newPEC |= (currentPEC & (0x01))<<1;
    newPEC |= in0;


    return newPEC;
}

uint8 writeCMD(uint16 command, uint8* args, uint8 arglen)
{
	if(arglen > 0)
	{
	uint8 buffer[6+arglen];
	buffer[0] = (command >> 8) & 0xFF;
	buffer[1] = (command) & 0xFF;
	calculatePEC(buffer, 4);
	for(uint8 i = 0; i < arglen; i++)
	{
		buffer[4+i] = args[i];
	}

	calculatePEC(&buffer[4], arglen+2); //Calculate PEC of Data Part with offset of 4 Bytes for CMD and CMD PEC

	mcuAdbmsCSLow();
	mcuSPITransmit(buffer, 6+arglen);
	mcuAdbmsCSHigh();

	}
	else
	{
	uint8 buffer[4];
	buffer[0] = (command >> 8) & 0xFF;
	buffer[1] = (command) & 0xFF;
	calculatePEC(buffer, 4);

	mcuAdbmsCSLow();

	mcuSPITransmit(buffer, 4);

	mcuAdbmsCSHigh();
	}

	return 0;
}

uint8 readCMD(uint16 command, uint8* buffer, uint8 buflen)
{
	//uint8* txbuffer = (uint8*) malloc(6+buflen);
	//uint8* rxbuffer = (uint8*) malloc(6+buflen);
	uint8 txbuffer[6+buflen];
	uint8 rxbuffer[6+buflen];

	txbuffer[0] = (command >> 8) & 0xFF;
	txbuffer[1] = (command) & 0xFF;
	calculatePEC(txbuffer, 4);

	mcuAdbmsCSLow();
	mcuSPITransmitReceive(rxbuffer, txbuffer, 6+buflen);
	mcuAdbmsCSHigh();


	for(uint8 i = 0; i<buflen; i++)
	{
		buffer[i] = rxbuffer[i+4];
	}

	uint8 peccheck = checkPEC(&rxbuffer[4], buflen+2);

	//free(txbuffer);
	//free(rxbuffer);

	if(peccheck == 0)
		return 0;
	else
	{
		static int err_cnt = 0;
		if (err_cnt++ > 100) {
		Error_Handler();
		} else {
			return 1;
		}
	}

}

void mcuAdbmsCSLow()
{
	HAL_GPIO_WritePin(CSB_GPIO_Port, CSB_Pin, GPIO_PIN_RESET);
}

void mcuAdbmsCSHigh()
{
	HAL_GPIO_WritePin(CSB_GPIO_Port, CSB_Pin, GPIO_PIN_SET);
}

uint8 mcuSPITransmit(uint8* buffer, uint8 buffersize)
{
	HAL_StatusTypeDef status;
	//status = HAL_SPI_Transmit(adbmsspi, buffer, buffersize, ADBMS_SPI_TIMEOUT);
	//uint8 *rxbuf = (uint8*) malloc(buffersize);
	uint8 rxbuf[buffersize];
	status = HAL_SPI_TransmitReceive(adbmsspi, buffer, rxbuf, buffersize, ADBMS_SPI_TIMEOUT);
	__HAL_SPI_CLEAR_OVRFLAG(adbmsspi);
	//free(rxbuf);
	return status;
}

uint8 mcuSPIReceive(uint8* buffer, uint8 buffersize)
{
	HAL_StatusTypeDef status;
	status = HAL_SPI_Receive(adbmsspi, buffer, buffersize, ADBMS_SPI_TIMEOUT);
	return status;
}

uint8 mcuSPITransmitReceive(uint8* rxbuffer, uint8* txbuffer, uint8 buffersize)
{
	HAL_StatusTypeDef status;
	status = HAL_SPI_TransmitReceive(adbmsspi, txbuffer, rxbuffer, buffersize, ADBMS_SPI_TIMEOUT);
	return status;
}

inline void mcuDelay(uint16 delay)
{
	HAL_Delay(delay);
}


