Overview

This tutorial is a step-by-step guide on how to build a Web UI dashboard on NUCLEO-F746ZG development board using Keil development environment. Once the dashboard is up and running, it is trivial to extend it using other examples - for example, to add MQTT connectivity, etc.

Step 1: Skeleton

In this step, we create a project skeleton. Run Keil, and

  • Create a new project
  • Select STM32F746ZG as an architecture
  • Choose the following components:
    • Board support / LED
    • CMSIS / Core
    • Device / Startup
    • STM32Cube Framework API / Classic
  • Click on "Resolve" to resolve dependencies, click "OK"
  • In the Project / Target1 / SourceGroup1, add "main.c" file, copy-paste and save:
#include "RTE_Components.h"
#include "stm32f7xx_hal.h"
#include "Board_LED.h"

int main(void) {
  HAL_Init();
  SystemCoreClockUpdate();

  LED_Initialize();  
  LED_On(2);
  while (1) {}
}
  • In the IDE toolbar, select Options / Debug tab, choose ST-Link debugger, enable "Download to flash". Click "Flash Download" tab, enable "Reset and Run".
  • Select "Target" tab, enable "Use MicroLib"
  • Select "C/C++" tab, set "Warnings" to "MISRA Compatible"
  • Click "Build", then "Load". The board should turn red LED

Step 2: Add RTOS

Now, we'll add RTOS support and create a blinking task. Later on, we transform the blinking task into a web server task.

  • Click on Run-Time Environment and enable the CMSIS / RTOS2 / Keil RTX5
  • In the project tree, open Device / startup_stm32f746xx.s file
  • Find a "HeapSize" line and increase heap size, then save:
Heap_Size       EQU     65536
  • Update main.c to the following code:
#include "RTE_Components.h"
#include "stm32f7xx_hal.h"
#include "Board_LED.h"
#include "cmsis_os2.h"

static void mytask(void * param) {
  LED_Initialize();  
  LED_On(2);
  int on = 0;
  for (;;) {
    on = !on;
    on ? LED_On(1) : LED_Off(1);
    osDelay(200);
  }
}

int main(void) {
  HAL_Init();
  SystemCoreClockUpdate();
  
  osKernelInitialize ();
  size_t stack_size = 4096;
  void *stk = malloc(stack_size);
  const osThreadAttr_t attr = { .stack_mem  = stk, .stack_size = stack_size };
  osThreadNew(mytask, NULL, &attr);
  osKernelStart();
}
  • Click on Build, then Load. The board should have blue LED blinking

Step 3. Update system clock

At this step we simply update system clock to run the board at maximum speed of 216 MHz.

  • Add a new file init.c to the Source Group 1, with the following content:
#include "stm32f7xx_hal.h"

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Configure LSE Drive Capability
  */
  HAL_PWR_EnableBkUpAccess();
  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 216;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 9;
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  /** Activate the Over-Drive mode
  */
  HAL_PWREx_EnableOverDrive();

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7);
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART3|RCC_PERIPHCLK_CLK48;
  PeriphClkInitStruct.Usart3ClockSelection = RCC_USART3CLKSOURCE_PCLK1;
  PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48SOURCE_PLL;
  HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
}
  • Modify main.c - add two extra lines in main():
int main(void) {
  HAL_Init();`
  extern void SystemClock_Config(void)    // Add this line
  SystemClock_Config();                   // And this line
  SystemCoreClockUpdate();
  ...

Clink on Build, Load. An LED should continue to blink.

Step 4. Add networking

  • Click on Run-Time Environment and enable the following:

    • Network / Core
    • Network / Socket / BSD
    • Network / Socket / TCP
    • Network / Socket / UDP
    • CMSIS Driver / Ethernet MAC (API) / Ethernet MAC
    • CMSIS Driver / Ethernet PHY (API) / LAN8742A
    • Network / Interface / ETH (set to 1)
  • Click on Resolve, then OK

  • In the Project explorer, click on "Device / RTE_Device.h" to open it

  • Choose "Configuration Wizard" tab at the bottom

  • Enable ETH and make sure it looks like this:

    ETH config
  • In the Project explorer, click on "Network / Net_Config_BSD.h" to open it, click on "Configuration Wizard" tab at the bottom. Increase the number of BSD sockets to 10 and listening sockets to 3. It is important to have a number of listening sockets minimum 3 (because 3 is the default Mongoose's MG_SOCK_LISTEN_BACKLOG_SIZE), otherwise listen() socket syscall is going to fail and nothing will work:

    BSD config
  • Similarly, change "Network / Net_Config_TCP":

    TCP config
  • And "Network / Net_Config_UDP":

    UDP config
  • In the main.c, add the rl_net.h include and the call to netInitialize():

#include "RTE_Components.h"
#include "stm32f7xx_hal.h"
#include "Board_LED.h"
#include "cmsis_os2.h"
#include "rl_net.h"

static void mytask(void * param) {
  LED_Initialize();
  netInitialize();

  LED_On(2);
  int on = 0;
  for (;;) {
    on = !on;
    on ? LED_On(1) : LED_Off(1);
    osDelay(200);
  }
}

int main(void) {
  HAL_Init();
  extern void SystemClock_Config(void);
  SystemClock_Config();
  SystemCoreClockUpdate();
  
  osKernelInitialize ();
  size_t stack_size = 4096;
  void *stk = malloc(stack_size);
  const osThreadAttr_t attr = { .stack_mem  = stk, .stack_size = stack_size };
  osThreadNew(mytask, NULL, &attr);
  osKernelStart();
}
  • Build, Load. The blue LED should continue blinking
  • At this point, we have our board running TCP/IP stack, but no networking application is using it.

Step 5. Add mongoose

  • Start your browser and download mongoose.h and mongoose.c directly into the project's directory
  • In the project explorer, SourceGroup1, choose "Add existing item" and add both mongoose.c and mongoose.h
  • Right-click on SourceGroup1, select Options, choose "C/C++" tab, paste this in the "Misc Controls" to enable RTX support in Mongoose:
  -DMG_ARCH=MG_ARCH_RTX
  • Add a very simple Web server. Modify main.c to the following:
#include "RTE_Components.h"
#include "stm32f7xx_hal.h"
#include "Board_LED.h"
#include "cmsis_os2.h"
#include "rl_net.h"
#include "mongoose.h"

uint64_t mg_millis(void) {  // Declare our own uptime function
    return osKernelGetTickCount();
}

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, "", "%s\n", "hi");
  }
}

static void mytask(void * param) {
  LED_Initialize();
  netInitialize();

  LED_On(2);
  int on = 0;
  
    struct mg_mgr mgr;
  mg_mgr_init(&mgr);   
  mg_http_listen(&mgr, "http://0.0.0.0:80", fn, &mgr);

  for (;;) {
    on = !on;
    on ? LED_On(1) : LED_Off(1);
    mg_mgr_poll(&mgr, 200);
  }
}

int main(void) {
  HAL_Init();
  extern void SystemClock_Config(void);
  SystemClock_Config();
  SystemCoreClockUpdate();
  
  osKernelInitialize ();
  size_t stack_size = 4096;
  void *stk = malloc(stack_size);
  const osThreadAttr_t attr = { .stack_mem  = stk, .stack_size = stack_size };
  osThreadNew(mytask, NULL, &attr);
  osKernelStart();
}
  • Build, Load. The blue LED should continue to blink
  • In order to find what IP address a DHCP server has assigned to our board, we could ping a broadcast address, or check the ARP table on a router. In our case, it is 192.168.2.22 - could be different in your case
$ arp -an
? (192.168.2.22) at 1e:30:6c:a2:45:5e on bridge100 ifscope [bridge]
  • Point your browser at http://192.168.2.22, see the "hi" message
  • We have a very basic web server up and running!

Step 6. Add Web UI

Let's add a more advanced Web UI to our web server.

  • Download two files to the project folder - they are both from the Mongoose dashboard example: packed_fs.c and web.c One contains embedded UI files, another one has a corresponding event handler function
  • Add those two files to the SourceGroup1 as existing files
  • Right-click on SourceGroup1, select Options, choose "C/C++" tab, paste this in the "Misc Controls":
-DMG_ARCH=MG_ARCH_RTX -DMG_ENABLE_PACKED_FS=1
  • Modify main.c to the following:

#include "RTE_Components.h"
#include "stm32f7xx_hal.h"
#include "Board_LED.h"
#include "cmsis_os2.h"
#include "rl_net.h"
#include "mongoose.h"

uint64_t mg_millis(void) {  // Declare our own uptime function
    return osKernelGetTickCount();
}

static void mytask(void * param) {
  LED_Initialize();
  netInitialize();

  LED_On(2);
  int on = 0;
  
  struct mg_mgr mgr;
  mg_mgr_init(&mgr);   
  extern void device_dashboard_fn(struct mg_connection *, int, void *, void *);
  mg_http_listen(&mgr, "http://0.0.0.0:80", device_dashboard_fn, &mgr);

  for (;;) {
    on = !on;
    on ? LED_On(1) : LED_Off(1);
    mg_mgr_poll(&mgr, 200);
  }
}

int main(void) {
  HAL_Init();
  extern void SystemClock_Config(void);
  SystemClock_Config();
  SystemCoreClockUpdate();
  
  osKernelInitialize ();
  size_t stack_size = 4096;
  void *stk = malloc(stack_size);
  const osThreadAttr_t attr = { .stack_mem  = stk, .stack_size = stack_size };
  osThreadNew(mytask, NULL, &attr);
  osKernelStart();
}
  • Build, Load
  • Wait a couple of seconds until DHCP assigns address, then point your browser at the assigned IP, see the dashboard UI