Skip to content

add STM32 backend (STM32duino / Arduino_Core_STM32)#359

Open
Tyuyt3975 wants to merge 5 commits into
gin66:masterfrom
Tyuyt3975:master
Open

add STM32 backend (STM32duino / Arduino_Core_STM32)#359
Tyuyt3975 wants to merge 5 commits into
gin66:masterfrom
Tyuyt3975:master

Conversation

@Tyuyt3975
Copy link
Copy Markdown

Add STM32 backend (STM32duino / Arduino_Core_STM32)

Overview

This PR adds a complete STM32 port for FastAccelStepper, targeting the STM32duino toolchain. Step pulses are generated via TIM2/TIM3 compare-match ISR with GPIO BSRR bit-bang, allowing any GPIO pin to be used as a step pin. Deferred queue filling runs in PendSV at the lowest NVIC priority.

The implementation has been reviewed across three audit rounds (R1–R3): critical bug fixes, CI/documentation, and line-by-line code verification against STM32duino core headers.


Supported families

Family Board example Timer Width Core
STM32F1 Blue Pill F103C8 TIM2 16-bit Cortex-M3
STM32F0 Nucleo-F091RC TIM2 16-bit Cortex-M0
STM32G0 Nucleo-G070RB TIM2 32-bit Cortex-M0+
STM32C0 STM32C031C6 TIM3 16-bit Cortex-M0+
STM32F4 Black Pill F401CC TIM2 32-bit Cortex-M4
STM32H7 Nucleo-H743ZI TIM2 32-bit Cortex-M7
STM32L0 Nucleo-L073RZ TIM2 16-bit Cortex-M0+
STM32L4 Nucleo-L476RG TIM2 32-bit Cortex-M4

STM32C0 does not have TIM2. The port uses TIM3, selected automatically via FAS_TIMER macros at compile time.


New / modified files

Source (4 files)

File Description
src/fas_arch/arduino_stm32.h FAS_STM32 define, reentrant-safe PRIMASK IRQ control (fasDisableInterrupts / fasEnableInterrupts), FAS_PSTR
src/pd_stm32/pd_config.h Compile-time config: TICKS_PER_S, queue topology, timing constants, feature flags
src/pd_stm32/stm32_queue.h StepperQueue class, GPIO BSRR direction macros, setDirPin() with null-check
src/pd_stm32/stm32_queue.cpp Timer init, ISR (all 4 channels), clock validation, PendSV deferred fill

New example file (1)

File Description
examples/StepperDemo/StepperPins_stm32.h Default pin mapping: STEP PA0–PA3, DIR PB0–PB3

CI / build (3 files)

File Change
extras/ci/build_matrix.yaml STM32 template + 5 board environments
extras/ci/platformio.ini Regenerated with 5 STM32 envs
.github/workflows/build_arduino_examples_matrix.yml Regenerated — 5 STM32 boards now build in CI

Key design details

Timer abstraction (FAS_TIMER macros)
Timer selection is resolved entirely at compile time:

#if defined(STM32C0xx)
    #define FAS_TIMER            TIM3
    #define FAS_TIMER_IRQn       TIM3_IRQn
    #define FAS_TIMER_RCC_ENABLE __HAL_RCC_TIM3_CLK_ENABLE()
    #define FAS_TIM_IS_16BIT
    #define FAS_TIMER_ARR_MAX    0xFFFF
#else
    #define FAS_TIMER            TIM2
    ...
#endif

16-bit wrap-safe CCR write (fas_tim_set_ccr)
STM32F1 (TIM2) and STM32C0 (TIM3) have 16-bit ARR. A naïve CNT + delay overflows above 65535. The fix:

*ccr = (cnt + delay) & 0xFFFF;

This correctly wraps: if cnt=65520, delay=432 → target=416, actual ticks = (65536−65520)+416 = 432. ✓

Memory barrier (FAS_DMB)
Cortex-M0/M0+ (F0, G0, L0, C0) do not have __DMB(). The wrapper uses __DSB() on ARMv6-M and __DMB() on ARMv7-M/ARMv8-M.main:

#if defined(__ARM_ARCH_6M__)
    #define FAS_DMB() __DSB()
#elif defined(__ARM_ARCH_7M__) || ...
    #define FAS_DMB() __DMB()
#else
    #define FAS_DMB() __DSB()  // safe fallback
#endif

Runtime clock validation
initStepTimer() reads the actual APB1 timer clock via getTimClock() and compares against TICKS_PER_S. If mismatched, it sets fas_stm32_clock_error (code 1 = impossible, code 2 = non-integer prescaler) and prints a warning to Serial at engine.init().

Two independent clock domains

  • Step timing: TIM2/TIM3 counter, driven by TICKS_PER_S (user-defined at compile time). Determines all motion parameters.
  • Cyclic refill: uwTick / SysTick, fires PendSV every ~3 ms. Does not affect step pulse accuracy.

Direction pin: atomic BSRR writes
Direction changes use BSRR (set: low half, clear: high half = mask << 16), valid on all STM32 families. No ODR read-modify-write race condition.


CI test matrix (5 boards)

Env MCU Key coverage
stm32_f103c8 F103 F1 16-bit TIM2 wrap regression
stm32_g070rb G070 __DSB() on M0+, 32-bit TIM2
stm32_f401cc F401 32-bit TIM2 baseline
stm32_h743zi H743 H7 D2CFGR clock detection
stm32_l476rg L476 else-branch clock detection

Usage

In platformio.ini, define TICKS_PER_S matching the board's APB1 timer clock:

[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
framework = arduino
build_flags =
    -DTICKS_PER_S=72000000   ; F103 @72MHz: APB1=36MHz ×2

[env:stm32c031c6]
platform = ststm32
board = generic_stm32c031c6tx
framework = arduino
build_flags =
    -DTICKS_PER_S=48000000   ; C031 @48MHz: APB1=48MHz ×1, TIM3

Deferred (out of scope for this PR)

  • README.md Warning Stop and SetCurrentPosition methods #2: update "TIM2 reserved" note to mention TIM3 on C0 (requires manual edit in fork's README)
  • RampCalculator 48 MHz constant: pending hardware verification on F091RC board

Change summary

Round Scope Count
R1 Bug fixes (code) 6 bugs: 16-bit wrap, FAS_DMB, C0 TIM3, clock validation, setDirPin null-check, macro guard
R2 CI + docs + pin mapping 6 actions: CI matrix (5 envs), StepperPins_stm32.h, pd_config.h examples, comments
R3 Line-by-line audit + cleanup 7 comment/UL cleanups; all macros verified against STM32duino source headers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants