STM32 ETHERNET #5. TCP CLIENT

This is the 5th tutorial in the STM32 ETHERNET series, and today we will see how to use our STM32 as the TCP Client. I would recommend you to go through the previous tutorials first, as I won’t explain everything here, like connections.
Go through the link above, and see how the connection was made in the first tutorial. Also see the TCP server tutorial, so that it will be a little easier to understand this one.

Some Insight into the CODE

Initializing the TCP Client

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, 102);
	tcp_connect(tpcb, &destIPADDR, 31, tcp_client_connected);
}
  • First we need to create the TCP Block using the function tcp_new ()
  • Next we will connect to the server with the IP 192.168.0.102 and Port 31

Once the client is connected to the server, the client connected callback will be called. Below is the code for the same.


Client Connected Callback

This callback is called, once the connection between the server and the client is successful.

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;
}
  • As you can see above, this callback initializes few other callbacks.
  • for example, the tcp_poll callback, tcp_sent callback, tcp_recv callback etc.
  • These are needed for the processing, and you shouldn’t modify them.
  • Here we are only interested in the tcp_recv callback. This is where we will write our code to handle the incoming data.

Client Receive Callback

tcp_recv initializes the tcp_client_recv function. It is basically a callback, which is called whenever the client receive some data from the server.

We will write our code to handle this received data.

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;
  
  ..............................
  ..............................
  ..............................
  
  
  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;
  }
  
  ...........................
  ...........................
  ...........................
  
  return ret_err;
}
  • The dots in the code above shows some predefined code to handle the errors. You should leave it as it is.
  • If the state is switched to connected, we can process the received data.
  • here tpcb stores all the info about the server and client, and pbuf stores all the info about the data.
  • Here I am storing the reference to the incoming buffer into the “es” structure, which is later passed to client handle, so that we can make use of this incoming data.
  • Then we need to Acknowledge the received data, and we do that by using the function tcp_recved
  • Now tcp_client_hanldle is called to handle the data received from the server.
  • We could have handled the data here also, but I am trying to keep the similarities in the coding between different protocols, and this is why I have created that another function just for the purpose of handling data.
  • After the data has been processed, we will free the buffer using pbuf_free

Client data Handler

tcp_client_handle handles the incoming data from the server.

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

}
  • This is the function, where you can write the code according to your requirement.
  • I am not doing anything special, but just getting the IP of the server and incrementing the counter variable.
  • This counter variable will be later used while sending the data to the server.
  • If you want to make use of the incoming data, the information about the data can be found in the “es” structure, and the info about the server and client can be found in the “tpcb” structure.

Sending the data to the server

Just like the UDP client, here also I have created a timer callback, which will be called every second.
inside the callback, we will send the data to the server.

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

}
  • Here you can see above I am merging the counter value with a string.
  • Then pbuf_alloc will allocate the memory for the pbuf we are going to send
  • pbuf_take will copy the string into the payload of the pbuf we are sending
  • tcp_client_send will send the pbuf to the server
  • And finally we will free the memory using pbuf_free
  • SO the counter value will only increment, if the client handle function is called, which will only happen when the client receive some data from the server
  • otherwise the client will keep sending the same counter value to the server every second


RESULT

  • As shown in the figure above, the client was sending the old counter value.
  • As soon as the server sent some data, the counter was incremented, and the client started sending new counter value every second

Check out the Video Below




Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Subscribe
Notify of

15 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
keyboard_arrow_up