WebRadioApp  0.1
Example - Simple HTTPS/IPv6 Server

Initial Steps


This guide is designed to be used with STM32H747I-DISCO board.Development in this case was carried out in the STM32CubeIDE provided by ST, but this framework can be used in any type of development environment. This is not a guide about how to use the STMCubeIDE. If you are interested in how to use this in more depth, please go to its documentation.

In this guide we are going to create an IPv6 HTTPS server.

Create a Project

First of all, we are going to begin with a simple project and then will add more complexity along the way. Let's create a simple UDP sender as starting point. Once you have installed the STM32CubeIDE, click on New > STM32 Project and a window similar to this will appear:

New Project - Step 1

Then select the STM32H747I-DISCO board and create a project in your workspace selecting C++ (you can also use C if you want).

New Project - Step 2
New Project - Step 3

If everything goes well, then you will have something like this:

New Project - Ready
Warning
In this guide we are going to focus on using the Cortex-M7 so every configuration will have Cortex-M7 runtime context.

Next, we need to configure the Ethernet peripheral anc the LwIP middleware and also make some physical modifications in the board.

Initial Code and Hardware Adjustments

There are a couple of adjustment that must be made to the STM32H747I-DISCO for successful compilation and execution of the framework.

Note
Before configuring any complex peripheral, always read the data-sheet and the errata of the device in the documentation thoroughly since it ofter contains key information.

STM32H747I-DISCO Hardware Adjustment

If we look closer at the documentation, we find information about the ethernet peripheral in section 5.7:

The STM32H747I-DISCO board supports 10 Mbps / 100 Mbps Ethernet communication with the U18 LAN8742A-CZ-TR PHY from MICROCHIP and CN7 integrated RJ45 connector. The Ethernet PHY is connected to the STM32H747XIH6 MCU via the RMII interface.The 25 MHz clock for the PHY is generated by oscillator X2. The 50 MHz clock for the STM32H747XIH6 is provided by the RMII_REF_CLK of the PHY.With the default setting, the Ethernet feature is not working because of a conflict between ETH_MDC and SAI4_D1 of the MEMs digital microphone. *Table 5 shows the possible settings of all solder bridges or resistor associated with the Ethernet on the board.*

Table 5. Ethernet related solder bridge and resistor settings



Bridge needed to enable ETH peripheral

Here we can see that the setting required to make the ETH signals not interfere with the microphone signals is SB8 closed, SB21 open. Take a look at the image to see exactly where those bridges are found in the STM32H747I-DISCO board.

STM32CubeH7 Version

Make sure you have the latest version of the STM32Cube firmware for the H7 chips (here the V1.9.0 was used).

Ethernet and LwIP Stack Configuration

Make sure you follow the steps in this guide provided by the STM community to correctly configure the Ethernet and LwIP stack. Pay particular attention to the section regarding linkerscript modification!

Attention
Please take into account that the ST guide is meant to be used with a FreeRTOS configuration. If you would like to follow the same configuration without an OS, there is an explanation on how to do so below.

Setting OS

If you have finished the previous configurations, you are probably wondering how to set this without an OS.

With_RTOS

Carry on as it is according to the ST guide.

Without_OS

First, be sure that you have configured LwIP in the CubeMx without an OS as:

LwIP No OS

Add the LwIP checking process into your while(1) function of your main file (or the place where you are checking processes):

...
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
...
}
/* USER CODE END 3 */
...

I also recommend commenting these lines in the Error_Handler function in the main file and modifying them later (sometimes it is good at the beginning to get started quickly instead of losing time finding starting errors).

...
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
// __disable_irq();
// while (0)
// {
// }
/* USER CODE END Error_Handler_Debug */
}
...

The final step is recheck that the clock configuration is correct as follows:

Clock Configuration

Up and Running

Now you have compiled the code correctly, you should be able to ping the device and receive UDP messages! On Linux you can do:

netcat –ul <port>
Note
From now on we going to build everything in a bare metal system approach (without any OS) since it is more interesting for us in that way.

STM32CubeIDE Filesystem


Here we will look in more depth at how the STM32CubeIDE structures the project. If you have been following this guide, you will probably have a filesystem like this:

STMCubeIDE filesystem
  1. We have the source code of our application with the respective header files. Also an important file is the startup file which allows the board to start the execution of our program correctly. Normally STM32 toolchain provides startup templates for each processor.
  2. The Driver folder is self explanatory. Here is where we should place the APIs ST provided for the DISCO board.
  3. This folder (generally in the root directory of a project) makes reference to a middleware place in 4. Configurations files of middlewares should normally be placed here. Later in this guide we are going to place some middlewares not supported by ST here.
  4. This is the default folder where STMCubeIDE places middlewares (normally these are added in the middleware tab in the configuration file, in our case WebRadio.ioc).

Properties of a Project

Another two important things to pay attention to are the include (where the libraries and drivers are included) and the symbols (for preprocessing) of the project. You can access them by right clicking on WebRadio_CM7 > Properties. Throughout this guide, we are going to modify some parameters there to make our project functional, so keep an eye on it.

Project - Includes
Project - Symbols

Set up an HTTP Server


Now let's create an HTTP server!

First, go to the LwIP middleware tab in the CubeMx and enable HTTP:

HTTP - CubeMx
Note
If for any reason you have the following error please follow the instructions below:
../../Middlewares/Third_Party/LwIP/src/include/lwip/apps/httpd_opts.h:386:27: fatal error: fsdata_custom.c: No such file or directory
386 | #define HTTPD_FSDATA_FILE "fsdata_custom.c"
    |                           ^~~~~~~~~~~~~~~~~
compilation terminated.

Next add httpd_init(); before the while(1) function of your main file which initializes the HTTP sever:

...
/* Infinite loop */
httpd_init();
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
...
}
/* USER CODE END 3 */
...
Note
You may remove now the previously made UDP code if you wish to.

If you now open a browser and enter the static IP we have configured: http://192.168.1.10/ the following result should appear:

HTTP Test

Adding TLS layer

Now let's add a TLS layer to our HTTP server. First, go to the middleware tab in the CubeMx and configure the following parameters:

Enable HTTPS
TLS LAYER

Enabling LWIP_ALTCP allows us to have an abstraction layer that links against a layer over the typical TCP. This option in conjunction with LWIP_ALTCP_TLS allows us to have support for SSL/TLS. Thus, the next thing we would need is set up the respective functions that link the tcp callbacks to the TLS layer protocol.

Adding MBEDLTS Library

CubeMx only allows the MBEDTLS library to be added for the same Cortex-M7 with the FREERTOS flags activated. Since we are configuring everything as a bare metal system, in this step we going to add the MBEDTLS library manually.

  • First download the latest MBEDTLS release
  • Extract that folder and then go to the STMCubeIDE, create a new folder named mbedTLS under the external Middlewares folder and copy include and library folder there:
Add MBEDTLS Library - STMCubeIDE
  • Delete the psa folder under the include folder and also the files related with it in the library folder (this is just for simplification, we could also leave them there but we'd have to include them in the path and configure certain modules. We do not need them for now):
Delete psa files - STMCubeIDE
  • Include those folders in the paths in the project configuration:
include MBEDTLS directory - STMCubeIDE
  • Add these symbols for configuration in the project:
TLS Symbols - STMCubeIDE
  • Create link to the .c files in the Cortex-M7 project for the MBEDTLS library. To do this create a folder in the internal Middlewares folder called mbedTLS and simply drag and drop the files:
TLS linking Files - STMCubeIDE
  • Configure the TLS layer. Here you can find information in this regard, but you can also copy our configuration file from the repository. Then add the file into the Inc folder:
TLS config file - STMCubeIDE

After these steps, you are able to compile the project without problems. Now we going to add some configurations to be able to add the TLS layer to our server.

There is a flag that has to be set but CubeMx does not provide a way of setting it so we have to do it manually.

To do this, first go to this folder:

./Middlewares/Third_Party/LwIP/src/include/lwip/apps

Find this file: altcp_tls_mbedtls_opts.h and change LWIP_ALTCP_TLS_MBEDTLS to 1:

...
#ifndef LWIP_ALTCP_TLS_MBEDTLS
#define LWIP_ALTCP_TLS_MBEDTLS 1
#endif
...

Normally this error is linked to the previous flag so setting LWIP_ALTCP_TLS_MBEDTLS to 1 solves this issue:

../Core/Src/main.c:160: undefined reference to `altcp_tls_create_config_server_privkey_cert'

Configure Random Number Generator

To use cryptography calculations for the TLS library, we going to use the random number generator configuration provided by the CubeMX. You can configure it in this tab:

Add RNG - STMCubeIDE

Additionally we have to configure the Entropy poll callback for the RNG. Simply add the following file hardware_rng.c into the Src folder:

#include "mbedtls_config.h"
#include <string.h>
#ifdef MBEDTLS_ENTROPY_HARDWARE_ALT
#include "main.h"
#include "entropy_poll.h"
extern RNG_HandleTypeDef hrng;
int mbedtls_hardware_poll(void *Data, unsigned char *Output, size_t Len,
size_t *oLen)
{
uint32_t index;
uint32_t randomValue;
for (index = 0; index < Len / 4; index++) {
if (HAL_RNG_GenerateRandomNumber(&hrng, &randomValue) == HAL_OK) {
*oLen += 4;
memset(&(Output[index * 4]), (int)randomValue, 4);
} else {
}
}
return 0;
}
#endif

The previous function will allow us to access the HAL for the RNG.

Expand the Memory

Due to the computations of the TLS layer, we should increase the default RAM to 521Kb. To do this go the linkerscript called STM32H747XIHX_FLASH.ld and change the RAM to 512Kb:

...
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x1200 ; /* required amount of heap */
_Min_Stack_Size = 0x1200 ; /* required amount of stack */
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K /* Memory is divided. Actual start is 0x24000000 and actual length is 512K */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* Memory is divided. Actual start is 0x08000000 and actual length is 2048K */
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
}
/* Sections */
SECTIONS
{
...

In the same way, we increase the values of the buffer and the TCP segment size as follows (you can also do it in the CubeMx in the Middleware>LwIP>Key Options):

#define PBUF_POOL_SIZE 64
#define PBUF_POOL_BUFSIZE 1524
#define TCP_MSS 1426

Setting Up HTTPS Server

Connection to an HTTP server over TLS is based on established trust of the server. This trust is established by means of digital certificates provided by the server and signed by certificate authorities. For our purposes of testing a simple example, a self-signed certificate suffices (it will obviously not be recognized as secure but it is merely proof of concept).

Use a Self-signed Certificate

MBEDTLS Library provides us with some sample certificates. To use them, simply add this library to the main file:

#include "mbedtls/certs.h"

The next step is to create our self-signed certificate and pass it to our server to set it up. MBEDTLS library provides us with handler functions to do this. altcp_tls_create_config_server_privkey_cert is one of them which creates a new TLS configuration.

We will do it as follows in our main file:

...
struct altcp_tls_config *configuration = altcp_tls_create_config_server_privkey_cert(
mbedtls_test_srv_key,
mbedtls_test_srv_key_len,
NULL,
0,
mbedtls_test_srv_crt,
mbedtls_test_srv_crt_len);
//httpd_init(); //HTTP Server
httpd_inits(configuration); //HTTPS Server
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
...
}
/* USER CODE END 3 */
...

Testing our HTTPS Server

If you now use any web browser and try to access https://192.168.1.10/, then you will have a reply like this:

HTTPS Server
HTTPS Server

We have successfully created an HTTPS server!!

Notice that here, Chrome says that is insecure. This is expected because it is a self-signed certificate.

Configuring IPv6

The next step is to configure the IPv6 of the HTTPS server. To achieve this and to simplify matters, it is preferable to set a DHCP server instead of a static IP.

Go to the LwIP setting at the Middleware section in CubeMx and simply enable DHCP:

DHCP Setting - STMCubeIDE

Then you can go to the IPv6 configuration and simply enable it also:

IPv6 Setting - STMCubeIDE

The final step – and the most difficult one to realize since STM32CubeMx does not provide any information in this regard – is to set the flag called NETIF_FLAG_MLD6 in the lower driver of the Ethernet peripheral (ethernetif.c file). This can be done as follows:

...
/* Accept broadcast address and ARP traffic */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP| NETIF_FLAG_MLD6;
#else
netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */
...

If this flag is not set, the multi-cast group of the device will not be joined and therefore no other external host can solicit the hardware address, i.e. the device cannot be accessed via IPv6.

Now let's modify the main to see which IP has been assigned to the device. To do this, change the main as follows:

  1. Extern the struct netif used by LwIP to configure the ethernet peripheral (line 5).
  2. Add certain variables to help us discover the assigned IP to the device (line 6).
  3. Add a helper to enable use of printf() (lines 8-15).
...
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern struct netif gnetif;
unsigned long IP, IP1, IP2, IP3 = 0;
#ifdef __GNUC__
int __io_putchar(int ch)
#else
int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;}
/* USER CODE END 0 */
...

Then go to the int main(void) and add the following lines before the while (1) loop to print the IPs that have been assigned to the device.

...
//httpd_init(); //HTTP Server
httpd_inits(configuration); //HTTPS Server
do {
IP = ((unsigned long)gnetif.ip_addr.u_addr.ip4.addr);
IP2 = IP1;
IP1 = IP;
} while (IP2 == IP1);
printf("IPv4 -> : %s \n\r", ip4addr_ntoa(ip_2_ip4(&(gnetif.ip_addr))));
printf("IPv6 -> : %s \n\r", ip6addr_ntoa(ip_2_ip6(&(gnetif.ip6_addr[2]))));
/* USER CODE END 2 */
/* Infinite loop */
...

Finally, if you have been able to configure everything correctly, you should receive a message in the console similar to this one, depending on your router configuration. In my case this is:

IPv4 -> : 192.168.1.10
IPv6 -> : FD00::80:E100:0 

Now we are able to access the device via those IPs!!

HTTPS Server
HTTPS Server IPv6
MX_LWIP_Process
void MX_LWIP_Process(void)
Read a received packet from the Ethernet buffers.
Definition: lwip.c:139
hrng
RNG_HandleTypeDef hrng
Definition: main.cpp:27
__io_putchar
int __io_putchar(int ch) __attribute__((weak))
Error_Handler
void Error_Handler(void)
This function is executed in case of error occurrence.
Definition: main.cpp:693
gnetif
struct netif gnetif
Definition: lwip.c:44
fputc
int fputc(int ch, FILE *f)
Definition: main.cpp:122