/*
 * uart.c
 *
 * Implementation of Universal Serial (UART) Library
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 *
 * Copyright (c) 2023 Georgy Moshkin <https://georgymoshkin.com>
 * All rights reserved.
 *
 */

#include "uart.h"

COM_TPort* myPortPtr;
COM_TPort myPort[COM_MAX_NUM];

void dummy485() {}

void COM_Select(uint8_t n)
{
	myPortPtr=&myPort[n];
}

void COM_SetTimeout(uint32_t t)
{
	myPortPtr->timeOut = t;
}

void COM_Init(uint8_t n,
		UART_HandleTypeDef *huartPtr,
		volatile uint8_t *rxBufPtr,
		uint16_t rxBufSize
		)
{
	COM_Select(n);
	myPortPtr->huartPtr = huartPtr;
	myPortPtr->rxBufPtr= (uint8_t *) rxBufPtr;
	myPortPtr->rxBufSize = rxBufSize;
	myPortPtr->rxPos = 0;
	myPortPtr->timeOut = 100; // default 100ms
	myPortPtr->rxFail = false;
	myPortPtr->_rs485tx=dummy485;
	myPortPtr->_rs485rx=dummy485;
	myPortPtr->

	HAL_UART_Receive_DMA(huartPtr, (uint8_t *) rxBufPtr, rxBufSize);

	// Disable error interrupts
	CLEAR_BIT(huartPtr->Instance->CR3, USART_CR3_EIE);
	CLEAR_BIT(huartPtr->Instance->CR1, USART_CR1_PEIE);
}

void COM_SetCallbacks485(void (*f485tx)(void), void (*f485rx)(void))
{
	myPortPtr->_rs485tx=f485tx;
	myPortPtr->_rs485rx=f485rx;
	myPortPtr->_rs485rx();
}


void COM_WaitTxDone(void)
{
	while (READ_BIT(MY_ISR(myPortPtr),MY_ISR_TC)==0) __NOP();
}

void COM_WriteFast(void *pData, uint16_t Size)
{
	COM_WaitTxDone();
	HAL_UART_Transmit_DMA(myPortPtr->huartPtr, pData,Size);
}

void COM_Write(void *pData, uint16_t Size)
{
	COM_WaitTxDone();
	myPortPtr->_rs485tx();
	HAL_UART_Transmit_DMA(myPortPtr->huartPtr, pData,Size);
	COM_WaitTxDone();
	myPortPtr->_rs485rx();
}

bool COM_ReadByteTimeout(uint8_t *b, uint32_t timeOut)
{
	uint16_t UART_BUFFER_SIZE=myPortPtr->rxBufSize;
	uint16_t dmaPos=UART_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(myPortPtr->huartPtr->hdmarx);

	uint32_t tickStart=HAL_GetTick();
	while ( (dmaPos==myPortPtr->rxPos) && ((HAL_GetTick() - tickStart) < timeOut) )
	{
		dmaPos=UART_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(myPortPtr->huartPtr->hdmarx);
	}

	if (dmaPos!=myPortPtr->rxPos)
	{
		*b=(myPortPtr->rxBufPtr)[myPortPtr->rxPos];
		myPortPtr->rxPos++;
		if (myPortPtr->rxPos==myPortPtr->rxBufSize) {myPortPtr->rxPos=0;}
		return true;
	}
	myPortPtr->rxFail=true;
	return false;
}

bool COM_ReadByteFast(void *b)
{
	return COM_ReadByteTimeout((uint8_t *)b,0);
}

bool COM_ReadByte(void *b)
{
	return COM_ReadByteTimeout((uint8_t *)b,myPortPtr->timeOut);
}

bool COM_ReadTimeout(uint8_t *destBuffer, uint16_t needLen, uint32_t timeOut)
{
	uint16_t UART_BUFFER_SIZE=myPortPtr->rxBufSize;
	uint16_t rxPos=myPortPtr->rxPos;
	uint16_t dataLen=0;
	uint16_t dmaPos=UART_BUFFER_SIZE-__HAL_DMA_GET_COUNTER(myPortPtr->huartPtr->hdmarx);
	uint16_t dmaPosOld=dmaPos;

	uint32_t tickStart=HAL_GetTick();
	do {
		dmaPosOld=dmaPos;
		dmaPos=UART_BUFFER_SIZE-__HAL_DMA_GET_COUNTER(myPortPtr->huartPtr->hdmarx);

		if (dmaPos!=dmaPosOld) {tickStart=HAL_GetTick();} // new byte arrived

		if (dmaPos>rxPos) {	dataLen=dmaPos-rxPos; }

		if (dmaPos<rxPos)
		{
			dataLen=UART_BUFFER_SIZE-rxPos;
			dataLen+=dmaPos;
		}
	} while ( (dataLen<needLen) && ((HAL_GetTick() - tickStart) < timeOut) );

	if (dataLen>=needLen)
	{
		if(rxPos+needLen-1<UART_BUFFER_SIZE)
		{
			memcpy(&destBuffer[0],&(myPortPtr->rxBufPtr)[rxPos],needLen);
		}
		else
		{
			uint16_t tailLen=UART_BUFFER_SIZE-rxPos;
			uint16_t headLen=needLen-tailLen;
			memcpy(&destBuffer[0],&(myPortPtr->rxBufPtr)[rxPos],tailLen);
			memcpy(&destBuffer[tailLen],&(myPortPtr->rxBufPtr)[0],headLen);
		}

		rxPos+=needLen;
		if (rxPos>=UART_BUFFER_SIZE) { rxPos-=UART_BUFFER_SIZE;}

		myPortPtr->rxPos=rxPos;
		return true;
	}


	if (timeOut>0) myPortPtr->rxPos=dmaPos; // abandon unread bytes
	myPortPtr->rxFail=true;
	return false;
}

bool COM_ReadFast(void *destBuffer, uint16_t needLen)
{
	return COM_ReadTimeout((uint8_t *)destBuffer, needLen, 0);
}

bool COM_Read(void *destBuffer, uint16_t needLen)
{
	return COM_ReadTimeout((uint8_t *)destBuffer, needLen, myPortPtr->timeOut);
}

bool COM_RxFail(void)
{
	return myPortPtr->rxFail;
}

void COM_CleanRxFail(void)
{
	myPortPtr->rxFail=false;
}

