UDP Server using LWIP NETCONN (RTOS)

This is yet another tutorial in the STM32 ETHERNET Series, but with this tutorial we will start the Ethernet with RTOS, NETCONN to be precise. So far we have covered the UDP, TCP and HTTP protocols, but they all were using the RAW Library, which is not how the ETHERNET is used generally.

It was just to get the idea about what each protocol can do, and how they work on the basic level. Starting this tutorial onwards, we will use the NETCONN Library, which uses the RTOS, and which is actually another layer on top of the basic UDP, TCP functions.

So in this tutorial, we will start with the UDP Server using the NETCONN.

NOTE: I am using STM32F750 Discovery Board, which have the RMII connection type and no memory configuration option. If you want the one with the memory configuration, check out https://youtu.be/Wg3edgNUsTk

CubeMX Setup

Let’s start with enabling the ethernet itself :

  • I have chosen the RMII connection type for the ethernet.
  • The PHY Address is set to 0, because I am using the on board Ethernet module.
  • The Rx Mode is set to Interrupt mode.

Since I have selected the Rx mode as the interrupt mode, the Ethernet global interrupt gets turned on automatically.


Picture above shows the Pins used for the Ethernet. Note that all the Pins are set to “Very High” Speed.


Now let’s take a look at the Free RTOS Settings

  • As shown above, I have enabled the FreeRTOS and I am using the CMSIS_V2. If you are not comfortable with V2, you can use V1 also.
  • Everything else is set to default, and I haven’t made any changes in RTOS settings.

As I mentioned, everything is set to default and you can see there is a default Task which gets created automatically. I am leaving this as it is and we will make the use of it later in our code.


Let’s see the LWIP Configuration

  • As shown in the picture above, I have disabled the DHCP and manually entered the addresses. This will help us getting the static IP also.
  • Since we are using the RTOS, you can see it’s enabled in the “RTOS Dependency” section.

  • In the “Key Options” Tab I have increased the heap to 10 KiloBytes.
  • Other than this, everything is still set to default.


The following Configuration is only for the Cortex M7 series MCUs

Now since I am using a cortex M7 based MCU, which don’t have enough Flash memory. I have to use the external flash and need to configure the MPU for the same. The following part of the configuration is only valid for the same.

  • The MPU Control mode is set to “Background Region Privileged access only + MPU disabled during hardfault”
  • I have enabled the Instruction prefetch, CPU ICache and DCache.

Following is the MPU Configuration for the external Flash, which is located at the memory address 0x90000000 and have the size of 16 MB.

  • Basically I have set the 16 MB Region (external flash) as the cacheable and bufferable region. This will configure the memory region as the Normal memory type with write back attribute.
  • The 1 MB in the beginning will also allow the instructions to be executed from this region.

If you want to learn more about MPU configuration and memory types, I would recommend you to watch STM32 MPU Config playlist on the topic https://www.youtube.com/playlist?list=PLfIJKC1ud8gjoY2McCCqkxXWuwiC2iSap



Some Insight into the code

The main function:

int main(void)
{
  /* Only related to cortex M7--------------------------------------------------------*/
  MPU_Config();
  
  SCB_EnableICache();

  SCB_EnableDCache();
  /*--------------------------------------------------------*/

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();

  osKernelInitialize();

  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

  osKernelStart();

  while (1)
  {

  }
}
  • Nothing special is happening in the Main function. The default task gets created, and the kernel is started.
  • The MPU configuration part is related to cortex M7 based MCUs

The default Task:

void StartDefaultTask(void *argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN 5 */

  udpserver_init();

  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END 5 */
}
  • In the default task, we will call the udpserver_init() function to initialize the UDP server ofcourse.
  • The MX_LWIP_Init() function initializes the LWIP and it is present in the default Task by default.

The udpserver_init Function:

void udpserver_init(void)
{
  sys_thread_new("udp_thread", udp_thread, NULL, DEFAULT_THREAD_STACKSIZE,osPriorityNormal);
}

Here we will create a new thread, called the udp thread.

  • The entry function will be udp_thread
  • The parameter is NULL
  • The Stack Size is set to Default i.e. 1024 Bytes
  • And the priority is set to Normal

The udp_thread:

static void udp_thread(void *arg)
{
	err_t err, recv_err;
	struct pbuf *txBuf;

	/* Create a new connection identifier */
	conn = netconn_new(NETCONN_UDP);

	if (conn!= NULL)
	{
		/* Bind connection to the port 7 */
		err = netconn_bind(conn, IP_ADDR_ANY, 7);

		if (err == ERR_OK)
		{
			/* The while loop will run everytime this Task is executed */
			while (1)
			{
				/* Receive the data from the connection */
				recv_err = netconn_recv(conn, &buf);

				if (recv_err == ERR_OK) // if the data is received
				{
					addr = netbuf_fromaddr(buf);  // get the address of the client
					port = netbuf_fromport(buf);  // get the Port of the client
					strcpy (msg, buf->p->payload);   // get the message from the client

					// Or modify the message received, so that we can send it back to the client
					int len = sprintf (smsg, "\"%s\" was sent by the Client\n", (char *) buf->p->payload);

					/* allocate pbuf from RAM*/
					txBuf = pbuf_alloc(PBUF_TRANSPORT,len, PBUF_RAM);

					/* copy the data into the buffer  */
					pbuf_take(txBuf, smsg, len);

					// refer the nebuf->pbuf to our pbuf
					buf->p = txBuf;

					netconn_connect(conn, addr, port);  // connect to the destination address and port

					netconn_send(conn,buf);  // send the netbuf to the client

					buf->addr.addr = 0;  // clear the address
					pbuf_free(txBuf);   // clear the pbuf
					netbuf_delete(buf);  // delete the netbuf
				}
			}
		}
		else
		{
			netconn_delete(conn);
		}
	}
}
  • Here first of all we will create a new netconn connection. NETCONN_UDP argument will create a UDP connection.
  • Next we will bind the connection to any available IP address and the Port 7. This will act as the IP and port of the server.
  • Then we will receive the data from the client. The netconn_recv function waits until the data is received.
  • After receiving the data, we will extract the address, port and message send by the client.
  • Since this is only a testing tutorial, we will modify this message, and store it in the smgs, so that we can send it back to client.
  • Next we must save the message in the pbuf in order to send it back to the client.
  • To do so, first we have to allocate the RAM for the pbuf.
  • Then pbuf_take will be used to copy the modified message into the pbuf.
  • Next we will refer the netbuf->pbuf to our pbuf. This is because in Netconn, we can only send the nebuf, so we must update the reference of the netbuf->pbuf.
  • Next we will connect to the client using the address, and port we stored earlier.
  • Finally send the netbuf to the client, which contains the updated message.


Result

Below is the screenshot of the data sent by the client and the response sent by the server.


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

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