/***************************************************************************//**
* @file  usart3_dma.c
* @brief UART with DMA
*//****************************************************************************/

//------------------------------C library---------------------------------------
#include <stddef.h>
#include <stdint.h>
#include <string.h>
//----------------------------user includes-------------------------------------
#include "usart3_dma.h"
//------------------------------------------------------------------------------




/* Private typedefs ----------------------------------------------------------*/

typedef struct {
   uint8_t BufA[USART3_TXDMA_BUF_SIZE];
   uint8_t BufB[USART3_TXDMA_BUF_SIZE];
   int     DataCntA;       // data count in BufA
   int     DataCntB;       // data count in BufB
   int     BufSelTx;       // -1:none, 0:A, 1:B
   int     BufSelWr;       // 0:A, 1:B
   uint8_t FlushA;
   uint8_t FlushB;
} Usart3TxBuf_t;

typedef void (*FuncProcData)(const uint8_t* data, uint16_t len);



/* Private macros ------------------------------------------------------------*/

#define ARRAY_COUNT(arr) (sizeof(arr) / sizeof((arr)[0]))


/* Private variables ---------------------------------------------------------*/
uint8_t        Usart3RxDmaBuf[USART3_RXDMA_BUF_SIZE] = {0};
Usart3TxBuf_t  Usart3TxDmaBuf = { {0},{0}, 0, 0, -1, 0 };

static FuncProcData ProcRxData = NULL; // function to process received data, set by Usart3_DMA_Init


/* Public variables ----------------------------------------------------------*/


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


/***************************************************************************//**
* @brief  USART3 init
*//****************************************************************************/
void Usart3_DMA_Init(void (*procRxData)(const uint8_t* data, uint16_t len)) {
   ProcRxData = procRxData;
   // USART and DMA initialization code should be generated by Cube MX
}

/***************************************************************************//**
* @brief  USART3 Rx/Tx DMA task, to be called periodically in main loop
*//****************************************************************************/
void Usart3_DMA_Task() {
   // Handle received data from USART3 DMA
   static uint32_t LastNDTR = USART3_RXDMA_BUF_SIZE;
   static uint32_t WritePos = 0U;
   uint32_t curNDTR = LL_DMA_GetBlkDataLength(USART3_GPDMA, USART3_DMA_RX_CHANNEL);
   uint32_t newBytes;
   if (curNDTR <= LastNDTR) {          // no Wrap-Around
       newBytes = LastNDTR - curNDTR;
   }else {                             // NDTR is bigger than before: Wrap-Around occurred
       newBytes = LastNDTR + (USART3_RXDMA_BUF_SIZE - curNDTR);
   }
   if (newBytes > 0U) {
       uint32_t startPos = WritePos;                // start of new data
       uint32_t endPos   = (WritePos + newBytes) % USART3_RXDMA_BUF_SIZE;  // end of new data
       if (startPos < endPos) {        // no Wrap-Around
          ProcRxData(&Usart3RxDmaBuf[startPos], newBytes);
       } else {                        // first: to buffer end, second: from buffer begin
           uint32_t part1 = USART3_RXDMA_BUF_SIZE - startPos;
           ProcRxData(&Usart3RxDmaBuf[startPos], part1);

           uint32_t part2 = newBytes - part1;
           ProcRxData(&Usart3RxDmaBuf[0], part2);
       }
       // Store new Write Position and NDTR
       WritePos = endPos;          // next start position for write
       LastNDTR = curNDTR;         // NTDR value for next cycle
   }

   // Handle USART3 Tx DMA transfer complete
   if (LL_DMA_IsActiveFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL)) {
      LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
      Usart3TxBuf_t* d = &Usart3TxDmaBuf;
      switch (d->BufSelTx) {
         case 0: { // transfer from BufA completed
            d->BufSelTx = -1;     // no transfer ongoing
            d->DataCntA = 0;      // reset BufA data count
            if (d->FlushB) {      // flush requested for BufB
               // start transfer from BufB
               LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               LL_DMA_SetSrcAddress(USART3_GPDMA, USART3_DMA_TX_CHANNEL, (uint32_t)d->BufB);
               LL_DMA_SetBlkDataLength(USART3_GPDMA, USART3_DMA_TX_CHANNEL, d->DataCntB);
               LL_DMA_EnableChannel(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               d->BufSelTx = 1;          // mark transfer from BufB ongoing
               d->BufSelWr = 0;          // switch write to BufA
               d->FlushB = 0;            // clear flush request
            }
         }break;
         case 1: { // transfer from BufB completed
            d->BufSelTx = -1;     // no transfer ongoing
            d->DataCntB = 0;      // reset BufB data count
            if (d->FlushA) {      // flush requested for BufA
               // start transfer from BufA
               LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               LL_DMA_SetSrcAddress(USART3_GPDMA, USART3_DMA_TX_CHANNEL, (uint32_t)d->BufA);
               LL_DMA_SetBlkDataLength(USART3_GPDMA, USART3_DMA_TX_CHANNEL, d->DataCntA);
               LL_DMA_EnableChannel(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               d->BufSelTx = 0;          // mark transfer from BufA ongoing
               d->BufSelWr = 1;          // switch write to BufB
               d->FlushA = 0;            // clear flush request
            }
         }break;
      }
   }
}

/***************************************************************************//**
* @brief  Write data to USART3 Tx buffer, and optionally start DMA transfer if flush is requested
* @param  src: data to write
* @param  n:   count of data
* @param  flush: if non-zero, request to start DMA transfer after writing data
*//****************************************************************************/
uint8_t Usart3_TxBufWrite(const void* src, size_t n, uint8_t flush) {
   Usart3TxBuf_t* d = &Usart3TxDmaBuf;
   switch (d->BufSelWr) {
      case 0: {        // BufA selected for write
         int newpos = d->DataCntA + n;
         if (newpos <= 2 * USART3_TXDMA_BUF_SIZE) { // enough space in the entire buffer
            if (d->BufSelTx == 1 && newpos > USART3_TXDMA_BUF_SIZE) {
               // transfer from BufB ongoing and BufA overflow
               return 4; // overflow: skip data
            }
            memcpy(&d->BufA[d->DataCntA], src, n);
            if (newpos > USART3_TXDMA_BUF_SIZE) { // BufA full
               d->DataCntA = USART3_TXDMA_BUF_SIZE; // BufA full
               // start transfer from BufA
               LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               LL_DMA_SetSrcAddress(USART3_GPDMA, USART3_DMA_TX_CHANNEL, (uint32_t)d->BufA);
               LL_DMA_SetBlkDataLength(USART3_GPDMA, USART3_DMA_TX_CHANNEL, d->DataCntA);
               LL_DMA_EnableChannel(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               d->BufSelTx = 0;          // mark transfer from BufA ongoing
               d->BufSelWr = 1;          // switch write to BufB
               d->DataCntB = newpos - USART3_TXDMA_BUF_SIZE; // adjust BufB write position
               if (flush) {
                  // BufA flush already started
                  d->FlushB = 1; // mark BufB flush requested
               }
            }else {                    // BufA not full yet
               d->DataCntA = newpos;
               if (flush) {
                  if (d->BufSelTx == -1) {   // flush requested and no transfer ongoing
                     // start transfer from BufA
                     LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
                     LL_DMA_SetSrcAddress(USART3_GPDMA, USART3_DMA_TX_CHANNEL, (uint32_t)d->BufA);
                     LL_DMA_SetBlkDataLength(USART3_GPDMA, USART3_DMA_TX_CHANNEL, d->DataCntA);
                     LL_DMA_EnableChannel(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
                     d->BufSelTx = 0;          // mark transfer from BufA ongoing
                     d->BufSelWr = 1;          // switch write to BufB
                  }else {
                     d->FlushA = 1;          // mark BufA flush requested
                  }
               }
            }
         }else {                           // not enough space in the entire buffer
            return 1; // overflow: skip data
         }
      }break;
      case 1: {   // BufB selected for write
         int newpos = d->DataCntB + n;
         if (newpos <= USART3_TXDMA_BUF_SIZE) { // enough space in BufB
            memcpy(&d->BufB[d->DataCntB], src, n);
            d->DataCntB = newpos;
            if (flush) {
               if (d->BufSelTx == -1) {   // flush requested and no transfer ongoing
                  // start transfer from BufB
                  LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
                  LL_DMA_SetSrcAddress(USART3_GPDMA, USART3_DMA_TX_CHANNEL, (uint32_t)d->BufB);
                  LL_DMA_SetBlkDataLength(USART3_GPDMA, USART3_DMA_TX_CHANNEL, d->DataCntB);
                  LL_DMA_EnableChannel(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
                  d->BufSelTx = 1;          // mark transfer from BufB ongoing
                  d->BufSelWr = 0;          // switch write to BufA
               }else {
                  d->FlushB = 1;          // mark BufB flush requested
               }
            }
         }else {                             // not enough space in BufB
            // try to flush BufB if possible
            if (d->BufSelTx == -1) {         // no transfer ongoing
               // start transfer from BufB
               LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               LL_DMA_SetSrcAddress(USART3_GPDMA, USART3_DMA_TX_CHANNEL, (uint32_t)d->BufB);
               LL_DMA_SetBlkDataLength(USART3_GPDMA, USART3_DMA_TX_CHANNEL, d->DataCntB);
               LL_DMA_EnableChannel(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
               d->BufSelTx = 1;          // mark transfer from BufB ongoing
               d->BufSelWr = 0;          // switch write to BufA
            }
            if (d->BufSelTx != 0) {       // BufA ready to write (not being transmitted)
               d->BufSelWr = 0;          // switch write to BufA
               if (n <= USART3_TXDMA_BUF_SIZE) { // enough space in BufA
                  memcpy(d->BufA, src, n);
                  d->DataCntA = n;
                  if (flush) {                  // flush requested
                     if (d->BufSelTx == -1) {   // no transfer ongoing
                        // start transfer from BufA
                        LL_DMA_ClearFlag_TC(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
                        LL_DMA_SetSrcAddress(USART3_GPDMA, USART3_DMA_TX_CHANNEL, (uint32_t)d->BufA);
                        LL_DMA_SetBlkDataLength(USART3_GPDMA, USART3_DMA_TX_CHANNEL, d->DataCntA);
                        LL_DMA_EnableChannel(USART3_GPDMA, USART3_DMA_TX_CHANNEL);
                        d->BufSelTx = 0;          // mark transfer from BufA ongoing
                        d->BufSelWr = 1;          // switch write to BufB
                     }else {                    // transfer ongoing
                        d->FlushA = 1;          // mark BufA flush requested
                     }
                  }
               }else {
                  return 2; // overflow: skip data
               }
            }else {
               return 3; // overflow: skip data
            }
         }
      }break;
   }
   return 0; // no error
}


