Add nucleo_f429zi

This commit is contained in:
cpq committed 2026-05-15 19:49:22 +01:00
1 parent 592d0e2790
commit 1d373ce53e
8 files changed
+731

No files matched your search

@@ -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.
@@ -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
@@ -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.*
+125
View File
@@ -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) {
}
+182
View File
@@ -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 <stm32f429xx.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
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();
}
@@ -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() */
}
@@ -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;
}
@@ -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)