STM32CubeIDE Nucleo-F746Z
Overview
This tutorial is a step-by-step guide on how to build a Mongoose-based Web UI dashboard on a NUCLEO F746ZG development board using the STM32CubeIDE development environment with lwIP and FreeRTOS.
We'll start from scratch, following these steps:
- Blink an LED
- Send text through a USART
- Redirect printf() output to a USART
- Change the system clock to the highest frequency
- Enable the Ethernet MAC Controller
- Configure FreeRTOS and lwIP with the PHY
- Blink an LED using the OS
- Integrate Mongoose Library into our project
- Configure FreeRTOS memory heap
- Implement a simple web server
- Implement the full device dashboard
1. Blink the blue LED
- Start STM32CubeIDE
- Click on
Create a New STM32 project
: - The part selector will open, type and select
STM32F746ZGT6
, then clickNext
- Pick a name for your project, and check the project location. This will be a C language project in which we'll generate an executable. Click
Finish
: - At the device configuration wizard, we'll configure the proper GPIO pin as an output. The blue LED in this board is connected to GPIO PB7, so click on pin
PB7
and chooseGPIO_Output
: - Now select the project and click on the hammer icon, that will generate the base project structure.
- Once this is done, we can write our blink code in
Core/Src/main.c
. Using some HAL functions we'll toggle the pin and wait for 500ms inside the main loop:HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7); HAL_Delay(500);
- Click on the hammer icon again, that will build the project:
- Now click on the green arrow icon, accept the debug configuration, and your board will be flashed with this code. You should soon see the blue LED start blinking
2. Send text through USART3
This board has a built-in ST-Link debugger, which also provides a USB serial port wired to PD8 and PD9. These pins can be used by USART3.
- At the device configuration wizard, click on pin
PD9
and chooseUSART3_RX
. Then click on pinPD8
and chooseUSART3_TX
: - Then click on
Connectivity
, then onUSART3
, and finally selectAsynchronous
mode, as we'll be using it as a UART: - Now click on the hammer icon, that will re-generate the base project structure and re-build it.
- Once that is done, the default code in CubeIDE has already included the USART setup and this defaults to using 115200 bps. let's add UART output to our code using one of the HAL functions:
HAL_UART_Transmit(&huart3, "hi\r\n", 4, 500);
- Now click on the green arrow icon, this will build the project and flash your board:
- With the terminal software of your choice, open the proper USB serial port at 115200 bps and you'll see the board greeting you:
$ picocom /dev/ttyACM0 -i -b 115200 ... hi
3: Redirect printf() to USART3
This will not only allow us to print texts in a simpler way, but also use printf() for debug output, which will enable Mongoose log output further on.
- Open
Core/Src/syscalls.c
; in the list of includes add:#include "main.h"
- Then find the
_write()
function; comment out its contents and add the following:extern UART_HandleTypeDef huart3; if (file == 1) HAL_UART_Transmit(&huart3, (uint8_t *)ptr, len, 1000);
- Now change the UART output in
Core/Src/main.c
to use printf():printf("hi 2\r\n");
- Now click on the green arrow icon, this will build the project and flash your board
- With the terminal software of your choice, open the proper USB serial port at 115200 and you'll see the board greeting you:
$ picocom /dev/ttyACM0 -i -b 115200 ... hi 2
4. Setup system clock
The board configuration defaults to setting the microcontroller to run from its HSI clock, that is, at 16 MHz. However, this microcontroller is able to run at higher speeds, up to 216 MHz, using its internal PLL.
- At the device configuration wizard, select the
Clock Configuration
tab. Within the block diagram, change HCLK to 216 MHz: - The wizard will inform us that it can't solve that with the current configuration, and ask to find a solution using other sources. Accept the suggestion and it will automatically configure the PLL:
- Now click on the green arrow icon to build the project and flash your board; the blue LED should blink again and the microcontroller will be running now at 216MHz
5. Setup Ethernet controller
- At the device configuration wizard, select the
Pinout & Configuration
tab. Click onSystem Core
, then onSYS
, and finally selectTIM6
as Timebase source: - Now click on
Connectivity
, then onETH
, and select Mode asRMII
. Then on the NVIC Interrupt Table section enable theEthernet global interrupt
: - Now go to the GPIO Settings section and set the RMII pins to those corresponding to this board. The Nucleo F746 ZG board is based on the MB1137 design, and its user manual is UM1974. We need to click on
PG11
andPG13
and configure them asETH_TX_EN
andETH_TXD0
, respectively: - Now click on the hammer icon, that will re-generate the base project structure and re-build it, adding support for the MAC controller.
6. Configure FreeRTOS and lwIP
- At the device configuration wizard, select the
Pinout & Configuration
tab. Click onMiddleware
, then onFREERTOS
, and finally selectCMSIS_V1
as interface:This will use CMSIS_OS as an abstraction layer between FreeRTOS and the rest of the generated code, including lwIP
- Now click on the Advanced settings section, then, on
Newlib settings
, enableUSE_NEWLIB_REENTRANT
: - Now go to the Tasks and Queues section and set the stack size for the only task (defaultTask) to 256 words; otherwise lwIP won't even start:
- At the device configuration wizard, on
Middleware
, click onLWIP
, and enable it. Make sure DHCP is enabled: - Now click on the Platform Settings section, then on
Driver_PHY
, selectLAN8742
.: - Now click on the hammer icon, that will re-generate the base project structure and re-build it, adding support for FreeRTOS, lwIP, and the PHY chip.
- Before we can do any tests, let's add a way to show our IP address in the log terminal. Add this to
Core/Src/main.c
, in theStartDefaultTask()
function:
Note that, depending on where we added our blink code, it may or may not have been preserved by CubeIDE. It doesn't really matter, as that code will not be executed as the scheduler takes over before that./* USER CODE BEGIN 5 */ extern struct netif gnetif; while (ip4_addr_isany_val(*netif_ip4_addr(&gnetif))) { osDelay(200); } printf("READY, IP: %s\n", ip4addr_ntoa(netif_ip4_addr(&gnetif))); /* Infinite loop */
- Now let's click on the green arrow icon to run the code. If it doesn't write anything or you find errors, CubeIDE might have messed around with
Core/Src/syscalls.c
, check it out and re-do our changes. - With the terminal software of your choice, configure it for 115200bps. Open the proper USB serial port and you'll see the log. Once the DHCP server in your network assigns an address to your board, you'll see it:
$ picocom /dev/ttyACM0 -i -b 115200 ... READY, IP: 192.168.2.10
- If you ping this address, it should respond
7. Blink LED with the RTOS
- Once this is done, we can bring our blink code back in
Core/Src/main.c
. This time we'll use the CMSIS_OS function call to instruct this task (defaultTask, in theStartDefaultTask()
function) to wait for 500ms, while lwIP runs in other task:/* Infinite loop */ for(;;) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7); osDelay(500); }
- Click on the hammer icon again, then on the green arrow icon, your board will be flashed and you should soon see the blue LED start blinking again
8. Integrate Mongoose
- You can download mongoose.h and mongoose.c with your browser; then drag and drop them to
Core/Inc/
andCore/Src/
, respectively. - You can also create new files, the header mongoose.h in
Core/Inc/
and the source mongoose.c in theCore/Src/
folder; and paste contents there: - First, let's include mongoose.h so we can call Mongoose API functions
- Now we need to tell Mongoose in which architecture it is running. This is done by defining the macro
MG_ARCH
and assigning it a proper value, in this caseMG_ARCH_FREERTOS_LWIP
. Right click on your project, clickProperties
. SelectC/C++ Build
, thenSettings
. Click on theTool Settings
tab, then selectMCU GCC Compiler
, thenPreprocessor
. Click on the add new symbol icon and addMG_ARCH=MG_ARCH_FREERTOS_LWIP
:Mongoose will now allocate memory through FreeRTOS, use lwIP socket interface, and get its timing from FreeRTOS.
- Now open
Core/Src/main.c
and inside theStartDefaultTask()
function, we'll initialize Mongoose. We'll also replace theprintf()
call by one of the built-in Mongoose logging macros:MG_INFO(("READY, IP: %s", ip4addr_ntoa(netif_ip4_addr(&gnetif)))); struct mg_mgr mgr; mg_mgr_init(&mgr); /* Infinite loop */ for(;;) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7); mg_mgr_poll(&mgr, 500); }
- Now click on the hammer icon to build the project. Mongoose has been integrated into our code
9. Configure FreeRTOS heap
Task Memory allocation defaults to dynamic. This means that memory for the stack of each new task, will be allocated from FreeRTOS heap. To work with applications requiring a high stack size, FreeRTOS heap size must be increased. We'll go ahead and set it at 64KB:
10: Simple web server
Let's modify Core/Src/main.c
to add a very simple web server; it will just respond with "ok" to any URI you request.
- Let's first add the HTTP event handler, this function will be called when the event manager processes an HTTP message:
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "ok\n"); }
- Finally, also in
Core/Src/main.c
but inside the StartDefaultTask() function, we'll add an HTTP listener on any address. We do this after the event manager initialization and of course before the infinite loop. Passing a pointer to the former functionfn
as a parameter, we instruct the event manager to call this event handler for that:mg_http_listen(&mgr, "http://0.0.0.0", fn, &mgr);
- Now go to the Tasks and Queues section and set the stack size for the only task (defaultTask) to 512 words; even though this is a very simple example, Mongoose uses a large stack:
- Now click on the green arrow icon to build the project and flash your board
- Once the DHCP server in your network assigns an address to your board, you'll see it in the log; it should be the same as before, anyway.
- Point your browser to
http://IP_ADDRESS
, whereIP_ADDRESS
is the board's IP address; you'll see the "ok" message
This is of course a very simple and limited example, but is also the cornerstone on which to build a RESTful server.
11. Full device dashboard
Let's add a more advanced Web UI to our web server, a full device dashboard functionality
- You can download net.c and packed_fs.c with your browser; then drag and drop them to
Core/Src/
- You can also create new files, name them net.c and packed_fs.c, and paste contents there, as we did in step 6
- The file net.c contains all the event handler functions to provide the necessary functionality; while packed_fs.c contains an embedded filesystem holding all the UI static files
- Now we need to tell Mongoose to include code for handling this packed embedded filesystem. This is done by setting the macro
MG_ENABLE_PACKED_FS=1
. - Since we'll be using several connections simultaneously, we need to tell lwIP so. Even though we are using the socket interface, internally in lwIP it uses a decoupling API called netconn, so we'll set the number of allowed connection high enough; let's set
MEMP_NUM_NETCONN=10
in the preprocessor symbols: - Finally, in our main() function, we'll modify the HTTP listener to use the device dashboard handler:
extern void device_dashboard_fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data); mg_http_listen(&mgr, "http://0.0.0.0", device_dashboard_fn, &mgr); /* Infinite loop */ for(;;) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7); mg_mgr_poll(&mgr, 500); }
- Before we can run this successfully, we need to increase the stack size as this is a more complex task. Go to the Tasks and Queues section and set the stack size for the only task (defaultTask) to 2048 words:
- Now click on the green arrow icon to build the project and flash your board
- Once the DHCP server in your network assigns an address to your board, you'll see it in the log; it should be the same as before, anyway.
- Point your browser to
http://IP_ADDRESS
, whereIP_ADDRESS
is the board's IP address; you should see a login screen.
From here on, if you want to try the dashboard features please go to the device dashboard tutorial and follow some of the steps depicted there.
We didn't change any other default lwIP parameters, for serious work and performance, a tuning operation might be needed.