diff --git a/tutorials/stm32/nucleo_f429zi/cubemx/README.md b/tutorials/stm32/nucleo_f429zi/cubemx/README.md new file mode 100644 index 00000000..3833cc51 --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/cubemx/README.md @@ -0,0 +1,98 @@ +# Mongoose CubeMX integration + +This is a step-by-step guide on how to bring up a production-grade Web UI +dashboard on STM32 Nucleo-F429ZI development board. + +A pre-configured project .zip is available for download at https://mongoose.ws. +However for those who want to configure the project from scratch, follow the steps +below. + +## Generate project code +- Open the .ioc file in your CubeMX +- Click on "Project Manager" + - If you're using CubeIDE, select "CubeIDE" in the toolchain/IDE dropdown + - If you're using VSCode, select "Cmake" in the toolchain/IDE dropdown + - If you're using Makefile, select "Makefile" in the toolchain/IDE dropdown +- Click "Generate Code" button on the top right + +This should generate project code in this directory the directory where .ioc file lives. +Open this directory in your IDE. + +## Configure .bin generation + +Normally, IDEs generate .elf firmware file, cause it is easier for a debugger +to load it to a device. However, for the firmware OTA update, the .bin file +is required. The .bin file is the firmware image that gets copied to the +beginning of the microcontroller's flash memory and gets executed after the +system boot. + +So, we need to tell the IDE to generate .bin firmware flash image file. + +- If you're using CubeIDE, select Project / Properties / C/C++ build / Settings + / MCU/MPU Post build outputs, enable "Convert to binary file (-O binary)" +- If you're using VSCode, add a section to the end of the top-level `CMakeLists.txt`: +```cmake +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O binary + ${PROJECT_NAME}.elf + ${PROJECT_NAME}.bin +) +``` + +## Call Mongoose from main.c + + +Edit `Core/Src/main.c` and update the following snippets - notice the `USER CODE BEGIN` / `USER CODE END` placeholders. At the top of the file: + +```c +/* USER CODE BEGIN Includes */ +#include "mongoose_glue.h" +/* USER CODE END Includes */ +``` + +Before the `main()` function: +```c +/* USER CODE BEGIN 0 */ +int _write(int fd, unsigned char *buf, int len) { + HAL_UART_Transmit(&huart3, buf, len, HAL_MAX_DELAY); + return len; +} + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { // New HTTP request received + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), HAL_GetTick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", HAL_GetTick()); + } + } +} + +// This blocks forever. Call it at the end of main(), or in a +// separate RTOS task. Give that task 8k stack space. +void run_mongoose(void) { + struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + + for (;;) { + mg_mgr_poll(&mgr, 0); + } +} +/* USER CODE END 0 */ +``` + +Inside the `main()` function: +```c +/* USER CODE BEGIN WHILE */ +run_mongoose(); // <----------------- Add this line +while (1) +{ + /* USER CODE END WHILE */ +``` + +## Build, flash, test + +Start serial console. +Use STM32CubeProgrammer to flash the firmware. diff --git a/tutorials/stm32/nucleo_f429zi/cubemx/nucleo_f429zi.ioc b/tutorials/stm32/nucleo_f429zi/cubemx/nucleo_f429zi.ioc new file mode 100644 index 00000000..a48d5836 --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/cubemx/nucleo_f429zi.ioc @@ -0,0 +1,165 @@ +#MicroXplorer Configuration settings - do not modify +CAD.formats=[] +CAD.pinconfig=Dual +CAD.provider= +ETH.IPParameters=MediaInterface +ETH.MediaInterface=HAL_ETH_RMII_MODE +File.Version=6 +KeepUserPlacement=false +Mcu.CPN=STM32F429ZIT6 +Mcu.Family=STM32F4 +Mcu.IP0=ETH +Mcu.IP1=NVIC +Mcu.IP2=RCC +Mcu.IP3=RNG +Mcu.IP4=SYS +Mcu.IP5=USART3 +Mcu.IPNb=6 +Mcu.Name=STM32F429ZITx +Mcu.Package=LQFP144 +Mcu.Pin0=PC1 +Mcu.Pin1=PA1 +Mcu.Pin10=PD9 +Mcu.Pin11=PG11 +Mcu.Pin12=PG13 +Mcu.Pin13=PB7 +Mcu.Pin14=VP_RNG_VS_RNG +Mcu.Pin15=VP_SYS_VS_Systick +Mcu.Pin2=PA2 +Mcu.Pin3=PA7 +Mcu.Pin4=PC4 +Mcu.Pin5=PC5 +Mcu.Pin6=PB0 +Mcu.Pin7=PB13 +Mcu.Pin8=PB14 +Mcu.Pin9=PD8 +Mcu.PinsNb=16 +Mcu.ThirdPartyNb=0 +Mcu.UserConstants= +Mcu.UserName=STM32F429ZITx +MxCube.Version=6.11.1 +MxDb.Version=DB.6.0.111 +NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.ETH_IRQn=true\:0\:0\:false\:false\:false\:true\:false\:true +NVIC.ForceEnableDMAVector=true +NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false +NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +PA1.Mode=RMII +PA1.Signal=ETH_REF_CLK +PA2.Mode=RMII +PA2.Signal=ETH_MDIO +PA7.Mode=RMII +PA7.Signal=ETH_CRS_DV +PB0.Locked=true +PB0.Signal=GPIO_Output +PB13.Mode=RMII +PB13.Signal=ETH_TXD1 +PB14.Locked=true +PB14.Signal=GPIO_Output +PB7.Locked=true +PB7.Signal=GPIO_Output +PC1.Mode=RMII +PC1.Signal=ETH_MDC +PC4.Mode=RMII +PC4.Signal=ETH_RXD0 +PC5.Mode=RMII +PC5.Signal=ETH_RXD1 +PD8.Locked=true +PD8.Mode=Asynchronous +PD8.Signal=USART3_TX +PD9.Locked=true +PD9.Mode=Asynchronous +PD9.Signal=USART3_RX +PG11.Locked=true +PG11.Mode=RMII +PG11.Signal=ETH_TX_EN +PG13.Locked=true +PG13.Mode=RMII +PG13.Signal=ETH_TXD0 +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=false +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32F429ZITx +ProjectManager.FirmwarePackage=STM32Cube FW_F4 V1.28.0 +ProjectManager.FreePins=false +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=0x200 +ProjectManager.KeepUserCode=true +ProjectManager.LastFirmware=true +ProjectManager.LibraryCopy=1 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain= +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=f429zi.ioc +ProjectManager.ProjectName=f429zi +ProjectManager.ProjectStructure= +ProjectManager.RegisterCallBack=RNG +ProjectManager.StackSize=0x400 +ProjectManager.TargetToolchain=STM32CubeIDE +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=true +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_ETH_Init-ETH-false-HAL-true,4-MX_USART3_UART_Init-USART3-false-HAL-true,5-MX_RNG_Init-RNG-false-HAL-true +RCC.48MHZClocksFreq_Value=45000000 +RCC.AHBFreq_Value=180000000 +RCC.APB1CLKDivider=RCC_HCLK_DIV4 +RCC.APB1Freq_Value=45000000 +RCC.APB1TimFreq_Value=90000000 +RCC.APB2CLKDivider=RCC_HCLK_DIV2 +RCC.APB2Freq_Value=90000000 +RCC.APB2TimFreq_Value=180000000 +RCC.CortexFreq_Value=180000000 +RCC.EthernetFreq_Value=180000000 +RCC.FCLKCortexFreq_Value=180000000 +RCC.FamilyName=M +RCC.HCLKFreq_Value=180000000 +RCC.HSE_VALUE=25000000 +RCC.HSI_VALUE=16000000 +RCC.I2SClocksFreq_Value=192000000 +RCC.IPParameters=48MHZClocksFreq_Value,AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2CLKDivider,APB2Freq_Value,APB2TimFreq_Value,CortexFreq_Value,EthernetFreq_Value,FCLKCortexFreq_Value,FamilyName,HCLKFreq_Value,HSE_VALUE,HSI_VALUE,I2SClocksFreq_Value,LCDTFTFreq_Value,LSE_VALUE,LSI_VALUE,MCO2PinFreq_Value,PLLCLKFreq_Value,PLLM,PLLN,PLLQ,PLLQCLKFreq_Value,RTCFreq_Value,RTCHSEDivFreq_Value,SAI_AClocksFreq_Value,SAI_BClocksFreq_Value,SYSCLKFreq_VALUE,SYSCLKSource,VCOI2SOutputFreq_Value,VCOInputFreq_Value,VCOOutputFreq_Value,VCOSAIOutputFreq_Value,VCOSAIOutputFreq_ValueQ,VCOSAIOutputFreq_ValueR,VcooutputI2S,VcooutputI2SQ +RCC.LCDTFTFreq_Value=24500000 +RCC.LSE_VALUE=32768 +RCC.LSI_VALUE=32000 +RCC.MCO2PinFreq_Value=180000000 +RCC.PLLCLKFreq_Value=180000000 +RCC.PLLM=8 +RCC.PLLN=180 +RCC.PLLQ=8 +RCC.PLLQCLKFreq_Value=45000000 +RCC.RTCFreq_Value=32000 +RCC.RTCHSEDivFreq_Value=12500000 +RCC.SAI_AClocksFreq_Value=24500000 +RCC.SAI_BClocksFreq_Value=24500000 +RCC.SYSCLKFreq_VALUE=180000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.VCOI2SOutputFreq_Value=384000000 +RCC.VCOInputFreq_Value=2000000 +RCC.VCOOutputFreq_Value=360000000 +RCC.VCOSAIOutputFreq_Value=98000000 +RCC.VCOSAIOutputFreq_ValueQ=24500000 +RCC.VCOSAIOutputFreq_ValueR=49000000 +RCC.VcooutputI2S=192000000 +RCC.VcooutputI2SQ=192000000 +USART3.IPParameters=VirtualMode +USART3.VirtualMode=VM_ASYNC +VP_RNG_VS_RNG.Mode=RNG_Activate +VP_RNG_VS_RNG.Signal=RNG_VS_RNG +VP_SYS_VS_Systick.Mode=SysTick +VP_SYS_VS_Systick.Signal=SYS_VS_Systick +board=custom +isbadioc=false diff --git a/tutorials/stm32/nucleo_f429zi/minimal/Makefile b/tutorials/stm32/nucleo_f429zi/minimal/Makefile new file mode 100644 index 00000000..248d3019 --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/minimal/Makefile @@ -0,0 +1,38 @@ +# Copyright (c) 2026 Cesanta Software Limited +# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/ + +CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_f4/Include +CFLAGS += -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 $(CFLAGS_EXTRA) +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +SOURCES = main.c hal.c +SOURCES += cmsis_f4/Source/Templates/gcc/startup_stm32f429xx.s + +TOP = ../../../.. +CFLAGS += -I$(TOP) +SOURCES += $(TOP)/mongoose.c + +all: firmware.bin + +firmware.elf: cmsis_core cmsis_f4 hal.h link.ld Makefile $(SOURCES) + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + @echo + @echo "To flash, run 'make flash', or use STM32CubeProgrammer" + +flash: firmware.bin + STM32_Programmer_CLI -c port=swd -e all -w firmware.elf -hardRst + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +cmsis_f4: + git clone -q -c advice.detachedHead=false --depth 1 -b v2.6.9 https://github.com/STMicroelectronics/cmsis_device_f4 $@ + +clean: + rm -rf firmware.* diff --git a/tutorials/stm32/nucleo_f429zi/minimal/hal.c b/tutorials/stm32/nucleo_f429zi/minimal/hal.c new file mode 100644 index 00000000..e519bff3 --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/minimal/hal.c @@ -0,0 +1,125 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" + +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) { + uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry + if (period == 0) return false; // Avoid division by zero + if (diff < period) return false; // Period has not elapsed yet + *t += (diff / period) * period; // Preserve cadence, skip missed periods + return true; +} + +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} + +uint64_t hal_get_tick(void) { + return s_ticks; +}; + +uint32_t SystemCoreClock = 160000000; +void SystemInit(void) { // Called automatically by startup code + hal_system_init(); // Enable FPU + hal_clock_init(); + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +void ExitRun0Mode(void) { +} + +struct stat; +__attribute__((weak)) int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); + prev_heap = s_current_heap_end; + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; + s_current_heap_end += incr; + return prev_heap; +} + +__attribute__((weak)) int _open(const char *path) { + (void) path; + return -1; +} + +__attribute__((weak)) int _close(int fd) { + (void) fd; + return -1; +} + +__attribute__((weak)) int _isatty(int fd) { + (void) fd; + return 1; +} + +__attribute__((weak)) int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +__attribute__((weak)) void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +__attribute__((weak)) void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +__attribute__((weak)) int _getpid(void) { + return -1; +} + +__attribute__((weak)) int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +__attribute__((weak)) int _unlink(const char *a) { + (void) a; + return -1; +} + +__attribute__((weak)) int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +__attribute__((weak)) int mkdir(const char *path, int mode) { + (void) path, (void) mode; + return -1; +} + +__attribute__((weak)) void _init(void) { +} diff --git a/tutorials/stm32/nucleo_f429zi/minimal/hal.h b/tutorials/stm32/nucleo_f429zi/minimal/hal.h new file mode 100644 index 00000000..4669da5d --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/minimal/hal.h @@ -0,0 +1,182 @@ +// Copyright (c) 2026 Cesanta Software Limited +// https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf +// https://www.st.com/resource/en/datasheet/stm32f429zi.pdf + +#pragma once + +#include + +#include +#include +#include +#include + + +extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now); +extern uint64_t hal_get_tick(void); +extern size_t hal_ram_free(void); +extern size_t hal_ram_used(void); +extern uint32_t SystemCoreClock; + +#define HAL_ETH_PINS \ + PIN('A', 1), /* ETH_REF_CLK */ \ + PIN('A', 2), /* ETH_MDIO */ \ + PIN('A', 7), /* ETH_CRS_DV */ \ + PIN('B', 13), /* ETH_TXD1 */ \ + PIN('C', 1), /* ETH_MDC */ \ + PIN('C', 4), /* ETH_RXD0 */ \ + PIN('C', 5), /* ETH_RXD1 */ \ + PIN('G', 11), /* ETH_TX_EN */ \ + PIN('G', 13) /* ETH_TXD0 */ + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +void hal_init(void); +size_t hal_ram_free(void); +size_t hal_ram_used(void); + +// 6.3.3: APB1 clock <= 45MHz; APB2 clock <= 90MHz +// 3.5.1, Table 11: configure flash latency (WS) in accordance to clock freq +// 33.4: The AHB clock must be at least 25 MHz when Ethernet is used +enum { HAL_APB1_PRE = 5 /* AHB clock / 4 */, HAL_APB2_PRE = 4 /* AHB clock / 2 */ }; +enum { PLL_HSI = 16, PLL_M = 8, PLL_N = 180, PLL_P = 2 }; // Run at 180 Mhz +#define FLASH_LATENCY 5 +#define SYS_FREQUENCY ((PLL_HSI * PLL_N / PLL_M / PLL_P) * 1000000) +#define APB2_FREQUENCY (SYS_FREQUENCY / (BIT(HAL_APB2_PRE - 3))) +#define APB1_FREQUENCY (SYS_FREQUENCY / (BIT(HAL_APB1_PRE - 3))) + +static inline void spin(volatile uint32_t count) { + while (count--) (void) 0; +} + +enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF, HAL_GPIO_MODE_ANALOG }; +enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN }; +enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_MEDIUM, HAL_GPIO_SPEED_HIGH, HAL_GPIO_SPEED_INSANE }; +enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN }; +#define HAL_GPIO(N) ((GPIO_TypeDef *) (0x40020000 + 0x400 * (N))) + +static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) { return HAL_GPIO(PINBANK(pin)); } +static inline void hal_gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int hal_gpio_read(uint16_t pin) { + return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void hal_gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint8_t n = (uint8_t) (PINNO(pin)); + RCC->AHB1ENR |= BIT(PINBANK(pin)); // Enable GPIO clock + CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n); + CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2)); + CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2)); + CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t) af) << ((n & 7) * 4)); + CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2)); +} +static inline void hal_gpio_input(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} +static inline void hal_gpio_output(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} + +static inline void hal_irq_exti_attach(uint16_t pin) { + uint8_t bank = (uint8_t) (PINBANK(pin)), n = (uint8_t) (PINNO(pin)); + SYSCFG->EXTICR[n / 4] &= ~(15UL << ((n % 4) * 4)); + SYSCFG->EXTICR[n / 4] |= (uint32_t) (bank << ((n % 4) * 4)); + EXTI->IMR |= BIT(n); + EXTI->RTSR |= BIT(n); + EXTI->FTSR |= BIT(n); + int irqvec = n < 5 ? 6 + n : n < 10 ? 23 : 40; // IRQ vector index, 10.1.2 + NVIC_SetPriority(irqvec, 3); + NVIC_EnableIRQ(irqvec); +} + +static inline void hal_uart_init(USART_TypeDef *uart, unsigned long baud) { + uint8_t af = 7; // Alternate function + uint16_t rx = 0, tx = 0; // pins + uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1 + + if (uart == USART1) freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(4); + if (uart == USART2) freq = APB1_FREQUENCY, RCC->APB1ENR |= BIT(17); + if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1ENR |= BIT(18); + + if (uart == USART1) tx = PIN('A', 9), rx = PIN('A', 10); + if (uart == USART2) tx = PIN('A', 2), rx = PIN('A', 3); + if (uart == USART3) tx = PIN('D', 8), rx = PIN('D', 9); + + hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af); + hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af); + uart->CR1 = 0; // Disable this UART + uart->BRR = freq / baud; // Set baud rate + uart->CR1 |= BIT(13) | BIT(2) | BIT(3); // Set UE, RE, TE +} +static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->DR = byte; + while ((uart->SR & BIT(7)) == 0) spin(1); +} +static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) { + while (len-- > 0) hal_uart_write_byte(uart, *(uint8_t *) buf++); +} +static inline int hal_uart_read_ready(USART_TypeDef *uart) { + return uart->SR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) { + return (uint8_t) (uart->DR & 255); +} + +static inline void hal_rng_init(void) { + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; + RNG->CR |= RNG_CR_RNGEN; +} +static inline uint32_t hal_rng_read(void) { + while ((RNG->SR & RNG_SR_DRDY) == 0) (void) 0; + return RNG->DR; +} + +// Hw pull-ups on PHY RXD0,1,DV to enable autonegotiation +static inline void hal_ethernet_init(void) { + // Initialise Ethernet. Enable MAC GPIO pins, see + // https://www.farnell.com/datasheets/2014265.pdf section 6.10 + uint16_t pins[] = {HAL_ETH_PINS}; + for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + hal_gpio_init(pins[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_INSANE, + HAL_GPIO_PULL_NONE, 11); // 11 is the Ethernet function + } + NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler + SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; // Use RMII. Goes first! + RCC->AHB1ENR |= + RCC_AHB1ENR_ETHMACEN | RCC_AHB1ENR_ETHMACTXEN | RCC_AHB1ENR_ETHMACRXEN; +} + +static inline void hal_clock_init(void) { + FLASH->ACR |= FLASH_LATENCY | BIT(8) | BIT(9); // Flash latency, prefetch + RCC->PLLCFGR &= ~((BIT(17) - 1)); // Clear PLL multipliers + RCC->PLLCFGR |= (((PLL_P - 2) / 2) & 3) << 16; // Set PLL_P + RCC->PLLCFGR |= PLL_M | (PLL_N << 6); // Set PLL_M and PLL_N + RCC->CR |= BIT(24); // Enable PLL + while ((RCC->CR & BIT(25)) == 0) (void) 0; // Wait until done + RCC->CFGR = (HAL_APB1_PRE << 10) | (HAL_APB2_PRE << 13); // Set prescalers + RCC->CFGR |= 2; // Set clock source to PLL + while ((RCC->CFGR & 12) == 0) (void) 0; // Wait until done + RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // Enable SYSCFG +} + +static inline void hal_system_init(void) { + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + __DSB(); + __ISB(); +} diff --git a/tutorials/stm32/nucleo_f429zi/minimal/link.ld b/tutorials/stm32/nucleo_f429zi/minimal/link.ld new file mode 100644 index 00000000..3a21639f --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/minimal/link.ld @@ -0,0 +1,30 @@ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08000000, LENGTH = 2048k + sram(rwx) : ORIGIN = 0x20000000, LENGTH = 192k /* remaining 64k in a separate address space */ +} +_estack = ORIGIN(sram) + LENGTH(sram); /* stack points to end of SRAM */ + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + .data : { + _sdata = .; /* for init_ram() */ + *(.first_data) + *(.data SORT(.data.*)) + *(.iram .iram* .iram.*) + _edata = .; /* for init_ram() */ + } > sram AT > flash + _sidata = LOADADDR(.data); + + .bss : { + _sbss = .; /* for init_ram() */ + *(.bss SORT(.bss.*) COMMON) + _ebss = .; /* for init_ram() */ + } > sram + + . = ALIGN(8); + _end = .; /* for cmsis_gcc.h and init_ram() */ +} diff --git a/tutorials/stm32/nucleo_f429zi/minimal/main.c b/tutorials/stm32/nucleo_f429zi/minimal/main.c new file mode 100644 index 00000000..2f3e99e5 --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/minimal/main.c @@ -0,0 +1,70 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" +#include "hal.h" + +#define DEBUG_UART USART3 +#define LED1 PIN('B', 0) +#define LED2 PIN('B', 7) +#define LED3 PIN('B', 14) + +// Redirect stdout debug output to UART +int _write(int fd, char *ptr, int len) { + if (fd == 1 || fd == 2) hal_uart_write_buf(DEBUG_UART, ptr, (size_t) len); + return len; +} + +static void blink_task(void) { + static uint64_t blink_timer = 0; + if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) { + hal_gpio_toggle(LED1); + } +} + +uint64_t mg_millis(void) { + return hal_get_tick(); +} + +bool mg_random(void *buf, size_t len) { + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = hal_rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { // New HTTP request received + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", + hal_get_tick()); + } + } +} + +int main(void) { + hal_clock_init(); + hal_rng_init(); + hal_gpio_output(LED1); + hal_gpio_output(LED2); + hal_gpio_output(LED3); + hal_uart_init(DEBUG_UART, 115200); + + hal_ethernet_init(); + + // Start a minimal web server + struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + + for (;;) { + mg_mgr_poll(&mgr, 0); + blink_task(); + } + + return 0; +} diff --git a/tutorials/stm32/nucleo_f429zi/minimal/mongoose_config.h b/tutorials/stm32/nucleo_f429zi/minimal/mongoose_config.h new file mode 100644 index 00000000..0472495f --- /dev/null +++ b/tutorials/stm32/nucleo_f429zi/minimal/mongoose_config.h @@ -0,0 +1,23 @@ +#pragma once + +#define MG_ARCH MG_ARCH_ARMGCC +#define MG_TLS MG_TLS_BUILTIN + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_DRIVER_STM32F 1 +#define MG_OTA MG_OTA_STM32F + +// Construct MAC address from the MCU unique ID. It is defined in the +// ST CMSIS header as UID_BASE +#define MGUID ((uint32_t *) 0x1FFF7A10) // Unique 96-bit chip ID +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + mac[0] = 2; \ + mac[1] = MGUID[0] & 255; \ + mac[2] = (MGUID[0] >> 10) & 255; \ + mac[3] = (MGUID[0] >> 19) & 255; \ + mac[4] = MGUID[1] & 255; \ + mac[5] = MGUID[2] & 255; \ + } while (0)