/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "board.h"
#include "fsl_lpuart_edma.h"
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
#include "fsl_dmamux.h"
#endif
#include "pin_mux.h"
#include "clock_config.h"

#include "fsl_iomuxc.h"

#include "fsl_gpio.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
 typedef enum{
    UART_RX,
    UART_TX,
 }UART_STATE;

/*
ENABLE_RTS_TRANSCEIVER to control 
whether use GPIO_AD_B0_15 as LPUART1_RTS_B signal to drive direction of external transceiver

ENABLE_RTS_TRANSCEIVER = 1, use LPUART_RTS mode as signal
ENABLE_RTS_TRANSCEIVER = 0, use GPIO to drive extenal transceiver direction

*/

/*

1.If we want to use debugger on board, then set SWO_PRINTF=0, and print debug info by other UARTs.
SWO_PRINTF=0 only


2.If we need debug info, since EVK's debug UART is occupted as RS485 port, we could define SWO_PRINTF=1 in Project seeting

Then We could use SWO to print out debug information, but need jlink attached to J21 on RT1060/RT1064 J21, and use Jlink(SWD mode) to debug this project.
short J45/J46, route LPUART1 to on board Debugger for log info
open J47/J48/J49/J50 to bypass debugger on board, avoid conflict with external JLink
Plug USB J41 for power and USB CDC RS485 info simulation. If externl RS485 transciver is used, open J45/J46 to interfcae external RS485 transciver

For RT1050/RT1020 SWO is not shared with TDO, if SWO pin is not routed to TDO, a flying wire is necessary
For RT1060/RT1064 SWO is shared with TDO on the same pin mux, not need a flying wire to connect SWO to TDO(PIN13).

SDK doesn't have support for SWO printf, so need relay on IDE(IAR or Keil) to implement it
For IAR: define SWO_PRINTF=1
DEBUGCONSOLE=0, 
undefine SDK_DEBUGCONSOLE_UART in fsl_debug_console.h
#if (SWO_PRINTF == 1)
#undef SDK_DEBUGCONSOLE_UART
#endif

Then Enable SWO stdout/stderr in project setting

For Keil:
define SWO_PRINTF=1
DEBUGCONSOLE=0, 
undefine SDK_DEBUGCONSOLE_UART in fsl_debug_console.h
#if (SWO_PRINTF == 1)
#undef SDK_DEBUGCONSOLE_UART
#endif

Then enable STDOUT/STDIN/STDIN in compiler->I/O

*/


#define DEMO_LPUART LPUART1
#define DEMO_LPUART_CLK_FREQ BOARD_DebugConsoleSrcFreq()
#define DEMO_LPUART_IRQn LPUART1_IRQn
#define DEMO_LPUART_IRQHandler LPUART1_IRQHandler

#define LPUART_TX_DMA_CHANNEL 0U
#define LPUART_RX_DMA_CHANNEL 1U
#define LPUART_TX_DMA_REQUEST kDmaRequestMuxLPUART1Tx
#define LPUART_RX_DMA_REQUEST kDmaRequestMuxLPUART1Rx
#define EXAMPLE_LPUART_DMAMUX_BASEADDR DMAMUX
#define EXAMPLE_LPUART_DMA_BASEADDR DMA0

#define UART_RX_BUFFER_LENGTH 2048
#define UART_TX_BUFFER_LENGTH 2048

#if (SWO_PRINTF == 1)
#undef SDK_DEBUGCONSOLE_UART
#endif

/*GPIO1_IO15 GPIO_AD_B0_15 ALT5*/
#define RTS_GPIO_PORT GPIO1
#define RTS_GPIO_PIN (15U)


/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/* LPUART user callback */
void LPUART_UserCallback(LPUART_Type *base, lpuart_edma_handle_t *handle, status_t status, void *userData);

/*******************************************************************************
 * Variables
 ******************************************************************************/

lpuart_edma_handle_t g_lpuartEdmaHandle;
edma_handle_t g_lpuartTxEdmaHandle;
edma_handle_t g_lpuartRxEdmaHandle;

lpuart_transfer_t sendXfer;
lpuart_transfer_t receiveXfer;


AT_NONCACHEABLE_SECTION_INIT(uint8_t g_txBuffer[UART_TX_BUFFER_LENGTH]) = {0};
AT_NONCACHEABLE_SECTION_INIT(uint8_t g_rxBuffer[UART_RX_BUFFER_LENGTH]) = {0};


uint32_t send_size = 0;
volatile bool reciveFrame                                          = false;

UART_STATE uart_state = UART_RX;

/*******************************************************************************
 * Code
 ******************************************************************************/

#if (ENABLE_RTS_TRANSCEIVER == 0)
/*切换RS485开关*/
void Set_RS485_Receive()
{
	uart_state = UART_RX;
	GPIO_PinWrite(RTS_GPIO_PORT, RTS_GPIO_PIN, 0U);//transceiver direction control output low

}
void Set_RS485_Transimit()
{
	uart_state = UART_TX;
	GPIO_PinWrite(RTS_GPIO_PORT, RTS_GPIO_PIN, 1U);//transceiver direction control output high

}
#endif


/*串口中断处理函数：TC和RDLE中断*/
void DEMO_LPUART_IRQHandler(void)
{
    uint8_t data;
	volatile uint32_t stat = LPUART_GetStatusFlags(DEMO_LPUART);
    uint32_t rx_count;
    /* If new data arrived. */
    if ((kLPUART_IdleLineFlag)&stat)
    {
        LPUART_ClearStatusFlags(DEMO_LPUART,kLPUART_IdleLineFlag);
#if (SWO_PRINTF == 1)
        printf("idle line\r\n");
#endif
        /*取出DMA RX buffer中的数据*/
		if (kStatus_NoTransferInProgress ==
			LPUART_TransferGetReceiveCountEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &rx_count))
		{
			rx_count = 0;
		}
        if(rx_count > 0)
        {
            memcpy(g_txBuffer,g_rxBuffer,rx_count);//将接收到的数据保存
            send_size = rx_count;
		    reciveFrame = true;//假设接收到完整的数据包了
        }


        /*并将RX buffer重置；这边可以优化，调用LPUART_ReceiveEDMA代码太庞杂*/
        //继续接收数据
#if (ENABLE_RTS_TRANSCEIVER == 0)
		Set_RS485_Receive();
#endif
		LPUART_TransferAbortReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle);
		LPUART_ReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &receiveXfer);
    }

    else if((kLPUART_RxOverrunFlag)&stat)
    {
		LPUART_ClearStatusFlags(DEMO_LPUART,kLPUART_RxOverrunFlag);
    }
    else if((kLPUART_NoiseErrorFlag)&stat)
    {
		LPUART_ClearStatusFlags(DEMO_LPUART,kLPUART_NoiseErrorFlag);

    }
    else if((kLPUART_FramingErrorFlag)&stat)
    {
		LPUART_ClearStatusFlags(DEMO_LPUART,kLPUART_FramingErrorFlag);

    }
    else if((kLPUART_ParityErrorFlag)&stat)
    {
		LPUART_ClearStatusFlags(DEMO_LPUART,kLPUART_ParityErrorFlag);

    }
    //如果TC中断被使能
	if(LPUART_GetEnabledInterrupts(DEMO_LPUART) & kLPUART_TransmissionCompleteFlag)
	{
		if((kLPUART_TransmissionCompleteFlag)&stat)//TC标志默认为1
		{
			//TC发生，禁止TC中断
			LPUART_DisableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);
			//重新使能接收
#if (ENABLE_RTS_TRANSCEIVER == 0)
			Set_RS485_Receive();
#endif
			LPUART_TransferAbortReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle);
			LPUART_ReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &receiveXfer);
		
		}
	}

    /* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate overlapping
      exception return operation might vector to incorrect interrupt */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
    __DSB();
#endif
}
/* LPUART user callback */
/*注意：这是DMA驱动的回调函数，并不是串口中断或者回调函数*/
void LPUART_UserCallback(LPUART_Type *base, lpuart_edma_handle_t *handle, status_t status, void *userData)
{
    userData = userData;

    /*
    DMA驱动自己定义的DMA发送完成标记
    发送结束，重置为接收模式,需要重置DMA指针等参数
    */
    if (kStatus_LPUART_TxIdle == status)
    {
#if (SWO_PRINTF == 1)
        printf("DMA tx done, begin start receive\r\n");
#endif
        //tx DMA完成中断，此时使能TC中断
		LPUART_EnableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);//设置TC中断
    }
    
	/*
	DMA驱动自己定义的DMA接收完成标记
	一般rx buffer足够长，不会发生DMA接收buffer满的情况，这个需要作为异常处理
	*/
    if (kStatus_LPUART_RxIdle == status)
    {
#if (SWO_PRINTF == 1)
        printf("DMA rx full\r\n");
#endif
        /*End user should carefully define DMA receive buffer length, avoid enter this state.
          Once such situation occurs for some possible bus error, end user should handler this situation by real case.
          in this demo, only skip existing buffer and restart DMA receiver again*/
	    LPUART_TransferAbortReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle);
	    LPUART_ReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &receiveXfer);

        
    }
}


/*!
 * @brief Main function
 */
int main(void)
{
    lpuart_config_t lpuartConfig;
    edma_config_t config;
    lpuart_transfer_t xfer;
#if (ENABLE_RTS_TRANSCEIVER == 0)
    gpio_pin_config_t rts_gpio_config = {kGPIO_DigitalOutput, 0, kGPIO_NoIntmode};
#endif

    BOARD_ConfigMPU();
    BOARD_InitPins();
    BOARD_BootClockRUN();

#if (SWO_PRINTF == 1)
	CLOCK_EnableClock(kCLOCK_Trace);
	CLOCK_SetDiv(kCLOCK_TraceDiv, 0);
	CLOCK_SetMux(kCLOCK_TraceMux, 3);
	IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_10_ARM_TRACE_SWO, 0U); 

	//SDK doesn't have support for SWO printf, so need relay on IDE(IAR or Keil) to implement it
    //BOARD_InitDebugConsole();
    
    printf("RS485 demo start\r\n");
#endif


    
    /* Initialize the LPUART. */
    /*
     * lpuartConfig.baudRate_Bps = 115200U;
     * lpuartConfig.parityMode = kLPUART_ParityDisabled;
     * lpuartConfig.stopBitCount = kLPUART_OneStopBit;
     * lpuartConfig.txFifoWatermark = 0;
     * lpuartConfig.rxFifoWatermark = 0;
     * lpuartConfig.enableTx = false;
     * lpuartConfig.enableRx = false;
     */
    LPUART_GetDefaultConfig(&lpuartConfig);
    lpuartConfig.baudRate_Bps = BOARD_DEBUG_UART_BAUDRATE;
    lpuartConfig.enableTx     = true;
    lpuartConfig.enableRx     = true;
    /*设置RX IDLE参数*/
    lpuartConfig.rxIdleType = kLPUART_IdleTypeStopBit;
    lpuartConfig.rxIdleConfig = kLPUART_IdleCharacter16;

    /*设置tx rx的fifo*/
    lpuartConfig.rxFifoWatermark = 0;//接收不使用fifo
    //如果不使用发送fifo，将其设置为0即可。
    lpuartConfig.txFifoWatermark = FSL_FEATURE_LPUART_FIFO_SIZEn(DEMO_LPUART) - 1;//少于最大fifo size

    LPUART_Init(DEMO_LPUART, &lpuartConfig, DEMO_LPUART_CLK_FREQ);


    //initialize direction control pin and function
#if (ENABLE_RTS_TRANSCEIVER == 0)
		GPIO_PinInit(RTS_GPIO_PORT, RTS_GPIO_PIN, &rts_gpio_config);
#else
		
		DEMO_LPUART->MODIR &= ~(LPUART_MODIR_TXRTSE(1) | LPUART_MODIR_TXRTSPOL(1));
		//enable transmitter RTS signal
		DEMO_LPUART->MODIR |= LPUART_MODIR_TXRTSE(1);
		//Transmitter RTS is active high.
		DEMO_LPUART->MODIR |= LPUART_MODIR_TXRTSPOL(1);
#endif


    /* Enable UART interrupt. */
    /*初始化状态为idle line，并需要使能错误中断，错误中断必须处理否则串口就会停止，直至错误标志被处理*/
    LPUART_EnableInterrupts(DEMO_LPUART, kLPUART_IdleLineInterruptEnable|
                                         kLPUART_RxOverrunInterruptEnable|
                                         kLPUART_NoiseErrorInterruptEnable|
                                         kLPUART_FramingErrorInterruptEnable|
                                         kLPUART_ParityErrorInterruptEnable);
                                         /*kLPUART_TxFifoOverflowInterruptEnable|
                                         kLPUART_RxFifoUnderflowInterruptEnable);*/
	//禁止TC中断
	LPUART_DisableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);
			
	EnableIRQ(DEMO_LPUART_IRQn);

#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
    /* Init DMAMUX */
    DMAMUX_Init(EXAMPLE_LPUART_DMAMUX_BASEADDR);
    /* Set channel for LPUART */
    DMAMUX_SetSource(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_TX_DMA_CHANNEL, LPUART_TX_DMA_REQUEST);
    DMAMUX_SetSource(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_RX_DMA_CHANNEL, LPUART_RX_DMA_REQUEST);
    DMAMUX_EnableChannel(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_TX_DMA_CHANNEL);
    DMAMUX_EnableChannel(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_RX_DMA_CHANNEL);
#endif
    /* Init the EDMA module */
    EDMA_GetDefaultConfig(&config);
    EDMA_Init(EXAMPLE_LPUART_DMA_BASEADDR, &config);
    EDMA_CreateHandle(&g_lpuartTxEdmaHandle, EXAMPLE_LPUART_DMA_BASEADDR, LPUART_TX_DMA_CHANNEL);
    EDMA_CreateHandle(&g_lpuartRxEdmaHandle, EXAMPLE_LPUART_DMA_BASEADDR, LPUART_RX_DMA_CHANNEL);

    /* Create LPUART DMA handle. */
    /*
    API中禁止了UART RX FIFO的使用，其实可以在idle中检查FIFO缓冲区中剩余字符

    对于callback：驱动中使用了两级callback：
    第一级是LPUART_SendEDMACallback/LPUART_ReceiveEDMACallback由驱动直接调用的私有callback，分别在g_lpuartRxEdmaHandle/g_lpuartTxEdmaHandle中
    第二级是用户自定义callback，在g_lpuartEdmaHandle中，
    */
    LPUART_TransferCreateHandleEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, LPUART_UserCallback, NULL, &g_lpuartTxEdmaHandle,
                                    &g_lpuartRxEdmaHandle);

	sendXfer.data		 = g_txBuffer;
	sendXfer.dataSize	 = UART_TX_BUFFER_LENGTH;
	
	receiveXfer.data	 = g_rxBuffer;
	receiveXfer.dataSize = UART_RX_BUFFER_LENGTH;

    //设置为接收状态
    /*
    接收的终结状态：
    1.接收DMA中断：在EDMA_HandleIRQ（）中处理,这个可以理解为错误，RXbuffer应该足够长
    2.UART idle中断：从已有DMA rx buffer中取出数据，处理，并发送回去
    3.一个字符都未收到
    */
#if (ENABLE_RTS_TRANSCEIVER == 0)
    Set_RS485_Receive();
#endif
	LPUART_ReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &receiveXfer);

	
    while(1)
    {
        if(reciveFrame)//接收到完整的数据包了，echo数据
        {
#if (SWO_PRINTF == 1)
            printf("send out %d\r\n",send_size);
#endif
            sendXfer.data		 = g_txBuffer;
	        sendXfer.dataSize	 = send_size;
#if (ENABLE_RTS_TRANSCEIVER == 0)
			Set_RS485_Transimit();
#endif
			LPUART_SendEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &sendXfer);

			reciveFrame = false;
        }

    }
}
