Skip to content

Commit 14a5e32

Browse files
committed
ports/stm32: Add Zephyr threading support.
Enable threading on STM32 ports using the Zephyr kernel integration layer. Tested on NUCLEO_F429ZI with 34/34 thread tests passing. Key changes: - Makefile: Add MICROPY_ZEPHYR_THREADING option and Zephyr kernel build rules - main.c: Initialize Zephyr kernel and configure static heap for threading - mpconfigport.h: Thread configuration with GIL and EVENT_POLL_HOOK - stm32_it.c: Fault handler instrumentation for debugging - zephyr_arch_stm32.c: STM32-specific Zephyr architecture implementation - SysTick/PendSV: Integrate with Zephyr scheduler for context switching Enable with: make BOARD=NUCLEO_F429ZI MICROPY_ZEPHYR_THREADING=1 Signed-off-by: Andrew Leech <[email protected]>
1 parent b2a1ceb commit 14a5e32

22 files changed

+1137
-85
lines changed

ports/stm32/Makefile

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,72 @@ include $(TOP)/extmod/extmod.mk
6060

6161
GIT_SUBMODULES += lib/libhydrogen lib/stm32lib lib/tinyusb
6262

63+
# Zephyr threading integration (optional)
64+
MICROPY_ZEPHYR_THREADING ?= 0
65+
66+
ifeq ($(MICROPY_ZEPHYR_THREADING),1)
67+
# Add Zephyr submodule
68+
GIT_SUBMODULES += lib/zephyr
69+
70+
# Remove -Werror for Zephyr headers
71+
CFLAGS := $(filter-out -Werror,$(CFLAGS))
72+
73+
# Set architecture for Zephyr
74+
ZEPHYR_ARCH := arm
75+
76+
# Enable idle thread for k_msleep() support in EVENT_POLL_HOOK
77+
# MUST be set BEFORE including zephyr_kernel.mk so idle.c is compiled
78+
MICROPY_ZEPHYR_USE_IDLE_THREAD = 1
79+
80+
# Include Zephyr kernel build
81+
include $(TOP)/extmod/zephyr_kernel/zephyr_kernel.mk
82+
83+
# Add Zephyr includes and flags
84+
CFLAGS += $(ZEPHYR_INC)
85+
CFLAGS += -Wno-error -Wno-macro-redefined
86+
# Define MICROPY_ZEPHYR_THREADING as a preprocessor symbol for C code
87+
CFLAGS += -DMICROPY_ZEPHYR_THREADING=1
88+
CFLAGS += -DMICROPY_ZEPHYR_USE_IDLE_THREAD=1
89+
90+
# CRITICAL: Explicitly add optimization flags for Zephyr builds
91+
# Without this, code is compiled with no optimization, causing 50-100x code size bloat
92+
CFLAGS += -Os -DNDEBUG
93+
94+
# Add STM32-specific Zephyr architecture implementation
95+
SRC_C += zephyr_arch_stm32.c
96+
97+
# Ensure Zephyr objects depend on generated headers
98+
$(addprefix $(BUILD)/, $(SRC_THIRDPARTY_C:.c=.o)): $(ZEPHYR_GEN_HEADERS)
99+
100+
# Pattern-specific CFLAGS for Zephyr objects
101+
# Zephyr kernel files: Use STM32 CMSIS headers + Zephyr config (for CONFIG_* symbols)
102+
$(BUILD)/$(TOP)/lib/zephyr/%.o: CFLAGS += -include $(TOP)/extmod/zephyr_kernel/zephyr_config_cortex_m.h -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function -Wno-return-type
103+
$(BUILD)/$(TOP)/extmod/zephyr_kernel/%.o: CFLAGS += -Wno-unused-parameter -Wno-unused-function
104+
105+
# Build Zephyr assembly files (context switching, PSP initialization)
106+
ZEPHYR_SWAP_HELPER_O = $(BUILD)/swap_helper.o
107+
ZEPHYR_PSP_SWITCH_O = $(BUILD)/zephyr_psp_switch.o
108+
OBJ += $(ZEPHYR_SWAP_HELPER_O)
109+
OBJ += $(ZEPHYR_PSP_SWITCH_O)
110+
OBJ += $(BUILD)/zephyr_offsets.o
111+
112+
# Common assembler flags for Zephyr .S files
113+
ZEPHYR_AS_FLAGS = -x assembler-with-cpp -D_ASMLANGUAGE \
114+
$(filter-out -std=% -Wdouble-promotion -Wfloat-conversion -Werror,$(CFLAGS)) \
115+
$(CFLAGS_MCU_$(MCU_SERIES)) \
116+
-include $(TOP)/extmod/zephyr_kernel/zephyr_config_cortex_m.h \
117+
-Wa,-mimplicit-it=thumb
118+
119+
$(ZEPHYR_SWAP_HELPER_O): $(TOP)/lib/zephyr/arch/arm/core/cortex_m/swap_helper.S $(ZEPHYR_GEN_HEADERS)
120+
$(ECHO) "CC $<"
121+
$(Q)$(CC) $(ZEPHYR_AS_FLAGS) -c -o $@ $<
122+
123+
$(ZEPHYR_PSP_SWITCH_O): $(TOP)/extmod/zephyr_kernel/zephyr_psp_switch.S
124+
$(ECHO) "CC $<"
125+
$(Q)$(CC) $(ZEPHYR_AS_FLAGS) -c -o $@ $<
126+
127+
endif
128+
63129
CROSS_COMPILE ?= arm-none-eabi-
64130
LD_DIR=boards
65131
USBDEV_DIR=usbdev
@@ -114,7 +180,11 @@ INC += -I$(TOP)/lib/tinyusb/src
114180
INC += -I$(TOP)/shared/tinyusb/
115181
INC += -Ilwip_inc
116182

183+
ifneq ($(MICROPY_ZEPHYR_THREADING),1)
117184
CFLAGS += $(INC) -Wall -Wpointer-arith -Werror -Wdouble-promotion -Wfloat-conversion -std=gnu99 -nostdlib $(CFLAGS_EXTRA)
185+
else
186+
CFLAGS += $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -std=gnu99 -nostdlib $(CFLAGS_EXTRA)
187+
endif
118188
CFLAGS += -D$(CMSIS_MCU) -DUSE_FULL_LL_DRIVER
119189
CFLAGS += $(CFLAGS_MCU_$(MCU_SERIES))
120190
CFLAGS += $(COPT)
@@ -507,13 +577,21 @@ $(BUILD)/mpremoteprocport.o: $(BUILD)/openamp/metal/config.h
507577
endif
508578
endif
509579

580+
# Conditionally exclude legacy threading files when using Zephyr
581+
# Note: pendsv.c and systick.c are kept because they contain utility functions,
582+
# but their interrupt handlers are conditionally compiled out via #if !MICROPY_ZEPHYR_THREADING
583+
ifeq ($(MICROPY_ZEPHYR_THREADING),1)
584+
SRC_C := $(filter-out pybthread.c mpthreadport.c,$(SRC_C))
585+
endif
586+
510587
# SRC_O should be placed first to work around this LTO bug with binutils <2.35:
511588
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83967
512589
OBJ += $(addprefix $(BUILD)/, $(SRC_O))
513590
OBJ += $(PY_O)
514591
OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o))
515592
OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o))
516593
OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o))
594+
OBJ += $(addprefix $(BUILD)/, $(SRC_THIRDPARTY_C:.c=.o))
517595
OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o))
518596
OBJ += $(addprefix $(BUILD)/, $(HAL_SRC_C:.c=.o))
519597
OBJ += $(addprefix $(BUILD)/, $(USBDEV_SRC_C:.c=.o))
@@ -682,6 +760,10 @@ $(BUILD)/firmware-trusted.bin: $(BUILD)/firmware.bin
682760

683761
# List of sources for qstr extraction
684762
SRC_QSTR += $(SRC_C) $(SRC_CXX) $(SHARED_SRC_C) $(GEN_PINS_SRC)
763+
# Add MicroPython-Zephyr integration files for root pointer extraction
764+
ifeq ($(MICROPY_ZEPHYR_THREADING),1)
765+
SRC_QSTR += $(ZEPHYR_MP_SRC_C)
766+
endif
685767

686768
# Making OBJ use an order-only dependency on the generated pins.h file
687769
# has the side effect of making the pins.h file before we actually compile

ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#define MICROPY_HW_BOARD_NAME "NUCLEO-F429ZI"
22
#define MICROPY_HW_MCU_NAME "STM32F429"
33

4+
// Enable Zephyr threading for development
5+
#define MICROPY_ZEPHYR_THREADING 1
6+
47
#define MICROPY_HW_HAS_SWITCH (1)
58
#define MICROPY_HW_HAS_FLASH (1)
69
#define MICROPY_HW_ENABLE_RNG (1)

ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ MICROPY_PY_SSL = 1
1111
MICROPY_SSL_MBEDTLS = 1
1212
MICROPY_HW_ENABLE_ISR_UART_FLASH_FUNCS_IN_RAM = 1
1313

14+
# Enable Zephyr threading for development
15+
MICROPY_ZEPHYR_THREADING = 1
16+
1417
FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py

ports/stm32/gccollect.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "py/gc.h"
2828
#include "py/mpthread.h"
29+
#include "py/mphal.h"
2930
#include "shared/runtime/gchelper.h"
3031
#include "shared/runtime/softtimer.h"
3132
#include "gccollect.h"

ports/stm32/irq.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ extern uint32_t irq_stats[IRQ_STATS_MAX];
5959
// value of the state variable, but rather just pass the return
6060
// value from disable_irq back to enable_irq.
6161

62+
#if MICROPY_ZEPHYR_THREADING
63+
// Zephyr IRQ management
64+
#include <zephyr/arch/cpu.h>
65+
66+
static inline uint32_t query_irq(void) {
67+
// For Zephyr, assume IRQs are enabled when not in atomic section
68+
// This is a simplification - Zephyr manages IRQs via arch_irq_lock/unlock
69+
return IRQ_STATE_ENABLED;
70+
}
71+
72+
static inline void enable_irq(mp_uint_t state) {
73+
arch_irq_unlock(state);
74+
}
75+
76+
static inline mp_uint_t disable_irq(void) {
77+
return arch_irq_lock();
78+
}
79+
80+
#else
81+
// STM32 native IRQ management
82+
6283
static inline uint32_t query_irq(void) {
6384
return __get_PRIMASK();
6485
}
@@ -73,6 +94,8 @@ static inline mp_uint_t disable_irq(void) {
7394
return state;
7495
}
7596

97+
#endif // MICROPY_ZEPHYR_THREADING
98+
7699
#if __CORTEX_M >= 0x03
77100

78101
// irqs with a priority value greater or equal to "pri" will be disabled
@@ -146,7 +169,14 @@ static inline void restore_irq_pri(uint32_t state) {
146169

147170
#else
148171

172+
// For Zephyr threading, SysTick uses priority 2 (maskable) instead of priority 0
173+
// arch_irq_lock() uses PRIMASK to mask ALL interrupts (except NMI/HardFault)
174+
// Priority 2 is chosen to be safely maskable and allow timely context switching
175+
#if MICROPY_ZEPHYR_THREADING
176+
#define IRQ_PRI_SYSTICK NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 2, 0)
177+
#else
149178
#define IRQ_PRI_SYSTICK NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 0, 0)
179+
#endif
150180

151181
// The UARTs have no FIFOs, so if they don't get serviced quickly then characters
152182
// get dropped. The handling for each character only consumes about 0.5 usec

0 commit comments

Comments
 (0)