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.