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.

We'll start from scratch, following these steps:

  1. Blink an LED
  2. Send text through a USART
  3. Redirect printf() output to a USART
  4. Change the system clock to the highest frequency
  5. Enable the Ethernet MAC Controller
  6. Integrate Mongoose Library into our project
  7. Implement a simple web server
  8. Implement the full device dashboard

Step 1: Blink the blue LED

  • Start STM32CubeIDE
  • Click on Create a New STM32 project:STMCube create project
  • The part selector will open, type and select STM32F746ZGT6, then click Nextpic
  • 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:pic
  • 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 choose GPIO_Output:pic
  • Now select the project and click on the hammer icon, that will generate the base project structure.pic
  • 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:pic
  • 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

Step 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 choose USART3_RX. Then click on pin PD8 and choose USART3_TX:pic
  • Then click on Connectivity, then on USART3, and finally select Asynchronous mode, as we'll be using it as a UART:pic
  • 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:pic
  • 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
    

Step 3: Redirect printf() output 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);
    
    pic
  • 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
    

Step 4: Change system clock frequency

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:pic
  • 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:pic
  • 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

Step 5: Enable the Ethernet MAC controller

  • At the device configuration wizard, select the Pinout & Configuration tab. Click on System Core, then on SYS, and finally select TIM6 as Timebase source:pic
  • Now click on Connectivity, then on ETH, and select Mode as RMII. Then on the NVIC Interrupt Table section enable the Ethernet global interrupt:pic
  • Now go to the GPIO Settings section and set the RMII pins to those corresponding to this board. The Nucleo-F746ZG board is based on the MB1137 design, and its user manual is UM1974. We need to click on PG11 and PG13 and configure them as ETH_TX_EN and ETH_TXD0, respectively:pic
  • Now click on the hammer icon, that will re-generate the base project structure and re-build it, adding support for the MAC controller.

Step 6: Integrate Mongoose

  • You can download mongoose.h and mongoose.c with your browser; then drag and drop them to Core/Inc/ and Core/Src/, respectively.
  • You can also create new files, the header mongoose.h in Core/Inc/ and the source mongoose.c in the Core/Src/ folder; and paste contents there:pic
  • First, let's include mongoose.h so we can call Mongoose API functionspic
  • 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 include mongoose_custom.h. Let's create this file and add this content to it:
    #pragma once
    
    #define MG_ARCH MG_ARCH_NEWLIB
    #define MG_ENABLE_MIP 1
    #define MG_ENABLE_CUSTOM_MILLIS 1
    
    Now Mongoose will work with its own embedded TCP/IP stack, but we still need to provide a time base
  • Note that, in step 5, STM32CubeIDE added its own interrupt handler for the Ethernet MAC controller, so now we need to remove it. The fastest way now is to open Core/Src/stm32f7xx_it.c, search for the definition of ETH_IRQHandler(), and change its name.pic
  • Now open Core/Src/main.c and outside of the main() function add a function uint64_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();
    }
    
    pic
  • Finally, also in Core/Src/main.c but inside the main() function, we'll initialize Mongoose, add the network initialization to get an IP address using DHCP, and since this controller does not provide a unique MAC address, we'll also specify some fancy one:
    struct mg_mgr mgr;
    mg_mgr_init(&mgr);
    
    struct mip_cfg c = {.mac = {0, 0, 1, 2, 3, 4}, .ip = 0, .mask = 0, .gw = 0};
    mip_init(&mgr, &c, &mip_driver_stm32, NULL);
    
    for (;;) mg_mgr_poll(&mgr, 100);
    
    pic
  • 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 and to add a carriage return. 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 --imap=lfcrlf
    ...
    b4e 2 mongoose.c:6382:onstatechange     READY, IP: 192.168.2.10
    
  • If you ping this address, it should respond

Step 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, void *fn_data) {
      if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "ok\n");
    }
    
    pic
  • 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 function fn 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);
    
    pic
  • 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, where IP_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.

Step 8: Add the full Web UI

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; so let's add this content to mongoose_custom.h:
    #define MG_ENABLE_FILE 0
    #define MG_ENABLE_PACKED_FS 1
    
  • 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);
    
    pic
  • 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, where IP_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.