Port STM32 - amir1387aht/MicroCS GitHub Wiki
Works with every STM32 family that has a Cube HAL: C0, F0, F1, F2, F3, F4, F7, G0, G4,
H5, H7, L0, L1, L4, L5, U5, WB, WL. The port uses the handles CubeMX generates (huart2,
hi2c1, hspi1, hadc1, htim3ā¦) and the official HAL_xxx() functions, so your clock
tree, pin muxing and DMA settings stay exactly as configured in CubeMX.
Nucleo-F401RE, F411RE, F446RE, G474RE, L476RG, H743ZI and the Black Pill F411CE have a complete
firmware in every release: copy the
.bin to the Nucleo's ST-LINK drive, open its virtual COM port at 115200 baud (or MicroCS
Studio) and type C#. Pins, flash layout and how to rebuild: firmware/.
- In CubeMX enable what you want to use from C#: a USART with its global interrupt (the
console), and any of I²C, SPI, ADC, TIM (PWM channels / an update interrupt for
Timer), DAC, I²S, QUADSPI/OCTOSPI, CAN/FDCAN, IWDG, RTC. Enable the EXTI interrupts for pins you wantGPIO.OnChangeon. - Add MicroCS to the project: source folders
src,modules,ports/stm32; include pathsinclude,ports/stm32. (CMake projects:set(MICROCS_PORT stm32)+add_subdirectory(MicroCS)+target_link_libraries(${CMAKE_PROJECT_NAME} microcs). Makefile projects:include MicroCS/microcs.mk.) - Copy
example_main.cintoCore/Src/, adjust the handle names and callmicrocs_main()inUSER CODE BEGIN 2. - Flash, open the ST-LINK virtual COM port at 115200 and type C#.
static mcs_stm32_board_t board;
static mcs_hal_t hal;
board.name = "NUCLEO-F446RE";
board.uart[2] = &huart2; // UART.Open(2, ...) + console
board.i2c[1] = &hi2c1; // I2C bus 1
board.spi[1] = &hspi1; board.spi_clock_hz[1] = HAL_RCC_GetPCLK2Freq(); // lets SPI.Open pick the prescaler
board.adc[0] = (mcs_stm32_adc_t){ &hadc1, ADC_CHANNEL_1 }; // ADC.Read(0)
board.pwm[0] = (mcs_stm32_pwm_t){ &htim3, TIM_CHANNEL_1, 0 }; // PWM.Set(0, ...)
board.timer[0] = (mcs_stm32_timer_t){ &htim6, 0 }; // Timer.Start(0, ...)
board.dac = &hdac; board.i2s[0] = &hi2s2; board.qspi[0] = &hqspi;
board.can[0] = &hcan1; /* or */ board.fdcan[0] = &hfdcan1;
board.rtc = &hrtc; board.iwdg = &hiwdg;
mcs_stm32_hal_init(&hal, &board);Only peripherals whose HAL_xxx_MODULE_ENABLED is set in stm32xxxx_hal_conf.h are compiled.
| C# | STM32 |
|---|---|
| pin numbers |
port * 16 + pin; names "PA5", "PC13" work everywhere |
GPIO.OnChange |
EXTI line of the pin (one pin per line, as in hardware) |
UART.Read / OnReceive
|
interrupt-driven ring buffer (MCS_STM32_UART_RXBUF) |
I2C.Scan |
HAL_I2C_IsDeviceReady |
SPI.Open(bus, hz, mode) |
re-programs prescaler / CPOL / CPHA when spi_clock_hz is given |
PWM.Set |
timer ARR/CCR computed from clock_hz (0 = from the APB clock) |
LedStrip |
ws2812 driver (MCS_ENABLE_WS2812=0 / -DMICROCS_WS2812=OFF leaves it out; replace the backend with mcs_driver_register, e.g. SPI + DMA): WS2812 on any pin, SysTick-timed bit-bang with interrupts masked during Show() (~30 µs per LED); HCLK ℠24 MHz |
Timer.Start |
timer update interrupt |
QSPI |
HAL_QSPI_Command / HAL_OSPI_Command
|
CAN |
bxCAN or FDCAN (classic frames), accept-all filter |
Hal.Micros |
DWT cycle counter (Cortex-M3+) or SysTick |
Hal.UniqueId |
96-bit UID |
By default (MCS_STM32_DEFINE_CALLBACKS=1) the port defines HAL_GPIO_EXTI_Callback,
HAL_UART_RxCpltCallback and HAL_UART_ErrorCallback. If your code already defines them,
set it to 0 and call mcs_stm32_exti(pin), mcs_stm32_uart_rx_cplt(huart) and
mcs_stm32_uart_error(huart) from yours. HAL_TIM_PeriodElapsedCallback usually exists
already (HAL tick on a TIM) ā call mcs_stm32_tim_elapsed(htim) from it, or set
MCS_STM32_DEFINE_TIM_CALLBACK=1. MCS_STM32_EXTI_HANDLERS=1 defines the EXTIx_IRQHandlers
when CubeMX does not.
The free top of the chip's own flash holds /boot.cs, /main.cs, uploads and script files
(LittleFS by default; YAFFS2, or the built-in TinyFS for just a few KB) ā see
example_main.c:
static mcs_stm32_flash_t flash;
static mcs_flashfs_t fs;
if (mcs_stm32_flash_init(&flash, 0, 0) == 0 && // top quarter of the flash
mcs_flashfs_mount(&fs, &flash.flash, 0, 0, MCS_FLASHFS_DEFAULT, MCS_FLASHFS_FORMAT_IF_NEEDED) == 0) {
cfg.fs_ops = fs.ops; cfg.fs_ctx = fs.ctx;
}- Every family: page flash (F0/F1/F3/G0/G4/L0/L1/L4/L5/U0/U5/C0/WB/WL, 2ā8 KB pages), sector
flash (F2/F4/F7: the region must lie in the uniform 128/256 KB top sectors, or ā with an explicit
address ā in a run of equal small sectors such as the 16 KB sectors 1ā3 when your linker script
keeps them free, see
firmware/stm32.ld; H5 8 KB, H7 128 KB). The program unit (8/16/32-byte flash words with ECC) is honoured; L0/L1 erase to 0x00 and the driver stores inverted bytes. On STM32WB the region stays below the wireless stack. - Pass an address and size to pick the region yourself (e.g. reserve it in the linker script); regions overlapping the firmware are refused.
- The CPU stalls while its own flash bank erases (up to 1ā2 s for a 128 KB F4 sector); uploads use the retrying Studio/shell protocol, so this is harmless.
-
No external flash, small chip (e.g. 8 KB of a 64/128 KB F103 or G0): TinyFS, built into
MicroCS ā
-DMICROCS_FS=tinyfs -DMICROCS_FS_SIZE=8192(CMake), orMCS_ENABLE_TINYFS 1andMCS_INTFLASH_SIZE 8192inmcs_user_config.h. Each flash page is one block; power loss at any moment keeps the last complete write. F2/F4/F7 need two top sectors (2 Ć 128 KB). - CMake (
MICROCS_PORT=stm32):-DMICROCS_FS=littlefsor-DMICROCS_FS=yaffs2adds the sources fromMICROCS_LITTLEFS_DIR/MICROCS_YAFFS2_DIR, a copy in the project (Middlewares/Third_Party/littlefs,third_party/,lib/...) or, with-DMICROCS_FS_DOWNLOAD=ON, a one-time download (without it the example uses a RAM disk). CubeIDE/Makefile projects addlfs.c lfs_util.candMCS_ENABLE_LFS 1,MCS_ENABLE_FLASH 1(inmcs_user_config.hor with-D) (see FILESYSTEM.md).
Heap guidance: 48ā64 KB for the REPL with a small RAM disk on F4/G4/L4 (128 KB SRAM),
160 KB+ on H7/H5/U5. Without the on-device compiler (MCS_ENABLE_COMPILER=0, run images
precompiled with mcs -C) 12ā32 KB is enough for many applications; see
LOW_RESOURCE.md.
With CMake (MICROCS_PORT=stm32) the auto profile is the default: the CMSIS device macro
of your CubeMX project (STM32F072xB, STM32G474xx, ā¦) selects the RAM/flash class from
include/profiles/mcs_target_stm32.h, and that
picks the profile ā min below 32 KB of RAM or 128 KB of flash (F070/F072/F103/G070/L073ā¦),
tiny on other 128 KB-flash parts, lowram, mcu or embedded as RAM grows, everything
on H5/H7/U5/F7. The HAL classes stay enabled
whenever the part has 128 KB of flash or more. In CubeIDE/Makefile projects put
#define MCS_PROFILE MCS_PROFILE_AUTO and #define MCS_PORT_HAL 1 in your mcs_user_config.h
(or -DMCS_PROFILE=MCS_PROFILE_AUTO -DMCS_PORT_HAL=1) to get the same.
Where one macro covers several sizes (STM32F103xB = F103x8 and F103xB) the smaller one is
assumed; define MCS_TARGET_RAM_KB / MCS_TARGET_FLASH_KB to override.
Every STM32 with at least 16 KB of SRAM and 64 KB of flash. These lines have less and
stop the build with an #error (define MCS_ALLOW_SMALL_TARGET=1 to try anyway):
| Family | Not supported |
|---|---|
| C0 | C011, C031, C051 |
| F0 | F030x4/x6/x8, F031, F038, F042, F048, F051, F058, F070x6 |
| F1 | F100 (except F100xE), F101x4āxB, F102, F103x4/x6 |
| F3 | F301x6, F302x6, F303x6/x8, F328, F334 |
| G0 | G030, G031, G041 (the 32 KB-flash G050x6/G051x6/G061x6 and G431x6 share a macro with their 64 KB versions and are not detected ā do not use them) |
| L0 | L010x4āx8, L011, L021, L031, L041, L051, L052, L053, L062, L063 |
| L1 | L100x6/x8/xB, L151x6/x8, L152x6/x8 |
| U0 | U031 |
tools/check_ports.sh stm32 <family> <device> <cpu> compiles the port with -Werror
against the official STM32Cube HAL headers of the family in 8 configurations (default, without
the ws2812/servo drivers, without drivers, auto profile, internal flash + LittleFS / YAFFS2 /
TinyFS) for 18 boards covering F0, F1, F4, F7, G0, G4, H5, H7, L0, L4, U5 and WB.
tools/build_stm32_firmware.sh links the ready-to-flash firmware for 7
boards, and tools/renode_check.py boots the F4 and H743 images in the Renode emulator and
uses their REPL (an expression, Hal.Board, a file in internal flash, the LED).