on
2024-03-07
04:19 AM
- edited on
2024-03-07
05:59 AM
by
Laurids_PETERSE
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.
With CubeMX downloaded, you can install the relevant packages by selecting help -> manage embedded software packages.
To start, we need to search for and select NUCLEO-H563ZI in the Board Selector
When prompted whether to initialize all peripherals with their default modes or not, select No.
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.
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:
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
To run this application, select the ETH peripheral and assign it to the nonsecure runtime context as an RMII interface.
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.
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.
For the NetXDuo core middleware, we need to enable and assign it to the nonsecure runtime context.
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).
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.)
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.
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.
That is it for the STM32CubeMX configuration. Navigate to the project manager tab, give your project a name, and generate the code for 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.
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 */
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.
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:
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:
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
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
With the board plugged in, open a serial communication terminal with putty:
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:
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:
RM0481: STM32H563/H573 and STM32H562 Arm®-based 32-bit MCUs
AN5347: Arm® TrustZone® features for STM32L5 and STM32U5 Series
@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!!!
@STea any news about my request for the MXCHIP EMW3080 wi-fi module instead of ethernet please?