STM32CubeIDE, step by step
Overview
This tutorial is a step-by-step guide on how to build a Mongoose-based Web UI dashboard on a NUCLEO development board using the STM32CubeIDE development environment. We provide instructions for NUCLEO-F746ZG, NUCLEO-F429ZI, and NUCLEO-H743ZI2.
Create a new Cube project
- Start STM32CubeIDE and create a new project.
- If you have an empty workspace, click on "Create a New STM32 project"
- Otherwise, follow the menu
File -> New -> STM32 Project
.
- The target selector will open, here we can mainly select an MCU/MPU (like the STM32F746ZG) or a board (like the Nucleo-F746ZG). The easiest way, if you have a commercial development board, is to go with it. If you are developing on a custom board, then choose your device.
- If you go with the board initialization, choose the
Board Selector
tab, type and select your board commercial part number, e.g.:NUCLEO-F746ZG
, then clickNext
. You'd probably want to mark it as favorite, as you will be making several projects for it. - If you go with the device selection instead, choose the
MCU/MPU Selector
tab, then type and select your device commercial part number, e.g.:STM32F746ZGT6
, then clickNext
- If you go with the board initialization, choose the
- 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
: - If you chose the
Board Selector
, Cube will ask if you want to initialize all peripherals to their default mode, answerNo
. We just need it to configure our GPIOs and avoid generating unnecessary driver code. - If you chose the
MCU/MPU Selector
, we'll be configuring necessary GPIOs as we need them. - If this is your first time with Cube or this device family, Cube will download the firmware package, containing the HAL for this device family, and install it after you agree to its license terms and conditions. We are now ready to go.
1. Blink the blue LED
At the device configuration wizard, we'll configure the proper GPIO pin as an output. The blue LED in the Nucleo-F746ZG and Nucleo-F429ZI boards is connected to GPIO PB7, so click on pin
On the Nucleo-H743ZI2 board, the LED is yellow and is connected to GPIO PE1.PB7
and chooseGPIO_Output
. Skip this if you created the project through theBoard Selector
, choose the proper pin for other boards: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 to USART3
These boards have 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
. Skip this if you created the project through theBoard Selector
: - Then click on
Connectivity
, then onUSART3
, and finally selectAsynchronous
mode, as we'll be using it as a UART: For some chips, for example the H743, you may have to "resolve clock issues", that is, allow Cube to find a suitable clock configuration. We'll see this in more detail later. - 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, 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. Change system clock frequency
The board configuration defaults to setting the microcontroller to run from its HSI clock, that is, at 16 MHz for the F4 and F7 and 64 MHz for the H7 series. However, these microcontrollers are able to run at higher speeds using their internal PLL.
Even though we won't use the Random Number Generator (RNG) peripheral in this tutorial, it affects the way the clock chain is setup, so we'll enable it first and get ready for other examples using it.
- To enable the RNG peripheral, at the device configuration wizard, click on
Security
, then onRNG
, and finally checkActivated
Now let's set the system clock, for the H743 see this appendix:
- At the device configuration wizard, select the
Clock Configuration
tab. Within the block diagram, change HCLK to the device maximum frequency, see table below:Board Device Max Clock Frequency NUCLEO-F429ZI STM32F429ZIT6 180 MHz NUCLEO-F746ZG STM32F746ZGT6 216 MHz - The following screen snapshot shows doing it for the F746, 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 its maximum clock frequency
The STM32Cube HAL requires a time base; it defaults to using the SysTick. For projects using an RTOS, as these usually take over the SysTick, it is good practice to select a different source.
- To change the timebase source, at the device configuration wizard, select the
Pinout & Configuration
tab. Click onSystem Core
, then onSYS
, and finally selectTIM6
as Timebase source:
5. Enable the Ethernet controller
- At the device configuration wizard, select the
Pinout & Configuration
tab. Click onConnectivity
, then onETH
, and select Mode asRMII
. Then on the NVIC Interrupt Table section enable theEthernet global interrupt
: - In projects where we won't be using the ETH HAL driver (in this tutorial or in any other project where we use Mongoose built-in TCP/IP stack):
- you can save some RAM by configuring
Rx Buffer Length
as 0 in the Parameter Settings section. - disable generation of the ETH IRQ Handler. Click on
System Core
, then onNVIC
, then on the Code Generation section disableGenerate IRQ Handler
for theEthernet global interrupt
:
- you can save some RAM by configuring
- If you created this new project for a board, that is, using the
Board Selector
, the GPIOs will automatically configure for it, so you can skip this step.- If you, instead, have used the
MCU/MPU Selector
, then go to the GPIO Settings section and set the RMII pins to those corresponding to your board. The Nucleo-F746ZG and Nucleo-F429ZI boards are based on the MB1137 design, and its user manual is UM1974. The Nucleo-H743ZI2 board, instead, is based on the MB1364 design, and its user manual is UM2407 (The former Nucleo-H743ZI board was based on MB1137). For these boards we need to click onPG11
andPG13
and configure them asETH_TX_EN
andETH_TXD0
, respectively.
- If you, instead, have used the
- 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. 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 when this symbol is not defined and there is no other clue, Mongoose will default to try to includemongoose_config.h
. Let's create this file and add this content to it:#pragma once #define MG_ARCH MG_ARCH_NEWLIB #define MG_ENABLE_TCPIP 1 #define MG_ENABLE_CUSTOM_MILLIS 1
- To enable the proper built-in driver for your board:
Board Add this macro NUCLEO-F429ZI #define MG_ENABLE_DRIVER_STM32F 1
NUCLEO-F746ZG #define MG_ENABLE_DRIVER_STM32F 1
NUCLEO-H743ZI #define MG_ENABLE_DRIVER_STM32H 1
NUCLEO-H743ZI2 #define MG_ENABLE_DRIVER_STM32H 1
- To enable the proper built-in driver for your board:
Open the file
Core/Src/main.c
and outside of the main() function add a functionuint64_t mg_millis(void)
, this function will call a HAL function to get the running time in milliseconds:uint64_t mg_millis(void) { return HAL_GetTick(); }
Finally, also in
Core/Src/main.c
but inside themain()
function, we'll initialize Mongoose, add the network initialization to get an IP address using DHCP, and since this microcontroller/board does not provide a unique MAC address, we'll also specify some fancy one:struct mg_mgr mgr; mg_mgr_init(&mgr); struct mg_tcpip_if mif = { .mac = {2, 0, 1, 2, 3, 4}, .driver = &mg_tcpip_driver_stm32f, }; mg_tcpip_init(&mgr, &mif); for (;;) mg_mgr_poll(&mgr, 100);
- For the STM32H driver, use
.driver = &mg_tcpip_driver_stm32h
- For the STM32H driver, use
Now click on the green arrow icon to build the project and flash your board; since our infinite loop is now before the one toggling the LED, the blue LED will not blink
With the terminal software of your choice, configure it for 115200bps. Open the proper USB serial port and you'll see Mongoose log. Once the DHCP server in your network assigns an address to your board, you'll see it:
picocom /dev/ttyACM0 -i -b 115200 ... b4e 2 mongoose.c:6382:onstatechange READY, IP: 192.168.2.10
If you ping this address, it should respond
7. Implement a 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.
- Outside of the main() function, 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) { if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "ok\n"); }
- Finally, also in
Core/Src/main.c
but inside the main() 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 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.
8. Add full Web Dashboard
Let's add a more advanced Web UI to our web server, a full device dashboard functionality
- You can download net.c, net.h and packed_fs.c with your browser; then drag and drop them to
Core/Src/
andCore/Inc/
respectively for source (.c
) and header (.h
) files. - You can also create new files, name them net.c, net.h 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
; so let's add this content tomongoose_config.h
:#define MG_ENABLE_PACKED_FS 1
- Now, let's include net.h so we can call the device dashboard functions. Let's add it just below where we included mongoose.h in step 6. The device dashboard code defaults to using port 8000, to be able to listen at port 80 we need to redefine the
HTTP_URL
macro; let's add a new preprocessor symbol. 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 addHTTP_URL="http://0.0.0.0/"
: - Finally, in our main() function, we'll remove the HTTP listener and call the device dashboard init function, that carries all needed initializations:
web_init(&mgr);
- 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.
How to import existing Cube projects
Now you are ready to start trying ready-made examples to develop your own code based on them. For that, you need to import these examples into your workspace.
We'll import one of the examples present in the Mongoose git repository:
Follow the Build Tools tutorial to setup git.
Start a terminal in the project directory and clone the Mongoose Library repo:
git clone https://github.com/cesanta/mongoose
Now we are ready to import a project:
- Start STM32CubeIDE
- If you have an empty workspace, you can click on
Import projects...
- Otherwise, follow the menu
File -> Import...
, then openGeneral
and selectExisting Projects into Workspace
; then clickNext
:
- If you have an empty workspace, you can click on
- Pick the project directory of your choice as
root directory
, make sure that the project is recognized and checked. As the repository uses symbolic links, do not copy the files, let Cube just import the Project where it is. ClickFinish
: - Click on the project in the
Project Explorer
tab, you are all set.
A1. Set H743 clock frequency
The H743 is quite a complex microcontroller, with several clock domains feeding a diversified network of internal buses.
At the device configuration wizard, click on
System Core
, and then onRCC
. Then on the Parameter Settings section make surePower Parameters -> Power Regulator
is set toPower Regulator Voltage Scale 3
, as high speeds require the highest CPU operating voltage. Then configureSystem Parameters -> Product revision
to the correct revision number for your chip, see table below:Board Device (Rev) Max CPU Clock Frequency Max HCLK Frequency NUCLEO-H743ZI STM32H743ZIT6 (Y) 400 MHz 200 MHz NUCLEO-H743ZI2 STM32H743ZIT6 (V) 480 MHz 240 MHz Then, at the device configuration wizard, select the
For revision 'Y' chips, you may also need to set the fractional part of the PLL divisor to 0.Clock Configuration
tab. Within the block diagram, change the HPRE prescaler to/2
, then set the System Clock Mux to thePLLCLK
; setDIVN1
within the PLL1 to your device maximum CPU clock frequency (see table above), and finally fix all domain prescalers setting D1PPRE, D2PPRE1, D2PPRE2, and D3PPRE to/2
. In the Cube versions we tried, the automatic solver did not work properly, so resist the temptation to use it.