UART-to-Ethernet Loopback 예제

이번 포스팅은 본 프로젝트의 마지막 단계로 Ethernet에서 수신한 데이터를 UART를 통해서 Loopback 한 다음에 Peer system으로 되돌려주는 예제를 구현하고 구현 내용을 설명한다.

변수 정의

UART 송수신을 DMA를 통해서 수행하기 때문에 UART DMA Tx를 위한 버퍼와 UART DMA Rx를 위한 버퍼가 있어야 한다. 또, UART DMA Rx의 경우는 데이터가 수신되는 시점을 알 수 없기 때문에 현재 수신된 데이터를 가리키는 포인터와 수신 버퍼에서 다음에 읽어가야할 데이터를 가리키는 포인터가 필요하다.

아래 그림은 이런 개념을 설명하고 있다.

여기서 가장 중요한 버퍼는 DMARxBuf인데 UART DMA Rx는 Circular 모드로 지정하였기 때문에 데이터가 언제 수신될지 알 수 없고 데이터가 수신되면 버퍼의 링 구조에 의해서 이미 유효한 데이터가 있는 지에 관계없이 그 다음 위치에 덮어 써 지게된다.

데이터 오버라이트가 발생하지 않도록 하기 위해서는 DMA Rx 관련 이벤트가 발생할 때 마다 DMARxBuf에 수신된 데이터를 User Buffer(RxBuf와 같은)로 복사해 두어야한다.

rdPtr, wrPtr, rcvdLen은 데이터 복사를 위해 필요한 변수 값이고 rcvdFlag는 데이터 복사가 필요한지를 표시하는 변수이다. 이들 변수를 어떻게 사용하는 지는 첨부한 예제 코드를 참조하기 바란다.

아래 코드는 위에 언급한 변수, 버퍼를 선언한 부분의 예이다.

...in main.c

//DMA 처리를 위한 송수신 버퍼
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 설정하기

아래 코드와 같이 uART DMA Rx를 ‘Enable’한다.

...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가 발생하면 수신한 데이터를 저장할 버퍼를 지정한다.

Printf 처리 수정하기

여러개의 UART 포트에 대해서 DMA로 송수신을 하는 것은 아주 빠른 처리를 필요로 한다. 이 과정에서 디버그 코드 출력을 위한 printf문이 성능에 영향을 줄 수 있기 때문에 다음과 같이 수정한다.

먼저 _write() 함수를 수정한다.


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;
}

다음은 while() 문 내에서 아래 코드를 추가한다.

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);
     }
}

U2E_tcps() 함수 작성하기

socket 연결, 연결해제, 이더넷 데이터 송수신 및 UART 데이터 송수신 등의 처리를 하는 함수로 U2E_tcps() 함수를 아래와 같이 구성한다.

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 : %d\r\n",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 arrived\r\n");
			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: %d\r\n", ret);
//				printf("\r\nrcvd data: \r\n%s\r\n\r\n", 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("================\r\n");
				printf("UART%d HAL_UART_Transmit_DMA sent: %d bytes\r\n", (sn + 1), ret);
			}
     	}

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

			UART_Data_Process(gethuart(sn));
			if(rcvdLen[sn] > 0)
			{
//				printf("\r\nRxBuf[%d] rcvdLen: %d \r\n%s\r\n", sn, rcvdLen[sn], RxBuf[sn]);
				ret = send(sn, RxBuf[sn], rcvdLen[sn]);
				printf("sent bytes: %d\r\n", 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: %d\r\n",
					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:CloseWait\r\n",sn);
#endif
         if((ret = disconnect(sn)) != SOCK_OK) return ret;
#ifdef _LOOPBACK_DEBUG_
         printf("%d:Socket Closed\r\n", sn);
#endif
         break;
      case SOCK_INIT :
#ifdef _LOOPBACK_DEBUG_
    	 printf("%d:Listen, TCP server loopback, port [%d]\r\n", sn, port);
#endif
         if( (ret = listen(sn)) != SOCK_OK) return ret;
         break;
      case SOCK_CLOSED:
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:TCP server loopback start\r\n",sn);
#endif
         if((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) return ret;
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:Socket opened\r\n",sn);
#endif
         break;
      default:
         break;
   }
   return 1;
}

여기서 가장 중요한 부분은 SOCK_ESTABLISHED 상태에서의 처리이다.

UartTxEnable 변수를 확인해서 관련 UART에서 아직 DMA 송신이 완료되지 않았는지를 확인해서 DMA 송신이 완료되었다면 이더넷에서 수신한 데이터가 있는 지를 확인한 후, 수신 데이터를 읽어와서 DMATxBuf로 옮기고 HAL_UART_Transmit_DMA 명령을 내린다.

또, DMARxBuf에 수신 데이터가 있는 지를 rcvdFlag를 체크해서 확인하고 수신 데이터가 있으면 UART_Data_Process() 함수를 호출해서 DMARxBuf에 수신된 데이터를 User 버퍼인 RxBuf로 안전하게 옮긴 후, RxBuf로 옮겨진 데이터를 send() 함수를 호출해서 ethernet를 통해서 상대방에게 전송한다.

이 과정은 반복적으로 수행한다.

이렇게 구성된 U2E_tcps() 함수는 main.c내의 while()문에서 호출되도록 한다.

...main.c

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

위 코드는 socket 1 번은 포트번호 5000번, socket 2번은 포트번호 5001번, socket3번은 포트번호 5002번으로 연결대기 중이라는 것을 의미하며. socket 번호는 UART 포트 번호와 상호 부합한다. socket 1번은 UART2, socket 2번은 UART3, socket 3번은 UART4 와 연결된다.

테스트 결과

Ethernet 과 연계된 Loopback Test는 WIZnet의 툴인 AX1.exe를 사용해서 수행하였다.

세개 UART를 동시에 사용했을 때, 각 포트당 평균 송신0.5Mbps, 수신 0.4M bps의 성능이 나오는 것을 확인할 수 있다.

여기까지 진행된 코드는 다음에서 다운로드할 수 있다.

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

1 Comment

Leave a Comment