UART-to-Ethernet Loopback Example

This post is the final step of this project. In this post, I implement an example that receives data from ethernet and feed back through UART, then send data to the peer system via ethernet again, and a brief explanation is attached.

Variable Declaration

As UART communication is done with DMA, we need some buffers for UART DMA Tx and UART DMA Rx. And we don’t know when data arrives in UART DMA Rx so that we need a pointer to data just arrived and another pointer to data to read next time.

Below explains this concept.

DMARxBuf is the most important. I define UART DMA Rx as a circular mode so that I don’t know when data arrive and it may happen that new data overwrite a valid data which is not processed yet due to ring structure and continuous receiving.

To avoid data overwriting, I should copy data into a User Buffer(RxBuf in here) whenever DMA Rx related event happens.

rdPtr, wrPtr and rcvdLen are variables for data copy. And rcvdFlag is a variable to inform where data copy is needed. Please refer to the attached code to know how to use these variables.

Below code is the example of variables and buffers declaration.

...in main.c

//Rx/Tx buffer for DMA processing
uint8_t DMATxBuf[4][DATA_BUF_SIZE] = {0,};
uint8_t DMARxBuf[4][DATA_BUF_SIZE] = {0,};

uint8_t RxBuf[4][DATA_BUF_SIZE] = {0,};

//UART
uint8_t UartTxEnable[4] = {1, 1, 1, 1};
uint8_t UartRxEnable[4] = {0, 0, 0, 0};

uint8_t DMATxStart[4] = {0, 0, 0, 0};

uint16_t ethDataLen3 = 0;
uint16_t ethDataLen4 = 0;

uint16_t _TX_Count[4] = {0,};
uint16_t _HT_Count[4] = {0,};
uint16_t _TC_Count[4] = {0,};
uint16_t _IDLE_Count[4] = {0,};

uint32_t totalSentBytes[4] = {0,};
uint32_t totalRcvdBytes[4] = {0,};

uint16_t wrPtr[4] = {0,};
uint16_t rdPtr[4] = {0,};
uint16_t rcvdLen[4] = {0,};
uint8_t rcvFlag[4] = {0,};

uint8_t ch;

UART DMA Rx Configuration

Do ‘Enable’ UART DMA Rx as below.

...main.c

  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  HAL_UART_Receive_DMA(&huart1, DMARxBuf[0], DATA_BUF_SIZE);

  __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
  HAL_UART_Receive_DMA(&huart2, DMARxBuf[1], DATA_BUF_SIZE);

  __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
  HAL_UART_Receive_DMA(&huart3, DMARxBuf[2], DATA_BUF_SIZE);

  __HAL_UART_ENABLE_IT(&huart4, UART_IT_IDLE);
  HAL_UART_Receive_DMA(&huart4, DMARxBuf[3], DATA_BUF_SIZE);

위와 같이 UART DMA Rx가 발생하면 수신한 데이터를 저장할 버퍼를 지정한다.

Modify Printf related codes.

Doing DMA transmission on multiple UART ports requires rapid processing. But printf() statements for printing out debug codes must affect to the system performance, so change some codes as below.

First, edit _write() function.


uint8_t debugBuf[DEBUG_BUF_SIZE];
uint16_t debugWrite = 0;
uint16_t debugRead = 0;
uint8_t uart5TxBuf[DEBUG_BUF_SIZE];
uint16_t uart5TxLen = 0;

int _write(int file, char *ptr, int len)
{

	for(int i=0; i<len; i++)
	{
		debugBuf[debugWrite++] = ptr[i];
		if(debugWrite >= DEBUG_BUF_SIZE)
			debugWrite = 0;
	}

	return len;
}

Next, add below code in while() block.

if(HAL_UART_GetState(&huart5) == HAL_UART_STATE_READY)
{
     if(debugWrite != debugRead)
     {
          if(debugWrite > debugRead)
          {
               uart5TxLen = debugWrite - debugRead;
               memcpy(uart5TxBuf, debugBuf + debugRead, uart5TxLen);
               debugRead = debugWrite;
          }else
          {
               uart5TxLen = DEBUG_BUF_SIZE - debugRead;
               memcpy(uart5TxBuf, debugBuf + debugRead, uart5TxLen);
               memcpy(uart5TxBuf + uart5TxLen, debugBuf, debugWrite);
               uart5TxLen += debugWrite;
               debugRead = debugWrite;
          }
          HAL_UART_Transmit_IT(&huart5, uart5TxBuf, uart5TxLen);
     }
}

Write U2E_tcps() function

socket connect, disconnect, send, receive, and UART communication are implemented in U2E_tcps() function as below.

int32_t U2E_tcps(uint8_t sn, uint16_t port)
{
   int32_t ret;
   uint16_t size = 0, sentsize=0;
   uint16_t i;

   uint8_t buf[DATA_BUF_SIZE] = {0,};

#ifdef _LOOPBACK_DEBUG_
   uint8_t destip[4];
   uint16_t destport;
#endif

   switch(getSn_SR(sn))
   {
      case SOCK_ESTABLISHED :
         if(getSn_IR(sn) & Sn_IR_CON)
         {
#ifdef _LOOPBACK_DEBUG_
			getSn_DIPR(sn, destip);
			destport = getSn_DPORT(sn);

			printf("%d:Connected - %d.%d.%d.%d : %drn",sn, destip[0], destip[1], destip[2], destip[3], destport);
#endif
			setSn_IR(sn,Sn_IR_CON);
         }

//		if(DMATxStart[sn])
		if(UartTxEnable[sn])
     	{
//			printf("data to read arrivedrn");
			if((size = getSn_RX_RSR(sn)) > 0) // Don't need to check SOCKERR_BUSY because it doesn't occur.
			{
				if(size > DATA_BUF_SIZE) size = DATA_BUF_SIZE;
				ret = recv(sn, DMATxBuf[sn], size);
				printf("rcvd bytes: %drn", ret);
//				printf("rnrcvd data: rn%srnrn", DMATxBuf[sn]);

				HAL_UART_Transmit_DMA(gethuart(sn), DMATxBuf[sn], ret);
				UartTxEnable[sn] = 0;
//				DMATxStart[sn] = 0;
				_TX_Count[sn] += 1;
				totalSentBytes[sn] += ret;
				printf("================rn");
				printf("UART%d HAL_UART_Transmit_DMA sent: %d bytesrn", (sn + 1), ret);
			}
     	}

		// DMA Rx Data가 있으면 User Buffer로 복사한다.
		if(rcvFlag[sn])
		{
	//		huart = gethuart(ch);
			printf("ch[%d] rcvFlag: %d, %d, %d, %drn", sn, rcvFlag[0], rcvFlag[1], rcvFlag[2], rcvFlag[3]);
			rcvFlag[sn] = 0;
			printf("ch[%d] rcvFlag: %d, %d, %d, %drn", sn, rcvFlag[0], rcvFlag[1], rcvFlag[2], rcvFlag[3]);

			UART_Data_Process(gethuart(sn));
			if(rcvdLen[sn] > 0)
			{
//				printf("rnRxBuf[%d] rcvdLen: %d rn%srn", sn, rcvdLen[sn], RxBuf[sn]);
				ret = send(sn, RxBuf[sn], rcvdLen[sn]);
				printf("sent bytes: %drn", ret);
			}
			printf("CH[%d] _TX_Count: %d, _HT_Count: %d, _TC_Count: %d, _IDLE_Count: %d, rdPtr: %d, wrPtr: %d, rcvdLen: %d, totalSentBytes: %d, totalRcvdBytes: %drn",
					sn, _TX_Count[sn], _HT_Count[sn], _TC_Count[sn], _IDLE_Count[sn], rdPtr[sn], wrPtr[sn], rcvdLen[sn], totalSentBytes[sn], totalRcvdBytes[sn]);
		}

         break;
      case SOCK_CLOSE_WAIT :
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:CloseWaitrn",sn);
#endif
         if((ret = disconnect(sn)) != SOCK_OK) return ret;
#ifdef _LOOPBACK_DEBUG_
         printf("%d:Socket Closedrn", sn);
#endif
         break;
      case SOCK_INIT :
#ifdef _LOOPBACK_DEBUG_
    	 printf("%d:Listen, TCP server loopback, port [%d]rn", sn, port);
#endif
         if( (ret = listen(sn)) != SOCK_OK) return ret;
         break;
      case SOCK_CLOSED:
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:TCP server loopback startrn",sn);
#endif
         if((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) return ret;
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:Socket openedrn",sn);
#endif
         break;
      default:
         break;
   }
   return 1;
}

The most important thing here is SOCK_ESTABLISHED status block.

In here, I check UartTxEnable variable whether UART DMA is still on progress. If DMA transmission completed (UartTxEnable is True), receive data from ethernet socket (if data is available) and then write data into DMATxBuf, finally call HAL_UART_Transmit_DMA to start UART DMA Tx.

And, I check rcvdFlag to know whether data is in DMARxBuf. If it is True, call UART_Data_Process() in order to copy data from DMATxBuf to User buffer(RxBuf in this example), Then send data in RxBuf back to the peer system with calling send()

This process is done recursively.

U2E_tcps() is called in while() block of main.c.

...main.c

U2E_tcps(1, 5000);
U2E_tcps(2, 5001);
U2E_tcps(3, 5002);

The above codes mean that socket 1 is listening to port num 5000, socket 2 port number 5001, socket3 port number 5002. socket number is corresponding to UART port number. socket 1 is related to UART2, socket 2 is UART3, socket 3 UART4.

Result

Ethernet Loopback Test is testes with AX1.exe from WIZnet.

When I used three UARTs simultaneously, Tx throughput is 0.5Mbps and Rx throughput is 0.4M bps each.

You can download the whole project file of today on below link.

https://github.com/javakys/WIZ14xSR_Proj/releases/tag/Ver0.8

1 Comment

Leave a Comment