cancel
Showing results for 
Search instead for 
Did you mean: 

How to create a IPv4 NetXDuo Ethernet UDP application for STM32H5 with TrustZone® enabled

STea
ST Employee

Summary

Our STM32Cube_FW_H5 package contains several UDP application examples that can be of great help. This article provides a detailed guide on how to develop an application using NetXDuo and benefit from TrustZone® isolation and security features. By using CubeMX, you can easily start an application on NetXDuo with TrustZone® enabled.

Prerequisites

  • NUCLEO-H563
  • STM32CubeIDE v1.13.2
  • STM32CubeMX v6.9.2
  • STM32Cube_FW_H5
  • Terminal emulation software (Tera Term, PuTTY...)
  • Packet sender (or any UDP packet sending utility that supports both IPv4 and IPv6)

With CubeMX downloaded, you can install the relevant packages by selecting help -> manage embedded software packages.

1. CubeMX configuration

To start, we need to search for and select NUCLEO-H563ZI in the Board Selector

STM32CubeMX Board SelectorSTM32CubeMX Board Selector

 

 When prompted whether to initialize all peripherals with their default modes or not, select No.

Enable TrustZoneEnable TrustZone

 

Since we want our project to utilize TrustZone®, we need to select the option "with TrustZone activated."

Note: We recommend clearing the MCU pinout before starting the configuration.

2. UART configuration

To visualize the state of our application and debug messages, we need to start by setting up a UART communication connected to the onboard STLINK. Doing so gives the ability to print the IPv4 address and incoming data on the terminal emulator.
We start by enabling USART3 in the "nonsecure" runtime context and set the mode to asynchronous. We do not have to change any other settings as the default configuration already suit our needs:

UART configurationUART configuration

 

We need to map the UARTs GPIOs to the onboard STLINKs. In the GUI (graphical user interface), you can CTRL+left click and drag the pins to reassign them.

USART3_RX -> PD9
USART3_TX ->PD8

UART pinoutUART pinout

 

3. Ethernet peripheral configuration

To run this application, select the ETH peripheral and assign it to the nonsecure runtime context as an RMII interface.

ETHERNET pinoutETHERNET pinout

 

We change some settings of the GPIOs for the ETH peripheral to reflect the board layout. Also, the setup of GPIO speed in ETH GPIO settings to "Very High" speed.

Next, we need to alter the ETH peripheral settings by changing the "Maximum Output Speed" to "Very High" in the GPIO tab and reassigning the following GPIOs:

ETH_TXD0 -> PG13
ETH_TX_EN -> PG11

After doing so, we enable the "Ethernet Global Interrupt" in the NVIC settings to enable interrupt handling as shown in the figure below. This is automatically assigned to the NVIC_NS. If you have other interrupts to handle, you need to be careful when assigning other priorities as other high-priority interrupts could lead the Ethernet peripheral to stop functioning.

STea_7-1707240344837.pngNVIC settingsNVIC settings

 

To handle the communication, we need to enable both ThreadX and NetxDuo.

ThreadX is used as an RTOS (Real time operating system) for the initialization tasks. As for NetxDuo, it is used in the task creation that handles UDP traffic.

 

Middleware inclusionMiddleware inclusion

 

 

4. ThreadX configuration

Note that the ThreadX configuration can be modified according to the application. For example, the stack usage and memory pools allocated can be increased based on the number of threads handled by the RTOS.
STea_10-1707241010245.png

 

ThreadX configurationThreadX configuration

 

5. NetXDuo configuration

For the NetXDuo core middleware, we need to enable and assign it to the nonsecure runtime context.

STea_12-1707241196148.pngMohamedAB_0-1708008866161.png

 

The network interface should be set to "Ethernet interface" and the "Ethernet phy interface" to "LAN8742 phy interface." This is necessary for the middleware to identify the physical interface it uses, so that it can prepare the proper initialization code for it (see figure section 2 above.)

In the NetXDuo tab, it is recommended to consider changing the NetXDuo memory pool size to 30 K (1024 *30) Furthermore, the stack size to 2048 (1024*2) to allow for more flexibility in allocating packets and increasing stack sizes (see figure section 3 below).

NetXduo configurationNetXduo configuration

 

Additionally, it may be beneficial to adjust the IPv4 address to a more distinctive host address, depending on the specific needs and constraints of the application. As our demo utilizes the UDP protocol, we need to enable it in the configuration. These parameters can be customized to optimize the performance and functionality of the application (see figure section 4 above.)

6. Changing the system time base to accommodate ThreadX

When using an RTOS, it is configured by default to use the SysTick timer to manage the timing of its threads. Since the default configuration for HAL libraries is to use SysTick for its timing operations, we must change the timebase for the system so that only ThreadX uses the SysTick.

In the following configuration, the "SYS timebase source" is set to TIM6 thus it needs to be assigned to the nonsecure runtime context first.

STea_14-1707241520747.png

 

Timebase accommodationTimebase accommodation

 

7. Clock configuration

All that is left for the CubeMX configuration is to set up the clock. To do so, we need to adjust the RCC settings before we can access the clock configuration tab. We start by setting HSE to "BYPASS Clock Source" as this Nucleo board sources a highly accurate reference 8MHz clock via the STLINK's clock output. It is advised to utilize an external, accurate, and stable high-speed oscillator. An example could be a quartz oscillator rather than an internal one when working with Ethernet applications.

STea_16-1707241583499.pngClock configurationClock configuration

 

That is it for the STM32CubeMX configuration. Navigate to the project manager tab, give your project a name, and generate the code for STM32CubeIDE

8. Adding the necessary code in STM32CubeIDE

We need to add some code to the linker file before we can move on to the application-level code. We must ensure that the linker considers the fact that the Ethernet descriptors and NetX memory pool are located inside a particular memory address range. Our configurations are not considered by the default linker file initial configuration. Start by opening the STM32H563ZITX_FLASH.ld linker file.

linker file of secure projectlinker file of secure project

 

By default, the linker already has a definition for the RAM in which our descriptors and Ethernet Rx buffers are written:

 

 

MEMORY
{
  RAM    (xrw)    : ORIGIN = 0x30000000,   LENGTH = 320K
  FLASH    (rx)    : ORIGIN = 0x0C000000,   LENGTH = 1016K
  FLASH_NSC    (rx)    : ORIGIN = 0x0C0FE000,   LENGTH = 8K
}

 

 Now, we just need to add a new memory section somewhere in the SECTIONS brackets in the linker file.

 

 

.tcp_sec (NOLOAD) : 
  {
	
    . = ABSOLUTE(0x30040000);
    *(.RxDecripSection)

    . = ABSOLUTE(0x30040060);
    *(.TxDecripSection)
    
    . = ABSOLUTE(0x30040200);
    *(.NetXPoolSection)
	
  } >RAM 

 

The descriptors and pool sections are placed in RAM, particularly within the previously mentioned SRAM3 which is assigned to the secure runtime context. Now, we need to provide access to those descriptors placed in the SRAM3 domain from the nonsecure domain. This is done by adding the following code segment in the MX_GTZC_S_Init(void) function found in the "main.c" file of the secure project.

 

static void MX_GTZC_S_Init(void)
{

  /* USER CODE BEGIN GTZC_S_Init 0 */
	 if (HAL_GTZC_TZSC_ConfigPeriphAttributes(GTZC_PERIPH_ETHERNET, GTZC_TZSC_PERIPH_PRIV) != HAL_OK)
	  {
	    Error_Handler();
	  }
  /* USER CODE END GTZC_S_Init 0 */
**THE REST STAYS AS GENERATED BY CUBEMX**

 


In the "main.c" file of the nonsecure project, we need to assign DMA Rx and Tx descriptors. Assign them to their dedicated regions previously defined in the linker file:

 

ETH_DMADescTypeDef  DMARxDscrTab[ETH_RX_DESC_CNT]__attribute__((section(".RxDecripSection"))); /* Ethernet Rx DMA Descriptors */
ETH_DMADescTypeDef  DMATxDscrTab[ETH_TX_DESC_CNT]__attribute__((section(".TxDecripSection")));/* Ethernet Tx DMA Descriptors */

 

In the "app_azure_rtos.c" file of the nonsecure project, we need to add the following definition for the NX_Pool_Buffer as shown in the figures below:

 

/* USER CODE BEGIN NX_Pool_Buffer */

#if defined (__GNUC__)
__attribute__ ((section(".NetXPoolSection")))
#endif

/* USER CODE END NX_Pool_Buffer */

 

 

STea_0-1707327884430.png

Next we need to modify the NVIC_INIT_ITNS3_VAL definition in /Core/Inc/partition_stm32h563xx.h file in the secure project to enable the ETH_IT. Usually, this step should be autogenerated and will be fixed in the upcoming updated version.

 

#define NVIC_INIT_ITNS3_VAL      0x00000400

 

Now that the pool and descriptors are placed in the desired addresses in SRAM3, we can proceed with the application-level source code.

9. Main application-level code

First and foremost, we overload the "putchar" function allowing us to print debug messages with our USART virtual com port using "printf" as shown below.

 

/* USER CODE BEGIN 0 */


int __io_putchar(int ch)
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART3 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}

/* USER CODE END 0 */

 

We can add the code that creates the UDP socket and receives messages. Since NetXDuo is outlined to work with ThreadX, the generated code has set up a framework to create an entry task for your NetXDuo application. In "main.c,"CubeMX has as of now generated code that calls a function to initialize ThreadX and NetXDuo. On-execution, the program executes the MX_NetXDuo_Init function in the "app_netxduo.c" file. This function initializes the IPv4 settings and creates the entry thread for the NetXDuo application. This file,`app_netxduo.c`, is where we add the main application code. 

First, we need to include "main.h" to use printf as shown below. Do not forget to include the "stdio.h" library in the "main.h" of the nonsecure project.

 

/* USER CODE BEGIN Includes */
#include "main.h"
/* USER CODE END Includes */

 

Next, we define a helper macro function that allows us to print out our IPv4 address on the UART virtual COM port:

 

/* USER CODE BEGIN PD */

#define PRINT_IP_ADDRESS(addr)    do { \
                                       printf("STM32 %s: %lu.%lu.%lu.%lu \n", #addr, \
                                       (addr >> 24) & 0xff,                          \
                                       (addr >> 16) & 0xff,                          \
                                       (addr >>   & 0xff,                          \
                                       (addr & 0xff));                               \
                                     } while(0)

/* USER CODE END PD */

 

We need to declare some global variables for later use with the IP address initialization. Then we need to import our UART handle from `main.c` in the nonsecure project so we can print UDP packets on the virtual COM port:

 

/* USER CODE BEGIN PV */
NX_UDP_SOCKET UDPSocket;
ULONG IpAddress;
ULONG NetMask;
extern UART_HandleTypeDef huart3;
/* USER CODE END PV */

 

Lastly, we can add the main application code within the nx_app_thread_entry(ULONG thread_input) function that does the following tasks:

  • Print out the IPv4 address
  • Create a UDP socket
  • Bind the socket to port 6000
  • Actively listen for incoming messages and print them on the UART virtual COM port

 

static VOID nx_app_thread_entry (ULONG thread_input)
{
	 /* USER CODE BEGIN Nx_App_Thread_Entry 0 */
	    UINT ret;
	    ULONG bytes_read;
	    UCHAR data_buffer[512];
	    NX_PACKET *data_packet;
	    /*
	     * Print IPv4
	     */
	    ret = nx_ip_address_get(&NetXDuoEthIpInstance, &IpAddress, &NetMask);
	    if (ret != TX_SUCCESS)
	    {
	    Error_Handler();
	    }
	    else
	    {
	        PRINT_IP_ADDRESS(IpAddress);
	    }

	    /*
	     * Create a UDP Socket and bind to it
	     */

	    ret = nx_udp_socket_create(&NetXDuoEthIpInstance, &UDPSocket, "UDP Server Socket", NX_IP_NORMAL, NX_FRAGMENT_OKAY, NX_IP_TIME_TO_LIVE, 512);
	    if (ret != NX_SUCCESS)
	    {
	        Error_Handler();
	    }

	  /* Bind to port 6000 */
	    ret = nx_udp_socket_bind(&UDPSocket, 6000, TX_WAIT_FOREVER);
	    if (ret != NX_SUCCESS)
	    {
	        Error_Handler();
	    }
	    else
	    {
	        printf("UDP Server listening on PORT 6000.. \r\n");
	    }

	    /*
	     * Main Task Loop
	     * Waits 1 second (100 centiseconds) for each UDP packet. If received, print out message
	     */
	    while (1)
	    {
	        TX_MEMSET(data_buffer, '\0', sizeof(data_buffer));

	        /* wait for data for 1 sec */
	        ret = nx_udp_socket_receive(&UDPSocket, &data_packet, 100);

	        if (ret == NX_SUCCESS)
	        {
	            nx_packet_data_retrieve(data_packet, data_buffer, &bytes_read);

	        /* Print our received data on UART com port*/
	              HAL_UART_Transmit(&huart3, (uint8_t *)data_buffer, bytes_read, 0xFFFF);
	              printf("\r\n");    // new line to make print out more readable
	        }

	    }
	  /* USER CODE END Nx_App_Thread_Entry 0 */
}

 

At this point, the project is ready for execution. But before doing so, we need to set up the board by configuring the necessary option bytes in STM32CubeProgrammer:

10. STM32CubeProgrammer configuration

In this section, we describe the option bytes configurations needed for this application.

User option bytes requirement (with STM32CubeProgrammer tool)

- TZEN = B4 System with TrustZone-M enabled
- SECWM1_STRT=0x0 SECWM1_END=0x7F All 128 sectors of internal flash Bank1 set as secure
- SECWM2_STRT=0x1 SECWM2_END=0x0 No sector of internal Flash Bank2 set as secure, hence Bank2 nonsecure

option byte config in CubeProgrammeroption byte config in CubeProgrammer

11. Setting-up debug configuration

To debug the example, you need to do the following:

1. Select and build the xxxxx_NS project, thus triggering automatic build of xxxxx_S project

2. Select the xxxxx_S project and select “Debug configuration”

3. Double-click on “STM32 Cortex-M C/C++ Application”

4. Select “Startup” > “Add” >

5. Select the xxxxx_NS project

6. Build configuration: Select Release/Debug

7. Click debug to debug the example

12. Testing the application

With the board plugged in, open a serial communication terminal with putty:

putty configurationputty configuration

 

 

For testing, we can use the packet sender tool with or echotool, which can be found on GitHub: 
https://packetsender.com/ 
Release EchoTool · PavelBansky/EchoTool (github.com).

Both tools support sending UDP messages as well as other protocols.
Before we can send any packets to the device, we need to connect the H563 board to a router or the pc and assign the correct IP address. It is also important that the addresses you have assigned to the H563 in the software align with the subnet of your router. You may need to open your router settings in a web browser for this information.

With the board hooked up to the router, plugged into a PC via USB, and PuTTY configured, if you reset the board, you should see the IPv4 address, and UDP port printed on the console:

Putty logPutty log

 

In the packet sender we can configure the UDP packet to customize these parameters, send the packets over the network, and see the messages print out on the terminal:Echotool configurationEchotool configuration

 

STea_6-1707330858051.png

13. Related links

RM0481: STM32H563/H573 and STM32H562 Arm®-based 32-bit MCUs

AN5347: Arm® TrustZone® features for STM32L5 and STM32U5 Series

UM3115: STM32H5 Nucleo-144 board (MB1404)

Comments
SedCore
Associate III

@STea Thanks for this tutorial, but could you also please make a similar tutorial using the B-U585I-IOT02A Discovery board with MXCHIP EMW3080 wi-fi module? And TCP instead of UDP. Thanks!!!

Version history
Last update:
‎2024-03-07 05:59 AM
Updated by: