본문 바로가기
임베디드 개발/STM32 (ARM Cortex-M)

STM32 ] TCP Client, lwIP Raw API

by eteo 2022. 10. 3.

 

 

 

 

 

CubeMX 설정

 

클라이언트가 서버에게 1초마다 한번씩 데이터를 전송하게끔 하기 위해 TIM1을 쓸 예정이다. TIM1은 APB2에서 클락소스를 공급받는다.

 

TIM1의 update interrupt를 enable한다.

 

 

 

아래 헤더파일과 소스파일을 포함한다.

tcpClientRAW.h
0.00MB
tcpClientRAW.c
0.01MB

 

 

 

 

 

 

 

main.c

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "tcpClientRAW.h"
/* USER CODE END Includes */

// ...

/* USER CODE BEGIN PV */
extern struct netif gnetif;
/* USER CODE END PV */

// ...

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_RTC_Init();
  MX_LWIP_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim1);
  tcp_client_init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  MX_LWIP_Process();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

코드는 서버 때와 비슷하다. 단 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 함수가 호출되도록 HAL_TIM_Base_Start_IT(&htim1); 해준다.

 

 

 

 

 

 

void tcp_client_init(void) 함수

/* IMPLEMENTATION FOR TCP CLIENT

1. Create TCP block.
2. connect to the server
3. start communicating
*/

void tcp_client_init(void)
{
	/* 1. create new tcp pcb */
	struct tcp_pcb *tpcb;

	tpcb = tcp_new();

	/* 2. Connect to the server */
	ip_addr_t destIPADDR;
	IP_ADDR4(&destIPADDR, 192, 168, 0, 11);
	tcp_connect(tpcb, &destIPADDR, 31, tcp_client_connected);
}

 

TCP 블락을 만들고 목적지 IP와 포트 no. 를 파라미터로 tcp_connect() 함수를 통해 서버와 연결한다.

 

 

 

 

 

 

 

static err_t tcp_client_connected(void *arg, struct tcp_pcb *newpcb, err_t err) 함수

/** This callback is called, when the client is connected to the server
 * Here we will initialise few other callbacks
 * and in the end, call the client handle function
  */
static err_t tcp_client_connected(void *arg, struct tcp_pcb *newpcb, err_t err)
{
  err_t ret_err;
  struct tcp_client_struct *es;

  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(err);

  /* allocate structure es to maintain tcp connection information */
  es = (struct tcp_client_struct *)mem_malloc(sizeof(struct tcp_client_struct));
  if (es != NULL)
  {
    es->state = ES_CONNECTED;
    es->pcb = newpcb;
    es->retries = 0;
    es->p = NULL;

    /* pass newly allocated es structure as argument to newpcb */
    tcp_arg(newpcb, es);

    /* initialize lwip tcp_recv callback function for newpcb  */
    tcp_recv(newpcb, tcp_client_recv);

    /* initialize lwip tcp_poll callback function for newpcb */
    tcp_poll(newpcb, tcp_client_poll, 0);


    /* initialize LwIP tcp_sent callback function */
    tcp_sent(newpcb, tcp_client_sent);

    /* handle the TCP data */
    tcp_client_handle(newpcb, es);

    ret_err = ERR_OK;
  }
  else
  {
    /*  close tcp connection */
    tcp_client_connection_close(newpcb, es);
    /* return memory error */
    ret_err = ERR_MEM;
  }
  return ret_err;
}

 

클라이언트가 서버와 연결되면 이  함수가 호출되고 또 몇몇 콜백함수를 지정한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) 함수

/** This callback is called, when the client receives some data from the server
 * if the data received is valid, we will handle the data in the client handle function
  */
static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
  struct tcp_client_struct *es;
  err_t ret_err;

  LWIP_ASSERT("arg != NULL",arg != NULL);

  es = (struct tcp_client_struct *)arg;

  /* if we receive an empty tcp frame from server => close connection */
  if (p == NULL)
  {
    /* remote host closed connection */
    es->state = ES_CLOSING;
    if(es->p == NULL)
    {
       /* we're done sending, close connection */
       tcp_client_connection_close(tpcb, es);
    }
    else
    {
      /* we're not done yet */
//      /* acknowledge received packet */
//      tcp_sent(tpcb, tcp_client_sent);

      /* send remaining data*/
//      tcp_client_send(tpcb, es);
    }
    ret_err = ERR_OK;
  }
  /* else : a non empty frame was received from server but for some reason err != ERR_OK */
  else if(err != ERR_OK)
  {
    /* free received pbuf*/
    if (p != NULL)
    {
      es->p = NULL;
      pbuf_free(p);
    }
    ret_err = err;
  }
  else if(es->state == ES_CONNECTED)
  {
   /* store reference to incoming pbuf (chain) */
    es->p = p;

    // tcp_sent has already been initialized in the beginning.
//    /* initialize LwIP tcp_sent callback function */
//    tcp_sent(tpcb, tcp_client_sent);

    /* Acknowledge the received data */
    tcp_recved(tpcb, p->tot_len);

    /* handle the received data */
    tcp_client_handle(tpcb, es);

    pbuf_free(p);

    ret_err = ERR_OK;
  }
  else if(es->state == ES_CLOSING)
  {
    /* odd case, remote side closing twice, trash data */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  else
  {
    /* unknown es->state, trash data  */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  return ret_err;
}

 

서버로 부터 데이터를 수신하면 콜백되는 함수다. ES_CONNECTED 상태일 때는 해당 분기로 들어와서 수신한 데이터를 필요에 따른 처리를 하고 수신했음을 확인한다.

 

 

int counter = 0;

// ...

/* Handle the incoming TCP Data */

static void tcp_client_handle (struct tcp_pcb *tpcb, struct tcp_client_struct *es)
{
	/* get the Remote IP */
	ip4_addr_t inIP = tpcb->remote_ip;
	uint16_t inPort = tpcb->remote_port;

	/* Extract the IP */
	char *remIP = ipaddr_ntoa(&inIP);

//	esTx->state = es->state;
//	esTx->pcb = es->pcb;
//	esTx->p = es->p;

	esTx = es;
	pcbTx = tpcb;

	counter++;

}

 

여기선 수신 데이터를 따로 처리는 안하고 전역으로 선언된 counter 변수를 ++ 한다. 그리고 전역 구조체 변수인 exTx와 pcbTx에 파라미터로 들어온값을 대입한다.

 

 

 

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 함수

extern TIM_HandleTypeDef htim1;

/* create a struct to store data */
struct tcp_client_struct *esTx = 0;

struct tcp_pcb *pcbTx = 0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	char buf[100];

	/* Prepare the first message to send to the server */
	int len = sprintf (buf, "Sending TCPclient Message %d\n", counter);

	if (counter !=0)
	{
		/* allocate pbuf */
		esTx->p = pbuf_alloc(PBUF_TRANSPORT, len , PBUF_POOL);


		/* copy data to pbuf */
		pbuf_take(esTx->p, (char*)buf, len);

		tcp_client_send(pcbTx, esTx);

		pbuf_free(esTx->p);
	}

}

1초마다 콜백되게 설정. pbuf_alloc()함수로 pbuf를 동적할당하고 pbuf_take로 데이터를 pbuf에 카피한다. 그리고 tcp_client_send() 함수를 사용에 서버로 데이터를 전송한 후 할당한 pbuf는 pbuf_free로 해제해준다.

 

 

 

 

 

tcp_client_send 함수

/** A function to send the data to the server
  */
static void tcp_client_send(struct tcp_pcb *tpcb, struct tcp_client_struct *es)
{
  struct pbuf *ptr;
  err_t wr_err = ERR_OK;

  while ((wr_err == ERR_OK) &&
         (es->p != NULL) &&
         (es->p->len <= tcp_sndbuf(tpcb)))
  {

    /* get pointer on pbuf from es structure */
    ptr = es->p;

    /* enqueue data for transmission */
    wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1);

    if (wr_err == ERR_OK)
    {
      u16_t plen;
      u8_t freed;

      plen = ptr->len;

      /* continue with next pbuf in chain (if any) */
      es->p = ptr->next;

      if(es->p != NULL)
      {
        /* increment reference count for es->p */
        pbuf_ref(es->p);
      }

     /* chop first pbuf from chain */
      do
      {
        /* try hard to free pbuf */
        freed = pbuf_free(ptr);
      }
      while(freed == 0);
     /* we can read more data now */
//     tcp_recved(tpcb, plen);
   }
   else if(wr_err == ERR_MEM)
   {
      /* we are low on memory, try later / harder, defer to poll */
     es->p = ptr;
   }
   else
   {
     /* other problem ?? */
   }
  }
}