diff --git a/.github/workflows/build_arduino_examples_matrix.yml b/.github/workflows/build_arduino_examples_matrix.yml index 190a39c2..cd66d5d1 100644 --- a/.github/workflows/build_arduino_examples_matrix.yml +++ b/.github/workflows/build_arduino_examples_matrix.yml @@ -10,10 +10,6 @@ on: pull_request: branches: [ master ] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: build: strategy: @@ -80,6 +76,11 @@ jobs: - atmelsam - rpipico - rpipico2 + - bluepill_f103c8 + - nucleo_g070rb + - blackpill_f401cc + - nucleo_h743zi + - nucleo_l476rg runs-on: ubuntu-latest diff --git a/.github/workflows/build_idf_examples_matrix.yml b/.github/workflows/build_idf_examples_matrix.yml index fda5b6e8..a5976fc5 100644 --- a/.github/workflows/build_idf_examples_matrix.yml +++ b/.github/workflows/build_idf_examples_matrix.yml @@ -15,10 +15,6 @@ on: pull_request: branches: [ master ] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: build: strategy: diff --git a/.gitignore b/.gitignore index e6429e95..c69c1fe9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,21 @@ .pio .dir .tested +.github +.cursor +.venv +.venv/ +venv/ +env/ +.env/ run_avr pio_dirs pio_espidf PCB/ -examples/*/*.h -examples/*/*.cpp +# 2026.06.15 Removed ignore on examples/ to allow .h files (e.g. StepperPins_stm32.h) +# to be tracked by git and available for CI build via build-pio-dirs.sh +# examples/*/*.h +# examples/*/*.cpp !examples/*/test_*.cpp extras/tests/pc_based/test_[0-9][0-9] extras/tests/pc_based/FastAccelStepper.cpp @@ -32,7 +41,8 @@ extras/tests/simavr_based/simavr extras/tests/simavr_based/test_sd*/Makefile extras/tests/simavr_based/test_seq*/Makefile extras/tests/esp32_hw_based/*.log -examples +# 2026.06.15 Removed ignore on examples/ to allow .h files +# examples extras/gen_log2_const/Log2Representation.cpp extras/gen_log2_const/main extras/tests/simavr_based/.fas diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a30d39..c466442f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -1.2.5: +1.2.5: - ESP32: Enable MCPWM/PCNT driver for IDF 5.3+ on ESP32-S3, ESP32-C6, ESP32-H2 - Add driverType()/driverTypeString() API to query ESP32 driver at runtime - StepperDemo: guard against selecting unconnected stepper motors diff --git a/README.md b/README.md index a5d50aaa..72dd07be 100644 --- a/README.md +++ b/README.md @@ -639,6 +639,157 @@ Found on youtube: As mentioned by kthod861 in [Issue #110](https://github.com/gin66/FastAccelStepper/issues/110): * [22 01 2021 Stepper POC3](https://youtu.be/fm2_VkUG10k) +## STM32 Arduino Support + +FastAccelStepper supports STM32 microcontrollers via the official +[Arduino Core STM32](https://github.com/stm32duino/Arduino_Core_STM32). + +### Architecture + +- **Step generation**: TIM2 CC interrupt + BSRR/BRR GPIO (push-pull OUTPUT) +- **Steppers**: Up to 4 (any GPIO pins, dynamic slot allocation) +- **Pulse width**: 6 µs (configurable via `STEP_PULSE_WIDTH_US`) +- **Cyclic fill**: PendSV exception, triggered from FAS_TIMER ISR (TIM2 or TIM3 on C0) every 3ms via uwTick +- **GPIO mode**: Standard push-pull OUTPUT (any GPIO pin works) +- **Direction**: Atomic BSRR set/reset — not ODR XOR (race-free) +- **Direction settling**: `_dir_delay_active` state machine, 30µs delay +- **Timer clock**: Auto-detection of TIM2 (or TIM3 on C0) with APB1 prescaler ×2 correction +- **Interrupt safety**: PRIMASK save/restore (reentrant) +- **SR handling**: Snapshot → clear all processed at once (rc_w0) +- **PendSV**: `__attribute__((weak))` — FreeRTOS compatible +- **C0 support**: STM32C0 series uses TIM3 instead of TIM2 (C0 has no TIM2). + TIM3 is 16-bit (ARR=0xFFFF). With PSC=2 → timer clock = 16MHz, resulting + minimum speed ≈ 244 steps/s (16,000,000 / 65535). + Prescaler PSC=2 is integer (48/16=3) → `fas_stm32_clock_error` = 0. + +### Default Pin Mapping + +| Stepper | Default Pin | Notes | +|---------|-------------|-------| +| 0 | PA0 | Any GPIO pin can be used | +| 1 | PA1 | (PA0-PA3 are conventional only) | +| 2 | PA2 | | +| 3 | PA3 | | + +### ⚠ Warnings + +1. **FreeRTOS compatibility** — FastAccelStepper uses `PendSV_Handler` (via `__attribute__((weak))`) for deferred queue filling. FreeRTOS may also claim PendSV for task scheduling. If both claim PendSV without coordination, a runtime crash will occur. + - **If you use FreeRTOS**: Add `-DDISABLE_FAS_PENDSV` to build flags, and call `engine->manageSteppers()` periodically from a low-priority task or timer. + - Doing nothing (using both PendSV handlers) will cause undefined behavior. +2. **FAS_TIMER is reserved** — Do not use TIM2 (or TIM3 on STM32C0) elsewhere. +3. **HAL timebase must be SysTick** (default). uwTick is used for cyclic fill. +4. **Do not call HAL_Delay() with steppers running** — TIM2 priority 0 may preempt SysTick. Use millis() polling. +5. **TICKS_PER_S must match the timer counter clock** — It MUST be defined in your + build environment (DO NOT define it in the sketch — separate compilation prevents + `#define` in `.ino` from affecting library `.cpp` files): + - **PlatformIO**: Add to `platformio.ini`: + ```ini + build_flags = -DTICKS_PER_S=18000000UL + ``` + - **Arduino IDE**: Create `build_opt.h` in the sketch folder: + Sketch → Show Sketch Folder → create text file named `build_opt.h` containing: + ```cpp + -DTICKS_PER_S=18000000UL + ``` + ⚠️ **The `-D` prefix is required** (not a C `#define` — the file is parsed by the + compiler's command-line argument parser). File must end with a newline. + - TIM2 is used on most STM32 families (TIM3 on STM32C0). + - Use prescaled value (actual timer clock ÷ PSC+1), typically 16-20MHz. + See `pd_stm32/pd_config.h` for examples for each board and the table below. +6. **Clock error**: After `engine.init()`, check `fas_stm32_clock_error`: + if non-zero, `TICKS_PER_S` exceeds actual timer clock. + +7. **Error codes**: After `engine.init()`, check `fas_stm32_clock_error`: + - `0` = OK (timer configured correctly) + - `1` = TICKS_PER_S > actual timer clock, or prescaler clamped at 65535 + (timing will be wrong — define correct TICKS_PER_S in build_flags) + - `2` = Non-integer prescaler (timer clock not divisible by TICKS_PER_S) + See `pd_stm32/stm32_queue.cpp` for details. + Check via serial monitor (must call `Serial.begin()` before `engine.init()`). + +8. **Hardware testing pending** — This STM32 port has been verified by CI compilation + (5 boards: F103, G070, F401, H743, L476) but has **NOT** been tested on physical hardware + with actual stepper motors. Clock calculations and prescalers are mathematically verified + (see `pd_stm32/stm32_queue.cpp`), but real-world timing, pulse generation, and + direction settling have not been validated. Use with caution. + +### Adding a new STM32 family + +To add support for a new STM32 family not yet in the supported list: + +1. **Check timer availability**: Consult the MCU reference manual (RM). + Does the MCU have TIM2? Is it 16-bit or 32-bit? If no TIM2 available, which timer + has 4 CC channels (CCR1-CCR4) that can be used instead? + +2. **Add an `#elif` branch** in `src/pd_stm32/stm32_queue.cpp`: + ```cpp + #elif defined(STM32XXxx) + #define FAS_TIMER TIM2 // or TIM3, etc. + #define FAS_TIMER_IRQn TIM2_IRQn + #define FAS_TIMER_RCC_ENABLE() __HAL_RCC_TIM2_CLK_ENABLE() + // #define FAS_TIM_IS_16BIT // uncomment if timer is 16-bit + #define FAS_TIMER_ARR_MAX 0xFFFFFFFF // or 0xFFFF if 16-bit + ``` + +3. **Update `fas_tim_set_ccr()`** (line ~207): add your family macro to the + 16-bit wrap guard condition if the timer is 16-bit. + +4. **Update `getTimClock()`** (line ~174): add APB clock detection if your + family uses a different RCC register layout. + +5. **Update CCR init** (line ~320): add your family to the hardcoded CCR + pointer selection block if it uses a different timer (e.g. TIM3 instead of TIM2). + +6. **Register your family macro** in `src/pd_stm32/pd_config.h` family guard. + +### TICKS_PER_S Reference Table + +| Board | MCU | Timer | TIM_CLK | TICKS_PER_S (prescaled) | PSC | Timer actual | Error | +|--------------------|----------|-------------|-----------|------------------------|-----|-------------|-------| +| Blue Pill | F103C8 | TIM2 **16-bit** | 72 MHz | 18000000 | 3 | 18.000 MHz | 0 ✅ | +| Black Pill V2 | F401CC | TIM2 32-bit | 84 MHz | 16800000 | 4 | 16.800 MHz | 0 ✅ | +| Nucleo-G070RB | G070RB | TIM3 **16-bit** | 64 MHz | 16000000 (default) | 3 | 16.000 MHz | 0 ✅ | +| Nucleo-H743ZI | H743ZI | TIM2 32-bit | 200 MHz | 20000000 | 9 | 20.000 MHz | 0 ✅ | +| Nucleo-H743ZI | H743ZI | TIM2 32-bit | 200 MHz | 16666666 | 11 | 16.667 MHz | 2 ⚠️ | +| Nucleo-L476RG | L476RG | TIM2 32-bit | 80 MHz | 16000000 (default) | 4 | 16.000 MHz | 0 ✅ | +| Nucleo-C031C6 | C031C6 | TIM3 **16-bit** | 48 MHz | 16000000 (default) | 2 | 16.000 MHz | 0 ✅ | +| Nucleo-F091RC | F091RC | TIM2 32-bit | 48 MHz | 16000000 (default) | 2 | 16.000 MHz | 0 ✅ | +| Nucleo-L073RZ | L073RZ | TIM2 **16-bit** | 32 MHz | 32000000 | 0 | 32.000 MHz | 0 ✅ | + + + +## Local Compile Test Results (STM32) + +FastAccelStepper has been compile-tested locally with the following STM32 boards using PlatformIO + Arduino framework. + +### ✅ Boards PASS (15/15 Arduino boards) + +| # | Board ID | Chip | Flash | RAM | Status | +|----|---------------------------|----------------|---------|--------|--------| +| 1 | `blackpill_f103c8` | STM32F103C8 | 21004 | 2628 | ✅ PASS | +| 2 | `bluepill_f103c8` | STM32F103C8 | 21004 | 2628 | ✅ PASS | +| 3 | `bluepill_f103c8_128k` | STM32F103CB | 21004 | 2628 | ✅ PASS | +| 4 | `genericSTM32F103C8` | STM32F103C8 | 20880 | 2628 | ✅ PASS | +| 5 | `black_f407ve` | STM32F407VE | 23724 | 2712 | ✅ PASS | +| 6 | `black_f407vg` | STM32F407VG | — | — | ✅ PASS | +| 7 | `genericSTM32F407VET6` | STM32F407VE | — | — | ✅ PASS | +| 8 | `disco_f407vg` | STM32F407VG | — | — | ✅ PASS | +| 9 | `blackpill_f411ce` | STM32F411CE | — | — | ✅ PASS | +| 10 | `genericSTM32F411CE` | STM32F411CE | — | — | ✅ PASS | +| 11 | `nucleo_f411re` | STM32F411RE | — | — | ✅ PASS | +| 12 | `blackpill_f401ce` | STM32F401CE | — | — | ✅ PASS | +| 13 | `blackpill_f401cc` | STM32F401CC | — | — | ✅ PASS | +| 14 | `genericSTM32F401CE` | STM32F401CE | — | — | ✅ PASS | +| 15 | `nucleo_f401re` | STM32F401RE | — | — | ✅ PASS | + +### ❌ Boards FAIL + +| Board ID | Chip | Reason | +|------------------------|---------------|--------------------------------------------------| +| `disco_f411ve` | STM32F411VE | Board does not support Arduino framework (mbed only). Excluded from Arduino test list. | + +**Note**: All tests were performed with `toolchain-gccarmnoneeabi 12.3.1` and `framework-arduinoststm32 2.12.0`. See `extras/doc/stm32_compile_report.md` for full details. + ## Contribution - Thanks ixil for pull request (https://github.com/gin66/FastAccelStepper/pull/19) for ATmega2560 diff --git a/collect_source_v2.py b/collect_source_v2.py new file mode 100644 index 00000000..e7505a44 --- /dev/null +++ b/collect_source_v2.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +""" +collect_source.py +----------------- +Chạy script này trong thư mục root của project. +Kết quả: [tên thư mục root].txt chứa cây thư mục ASCII + toàn bộ nội dung source code. +""" + +import os +import sys + +# ── Cấu hình ────────────────────────────────────────────────────────────────── + +# Phần mở rộng được coi là source code (thêm/bớt tuỳ ý) +SOURCE_EXTENSIONS = { + ".py", ".js", ".ts", ".jsx", ".tsx", + ".java", ".kt", ".scala", + ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", + ".cs", ".go", ".rs", ".swift", + ".rb", ".php", ".lua", ".r", + ".html", ".htm", ".css", ".scss", ".sass", ".less", + ".sh", ".bash", ".zsh", ".fish", ".ps1", ".bat", ".cmd", + ".sql", ".graphql", ".proto", + ".json", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf", ".env", + ".xml", ".md", ".rst", ".txt", + ".dockerfile", ".makefile", + # thêm tuỳ ý... +} + +# Tên file / thư mục cần bỏ qua hoàn toàn +IGNORE_NAMES = { + ".git", ".svn", ".hg", + "__pycache__", ".mypy_cache", ".pytest_cache", + "node_modules", ".npm", ".yarn", + "venv", ".venv", "env", ".env", + ".idea", ".vscode", + "dist", "build", "out", ".next", ".nuxt", + "coverage", ".coverage", +} + +# ── Helpers ──────────────────────────────────────────────────────────────────── + +def should_ignore(name: str) -> bool: + return name in IGNORE_NAMES or name.startswith(".") + + +def is_source_file(name: str) -> bool: + _, ext = os.path.splitext(name) + return ext.lower() in SOURCE_EXTENSIONS + + +def build_tree(root: str, prefix: str = "") -> list[str]: + """Trả về danh sách dòng ASCII tree.""" + lines = [] + try: + entries = sorted(os.scandir(root), key=lambda e: (not e.is_dir(), e.name.lower())) + except PermissionError: + return lines + + entries = [e for e in entries if not should_ignore(e.name)] + + for i, entry in enumerate(entries): + connector = "└── " if i == len(entries) - 1 else "├── " + lines.append(f"{prefix}{connector}{entry.name}") + if entry.is_dir(): + extension = " " if i == len(entries) - 1 else "│ " + lines.extend(build_tree(entry.path, prefix + extension)) + return lines + + +def collect_files(root: str) -> list[str]: + """Trả về danh sách đường dẫn tuyệt đối của tất cả source file, theo thứ tự.""" + result = [] + for dirpath, dirnames, filenames in os.walk(root, topdown=True): + # Lọc thư mục bị ignore (chỉnh sửa in-place để os.walk không đi vào) + dirnames[:] = sorted( + [d for d in dirnames if not should_ignore(d)] + ) + for fname in sorted(filenames): + if not should_ignore(fname) and is_source_file(fname): + result.append(os.path.join(dirpath, fname)) + return result + + +def read_file_safe(path: str) -> str: + """Đọc file, fallback sang latin-1 nếu UTF-8 lỗi.""" + try: + with open(path, "r", encoding="utf-8") as f: + return f.read() + except UnicodeDecodeError: + with open(path, "r", encoding="latin-1") as f: + return f.read() + + +# ── Main ─────────────────────────────────────────────────────────────────────── + +def main(): + root = os.path.abspath(".") + root_name = os.path.basename(root) + output_path = os.path.join(root, f"{root_name}.txt") + + print(f"📁 Root : {root}") + print(f"📄 Output : {output_path}") + + source_files = collect_files(root) + + # Loại output file và bản thân script khỏi danh sách + script_path = os.path.abspath(__file__) + source_files = [ + f for f in source_files + if os.path.abspath(f) != os.path.abspath(output_path) + and os.path.abspath(f) != script_path + ] + + with open(output_path, "w", encoding="utf-8") as out: + # ── 1. Cây thư mục ── + out.write(f"{root_name}/\n") + tree_lines = build_tree(root) + out.write("\n".join(tree_lines)) + out.write("\n\n") + out.write("=" * 60 + "\n\n") + + # ── 2. Nội dung từng file ── + for fpath in source_files: + rel_path = os.path.relpath(fpath, root) + fname = os.path.basename(fpath) + + out.write(f"📄 FILE: {rel_path}\n") + out.write("-" * 60 + "\n") + out.write(read_file_safe(fpath)) + # Đảm bảo luôn xuống dòng trước footer + out.write("\n") + out.write(f"======end of [{fname}]======\n\n") + + print(f"✅ Đã ghi {len(source_files)} file vào {output_path}") + + +if __name__ == "__main__": + main() diff --git a/examples/ExternalCall/ExternalCall.ino b/examples/ExternalCall/ExternalCall.ino index 48377bba..55452459 100644 --- a/examples/ExternalCall/ExternalCall.ino +++ b/examples/ExternalCall/ExternalCall.ino @@ -8,14 +8,35 @@ #endif // As in StepperDemo for Motor 1 on AVR +#if defined(ARDUINO_ARCH_AVR) #define dirPinStepper1 5 #define enablePinStepper1 6 #define stepPinStepper1 9 // OC1A in case of AVR - -// As in StepperDemo for Motor 2 on AVR #define dirPinStepper2 7 #define enablePinStepper2 8 #define stepPinStepper2 10 // OC1B in case of AVR +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) +#define dirPinStepper1 18 +#define enablePinStepper1 26 +#define stepPinStepper1 17 +#define dirPinStepper2 19 +#define enablePinStepper2 PIN_UNDEFINED +#define stepPinStepper2 16 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper1 PB0 +#define enablePinStepper1 PA4 +#define stepPinStepper1 PA0 +#define dirPinStepper2 PB1 +#define enablePinStepper2 PIN_UNDEFINED +#define stepPinStepper2 PA1 +#else +#define dirPinStepper1 18 +#define enablePinStepper1 26 +#define stepPinStepper1 17 +#define dirPinStepper2 19 +#define enablePinStepper2 PIN_UNDEFINED +#define stepPinStepper2 16 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepper1 = NULL; @@ -31,7 +52,11 @@ void setup() { engine.setExternalCallForPin(setExternalPin); stepper1 = engine.stepperConnectToPin(stepPinStepper1); if (stepper1) { +#if defined(ARDUINO_ARCH_STM32) + stepper1->setDirectionPin(dirPinStepper1 | PIN_EXTERNAL_FLAG, true, 1000); +#else stepper1->setDirectionPin(dirPinStepper1 | PIN_EXTERNAL_FLAG); +#endif stepper1->setEnablePin(enablePinStepper1); stepper1->setAutoEnable(true); @@ -41,7 +66,12 @@ void setup() { stepper2 = engine.stepperConnectToPin(stepPinStepper2); if (stepper2) { +#if defined(ARDUINO_ARCH_STM32) + stepper2->setDirectionPin(dirPinStepper2, true, 1000); + stepper2->setAutoEnable(false); +#else stepper2->setDirectionPin(dirPinStepper2); +#endif stepper2->setEnablePin(enablePinStepper2); stepper2->setAutoEnable(true); diff --git a/examples/Issue150/Issue150.ino b/examples/Issue150/Issue150.ino index 2d05f2bd..2f5d6318 100644 --- a/examples/Issue150/Issue150.ino +++ b/examples/Issue150/Issue150.ino @@ -14,6 +14,10 @@ #define dirPinStepperESP 18 #define stepPinStepperESP 17 #define enablePinStepperESP 26 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define stepPinStepper PA0 +#define enablePinStepper PA4 #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -33,6 +37,10 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepperESP); stepper->setDirectionPin(dirPinStepperESP); stepper->setEnablePin(enablePinStepperESP, true); +#elif defined(ARDUINO_ARCH_STM32) + stepper = engine.stepperConnectToPin(stepPinStepper); + stepper->setDirectionPin(dirPinStepper, true, 1000); + stepper->setEnablePin(enablePinStepper, true); #endif stepper->setAutoEnable(true); diff --git a/examples/Issue151/Issue151.ino b/examples/Issue151/Issue151.ino index 7cd4bfc0..e2243927 100644 --- a/examples/Issue151/Issue151.ino +++ b/examples/Issue151/Issue151.ino @@ -14,6 +14,10 @@ #define dirPinStepperESP 18 #define stepPinStepperESP 17 #define enablePinStepperESP 26 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define stepPinStepper PA0 +#define enablePinStepper PA4 #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -33,6 +37,10 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepperESP); stepper->setDirectionPin(dirPinStepperESP); stepper->setEnablePin(enablePinStepperESP, true); +#elif defined(ARDUINO_ARCH_STM32) + stepper = engine.stepperConnectToPin(stepPinStepper); + stepper->setDirectionPin(dirPinStepper, true, 1000); + stepper->setEnablePin(enablePinStepper, true); #endif stepper->setAutoEnable(true); diff --git a/examples/Issue152/Issue152.ino b/examples/Issue152/Issue152.ino index 9b89f70b..c9ca4d86 100644 --- a/examples/Issue152/Issue152.ino +++ b/examples/Issue152/Issue152.ino @@ -14,6 +14,10 @@ #define dirPinStepperESP 18 #define stepPinStepperESP 17 #define enablePinStepperESP 26 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define stepPinStepper PA0 +#define enablePinStepper PA4 #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -33,6 +37,10 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepperESP); stepper->setDirectionPin(dirPinStepperESP); stepper->setEnablePin(enablePinStepperESP, true); +#elif defined(ARDUINO_ARCH_STM32) + stepper = engine.stepperConnectToPin(stepPinStepper); + stepper->setDirectionPin(dirPinStepper, true, 1000); + stepper->setEnablePin(enablePinStepper, true); #endif stepper->setAutoEnable(true); diff --git a/examples/Issue172/Issue172.ino b/examples/Issue172/Issue172.ino index 39f12385..2ebd8881 100644 --- a/examples/Issue172/Issue172.ino +++ b/examples/Issue172/Issue172.ino @@ -14,6 +14,10 @@ #define dirPinStepperESP 18 #define stepPinStepperESP 17 #define enablePinStepperESP 26 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define stepPinStepper PA0 +#define enablePinStepper PA4 #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -33,6 +37,10 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepperESP); stepper->setDirectionPin(dirPinStepperESP); stepper->setEnablePin(enablePinStepperESP, true); +#elif defined(ARDUINO_ARCH_STM32) + stepper = engine.stepperConnectToPin(stepPinStepper); + stepper->setDirectionPin(dirPinStepper, true, 1000); + stepper->setEnablePin(enablePinStepper, true); #endif stepper->setAutoEnable(true); diff --git a/examples/Issue173/Issue173.ino b/examples/Issue173/Issue173.ino index 646ccf74..86f7d0b3 100644 --- a/examples/Issue173/Issue173.ino +++ b/examples/Issue173/Issue173.ino @@ -14,6 +14,10 @@ #define dirPinStepperESP 18 #define stepPinStepperESP 17 #define enablePinStepperESP 26 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define stepPinStepper PA0 +#define enablePinStepper PA4 #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -34,6 +38,10 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepperESP); stepper->setDirectionPin(dirPinStepperESP); stepper->setEnablePin(enablePinStepperESP, true); +#elif defined(ARDUINO_ARCH_STM32) + stepper = engine.stepperConnectToPin(stepPinStepper); + stepper->setDirectionPin(dirPinStepper, true, 1000); + stepper->setEnablePin(enablePinStepper, true); #endif stepper->setAutoEnable(true); diff --git a/examples/Issue174/Issue174.ino b/examples/Issue174/Issue174.ino index 06bbeedb..ead110a7 100644 --- a/examples/Issue174/Issue174.ino +++ b/examples/Issue174/Issue174.ino @@ -7,8 +7,16 @@ long chirpTimeInitial = 0; // #define stepPinStepper 9 // step pin must be pin 9, 10 or 11 // Stepper Wiring +#if defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) #define dirPinStepper 19 #define stepPinStepper 14 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define stepPinStepper PA0 +#else +#define dirPinStepper 8 +#define stepPinStepper 9 +#endif // no clue what this does FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -49,7 +57,11 @@ void setup() { Serial.println("Setup stepper!"); // Stepper Parameters +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, false, 1000); +#else stepper->setDirectionPin(dirPinStepper, false); +#endif stepper->setAutoEnable(true); stepper->setSpeedInHz(maxStepperSpeed); // steps/s diff --git a/examples/Issue208/Issue208.ino b/examples/Issue208/Issue208.ino index d4ec04c0..f4f6d3c3 100644 --- a/examples/Issue208/Issue208.ino +++ b/examples/Issue208/Issue208.ino @@ -13,6 +13,10 @@ #define dirPinStepperESP 18 #define stepPinStepperESP 17 #define enablePinStepperESP 26 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define stepPinStepper PA0 +#define enablePinStepper PA4 #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -92,6 +96,9 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepperESP); stepper->setDirectionPin(dirPinStepperESP); stepper->setEnablePin(enablePinStepperESP, true); +#elif defined(ARDUINO_ARCH_STM32) + stepper = engine.stepperConnectToPin(stepPinStepper); + stepper->setDirectionPin(dirPinStepper, true, 1000); #endif stepper->setAutoEnable(true); diff --git a/examples/Issue250/Issue250.ino b/examples/Issue250/Issue250.ino index fe062739..91796db3 100644 --- a/examples/Issue250/Issue250.ino +++ b/examples/Issue250/Issue250.ino @@ -6,14 +6,23 @@ #endif // As in StepperDemo for Motor 1 on AVR +#if defined(ARDUINO_ARCH_AVR) #define dirPinStepper 5 #define enablePinStepper 6 #define stepPinStepper 9 // OC1A in case of AVR - -// As in StepperDemo for Motor 1 on ESP32 -// #define dirPinStepper 18 -// #define enablePinStepper 26 -// #define stepPinStepper 19 +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) +#define dirPinStepper 18 +#define enablePinStepper 26 +#define stepPinStepper 19 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define enablePinStepper PA4 +#define stepPinStepper PA0 +#else +#define dirPinStepper 5 +#define enablePinStepper 6 +#define stepPinStepper 9 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepper = NULL; @@ -23,7 +32,11 @@ void setup() { engine.init(); stepper = engine.stepperConnectToPin(stepPinStepper); if (stepper) { +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(true); @@ -97,4 +110,4 @@ void loop() { sleep_cpu(); #endif } -} +} \ No newline at end of file diff --git a/examples/Issue280/Issue280.ino b/examples/Issue280/Issue280.ino index 469e2302..d8b1baad 100644 --- a/examples/Issue280/Issue280.ino +++ b/examples/Issue280/Issue280.ino @@ -1,14 +1,27 @@ #include "FastAccelStepper.h" #ifdef SIMULATOR -#include #include #endif // As in StepperDemo for Motor 1 on AVR +#if defined(ARDUINO_ARCH_AVR) #define dirPinStepper 5 #define enablePinStepper 6 #define stepPinStepper 9 // OC1A in case of AVR +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) +#define dirPinStepper 18 +#define enablePinStepper 26 +#define stepPinStepper 17 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define enablePinStepper PA4 +#define stepPinStepper PA0 +#else +#define dirPinStepper 5 +#define enablePinStepper 6 +#define stepPinStepper 9 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepper = NULL; @@ -18,7 +31,11 @@ void setup() { engine.init(); stepper = engine.stepperConnectToPin(stepPinStepper); if (stepper) { +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(true); @@ -85,4 +102,4 @@ void loop() { noInterrupts(); sleep_cpu(); #endif -} +} \ No newline at end of file diff --git a/examples/LinearAcceleration/LinearAcceleration.ino b/examples/LinearAcceleration/LinearAcceleration.ino index 4798cdbf..d72e2d3b 100644 --- a/examples/LinearAcceleration/LinearAcceleration.ino +++ b/examples/LinearAcceleration/LinearAcceleration.ino @@ -6,9 +6,23 @@ // #define stepPinStepper 9 // OC1A in case of AVR // As in StepperDemo for Motor 1 on ESP32 +#if defined(ARDUINO_ARCH_AVR) +#define dirPinStepper 5 +#define enablePinStepper 6 +#define stepPinStepper 9 +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) #define dirPinStepper 18 #define enablePinStepper 26 #define stepPinStepper 17 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define enablePinStepper PA4 +#define stepPinStepper PA0 +#else +#define dirPinStepper 18 +#define enablePinStepper 26 +#define stepPinStepper 17 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepper = NULL; @@ -40,7 +54,11 @@ void setup() { #endif if (stepper) { Serial.println("HAVE STEPPER"); +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(true); diff --git a/examples/MoveTimed/MoveTimed.ino b/examples/MoveTimed/MoveTimed.ino index 6d4c040b..564ceff5 100644 --- a/examples/MoveTimed/MoveTimed.ino +++ b/examples/MoveTimed/MoveTimed.ino @@ -1,19 +1,31 @@ -#if defined(ARDUINO_ARCH_AVR) -#include "AVRStepperPins.h" +#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_STM32) #include "FastAccelStepper.h" #ifdef SIMULATOR #include #endif -// As in StepperDemo for Motor 1 on AVR +#if defined(ARDUINO_ARCH_AVR) +#include "AVRStepperPins.h" #define dirPinStepperX 5 #define enablePinStepperX 6 #define stepPinStepperX stepPinStepper1A - -// As in StepperDemo for Motor 2 on AVR #define dirPinStepperY 7 #define enablePinStepperY 8 #define stepPinStepperY stepPinStepper1B +#elif defined(ARDUINO_ARCH_STM32) +#undef dirPinStepperX +#define dirPinStepperX PB0 +#undef enablePinStepperX +#define enablePinStepperX PA4 +#undef stepPinStepperX +#define stepPinStepperX PA0 +#undef dirPinStepperY +#define dirPinStepperY PB1 +#undef enablePinStepperY +#define enablePinStepperY PIN_UNDEFINED +#undef stepPinStepperY +#define stepPinStepperY PA1 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepperX = NULL; @@ -55,10 +67,18 @@ void setup() { Serial.println("Cannot initialize steppers"); } } +#if defined(ARDUINO_ARCH_STM32) + stepperX->setDirectionPin(dirPinStepperX, true, 1000); +#else stepperX->setDirectionPin(dirPinStepperX); +#endif stepperX->setEnablePin(enablePinStepperX); stepperX->setAutoEnable(false); +#if defined(ARDUINO_ARCH_STM32) + stepperY->setDirectionPin(dirPinStepperY, true, 1000); +#else stepperY->setDirectionPin(dirPinStepperY); +#endif stepperY->setEnablePin(enablePinStepperY); stepperY->setAutoEnable(false); diff --git a/examples/PrintPosition/PrintPosition.ino b/examples/PrintPosition/PrintPosition.ino index a3fc988d..3e766366 100644 --- a/examples/PrintPosition/PrintPosition.ino +++ b/examples/PrintPosition/PrintPosition.ino @@ -1,9 +1,23 @@ #include "FastAccelStepper.h" // As in StepperDemo for Motor 1 on AVR -#define dirPinStepper 17 +#if defined(ARDUINO_ARCH_AVR) +#define stepPinStepper 9 +#define enablePinStepper 6 +#define dirPinStepper 5 +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) +#define stepPinStepper 16 #define enablePinStepper 25 +#define dirPinStepper 17 +#elif defined(ARDUINO_ARCH_STM32) +#define stepPinStepper PA0 +#define enablePinStepper PA4 +#define dirPinStepper PB0 +#else #define stepPinStepper 16 +#define enablePinStepper 25 +#define dirPinStepper 17 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepper = NULL; @@ -22,7 +36,11 @@ void setup() { Serial.println((unsigned int)stepper); Serial.println((unsigned int)&engine); if (stepper) { +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(true); @@ -54,4 +72,4 @@ void loop() { Serial.flush(); delay(10000); } -} +} \ No newline at end of file diff --git a/examples/RawAccess/RawAccess.ino b/examples/RawAccess/RawAccess.ino index 7f5e4556..7534d914 100644 --- a/examples/RawAccess/RawAccess.ino +++ b/examples/RawAccess/RawAccess.ino @@ -1,9 +1,23 @@ #include "FastAccelStepper.h" // for avr: either use pin 9 or 10 aka OC1A or OC1B +#if defined(ARDUINO_ARCH_AVR) +#define stepPinStepper 9 +#define enablePinStepper 6 +#define dirPinStepper 5 +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) #define stepPinStepper 17 #define enablePinStepper 26 #define dirPinStepper 18 +#elif defined(ARDUINO_ARCH_STM32) +#define stepPinStepper PA0 +#define enablePinStepper PA4 +#define dirPinStepper PB0 +#else +#define stepPinStepper 17 +#define enablePinStepper 26 +#define dirPinStepper 18 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepper; @@ -17,7 +31,11 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepper); if (stepper) { +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(true); } diff --git a/examples/RawAccessTest/RawAccessTest.ino b/examples/RawAccessTest/RawAccessTest.ino index 56937707..747f89cd 100644 --- a/examples/RawAccessTest/RawAccessTest.ino +++ b/examples/RawAccessTest/RawAccessTest.ino @@ -26,6 +26,10 @@ const uint8_t dirPinStepper = 3; const uint8_t stepPinStepper = 15; // only defined to satisfy compiler const uint8_t enablePinStepper = 13; const uint8_t dirPinStepper = 14; +#elif defined(ARDUINO_ARCH_STM32) +const uint8_t stepPinStepper = PA0; +const uint8_t enablePinStepper = PA4; +const uint8_t dirPinStepper = PB0; #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); diff --git a/examples/RawAccessWithPause/RawAccessWithPause.ino b/examples/RawAccessWithPause/RawAccessWithPause.ino index 2731414b..6aa345c3 100644 --- a/examples/RawAccessWithPause/RawAccessWithPause.ino +++ b/examples/RawAccessWithPause/RawAccessWithPause.ino @@ -1,9 +1,23 @@ #include "FastAccelStepper.h" // for avr: either use pin 9 or 10 aka OC1A or OC1B +#if defined(ARDUINO_ARCH_AVR) +#define stepPinStepper 9 +#define enablePinStepper 6 +#define dirPinStepper 5 +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) #define stepPinStepper 17 #define enablePinStepper 26 #define dirPinStepper 18 +#elif defined(ARDUINO_ARCH_STM32) +#define stepPinStepper PA0 +#define enablePinStepper PA4 +#define dirPinStepper PB0 +#else +#define stepPinStepper 17 +#define enablePinStepper 26 +#define dirPinStepper 18 +#endif FastAccelStepperEngine engine = FastAccelStepperEngine(); FastAccelStepper *stepper; @@ -17,7 +31,11 @@ void setup() { stepper = engine.stepperConnectToPin(stepPinStepper); if (stepper) { +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(false); stepper->enableOutputs(); diff --git a/examples/RawOneTurn/RawOneTurn.ino b/examples/RawOneTurn/RawOneTurn.ino index 9105a5ca..b475649a 100644 --- a/examples/RawOneTurn/RawOneTurn.ino +++ b/examples/RawOneTurn/RawOneTurn.ino @@ -12,7 +12,11 @@ void setup() { engine.init(); stepper = engine.stepperConnectToPin(stepPinStepper); if (stepper) { +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(true); stepper->enableOutputs(); diff --git a/examples/RawOneTurn/pinning.h b/examples/RawOneTurn/pinning.h index d1192164..ab2d9a20 100644 --- a/examples/RawOneTurn/pinning.h +++ b/examples/RawOneTurn/pinning.h @@ -12,6 +12,13 @@ #define stepPinStepper 17 #define enablePinStepper 26 #define dirPinStepper 18 +#elif defined(ARDUINO_ARCH_STM32) +#define stepPinStepper PA0 +#define enablePinStepper PA4 +#define dirPinStepper PB0 +#define vTaskDelay(xx) \ + { \ + } #else #define vTaskDelay(xx) \ { \ diff --git a/examples/StepperDemo/StepperDemo.ino b/examples/StepperDemo/StepperDemo.ino index 17368f85..627a7183 100644 --- a/examples/StepperDemo/StepperDemo.ino +++ b/examples/StepperDemo/StepperDemo.ino @@ -1,4 +1,3 @@ -#include "AVRStepperPins.h" #include "FastAccelStepper.h" #include "test_seq.h" @@ -27,6 +26,8 @@ #include "StepperPins_sam.h" #elif defined(PICO_RP2040) || defined(PICO_RP2350) #include "StepperPins_pico.h" +#elif defined(ARDUINO_ARCH_STM32) +#include "StepperPins_stm32.h" #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); diff --git a/examples/StepperDemo/StepperPins_stm32.h b/examples/StepperDemo/StepperPins_stm32.h new file mode 100644 index 00000000..8ffe2bc6 --- /dev/null +++ b/examples/StepperDemo/StepperPins_stm32.h @@ -0,0 +1,113 @@ +// Stepper pin mapping for STM32 +// Include this in your sketch to set default pins: +// #include "StepperPins_stm32.h" +#ifndef STEPPERPINS_STM32_H +#define STEPPERPINS_STM32_H + +#include "StepperConfig.h" + +// ==================================================================== +// STEP pins — PA0-PA3. +// On STM32, ANY GPIO can be a step pin (timer is used only for +// interrupt timing, not direct pin output). +// Compatible with: F103, G070, F401, H743, L476 +// ==================================================================== +#ifndef STEP_PIN_STEPPER_0 +#define STEP_PIN_STEPPER_0 PA0 +#endif +#ifndef STEP_PIN_STEPPER_1 +#define STEP_PIN_STEPPER_1 PA1 +#endif +#ifndef STEP_PIN_STEPPER_2 +#define STEP_PIN_STEPPER_2 PA2 +#endif +#ifndef STEP_PIN_STEPPER_3 +#define STEP_PIN_STEPPER_3 PA3 +#endif + +// ==================================================================== +// DIR pins — PB0-PB3. +// Available on all 5 CI boards (48-pin to 144-pin). +// ==================================================================== +#ifndef DIR_PIN_STEPPER_0 +#define DIR_PIN_STEPPER_0 PB0 +#endif +#ifndef DIR_PIN_STEPPER_1 +#define DIR_PIN_STEPPER_1 PB1 +#endif +#ifndef DIR_PIN_STEPPER_2 +#define DIR_PIN_STEPPER_2 PB2 +#endif +#ifndef DIR_PIN_STEPPER_3 +#define DIR_PIN_STEPPER_3 PB3 +#endif + +// ==================================================================== +// ENABLE pin — PA4. +// PA4 is NOT LED_BUILTIN on any board, available on all 5 CI boards. +// ==================================================================== +#ifndef ENABLE_PIN_STEPPER_0 +#define ENABLE_PIN_STEPPER_0 PA4 +#endif + +// ==================================================================== +// Config array for StepperDemo +// MAX_STEPPER = 4 (from pd_config.h) +// dir_change_delay = 1000µs (= 1ms) for stable direction change +// ==================================================================== +const uint8_t led_pin = PIN_UNDEFINED; +const struct stepper_config_s stepper_config[MAX_STEPPER] = { + { + step : PA0, + enable_low_active : PA4, + enable_high_active : PIN_UNDEFINED, + direction : PB0, + dir_change_delay : 1000, + direction_high_count_up : true, + auto_enable : true, // has valid enable pin PA4 + on_delay_us : 50, + off_delay_ms : 1000, + }, + { + step : PA1, + enable_low_active : PIN_UNDEFINED, + enable_high_active : PIN_UNDEFINED, + direction : PB1, + dir_change_delay : 1000, + direction_high_count_up : true, + auto_enable : false, // no enable pin for stepper 1 + on_delay_us : 500, + off_delay_ms : 1000, + }, + // stepper 3 — disabled; explicit PIN_UNDEFINED to avoid zero-init direction=PA0 + { + step : PIN_UNDEFINED, + enable_low_active : PIN_UNDEFINED, + enable_high_active : PIN_UNDEFINED, + direction : PIN_UNDEFINED, + dir_change_delay : 0, + direction_high_count_up : true, + auto_enable : false, + on_delay_us : 0, + off_delay_ms : 0, + }, + // stepper 4 — disabled; explicit PIN_UNDEFINED to avoid zero-init direction=PA0 + { + step : PIN_UNDEFINED, + enable_low_active : PIN_UNDEFINED, + enable_high_active : PIN_UNDEFINED, + direction : PIN_UNDEFINED, + dir_change_delay : 0, + direction_high_count_up : true, + auto_enable : false, + on_delay_us : 0, + off_delay_ms : 0, + }, +}; +// Only 1 config variant — can add more later +#define NUM_CONFIGS 1 +const struct stepper_config_set_s stepper_configs[NUM_CONFIGS] = { + {"Default", stepper_config}, +}; + +#endif /* STEPPERPINS_STM32_H */ \ No newline at end of file diff --git a/examples/StepperDemo/generic.h b/examples/StepperDemo/generic.h index b033ed47..72b41b06 100644 --- a/examples/StepperDemo/generic.h +++ b/examples/StepperDemo/generic.h @@ -8,7 +8,7 @@ #if defined(ARDUINO_ARCH_AVR) #define get_char(x) pgm_read_byte(x) #define MSG_TYPE PGM_P -#elif defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAM) +#elif defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAM) || defined(ARDUINO_ARCH_STM32) #define get_char(x) *x #define MSG_TYPE const char* #elif defined(ESP_PLATFORM) diff --git a/examples/UsageExample/UsageExample.ino b/examples/UsageExample/UsageExample.ino index d3c8fe24..0fa3507e 100644 --- a/examples/UsageExample/UsageExample.ino +++ b/examples/UsageExample/UsageExample.ino @@ -6,17 +6,26 @@ // #define stepPinStepper 9 // OC1A in case of AVR // As in StepperDemo for Motor 1 on ESP32 +#if defined(ARDUINO_ARCH_AVR) +#define dirPinStepper 5 +#define enablePinStepper 6 +#define stepPinStepper 9 +#elif defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) #define dirPinStepper 18 #define enablePinStepper 26 #define stepPinStepper 17 - -#if defined(PICO_RP2040) || defined(PICO_RP2350) -#undef dirPinStepper -#undef enablePinStepper -#undef stepPinStepper +#elif defined(PICO_RP2040) || defined(PICO_RP2350) #define dirPinStepper 15 #define enablePinStepper 13 #define stepPinStepper 14 +#elif defined(ARDUINO_ARCH_STM32) +#define dirPinStepper PB0 +#define enablePinStepper PA4 +#define stepPinStepper PA0 +#else +#define dirPinStepper 18 +#define enablePinStepper 26 +#define stepPinStepper 17 #endif FastAccelStepperEngine engine = FastAccelStepperEngine(); @@ -26,7 +35,11 @@ void setup() { engine.init(); stepper = engine.stepperConnectToPin(stepPinStepper); if (stepper) { +#if defined(ARDUINO_ARCH_STM32) + stepper->setDirectionPin(dirPinStepper, true, 1000); +#else stepper->setDirectionPin(dirPinStepper); +#endif stepper->setEnablePin(enablePinStepper); stepper->setAutoEnable(true); diff --git a/extras/StepperPins_stm32.h b/extras/StepperPins_stm32.h new file mode 100644 index 00000000..b80e1d10 --- /dev/null +++ b/extras/StepperPins_stm32.h @@ -0,0 +1,34 @@ +#ifndef STEPPERPINS_STM32_H +#define STEPPERPINS_STM32_H + +// ==================================================================== +// Default step pin mapping for STM32 platforms +// +// These defaults map steppers 0-3 to PA0-PA3 (TIM2 channels 1-4). +// Override by defining before including this header: +// +// #define STEP_PIN_STEPPER_0 PB0 +// #include "StepperPins_stm32.h" +// +// Note: On STM32, any GPIO pin can be used as a step pin. +// PA0-PA3 are only a convention — the timer is used only for +// interrupt timing, not direct pin output. +// ==================================================================== + +#ifndef STEP_PIN_STEPPER_0 +#define STEP_PIN_STEPPER_0 PA0 +#endif + +#ifndef STEP_PIN_STEPPER_1 +#define STEP_PIN_STEPPER_1 PA1 +#endif + +#ifndef STEP_PIN_STEPPER_2 +#define STEP_PIN_STEPPER_2 PA2 +#endif + +#ifndef STEP_PIN_STEPPER_3 +#define STEP_PIN_STEPPER_3 PA3 +#endif + +#endif /* STEPPERPINS_STM32_H */ \ No newline at end of file diff --git a/extras/ci/build_matrix.yaml b/extras/ci/build_matrix.yaml index abb31f7b..aecc889f 100644 --- a/extras/ci/build_matrix.yaml +++ b/extras/ci/build_matrix.yaml @@ -152,6 +152,12 @@ templates: framework: arduino lib_extra_dirs: . + stm32: + platform: ststm32 + framework: arduino + build_flags: ["-Wall"] + lib_extra_dirs: . + environments: esp32: template: esp32_arduino @@ -285,6 +291,31 @@ environments: board_upload_psram_length: 1048576 build_flags: ["-D__FREERTOS=1"] + bluepill_f103c8: + template: stm32 + board: bluepill_f103c8 + build_flags_extra: ["-DTICKS_PER_S=18000000UL"] # F103: 72M÷4(PSC=3) = 18MHz + + nucleo_g070rb: + template: stm32 + board: nucleo_g070rb + # G0: 64M÷4(PSC=3) = 16MHz → dùng default TICKS_PER_S=16000000UL từ pd_config.h + + blackpill_f401cc: + template: stm32 + board: blackpill_f401cc + build_flags_extra: ["-DTICKS_PER_S=16800000UL"] # F401: 84M÷5(PSC=4) = 16.8MHz + + nucleo_h743zi: + template: stm32 + board: nucleo_h743zi + build_flags_extra: ["-DTICKS_PER_S=20000000UL"] # H743 @400MHz: 200M÷10(PSC=9) = 20MHz + + nucleo_l476rg: + template: stm32 + board: nucleo_l476rg + # L4: 80M÷5(PSC=4) = 16MHz → dùng default TICKS_PER_S=16000000UL từ pd_config.h + versioned_environments: esp32: prefix: esp32 @@ -429,6 +460,11 @@ workflows: - atmelsam - rpipico - rpipico2 + - bluepill_f103c8 + - nucleo_g070rb + - blackpill_f401cc + - nucleo_h743zi + - nucleo_l476rg script: build-platformio.sh idf: diff --git a/extras/ci/platformio.ini b/extras/ci/platformio.ini index 826ef70d..5e119f0c 100644 --- a/extras/ci/platformio.ini +++ b/extras/ci/platformio.ini @@ -195,6 +195,41 @@ build_flags = -D__FREERTOS=1 lib_extra_dirs = . board_upload.psram_length = 1048576 +[env:bluepill_f103c8] +platform = ststm32 +board = bluepill_f103c8 +framework = arduino +build_flags = -Wall -DTICKS_PER_S=18000000UL +lib_extra_dirs = . + +[env:nucleo_g070rb] +platform = ststm32 +board = nucleo_g070rb +framework = arduino +build_flags = -Wall +lib_extra_dirs = . + +[env:blackpill_f401cc] +platform = ststm32 +board = blackpill_f401cc +framework = arduino +build_flags = -Wall -DTICKS_PER_S=16800000UL +lib_extra_dirs = . + +[env:nucleo_h743zi] +platform = ststm32 +board = nucleo_h743zi +framework = arduino +build_flags = -Wall -DTICKS_PER_S=20000000UL +lib_extra_dirs = . + +[env:nucleo_l476rg] +platform = ststm32 +board = nucleo_l476rg +framework = arduino +build_flags = -Wall +lib_extra_dirs = . + [env:esp32_V7_0_1] platform = espressif32 @ 7.0.1 board = esp32dev diff --git a/extras/doc/stm32_compile_report.md b/extras/doc/stm32_compile_report.md new file mode 100644 index 00000000..6af1af27 --- /dev/null +++ b/extras/doc/stm32_compile_report.md @@ -0,0 +1,48 @@ +# STM32 Compile Test Results + +PlatformIO 6.1.19, `toolchain-gccarmnoneeabi 12.3.1`, `framework-arduinoststm32 2.12.0`. + +## STM32F1 (Cortex-M3, 72MHz) + +| Board | Chip | Result | +|-------|------|--------| +| `blackpill_f103c8` | STM32F103C8 | ✅ PASS | +| `bluepill_f103c8` | STM32F103C8 | ✅ PASS | +| `bluepill_f103c8_128k` | STM32F103CB | ✅ PASS | +| `genericSTM32F103C8` | STM32F103C8 | ✅ PASS | + +## STM32F4 (Cortex-M4, 168MHz) + +| Board | Chip | Result | +|-------|------|--------| +| `black_f407ve` | STM32F407VE | ✅ PASS | +| `black_f407vg` | STM32F407VG | ✅ PASS | +| `genericSTM32F407VET6` | STM32F407VE | ✅ PASS | +| `disco_f407vg` | STM32F407VG | ✅ PASS | + +## STM32F411 (Cortex-M4, 100MHz) + +| Board | Chip | Result | +|-------|------|--------| +| `blackpill_f411ce` | STM32F411CE | ✅ PASS | +| `genericSTM32F411CE` | STM32F411CE | ✅ PASS | +| `nucleo_f411re` | STM32F411RE | ✅ PASS | +| `disco_f411ve` | STM32F411VE | ❌ FAIL (no Arduino framework support, mbed only) | + +## STM32F401 (Cortex-M4, 84MHz) + +| Board | Chip | Result | +|-------|------|--------| +| `blackpill_f401ce` | STM32F401CE | ✅ PASS | +| `blackpill_f401cc` | STM32F401CC | ✅ PASS | +| `genericSTM32F401CE` | STM32F401CE | ✅ PASS | +| `nucleo_f401re` | STM32F401RE | ✅ PASS | + +## Summary + +| Category | Count | +|----------|-------| +| ✅ Boards PASS | **15/16** | +| ❌ Boards FAIL (code) | **0** | +| ❌ Boards FAIL (platform limit) | **1** (`disco_f411ve` — no Arduino) | +| **PASS rate (Arduino boards)** | **15/15 = 100%** | \ No newline at end of file diff --git a/library.properties b/library.properties index bf955629..ea4e6834 100644 --- a/library.properties +++ b/library.properties @@ -3,10 +3,10 @@ version=1.2.5 license=MIT author=Jochen Kiemes maintainer=Jochen Kiemes -sentence=A high speed stepper library for Atmega 168/168p/328/328p (nano), 32u4 (leonardo), 2560, ESP32, ESP32S2, ESP32S3, ESP32C3, ESP32C6, Atmel SAM Due, Raspberry pi pico and pico 2 -paragraph=Drive stepper motors with acceleration/deceleration profile up to 50 kSteps/s (Atmega) and 200kSteps/s (esp32). +sentence=A high speed stepper library for Atmega 168/168p/328/328p (nano), 32u4 (leonardo), 2560, ESP32, ESP32S2, ESP32S3, ESP32C3, ESP32C6, Atmel SAM Due, Raspberry pi pico and pico 2, and STM32 +paragraph=Drive stepper motors with acceleration/deceleration profile up to 50 kSteps/s (Atmega) and 200kSteps/s (esp32). Supports STM32F1/F4/G0/H7 series via STM32duino core. url=https://github.com/gin66/FastAccelStepper repository=https://github.com/gin66/FastAccelStepper.git -architectures=avr,esp32,sam,rp2040,rp2350 +architectures=avr,esp32,sam,rp2040,rp2350,stm32 category=Device Control -dot_a_linkage=true +dot_a_linkage=true \ No newline at end of file diff --git a/src/FastAccelStepper.cpp b/src/FastAccelStepper.cpp index 32c3818f..b68c81e5 100644 --- a/src/FastAccelStepper.cpp +++ b/src/FastAccelStepper.cpp @@ -134,8 +134,13 @@ AqeResultCode FastAccelStepper::addQueueEntry( if (_dirPin & PIN_EXTERNAL_FLAG) { if (dir_change_needed) { if (!handleExternalDirectionPin(q, cmd->count_up)) { + // V17: Clamp to uint16_t max to prevent narrowing conversion + // (US_TO_TICKS(2000) can exceed 65535 for TICKS_PER_S > 32M). + // This is a safety guard — boards with TICKS_PER_S ≤ 32M are unaffected. + uint32_t _pause_ticks = US_TO_TICKS(2000); + if (_pause_ticks > 65535) _pause_ticks = 65535; struct stepper_command_s pause_cmd = { - .ticks = US_TO_TICKS((uint16_t)2000), + .ticks = (uint16_t)_pause_ticks, .steps = 0, .count_up = cmd->count_up}; res = q->addQueueEntry(&pause_cmd, start); diff --git a/src/fas_arch/arduino_stm32.h b/src/fas_arch/arduino_stm32.h new file mode 100644 index 00000000..67b59524 --- /dev/null +++ b/src/fas_arch/arduino_stm32.h @@ -0,0 +1,21 @@ +#ifndef FAS_ARCH_ARDUINO_STM32_H +#define FAS_ARCH_ARDUINO_STM32_H + +#define FAS_STM32 + +#include +#include +#include + +// PRIMASK reentrant-safe interrupt control +// Saves and restores PRIMASK to support nested disable/enable calls +#define fasDisableInterrupts() \ + uint32_t __fas_prim = __get_PRIMASK(); __disable_irq() +#define fasEnableInterrupts() \ + __set_PRIMASK(__fas_prim) + +#define FAS_PSTR(s) (s) +// PIN_UNDEFINED (255) and PIN_EXTERNAL_FLAG (128) are defined in +// FastAccelStepper.h. Do NOT redefine here to avoid -Wmacro-redefined. + +#endif /* FAS_ARCH_ARDUINO_STM32_H */ \ No newline at end of file diff --git a/src/fas_arch/common.h b/src/fas_arch/common.h index 2fb4c914..09f76349 100644 --- a/src/fas_arch/common.h +++ b/src/fas_arch/common.h @@ -94,6 +94,11 @@ struct queue_end_s { #include "fas_arch/arduino_rp_pico.h" #include "pd_pico/pd_config.h" +#elif defined(ARDUINO_ARCH_STM32) +// STM32 family (STM32duino core) +#include "fas_arch/arduino_stm32.h" +#include "pd_stm32/pd_config.h" + #else #error "Unsupported devices" #endif diff --git a/src/fas_queue/base.h b/src/fas_queue/base.h index 24b72ebf..59432135 100644 --- a/src/fas_queue/base.h +++ b/src/fas_queue/base.h @@ -53,7 +53,9 @@ class StepperQueueBase { dirHighCountsUp = true; dirPin = PIN_UNDEFINED; // intentionally slow speed to make missing initialization detectable - max_speed_in_ticks = TICKS_PER_S / 1000; + // Clamp to uint16_t max to prevent narrowing conversion overflow + // (e.g. TICKS_PER_S > 65M would overflow uint16_t without clamp) + max_speed_in_ticks = (uint16_t)fas_min((uint32_t)(TICKS_PER_S / 1000), (uint32_t)65535); _last_command_ticks = 65535; } diff --git a/src/fas_queue/queue_utils.cpp b/src/fas_queue/queue_utils.cpp index 901661bc..5c3ce0c4 100644 --- a/src/fas_queue/queue_utils.cpp +++ b/src/fas_queue/queue_utils.cpp @@ -48,6 +48,9 @@ bool StepperQueue::hasTicksInQueue(uint32_t min_ticks) const { return false; } +#if !defined(ARDUINO_ARCH_STM32) +// STM32 uses its own implementation in pd_stm32/stm32_queue.cpp +// with cached _last_command_ticks for better performance. bool StepperQueue::getActualTicksWithDirection( struct actual_ticks_s* speed) const { // Retrieve current step rate from the current command. @@ -76,3 +79,4 @@ bool StepperQueue::getActualTicksWithDirection( } return false; } +#endif /* !defined(ARDUINO_ARCH_STM32) */ \ No newline at end of file diff --git a/src/fas_queue/stepper_queue.h b/src/fas_queue/stepper_queue.h index 31133b0d..a704fc18 100644 --- a/src/fas_queue/stepper_queue.h +++ b/src/fas_queue/stepper_queue.h @@ -19,6 +19,8 @@ #include "pd_pico/pico_queue.h" #elif defined(SUPPORT_ESP32) #include "pd_esp32/esp32_queue.h" +#elif defined(FAS_STM32) +#include "pd_stm32/stm32_queue.h" #else #error "Unsupported architecture" #endif diff --git a/src/fas_ramp/RampCalculator.h b/src/fas_ramp/RampCalculator.h index 52c2f564..b98cd23c 100644 --- a/src/fas_ramp/RampCalculator.h +++ b/src/fas_ramp/RampCalculator.h @@ -12,25 +12,146 @@ #define LOG2_ACCEL_FACTOR LOG2_CONST_128E12 #define US_TO_TICKS(u32) ((u32) * 16) #define TICKS_TO_US(u32) ((u32) / 16) + +// === STM32H743 @400MHz default path === +// TIM2 @200MHz, PSC=11 → timer actual = 200M/12 = 16.666.667 Hz +// Avoids ~4% timing error when user doesn't override TICKS_PER_S +// Note: modulo check gives false positive (200M % 16666666 = 8), +// but timing is <0.0001% error. Use 20000000 for error=0. +#elif (TICKS_PER_S == 16666666L) +#define LOG2_TICKS_PER_S ((log2_value_t)0x2FFB) // VERIFIED: log2(16666666)*512 = 12283.41 +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x2EFB) // VERIFIED: log2(16666666/√2)*512 = 12027.41 +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x5DF6) // VERIFIED: 2×0x2FFB−512 = 24054 = 0x5DF6 +#define US_TO_TICKS(u32) ((uint32_t)((u32) * 50 / 3)) +#define TICKS_TO_US(u32) ((uint32_t)((u32) * 3 / 50)) + +// === STM32F103: 72MHz÷4(PSC=3)=18MHz === +#elif (TICKS_PER_S == 18000000L) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3034) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x2F34) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x5E68) +#define US_TO_TICKS(u32) ((u32) * 18) +#define TICKS_TO_US(u32) ((u32) / 18) + +// === STM32F401: 84MHz÷5(PSC=4)=16.8MHz === +#elif (TICKS_PER_S == 16800000L) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3001) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x2F01) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x5E02) +#define US_TO_TICKS(u32) ((uint32_t)((u32) * 168 / 10)) +#define TICKS_TO_US(u32) ((uint32_t)((u32) * 10 / 168)) + +// === STM32H743 @400MHz: 200MHz÷10(PSC=9)=20MHz === +#elif (TICKS_PER_S == 20000000L) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3082) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x2F82) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x5F04) +#define US_TO_TICKS(u32) ((u32) * 20) +#define TICKS_TO_US(u32) ((u32) / 20) + #elif (TICKS_PER_S == 21000000L) #define LOG2_TICKS_PER_S LOG2_CONST_21E6 #define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 LOG2_CONST_21E6_DIV_SQRT_OF_2 #define LOG2_ACCEL_FACTOR LOG2_CONST_2205E11 #define US_TO_TICKS(u32) ((u32) * 21) #define TICKS_TO_US(u32) ((u32) / 21) +#elif (TICKS_PER_S == 32000000L) +// STM32L0, RP2040 +#define LOG2_TICKS_PER_S ((log2_value_t)0x31dd) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x30dd) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x61ba) +#define US_TO_TICKS(u32) ((u32) * 32) +#define TICKS_TO_US(u32) ((u32) / 32) +#elif (TICKS_PER_S == 48000000L) +// STM32F0/G0/WL — STM32 fork: UNVERIFIED_IN_CI (prescaled to 16M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3308) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x3208) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x6411) +#define US_TO_TICKS(u32) ((u32) * 48) +#define TICKS_TO_US(u32) ((u32) / 48) +#elif (TICKS_PER_S == 64000000L) +// STM32G0/WB — STM32 fork: UNVERIFIED_IN_CI (prescaled to 16M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x33dd) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x32dd) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x65ba) +#define US_TO_TICKS(u32) ((u32) * 64) +#define TICKS_TO_US(u32) ((u32) / 64) +#elif (TICKS_PER_S == 72000000L) +// STM32F1/L1 — STM32 fork: UNVERIFIED_IN_CI (prescaled to 18M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3434) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x3334) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x6668) +#define US_TO_TICKS(u32) ((u32) * 72) +#define TICKS_TO_US(u32) ((u32) / 72) +#elif (TICKS_PER_S == 80000000L) +// STM32L4 — STM32 fork: UNVERIFIED_IN_CI (prescaled to 16M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3482) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x3382) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x6704) +#define US_TO_TICKS(u32) ((u32) * 80) +#define TICKS_TO_US(u32) ((u32) / 80) +#elif (TICKS_PER_S == 84000000L) +// STM32F401/411 — STM32 fork: UNVERIFIED_IN_CI (prescaled to 16.8M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x34a6) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x33a6) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x674c) +#define US_TO_TICKS(u32) ((u32) * 84) +#define TICKS_TO_US(u32) ((u32) / 84) +#elif (TICKS_PER_S == 100000000L) +// STM32F411/746 — STM32 fork: UNVERIFIED_IN_CI (prescaled to ≤20M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3527) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x3427) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x684d) +#define US_TO_TICKS(u32) ((u32) * 100) +#define TICKS_TO_US(u32) ((u32) / 100) +#elif (TICKS_PER_S == 120000000L) +// STM32L4+/F4 — STM32 fork: UNVERIFIED_IN_CI (prescaled to ≤20M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x35ad) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x34ad) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x695a) +#define US_TO_TICKS(u32) ((u32) * 120) +#define TICKS_TO_US(u32) ((u32) / 120) +#elif (TICKS_PER_S == 168000000L) +// STM32F405/407 — STM32 fork: UNVERIFIED_IN_CI (prescaled to ≤20M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x36a6) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x35a6) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x6b4c) +#define US_TO_TICKS(u32) ((u32) * 168) +#define TICKS_TO_US(u32) ((u32) / 168) +#elif (TICKS_PER_S == 170000000L) +// STM32F3/G4 — STM32 fork: UNVERIFIED_IN_CI (prescaled to ≤20M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x36af) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x35af) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x6b5e) +#define US_TO_TICKS(u32) ((u32) * 170) +#define TICKS_TO_US(u32) ((u32) / 170) +#elif (TICKS_PER_S == 216000000L) +// STM32F7 — STM32 fork: UNVERIFIED_IN_CI (prescaled to ≤20M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x375f) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x365f) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x6cbe) +#define US_TO_TICKS(u32) ((u32) * 216) +#define TICKS_TO_US(u32) ((u32) / 216) +#elif (TICKS_PER_S == 480000000L) +// STM32H7 (default) — STM32 fork: UNVERIFIED_IN_CI (prescaled to 20M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x39ad) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x38ad) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x715a) +#define US_TO_TICKS(u32) ((u32) * 480) +#define TICKS_TO_US(u32) ((u32) / 480) +#elif (TICKS_PER_S == 550000000L) +// STM32H7 (overclock) — STM32 fork: UNVERIFIED_IN_CI (prescaled to ≤20M) +#define LOG2_TICKS_PER_S ((log2_value_t)0x3a12) +#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 ((log2_value_t)0x3912) +#define LOG2_ACCEL_FACTOR ((log2_value_t)0x7224) +#define US_TO_TICKS(u32) ((u32) * 550) +#define TICKS_TO_US(u32) ((u32) / 550) #else -#define SUPPORT_LOG2_TIMER_FREQ_VARIABLES -#define LOG2_TICKS_PER_S log2_timer_freq -#define LOG2_TICKS_PER_S_DIV_SQRT_OF_2 log2_timer_freq_div_sqrt_of_2 -#define LOG2_ACCEL_FACTOR log2_timer_freq_square_div_2 -// This overflows for approx. 1s at 40 MHz, only -#define US_TO_TICKS(u32) \ - ((uint32_t)((((uint32_t)((u32) * (TICKS_PER_S / 10000L))) / 100L))) - -// This calculation needs more work -#define TICKS_TO_US(u32) \ - ((uint32_t)((((uint32_t)((u32) / (TICKS_PER_S / 1000000L))) / 1L))) - +// V17: Replaced SUPPORT_LOG2_TIMER_FREQ_VARIABLES with #error. +// All CI boards use predefined entries — this branch is never reached. +// Supported values: 16M, 18M, 20M, 21M, 32M, 48M, 64M, 72M, 80M, 84M, +// 100M, 120M, 168M, 170M, 216M, 480M, 550M +#error "Unsupported TICKS_PER_S. Use timer prescaler to match a supported value." #endif #ifdef TEST_TIMING diff --git a/src/fas_ramp/RampControl.cpp b/src/fas_ramp/RampControl.cpp index 800c4fb9..dd31549b 100644 --- a/src/fas_ramp/RampControl.cpp +++ b/src/fas_ramp/RampControl.cpp @@ -6,24 +6,23 @@ #include "fas_ramp/RampControl.h" #include "fas_arch/common.h" -#ifdef SUPPORT_LOG2_TIMER_FREQ_VARIABLES -static log2_value_t log2_timer_freq; -static log2_value_t log2_timer_freq_div_sqrt_of_2; -static log2_value_t log2_timer_freq_square_div_2; -#endif +// V17: Removed — SUPPORT_LOG2_TIMER_FREQ_VARIABLES no longer defined. +// #ifdef SUPPORT_LOG2_TIMER_FREQ_VARIABLES +// static log2_value_t log2_timer_freq; +// static log2_value_t log2_timer_freq_div_sqrt_of_2; +// static log2_value_t log2_timer_freq_square_div_2; +// #endif void ramp_rw_s::init() { __builtin_memset(this, 0, sizeof(*this)); curr_ticks = TICKS_FOR_STOPPED_MOTOR; } +// V17: init_ramp_module() body was only the SUPPORT_LOG2_TIMER_FREQ_VARIABLES +// block. Since that macro is never defined, the function is now empty. +// Keep it as a no-op for future compatibility. void init_ramp_module() { -#ifdef SUPPORT_LOG2_TIMER_FREQ_VARIABLES - log2_timer_freq = log2_from((uint32_t)TICKS_PER_S); - log2_timer_freq_div_sqrt_of_2 = - log2_shr(log2_multiply(log2_timer_freq, log2_timer_freq), 1); - log2_timer_freq_square_div_2 = log2_shr(log2_square(log2_timer_freq), 1); -#endif + // SUPPORT_LOG2_TIMER_FREQ_VARIABLES block removed in V17 } //************************************************************************************************* diff --git a/src/fas_ramp/RampGenerator.h b/src/fas_ramp/RampGenerator.h index 7bd38495..8ca2f2d6 100644 --- a/src/fas_ramp/RampGenerator.h +++ b/src/fas_ramp/RampGenerator.h @@ -7,11 +7,14 @@ class FastAccelStepper; -#ifdef SUPPORT_LOG2_TIMER_FREQ_VARIABLES -extern log2_value_t log2_timer_freq; -extern log2_value_t log2_timer_freq_div_sqrt_of_2; -extern log2_value_t log2_timer_freq_square_div_2; -#endif +// V17: Removed — SUPPORT_LOG2_TIMER_FREQ_VARIABLES no longer defined. +// All CI boards use predefined TICKS_PER_S values. #else fallback in +// RampCalculator.h was replaced with #error, so this code path is dead. +// #ifdef SUPPORT_LOG2_TIMER_FREQ_VARIABLES +// extern log2_value_t log2_timer_freq; +// extern log2_value_t log2_timer_freq_div_sqrt_of_2; +// extern log2_value_t log2_timer_freq_square_div_2; +// #endif class RampGenerator { private: diff --git a/src/pd_stm32/pd_config.h b/src/pd_stm32/pd_config.h new file mode 100644 index 00000000..d8137f66 --- /dev/null +++ b/src/pd_stm32/pd_config.h @@ -0,0 +1,155 @@ +#ifndef PD_STM32_CONFIG_H +#define PD_STM32_CONFIG_H + +#include + +// ==================================================================== +// Compile-time TICKS_PER_S +// +// Default is 16MHz. Each board's TIM2 prescaler (stm32_queue.cpp) brings the +// actual timer clock close to this value, ensuring all tick values fit in uint16_t +// (no overflow for 1ms/2ms pipeline operations). +// +// Timer clock formula: TIM_CLK = PCLK1 * (APB1_prescaler > 1 ? 2 : 1) +// Prescaler formula: PSC = (actual_timer_clk / TICKS_PER_S) - 1 +// +// CI boards with override (build_flags_extra in build_matrix.yaml): +// F103 (bluepill): -DTICKS_PER_S=18000000 (72M÷4, PSC=3) → 18MHz +// F401 (blackpill): -DTICKS_PER_S=16800000 (84M÷5, PSC=4) → 16.8MHz +// H743 (nucleo): -DTICKS_PER_S=20000000 (200M÷10, PSC=9) → 20MHz +// +// CI boards using default (no override): +// G070 (nucleo): 64M÷4 (PSC=3) → 16MHz — exact +// L476 (nucleo): 80M÷5 (PSC=4) → 16MHz — exact +// +// If TICKS_PER_S is not a supported predefined value, RampCalculator.h will +// emit a clear #error. Always use timer prescaler to match a supported value. +// ==================================================================== +#ifndef TICKS_PER_S +#define TICKS_PER_S 16000000UL +#endif + +// ---- Queue topology ---- +#define MAX_STEPPER 4 +#define NUM_QUEUES 4 +#define QUEUE_LEN 32 + +// ---- Pulse width (configurable) ---- +#ifndef STEP_PULSE_WIDTH_US +#define STEP_PULSE_WIDTH_US 6 +#endif +#define STEP_PULSE_WIDTH_TICKS ((uint32_t)(STEP_PULSE_WIDTH_US * (TICKS_PER_S / 1000000UL))) + +// ---- Timing constants ---- +#define MIN_CMD_TICKS (TICKS_PER_S / 5000) +#define MIN_DIR_DELAY_US 200 +#define MAX_DIR_DELAY_US (65535 / (TICKS_PER_S / 1000000UL)) +#define DELAY_MS_BASE 2 +#define CYCLIC_INTERVAL_MS 3 + +// ==================================================================== +// NOTE: STM32F1 TIM2 is 16-bit only. +// C0/G0 TIM3 is 16-bit (ARR=0xFFFF). With PSC=2 => timer=16MHz. +// L0 TIM2 is also 16-bit (RM0367 §24). +// Min speed = 16MHz / 65536 ≈ 244 steps/s. +// ARR = 0xFFFFFFFF is masked to 0xFFFF by F1 hardware. +// Minimum speed = TICKS_PER_S / 65536 ≈ 1098 steps/s @72MHz. +// ==================================================================== + +// ---- Feature flags ---- +#define SUPPORT_QUEUE_ENTRY_END_POS_U16 +#define NEED_GENERIC_GET_CURRENT_POSITION +#define noop_or_wait __NOP() +#define DEBUG_LED_HALF_PERIOD 50 + +// ==================================================================== +// Timer Grouping — FAS_STM32_TIMER_16BIT / FAS_STM32_TIMER_32BIT +// +// Helper macro for fas_tim_set_ccr(). Each family is listed explicitly. +// #else is only used for #error — to report an unsupported family. +// +// CMSIS/RM-verified assignments: +// 16-bit timers (Group A+B): +// Group A: TIM3 used (C0 does not have TIM2, G0 TIM3 is the common denominator) +// Group B: TIM2 HW-limited to 16-bit (F1, L0, L1) +// +// 32-bit timers (Group C): +// TIM2 has a 32-bit counter. Includes F0, F2, F3, F4, F7, G4, H5, H7, +// L4, L5, MP1, U0, U3, U5, WBA, WB, WL +// +// Excluded: +// WB0x — no general-purpose timer (BLE-optimized) +// WL3x — no RCC_CFGR_PPRE register (non-standard clock tree) +// ==================================================================== +#if defined(STM32C0xx) || defined(STM32G0xx) + // Group A: TIM3 16-bit + #define FAS_STM32_TIMER_16BIT + +#elif defined(STM32F1xx) || defined(STM32L0xx) || defined(STM32L1xx) + // Group B: TIM2 16-bit + #define FAS_STM32_TIMER_16BIT + +#elif defined(STM32F0xx) || defined(STM32F2xx) || defined(STM32F3xx) || \ + defined(STM32F4xx) || defined(STM32F7xx) || defined(STM32G4xx) || \ + defined(STM32H5xx) || defined(STM32H7xx) || defined(STM32L4xx) || \ + defined(STM32L5xx) || defined(STM32MP1xx) || defined(STM32U0xx) || \ + defined(STM32U3xx) || defined(STM32U5xx) || defined(STM32WBAxx) || \ + defined(STM32WBxx) || defined(STM32WLxx) + // Group C: TIM2 32-bit (17 families) + #define FAS_STM32_TIMER_32BIT + +#elif defined(STM32WL3x) + // WL3x has TIM2 (32-bit) but no RCC_CFGR_PPRE register + #error "FAS: STM32WL3x unsupported — non-standard APB clock (no PPRE register)." + +#elif defined(STM32WB0x) + // WB0x has no general-purpose timer + #error "FAS: STM32WB0x unsupported — no TIM2/TIM3 peripheral. \ +Use STM32WBxx (Cortex-M4) for FAS support." + +#else + #error "FAS: Unsupported STM32 family. \ +See src/pd_stm32/stm32_queue.cpp (product-line detection at line ~14). \ +Add your board's product_line macro there, then add family \ +(e.g. STM32XXxx) to this block and timer selection block." +#endif + +// ==================================================================== +// STM32 Family Guard +// +// +// ⚠️ STM32WB0x and STM32WL3x are deliberately excluded: +// WB0x — no general-purpose timer +// WL3x — no RCC_CFGR_PPRE register (non-standard clock tree) +// ==================================================================== +// Explicit unsupported guard using CMSIS product-line macros. +// These macros exist at parse time (from compiler -D flags or CMSIS headers), +// unlike custom FAS family macros (STM32WL3x/STM32WB0x) which are defined +// later in stm32_queue.cpp Layer 1. +#if defined(STM32WL3RX) || defined(STM32WL3XX) + #error "FAS: STM32WL3x detected but unsupported. \ +STM32WL3x has no RCC_CFGR_PPRE register (non-standard clock tree). \ +Use STM32WLxx for FAS support." + +#elif defined(STM32WB05) || defined(STM32WB06) || defined(STM32WB07) || \ + defined(STM32WB09) + #error "FAS: STM32WB0x detected but unsupported. \ +STM32WB0x has no general-purpose timer (BLE-optimized part). \ +Use STM32WBxx (Cortex-M4) for FAS support." +#endif + +#if !defined(STM32C0xx) && !defined(STM32F0xx) && !defined(STM32F1xx) && \ + !defined(STM32F2xx) && !defined(STM32F3xx) && !defined(STM32F4xx) && \ + !defined(STM32F7xx) && !defined(STM32G0xx) && !defined(STM32G4xx) && \ + !defined(STM32H5xx) && !defined(STM32H7xx) && !defined(STM32L0xx) && \ + !defined(STM32L1xx) && !defined(STM32L4xx) && !defined(STM32L5xx) && \ + !defined(STM32MP1xx) && !defined(STM32U0xx) && !defined(STM32U3xx) && \ + !defined(STM32U5xx) && !defined(STM32WBAxx) && !defined(STM32WBxx) && \ + !defined(STM32WLxx) +#error "FAS: STM32 family not detected. \ +See src/pd_stm32/stm32_queue.cpp (product-line detection at line ~14). \ +Add your board's product_line macro there, then add family \ +(e.g. STM32XXxx) to timer selection block (line ~110) and this guard." +#endif + +#endif /* PD_STM32_CONFIG_H */ \ No newline at end of file diff --git a/src/pd_stm32/stm32_queue.cpp b/src/pd_stm32/stm32_queue.cpp new file mode 100644 index 00000000..ca586df7 --- /dev/null +++ b/src/pd_stm32/stm32_queue.cpp @@ -0,0 +1,1084 @@ +#include "fas_queue/stepper_queue.h" +#include "log2/Log2Representation.h" +#include "fas_ramp/RampControl.h" + +#if defined(ARDUINO_ARCH_STM32) + +// ==================================================================== +// STM32 Family Detection (3-Layer Architecture) +// +// Layer 1: Product-Line Detection +// stm32duino core passes per-device macros via build.product_line +// (-DSTM32G070xx, -DSTM32F103xB). These are ALWAYS available from +// compiler flags. We map them to family macros (STM32G0xx, STM32F1xx). +// +// Layer 2: Legacy Aliases +// CMSIS device headers (stm32f1xx.h) define short names (STM32F1). +// These aliases catch non-Arduino frameworks (CubeMX, Mbed, Zephyr). +// +// Layer 3: Timer Grouping +// pd_config.h defines FAS_STM32_TIMER_16BIT / FAS_STM32_TIMER_32BIT +// based on the detected family macro. +// ==================================================================== + +// ===== LAYER 1: Product-Line to Family Macro Map ===== +// Source: official stm32_def_build.h from framework-arduinoststm32 + +// STM32C0xx +#if defined(STM32C011xx) || defined(STM32C031xx) || defined(STM32C051xx) || \ + defined(STM32C071xx) || defined(STM32C091xx) || defined(STM32C092xx) + #ifndef STM32C0xx + #define STM32C0xx + #endif +#endif + +// STM32F0xx +#if defined(STM32F030x6) || defined(STM32F030x8) || defined(STM32F030xC) || \ + defined(STM32F031x6) || defined(STM32F038xx) || defined(STM32F042x6) || \ + defined(STM32F048xx) || defined(STM32F051x8) || defined(STM32F058xx) || \ + defined(STM32F070x6) || defined(STM32F070xB) || defined(STM32F071xB) || \ + defined(STM32F072xB) || defined(STM32F078xx) || defined(STM32F091xC) || \ + defined(STM32F098xx) + #ifndef STM32F0xx + #define STM32F0xx + #endif +#endif + +// STM32F1xx +#if defined(STM32F100xB) || defined(STM32F100xE) || defined(STM32F101x6) || \ + defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \ + defined(STM32F102x6) || defined(STM32F102xB) || defined(STM32F103x6) || \ + defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) || \ + defined(STM32F105xC) || defined(STM32F107xC) + #ifndef STM32F1xx + #define STM32F1xx + #endif +#endif + +// STM32F2xx +#if defined(STM32F205xx) || defined(STM32F207xx) || defined(STM32F215xx) || \ + defined(STM32F217xx) + #ifndef STM32F2xx + #define STM32F2xx + #endif +#endif + +// STM32F3xx +#if defined(STM32F301x8) || defined(STM32F302x8) || defined(STM32F302xC) || \ + defined(STM32F302xE) || defined(STM32F303x8) || defined(STM32F303xC) || \ + defined(STM32F303xE) || defined(STM32F318xx) || defined(STM32F328xx) || \ + defined(STM32F334x8) || defined(STM32F358xx) || defined(STM32F373xC) || \ + defined(STM32F378xx) || defined(STM32F398xx) + #ifndef STM32F3xx + #define STM32F3xx + #endif +#endif + +// STM32F4xx +#if defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F405xx) || \ + defined(STM32F407xx) || defined(STM32F410Cx) || defined(STM32F410Rx) || \ + defined(STM32F410Tx) || defined(STM32F411xE) || defined(STM32F412Cx) || \ + defined(STM32F412Rx) || defined(STM32F412Vx) || defined(STM32F412Zx) || \ + defined(STM32F413xx) || defined(STM32F415xx) || defined(STM32F417xx) || \ + defined(STM32F423xx) || defined(STM32F427xx) || defined(STM32F429xx) || \ + defined(STM32F437xx) || defined(STM32F439xx) || defined(STM32F446xx) || \ + defined(STM32F469xx) || defined(STM32F479xx) + #ifndef STM32F4xx + #define STM32F4xx + #endif +#endif + +// STM32F7xx +#if defined(STM32F722xx) || defined(STM32F723xx) || defined(STM32F730xx) || \ + defined(STM32F732xx) || defined(STM32F733xx) || defined(STM32F745xx) || \ + defined(STM32F746xx) || defined(STM32F750xx) || defined(STM32F756xx) || \ + defined(STM32F765xx) || defined(STM32F767xx) || defined(STM32F769xx) || \ + defined(STM32F777xx) || defined(STM32F779xx) + #ifndef STM32F7xx + #define STM32F7xx + #endif +#endif + +// STM32G0xx (bao gồm STM32G070xx) +#if defined(STM32G030xx) || defined(STM32G031xx) || defined(STM32G041xx) || \ + defined(STM32G050xx) || defined(STM32G051xx) || defined(STM32G061xx) || \ + defined(STM32G070xx) || defined(STM32G071xx) || defined(STM32G081xx) || \ + defined(STM32G0B0xx) || defined(STM32G0B1xx) || defined(STM32G0C1xx) || \ + defined(STM32GBK1CB) + #ifndef STM32G0xx + #define STM32G0xx + #endif +#endif + +// STM32G4xx +#if defined(STM32G411xB) || defined(STM32G411xC) || defined(STM32G414xx) || \ + defined(STM32G431xx) || defined(STM32G441xx) || defined(STM32G471xx) || \ + defined(STM32G473xx) || defined(STM32G474xx) || defined(STM32G483xx) || \ + defined(STM32G484xx) || defined(STM32G491xx) || defined(STM32G4A1xx) + #ifndef STM32G4xx + #define STM32G4xx + #endif +#endif + +// STM32H5xx +#if defined(STM32H503xx) || defined(STM32H523xx) || defined(STM32H533xx) || \ + defined(STM32H562xx) || defined(STM32H563xx) || defined(STM32H573xx) + #ifndef STM32H5xx + #define STM32H5xx + #endif +#endif + +// STM32H7xx +#if defined(STM32H723xx) || defined(STM32H725xx) || defined(STM32H730xx) || \ + defined(STM32H730xxQ) || defined(STM32H733xx) || defined(STM32H735xx) || \ + defined(STM32H742xx) || defined(STM32H743xx) || defined(STM32H745xG) || \ + defined(STM32H745xx) || defined(STM32H747xG) || defined(STM32H747xx) || \ + defined(STM32H750xx) || defined(STM32H753xx) || defined(STM32H755xx) || \ + defined(STM32H757xx) || defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || \ + defined(STM32H7B0xx) || defined(STM32H7B0xxQ) || defined(STM32H7B3xx) || \ + defined(STM32H7B3xxQ) + #ifndef STM32H7xx + #define STM32H7xx + #endif +#endif + +// STM32L0xx +#if defined(STM32L010x4) || defined(STM32L010x6) || defined(STM32L010x8) || \ + defined(STM32L010xB) || defined(STM32L011xx) || defined(STM32L021xx) || \ + defined(STM32L031xx) || defined(STM32L041xx) || defined(STM32L051xx) || \ + defined(STM32L052xx) || defined(STM32L053xx) || defined(STM32L062xx) || \ + defined(STM32L063xx) || defined(STM32L071xx) || defined(STM32L072xx) || \ + defined(STM32L073xx) || defined(STM32L081xx) || defined(STM32L082xx) || \ + defined(STM32L083xx) + #ifndef STM32L0xx + #define STM32L0xx + #endif +#endif + +// STM32L1xx +#if defined(STM32L100xB) || defined(STM32L100xBA) || defined(STM32L100xC) || \ + defined(STM32L151xB) || defined(STM32L151xBA) || defined(STM32L151xC) || \ + defined(STM32L151xCA) || defined(STM32L151xD) || defined(STM32L151xDx) || \ + defined(STM32L151xE) || defined(STM32L152xB) || defined(STM32L152xBA) || \ + defined(STM32L152xC) || defined(STM32L152xCA) || defined(STM32L152xD) || \ + defined(STM32L152xDx) || defined(STM32L152xE) || defined(STM32L162xC) || \ + defined(STM32L162xCA) || defined(STM32L162xD) || defined(STM32L162xDx) || \ + defined(STM32L162xE) + #ifndef STM32L1xx + #define STM32L1xx + #endif +#endif + +// STM32L4xx +#if defined(STM32L412xx) || defined(STM32L422xx) || defined(STM32L431xx) || \ + defined(STM32L432xx) || defined(STM32L433xx) || defined(STM32L442xx) || \ + defined(STM32L443xx) || defined(STM32L451xx) || defined(STM32L452xx) || \ + defined(STM32L462xx) || defined(STM32L471xx) || defined(STM32L475xx) || \ + defined(STM32L476xx) || defined(STM32L485xx) || defined(STM32L486xx) || \ + defined(STM32L496xx) || defined(STM32L4A6xx) || defined(STM32L4P5xx) || \ + defined(STM32L4Q5xx) || defined(STM32L4R5xx) || defined(STM32L4R7xx) || \ + defined(STM32L4R9xx) || defined(STM32L4S5xx) || defined(STM32L4S7xx) || \ + defined(STM32L4S9xx) + #ifndef STM32L4xx + #define STM32L4xx + #endif +#endif + +// STM32L5xx +#if defined(STM32L552xx) || defined(STM32L562xx) + #ifndef STM32L5xx + #define STM32L5xx + #endif +#endif + +// STM32MP1xx +#if defined(STM32MP151Axx) || defined(STM32MP151Cxx) || defined(STM32MP153Axx) || \ + defined(STM32MP153Cxx) || defined(STM32MP157Axx) || defined(STM32MP157Cxx) || \ + defined(STM32MP15xx) + #ifndef STM32MP1xx + #define STM32MP1xx + #endif +#endif + +// STM32U0xx +#if defined(STM32U031xx) || defined(STM32U073xx) || defined(STM32U083xx) + #ifndef STM32U0xx + #define STM32U0xx + #endif +#endif + +// STM32U3xx +#if defined(STM32U375xx) || defined(STM32U385xx) + #ifndef STM32U3xx + #define STM32U3xx + #endif +#endif + +// STM32U5xx +#if defined(STM32U535xx) || defined(STM32U545xx) || defined(STM32U575xx) || \ + defined(STM32U585xx) || defined(STM32U595xx) || defined(STM32U599xx) || \ + defined(STM32U5A5xx) || defined(STM32U5A9xx) || defined(STM32U5F7xx) || \ + defined(STM32U5F9xx) || defined(STM32U5G7xx) || defined(STM32U5G9xx) + #ifndef STM32U5xx + #define STM32U5xx + #endif +#endif + +// STM32WB0x (⚠️ product line without 'xx' suffix) +#if defined(STM32WB05) || defined(STM32WB06) || defined(STM32WB07) || \ + defined(STM32WB09) + #ifndef STM32WB0x + #define STM32WB0x + #endif +#endif + +// STM32WBAxx +#if defined(STM32WBA50xx) || defined(STM32WBA52xx) || defined(STM32WBA54xx) || \ + defined(STM32WBA55xx) || defined(STM32WBA5Mxx) || defined(STM32WBA62xx) || \ + defined(STM32WBA63xx) || defined(STM32WBA64xx) || defined(STM32WBA65xx) || \ + defined(STM32WBA6Mxx) + #ifndef STM32WBAxx + #define STM32WBAxx + #endif +#endif + +// STM32WBxx +#if defined(STM32WB10xx) || defined(STM32WB15xx) || defined(STM32WB1Mxx) || \ + defined(STM32WB30xx) || defined(STM32WB35xx) || defined(STM32WB50xx) || \ + defined(STM32WB55xx) || defined(STM32WB5Mxx) + #ifndef STM32WBxx + #define STM32WBxx + #endif +#endif + +// STM32WL3x (⚠️ Pattern: STM32WL3XX, STM32WL3RX) +#if defined(STM32WL3RX) || defined(STM32WL3XX) + #ifndef STM32WL3x + #define STM32WL3x + #endif +#endif + +// STM32WLxx +#if defined(STM32WL54xx) || defined(STM32WL55xx) || defined(STM32WL5Mxx) || \ + defined(STM32WLE4xx) || defined(STM32WLE5xx) + #ifndef STM32WLxx + #define STM32WLxx + #endif +#endif + +// ===== LAYER 2: Legacy Short-Name Aliases ===== +// For non-Arduino frameworks (CubeMX, Mbed, Zephyr) +// CMSIS device headers define STM32F1, STM32G0 etc. +#if defined(STM32C0) && !defined(STM32C0xx) +#define STM32C0xx +#endif +#if defined(STM32F0) && !defined(STM32F0xx) +#define STM32F0xx +#endif +#if defined(STM32F1) && !defined(STM32F1xx) +#define STM32F1xx +#endif +#if defined(STM32F2) && !defined(STM32F2xx) +#define STM32F2xx +#endif +#if defined(STM32F3) && !defined(STM32F3xx) +#define STM32F3xx +#endif +#if defined(STM32F4) && !defined(STM32F4xx) +#define STM32F4xx +#endif +#if defined(STM32F7) && !defined(STM32F7xx) +#define STM32F7xx +#endif +#if defined(STM32G0) && !defined(STM32G0xx) +#define STM32G0xx +#endif +#if defined(STM32G4) && !defined(STM32G4xx) +#define STM32G4xx +#endif +#if defined(STM32H5) && !defined(STM32H5xx) +#define STM32H5xx +#endif +#if defined(STM32H7) && !defined(STM32H7xx) +#define STM32H7xx +#endif +#if defined(STM32L0) && !defined(STM32L0xx) +#define STM32L0xx +#endif +#if defined(STM32L1) && !defined(STM32L1xx) +#define STM32L1xx +#endif +#if defined(STM32L4) && !defined(STM32L4xx) +#define STM32L4xx +#endif +#if defined(STM32L5) && !defined(STM32L5xx) +#define STM32L5xx +#endif +#if defined(STM32MP1) && !defined(STM32MP1xx) +#define STM32MP1xx +#endif +#if defined(STM32U0) && !defined(STM32U0xx) +#define STM32U0xx +#endif +#if defined(STM32U3) && !defined(STM32U3xx) +#define STM32U3xx +#endif +#if defined(STM32U5) && !defined(STM32U5xx) +#define STM32U5xx +#endif +#if defined(STM32WB0) && !defined(STM32WB0x) +#define STM32WB0x +#endif +#if defined(STM32WBA) && !defined(STM32WBAxx) +#define STM32WBAxx +#endif +#if defined(STM32WB) && !defined(STM32WBxx) +#define STM32WBxx +#endif +#if defined(STM32WL3) && !defined(STM32WL3x) +#define STM32WL3x +#endif +#if defined(STM32WL) && !defined(STM32WLxx) +#define STM32WLxx +#endif + +// ==================================================================== +// FAS_DMB — Data Memory Barrier wrapper +// +// ARMv6-M (M0/M0+) does not have __DMB(). Use __DSB() instead. +// ARMv7-M (M3/M4/M7) and ARMv8-M.main (M33) have __DMB(). +// ARMv8-M.base (M23) does not have __DMB() — reserved, use __DSB(). +// +// Affected STM32 families: +// __DMB() OK: F1, F4, F7, H7, G4, L4, WB, WL, L5, U5, H5 +// __DSB() needed: G0, F0, L0, C0 (ARMv6-M, __ARM_ARCH_6M__) +// Reserved: M23 (ARMv8-M.base, __ARM_ARCH_8M_BASE__) +// +// GCC and ARMCC define __ARM_ARCH_6M__ automatically when compiling +// with -mcpu=cortex-m0 or -mcpu=cortex-m0plus. +// ==================================================================== + +// ==================================================================== +// FAS_SPURIOUS_MAX — Spurious Interrupt Guard (Phase 2A) +// +// If a timer channel fires an interrupt when no queue is active (e.g. due +// to EMI or configuration error), the ISR would loop indefinitely clearing +// the flag. This guard counts consecutive spurious interrupts per channel +// and disables the channel after FAS_SPURIOUS_MAX occurrences. +// ==================================================================== +#define FAS_SPURIOUS_MAX 10 +static uint8_t fas_spurious_count[4] = {0, 0, 0, 0}; +#if defined(__ARM_ARCH_6M__) + // M0/M0+ (G0, F0, L0, C0): not have __DMB() + #define FAS_DMB() __DSB() +#elif defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) || \ + defined(__ARM_ARCH_8M_MAIN__) + // M3/M4/M7/M33: have __DMB() + #define FAS_DMB() __DMB() + // reserved: __ARM_ARCH_8M_BASE__ (M23) → use __DSB() +#else + #define FAS_DMB() __DSB() // fallback +#endif + +// ==================================================================== +// Timer selection — explicit per-family (24 families) +// +// Mỗi STM32 family có #elif riêng. Nếu board của bạn chưa có trong +// danh sách, thêm #elif tương ứng. Xem README để biết thông số cần. +// +// Families được nhóm theo timer type (CMSIS/RM-verified): +// Group A (TIM3 16-bit): C0, G0 +// Group B (TIM2 16-bit): F1, L0, L1 +// Group C (TIM2 32-bit): F0, F2, F3, F4, F7, G4, H5, H7, L4, L5, +// MP1, U0, U3, U5, WBA, WB, WL +// Excluded: WB0x (no general-purpose timer) +// WL3x (no RCC_CFGR_PPRE — non-standard clock tree) +// +// CMSIS evidence for "single APB PPRE": +// F0: stm32f091xc.h — RCC_CFGR_PPRE (bit 8, 3-bit mask 0x7) +// U0: stm32u031xx.h — RCC_CFGR_PPRE (bit 12, 3-bit mask 0x7) +// C0/G0/L0: same macro, same bit position +// WL3x: NONE — excluded (no PPRE register) +// +// CMSIS evidence for "dual APB PPRE1": +// All families: confirmed via findstr /M "RCC_CFGR_PPRE1" in hal_rcc.h +// ==================================================================== +#if defined(STM32C0xx) || defined(STM32G0xx) + // Group A: TIM3 16-bit + // G0: TIM2 availability varies (G030/G031 = no TIM2, G0B1/G0C1 = has TIM2). + // TIM3 exists on ALL G0 parts — common denominator for reliable support. + #define FAS_TIMER TIM3 + #define FAS_TIMER_IRQn TIM3_IRQn + #define FAS_TIMER_RCC_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE() + #define FAS_TIMER_ARR_MAX 0xFFFF + +#elif defined(STM32F1xx) || defined(STM32L0xx) || defined(STM32L1xx) + // Group B: TIM2 16-bit (F1 RM0008, L0 RM0367 §24, L1 RM0038) + #define FAS_TIMER TIM2 + #define FAS_TIMER_IRQn TIM2_IRQn + #define FAS_TIMER_RCC_ENABLE() __HAL_RCC_TIM2_CLK_ENABLE() + #define FAS_TIMER_ARR_MAX 0xFFFF + +#elif defined(STM32F0xx) || defined(STM32F2xx) || defined(STM32F3xx) || \ + defined(STM32F4xx) || defined(STM32F7xx) || defined(STM32G4xx) || \ + defined(STM32H5xx) || defined(STM32H7xx) || defined(STM32L4xx) || \ + defined(STM32L5xx) || defined(STM32MP1xx) || defined(STM32U0xx) || \ + defined(STM32U3xx) || defined(STM32U5xx) || defined(STM32WBAxx) || \ + defined(STM32WBxx) || defined(STM32WLxx) + // Group C: TIM2 32-bit — 17 families + // F0: RM0091 §18 "32-bit counter" (previously in Group B in v12). + // U0: stm32u031xx.h — TIM2_TypeDef with __IO uint32_t CNT → 32-bit + // PPRE at bit 12 (differs from other M0+ at bit 8). 3-bit field (mask 0x7). + // Macros handle position — code is transportable. + // F2/F3/F4/F7/G4/H5/H7/L4/L5/MP1/U3/U5/WBA/WB/WL: confirmed 32-bit + #define FAS_TIMER TIM2 + #define FAS_TIMER_IRQn TIM2_IRQn + #define FAS_TIMER_RCC_ENABLE() __HAL_RCC_TIM2_CLK_ENABLE() + #define FAS_TIMER_ARR_MAX 0xFFFFFFFF + +#elif defined(STM32WL3x) + // WL3x has no RCC_CFGR_PPRE register — stm32wl3xx.h uses CLKSYSDIV instead. + // TIM2 exists and is 32-bit (uint32_t CNT) but clock detection is unsupported. + // No CI board available — pending hardware verification. + #error "FAS: STM32WL3x unsupported — no RCC_CFGR_PPRE register (non-standard clock tree). \ +Pending hardware verification — contact maintainer to add support." + +#elif defined(STM32WB0x) + // WB0x (WB05/WB06/WB07/WB09) has NO general-purpose timer + // TIM2_IRQn exists but no TIM2_TypeDef/CNT/ARR/CCR registers + #error "FAS: STM32WB0x unsupported — no general-purpose timer with CCR registers. \ +Use STM32WBxx (Cortex-M4) for FAS support." + +#else + #error "FAS: Unsupported STM32 family. \ +Add product-line macro to detection block (line ~14), then add family \ +to timer selection block (line ~110). See README STM32 section." +#endif + +// ==================================================================== +// Static data +// ==================================================================== +static FastAccelStepperEngine* fas_engine = NULL; +static uint8_t stepper_allocated_mask = 0; +static volatile bool _cyclic_pending = false; +static uint32_t _last_cyclic_tick = 0; +uint8_t fas_stm32_clock_error = 0; +uint32_t fas_stm32_clock_tim_clk = 0; // Cached timer clock for warning output +StepperQueue* StepperQueue::_ch_to_queue[4] = {NULL, NULL, NULL, NULL}; + +// ==================================================================== +// Timer clock detection +// +// Timer counter clock = PCLK1 * (APB1_prescaler==1 ? 1 : 2) +// +// The APB1 prescaler is in: +// - H7: RCC->D2CFGR.D2PPRE1 (D2 domain, encoding: 0-3=÷1, 4=÷2, 5=÷4, 6=÷8, 7=÷16) +// - G0/C0: RCC->CFGR.PPRE (single APB bus, same encoding) +// - Others F1/F4/F7/L1/L4/G4/WB: RCC->CFGR.PPRE1 (same encoding) +// +// NOTE: APB prescaler field encoding (all STM32 families): +// 0-3 = ÷1 (0,1 valid; 2,3 reserved) +// 4 = ÷2 +// 5 = ÷4 +// 6 = ÷8 +// 7 = ÷16 +// Condition for clock ×2: prescaler > 1 ↔ field >= 4 +// ==================================================================== +static uint32_t getTimClock(void) { + uint32_t pclk1 = HAL_RCC_GetPCLK1Freq(); +#if defined(STM32H7xx) + // H7 series: D2 domain, D2CFGR register + uint32_t dppre1 = (RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) >> RCC_D2CFGR_D2PPRE1_Pos; + if (dppre1 >= 4) pclk1 *= 2; +#elif defined(STM32C0xx) || defined(STM32F0xx) || defined(STM32G0xx) || \ + defined(STM32L0xx) || defined(STM32U0xx) + // Single APB bus (Cortex-M0+): uses RCC_CFGR_PPRE (no '1') + // C0, F0, G0, L0, U0 + // U0: PPRE at bit 12 (macro handles difference automatically) + uint32_t pp = (RCC->CFGR & RCC_CFGR_PPRE) >> RCC_CFGR_PPRE_Pos; + if (pp >= 4) pclk1 *= 2; +#elif defined(STM32WL3x) + // WL3x has no RCC_CFGR_PPRE — non-standard clock tree + #error "FAS: STM32WL3x unsupported — requires WL3x-specific APB clock detection. \ +No CI board available — contact maintainer to add support." +#elif defined(STM32H5xx) + // H5 series: APB1 prescaler is in RCC->CFGR2 (not CFGR), per RM0481 + uint32_t pp = (RCC->CFGR2 & RCC_CFGR2_PPRE1) >> RCC_CFGR2_PPRE1_Pos; + if (pp >= 4) pclk1 *= 2; + +#elif defined(STM32F1xx) || defined(STM32F2xx) || defined(STM32F3xx) || \ + defined(STM32F4xx) || defined(STM32F7xx) || defined(STM32G4xx) || \ + defined(STM32L1xx) || defined(STM32L4xx) || \ + defined(STM32L5xx) || defined(STM32MP1xx) || defined(STM32U3xx) || \ + defined(STM32U5xx) || defined(STM32WBAxx) || defined(STM32WBxx) || \ + defined(STM32WLxx) + // Multi-APB: uses RCC_CFGR_PPRE1 + uint32_t pp = (RCC->CFGR & RCC_CFGR_PPRE1) >> RCC_CFGR_PPRE1_Pos; + if (pp >= 4) pclk1 *= 2; +#else + #error "FAS: Unsupported STM32 family in getTimClock(). \ +Add APB prescaler detection for your family." +#endif + return pclk1; +} + +// ==================================================================== +// fas_tim_set_ccr — Write CCR with 16-bit wrap handling (F1, C0) +// +// On 16-bit timers (F1 TIM2, C0 TIM3), (cnt + delay) may exceed 0xFFFF. +// The & 0xFFFF mask correctly handles the wrap: +// target = (cnt + delay) & 0xFFFF = cnt + delay - 65536 (if overflow) +// Actual ticks = (0xFFFF - cnt + 1) + target +// = (65536 - cnt) + (cnt + delay - 65536) = delay ✓ +// +// Example: cnt=65520, delay=432 +// target = (65520+432) & 0xFFFF = 416 +// ticks = (65536-65520) + 416 = 16 + 416 = 432 = delay ✓ +// +// On 32-bit timers, simple addition is safe (no overflow in practice). +// ==================================================================== +static inline void fas_tim_set_ccr(volatile uint32_t* ccr, uint32_t delay) { +#if defined(FAS_STM32_TIMER_16BIT) + uint32_t cnt = FAS_TIMER->CNT; + // 16-bit timer: (cnt + delay) & 0xFFFF handles wrap correctly + *ccr = (cnt + delay) & 0xFFFF; +#elif defined(FAS_STM32_TIMER_32BIT) + *ccr = FAS_TIMER->CNT + delay; +#else + #error "FAS: FAS_STM32_TIMER_16BIT or _32BIT not defined. \ +Check pd_config.h timer grouping." +#endif +} + +// ==================================================================== +// Step timer initialization +// Called once when first stepper is initialized. +// Uses FAS_TIMER macros to support TIM2 (all families) or TIM3 (C0). +// ==================================================================== +static void initStepTimer(void) { + static bool initialized = false; + if (initialized) return; + initialized = true; + + // Enable timer clock (TIM2 on most, TIM3 on C0) + FAS_TIMER_RCC_ENABLE(); + + // Cache the actual timer clock for later warning output + fas_stm32_clock_tim_clk = getTimClock(); + + // ---- Clock validation ---- + if (TICKS_PER_S == 0 || TICKS_PER_S > fas_stm32_clock_tim_clk) { + // Cannot achieve TICKS_PER_S at this clock frequency. + // Run at maximum rate (PSC=0). All timing will be incorrect. + fas_stm32_clock_error = 1; + } else if (fas_stm32_clock_tim_clk % TICKS_PER_S != 0) { + // Non-integer prescaler — timer tick rate will deviate. + // Example: 84MHz TIM2 with TICKS_PER_S=72MHz → psc=(84/72)-1=0 → timer at 84MHz, +16.7% error. + // (Addition in v8: error code 2 was not present in original code.) + fas_stm32_clock_error = 2; + } + + // Compute prescaler + uint32_t psc; + if (fas_stm32_clock_error == 1) { + psc = 0; // Run at maximum rate (timing will be wrong) + } else { + psc = (fas_stm32_clock_tim_clk / TICKS_PER_S) - 1; + if (psc > 65535) { + psc = 65535; + fas_stm32_clock_error = 1; // Prescaler clamped — timing will be wrong + } + } + + // Configure timer + FAS_TIMER->CR1 = 0; + FAS_TIMER->PSC = psc; + FAS_TIMER->ARR = FAS_TIMER_ARR_MAX; + FAS_TIMER->EGR |= TIM_EGR_UG; // Reload shadow registers (PSC, ARR) + FAS_TIMER->DIER = 0; // All interrupts disabled initially + + // Force LOW all channels (OCxM = 100 = Force Inactive) + // Using HAL constants ensures correct 4-bit OCxM on all STM32 families + // (F1/F4: 3-bit, others: 4-bit with bit[3]=0 in CCMR bit 16). + FAS_TIMER->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC2M_2; // OC1M=4, OC2M=4 + FAS_TIMER->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M_2; // OC3M=4, OC4M=4 + + // NVIC configuration + NVIC_SetPriority(FAS_TIMER_IRQn, 0); // Highest priority for step timing + NVIC_EnableIRQ(FAS_TIMER_IRQn); + + // Start timer + FAS_TIMER->CR1 |= TIM_CR1_CEN; +} + +// ==================================================================== +// Dynamic slot allocation — supports ANY GPIO pin for step +// ==================================================================== +static int8_t findFreeSlot(void) { + for (int i = 0; i < MAX_STEPPER; i++) { + if (!(stepper_allocated_mask & (1 << i))) { + return i; + } + } + return -1; // All slots used +} + +// ==================================================================== +// Queue initialization +// ==================================================================== +void StepperQueue::init(uint8_t queue_num, uint8_t step_pin) { + static const uint8_t ch_map[4] = {0, 1, 2, 3}; + _timer_ch = ch_map[queue_num]; + + // Ensure step timer is initialized (TIM2 / TIM3 on C0) + initStepTimer(); + + // Step pin GPIO configuration — validate port first + _step_pin = step_pin; + _step_port = digitalPinToPort(step_pin); + if (!_step_port) return; // Invalid pin — init fails silently, ISR skips + + uint32_t mask = digitalPinToBitMask(step_pin); + _step_set_mask = mask; + + // Clear mask: BSRR high half = reset (mask << 16 works on ALL families) + _step_clr_mask = mask << 16; + + // Configure pin as OUTPUT, initial LOW + pinMode(step_pin, OUTPUT); + digitalWrite(step_pin, LOW); + + // Store CCR register pointer for fast ISR access + // Must match the timer type: TIM2 on most families, TIM3 on C0 + // This is the only place where the concrete timer register is referenced. + // All other CCR writes go through _ccr_reg (fast pointer) or fas_tim_set_ccr(). +#if defined(STM32C0xx) || defined(STM32G0xx) + // Group A: TIM3 CCR1-CCR4 + volatile uint32_t* ccr[] = {&TIM3->CCR1, &TIM3->CCR2, &TIM3->CCR3, &TIM3->CCR4}; +#elif defined(STM32F0xx) || defined(STM32F1xx) || defined(STM32F2xx) || \ + defined(STM32F3xx) || defined(STM32F4xx) || defined(STM32F7xx) || \ + defined(STM32G4xx) || defined(STM32H5xx) || defined(STM32H7xx) || \ + defined(STM32L0xx) || defined(STM32L1xx) || defined(STM32L4xx) || \ + defined(STM32L5xx) || defined(STM32MP1xx) || defined(STM32U0xx) || \ + defined(STM32U3xx) || defined(STM32U5xx) || defined(STM32WBAxx) || \ + defined(STM32WBxx) || defined(STM32WLxx) + // Groups B + C: TIM2 CCR1-CCR4 + volatile uint32_t* ccr[] = {&TIM2->CCR1, &TIM2->CCR2, &TIM2->CCR3, &TIM2->CCR4}; +#elif defined(STM32WL3x) + #error "FAS: STM32WL3x unsupported — no timer CCR registers (no PPRE clock)." +#elif defined(STM32WB0x) + #error "FAS: STM32WB0x unsupported — no timer CCR registers." +#else + #error "FAS: Unsupported STM32 family in CCR init." +#endif + _ccr_reg = ccr[_timer_ch]; + + // Register channel-to-queue mapping + _ch_to_queue[_timer_ch] = this; + // _initialized = true was removed (fix_plan_v3 FIX #7) + _isRunning = false; +} + +// ==================================================================== +// Start / Stop +// ==================================================================== +void StepperQueue::startQueue(void) { + _isRunning = true; + _pulse_high = false; + _dir_delay_active = false; + + // Write CCR first, then barrier, then enable interrupt + // Use fas_tim_set_ccr for 16-bit safe CCR write (handles F1/C0 wrap) + fas_tim_set_ccr(_ccr_reg, TICKS_PER_S / 1000000); // ~1µs offset + FAS_DMB(); + + // Save/restore PRIMASK for reentrant-safe IRQ disable + uint32_t prim = __get_PRIMASK(); + __disable_irq(); + FAS_TIMER->DIER |= CCXIE_BIT(_timer_ch); + if (!prim) __enable_irq(); +} + +void StepperQueue::forceStop(void) { + // Save/restore PRIMASK for reentrant-safe IRQ disable + uint32_t prim = __get_PRIMASK(); + __disable_irq(); + FAS_TIMER->DIER &= ~CCXIE_BIT(_timer_ch); + _isRunning = false; + read_idx = next_write_idx; // Discard remaining queue entries + if (!prim) __enable_irq(); + + // Ensure step pin is LOW (BSRR high-half clear works on ALL families) + if (_step_port) { + _step_port->BSRR = _step_clr_mask; + } +} + +void StepperQueue::connect(void) {} +void StepperQueue::disconnect(void) {} + +// ==================================================================== +// Speed adjustment based on number of active steppers +// ==================================================================== +void StepperQueue::adjustSpeedToStepperCount(uint8_t steppers) { + if (steppers == 1) + max_speed_in_ticks = STEP_PULSE_WIDTH_TICKS * 2; + else if (steppers == 2) + max_speed_in_ticks = STEP_PULSE_WIDTH_TICKS * 3; + else + max_speed_in_ticks = STEP_PULSE_WIDTH_TICKS * 4; + + if (max_speed_in_ticks < MIN_CMD_TICKS) + max_speed_in_ticks = MIN_CMD_TICKS; +} + +// ==================================================================== +// getActualTicksWithDirection — retrieve current step rate +// ==================================================================== +bool StepperQueue::getActualTicksWithDirection( + struct actual_ticks_s* speed) const { + fasDisableInterrupts(); + speed->count_up = queue_end.count_up; + speed->ticks = _last_command_ticks; + fasEnableInterrupts(); + inject_fill_interrupt(0); + return true; +} + +// ==================================================================== +// Cyclic PendSV trigger +// Called at end of FAS_TIMER_IRQHandler every ~3ms. +// Triggers PendSV exception to fill queues without consuming ISR time. +// ==================================================================== +static void cyclic_check_and_pend(void) { + uint32_t now = HAL_GetTick(); + if ((now - _last_cyclic_tick) >= CYCLIC_INTERVAL_MS) { + _last_cyclic_tick = now; + if (!_cyclic_pending) { + _cyclic_pending = true; + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + } + } +} + +// ==================================================================== +// Clock error reporting — prints warning to Serial if clock mismatch +// +// Call site: engine.init() → FastAccelStepperEngine::init() → fas_init_engine() +// → fas_stm32_report_clock_error() +// +// ⚠️ User MUST call Serial.begin() BEFORE engine.init(). +// If Serial is not initialized, the warning is silently dropped +// (no crash, but user won't see the error). +// +// Error codes: +// 0 = OK (no error) +// 1 = TICKS_PER_S > actual timer clock, OR prescaler clamped at 65535 +// (timing will be wrong — define correct TICKS_PER_S in build_flags) +// 2 = Non-integer prescaler (tim2_clk % TICKS_PER_S != 0) +// (timing will be slightly off — define correct TICKS_PER_S in build_flags) +// ==================================================================== +static void fas_stm32_report_clock_error(void) { + if (fas_stm32_clock_error == 0) return; + if (!Serial) return; // Serial not initialized → silent, no crash + + Serial.print("[FAS] WARNING: Step timer clock error (code="); + Serial.print(fas_stm32_clock_error); + Serial.println(")"); + + switch (fas_stm32_clock_error) { + case 1: + Serial.println(" Cause: TICKS_PER_S > actual timer clock, or prescaler > 65535."); + break; + case 2: + Serial.println(" Cause: Non-integer prescaler (timer clock not divisible by TICKS_PER_S)."); + break; + } + Serial.print(" TICKS_PER_S="); + Serial.print(TICKS_PER_S); + Serial.print(" Timer_CLK="); + Serial.println(fas_stm32_clock_tim_clk); + Serial.println(" Fix: add -DTICKS_PER_S=xxx in platformio.ini (see debug_plan_v8.md appendix B)."); +} + +// ==================================================================== +// FAS_TIMER_IRQHandler — Step pulse generation (all 4 channels) +// +// The ISR name depends on which timer is used: +// - C0: TIM3_IRQHandler (handles TIM3->SR, TIM3->DIER, TIM3->CNT) +// - Others: TIM2_IRQHandler (handles TIM2->SR, TIM2->DIER, TIM2->CNT) +// +// State machine: +// Phase 1 (_pulse_high=false): Set step pin HIGH, schedule LOW +// Phase 2 (_pulse_high=true): Set step pin LOW, process queue +// DirSettle (_dir_delay_active): Direction settling complete → start pulse +// +// SR handling: +// Snapshot SR at entry. Build ch_processed mask of all handled flags. +// Clear all at end by writing ~ch_processed (rc_w0 behavior). +// +// IMPORTANT: All CCR writes use fas_tim_set_ccr() for 16-bit wrap safety. +// ==================================================================== +#if defined(STM32C0xx) || defined(STM32G0xx) +void TIM3_IRQHandler(void) { +#elif defined(STM32F0xx) || defined(STM32F1xx) || defined(STM32F2xx) || \ + defined(STM32F3xx) || defined(STM32F4xx) || defined(STM32F7xx) || \ + defined(STM32G4xx) || defined(STM32H5xx) || defined(STM32H7xx) || \ + defined(STM32L0xx) || defined(STM32L1xx) || defined(STM32L4xx) || \ + defined(STM32L5xx) || defined(STM32MP1xx) || defined(STM32U0xx) || \ + defined(STM32U3xx) || defined(STM32U5xx) || defined(STM32WBAxx) || \ + defined(STM32WBxx) || defined(STM32WLxx) +void TIM2_IRQHandler(void) { +#elif defined(STM32WL3x) + #error "FAS: STM32WL3x unsupported — no timer ISR available (no PPRE clock)." +#elif defined(STM32WB0x) + #error "FAS: STM32WB0x unsupported — no timer ISR available." +#else + #error "FAS: Unsupported STM32 family in ISR name. \ +Add ISR handler name for your family's timer." +#endif + uint32_t sr = FAS_TIMER->SR; + uint32_t ch_processed = 0; + + for (uint8_t ch = 0; ch < 4; ch++) { + uint32_t ccif = CCXIF_BIT(ch); + if (!(sr & ccif)) continue; + + // Always mark for clear — prevents infinite loop from spurious IRQs + ch_processed |= ccif; + + StepperQueue* q = StepperQueue::_ch_to_queue[ch]; + // Phase 2A: Spurious interrupt guard — count consecutive inactive + // channel interrupts. If EMI or config error triggers repeated + // flags, disable channel after FAS_SPURIOUS_MAX occurrences. + if (!q || !q->_isRunning) { + fas_spurious_count[ch]++; + if (fas_spurious_count[ch] >= FAS_SPURIOUS_MAX) { + FAS_TIMER->DIER &= ~CCXIE_BIT(ch); + fas_spurious_count[ch] = 0; + } + continue; + } + + if (q->_pulse_high) { + // ====== Phase 2: pulse end ====== + // The step pin was HIGH; bring it LOW (BSRR high-half clear). + q->_pulse_high = false; + q->_step_port->BSRR = q->_step_clr_mask; + + // Read queue entry + uint8_t rp = q->read_idx; + uint8_t wp = q->next_write_idx; + + if (rp == wp) { + // Queue empty — stop this channel + FAS_TIMER->DIER &= ~CCXIE_BIT(ch); + q->_isRunning = false; + continue; + } + + struct queue_entry* e = &q->entry[rp & QUEUE_LEN_MASK]; + q->_last_command_ticks = e->ticks; + + if (e->steps > 1) { + // Multi-step command: reduce step count, continue with same period + e->steps--; + fas_tim_set_ccr(q->_ccr_reg, e->ticks); + } else { + // Single step complete — advance to next entry + rp++; + q->read_idx = rp; + + if (rp == wp) { + FAS_TIMER->DIER &= ~CCXIE_BIT(ch); + q->_isRunning = false; + continue; + } + + e = &q->entry[rp & QUEUE_LEN_MASK]; + + // Handle direction change (BSRR atomic — NOT ODR XOR) + if (e->toggle_dir && q->_dir_bsrr) { + if (e->dirPinState) { + *q->_dir_bsrr = q->_dir_set_mask; + } else { + *q->_dir_bsrr = q->_dir_clr_mask; + } + e->toggle_dir = 0; // Clear flag — prevents double-toggle + + // Insert direction settling delay + uint32_t dd = AFTER_SET_DIR_PIN_DELAY_US * (TICKS_PER_S / 1000000UL); + if (dd < MIN_CMD_TICKS) dd = MIN_CMD_TICKS; + fas_tim_set_ccr(q->_ccr_reg, dd); + q->_dir_delay_active = true; + continue; + } + + // No direction change — schedule next step pulse + fas_tim_set_ccr(q->_ccr_reg, e->ticks); + } + } else if (q->_dir_delay_active) { + // ====== Direction settling complete ====== + // The settling delay has elapsed. Check if the current entry + // has steps>0 before emitting a pulse. + q->_dir_delay_active = false; + + uint8_t rp = q->read_idx; + struct queue_entry* e = &q->entry[rp & QUEUE_LEN_MASK]; + if (e->steps > 0) { + // Real step: start pulse (set pin HIGH) + q->_pulse_high = true; + q->_step_port->BSRR = q->_step_set_mask; + fas_tim_set_ccr(q->_ccr_reg, STEP_PULSE_WIDTH_TICKS); + } else { + // steps=0 after dir settle: advance read_idx, schedule pause + q->read_idx = rp + 1; // advance entry + q->_pulse_high = false; + fas_tim_set_ccr(q->_ccr_reg, e->ticks); + // NEXT ISR: Phase 1, reads entry at the new read_idx + } + } else { + // ====== Phase 1: pulse start ====== + uint8_t rp = q->read_idx; + uint8_t wp = q->next_write_idx; + // Queue-empty guard: check before reading entry (Phase 2 has this, Phase 1 was missing) + if (rp == wp) { + FAS_TIMER->DIER &= ~CCXIE_BIT(ch); + q->_isRunning = false; + continue; + } + struct queue_entry* e = &q->entry[rp & QUEUE_LEN_MASK]; + if (e->steps > 0) { + // Start pulse: set pin HIGH, schedule LOW after step pulse width + q->_pulse_high = true; + q->_step_port->BSRR = q->_step_set_mask; + fas_tim_set_ccr(q->_ccr_reg, STEP_PULSE_WIDTH_TICKS); + } else { + // steps=0 pause: advance read_idx, schedule pause duration + q->read_idx = rp + 1; + q->_pulse_high = false; + fas_tim_set_ccr(q->_ccr_reg, e->ticks); + // After pause ticks, ISR re-enters Phase 1 with the NEXT entry + } + } + } + + // Clear all processed flags at once (rc_w0: bits set to 1 are ignored) + FAS_TIMER->SR = ~ch_processed; + + // Trigger cyclic queue fill + cyclic_check_and_pend(); +} + +// ==================================================================== +// PendSV_Handler — Deferred queue fill +// +// Weak attribute allows FreeRTOS to override this handler. +// Define DISABLE_FAS_PENDSV to skip installation entirely. +// +// ══════════════════════════════════════════════════════════════════════ +// Phase 2B: FreeRTOS Compatibility Warning +// +// FastAccelStepper uses PendSV_Handler (weak attribute) for deferred +// queue filling. FreeRTOS may also claim PendSV for context switching. +// If both are active, they will conflict at runtime. +// +// If you use FreeRTOS: +// 1. Add -DDISABLE_FAS_PENDSV to your build flags +// 2. Call engine->manageSteppers() from a low-priority task/timer +// +// Phase 2C: NVIC Priority (Jitter Protection) +// +// TIMER must have the HIGHEST priority (0) to ensure tick-exact step +// pulse timing. PendSV must have the LOWEST priority so it only runs +// when CPU is idle, never blocking step generation (set in initStepTimer +// and fas_init_engine respectively). +// ══════════════════════════════════════════════════════════════════════ +// ==================================================================== +#if !defined(DISABLE_FAS_PENDSV) +#if defined(configUSE_PORT_OPTIMISED_TASK_SELECTION) || \ + defined(configUSE_TICKLESS_IDLE) || \ + defined(INC_FREERTOS_H) || \ + defined(FREERTOS_CONFIG_H) +#pragma message "FAS: PendSV_Handler may conflict if FreeRTOS uses PendSV. Define DISABLE_FAS_PENDSV to skip." +#endif +__attribute__((weak)) void PendSV_Handler(void) { + _cyclic_pending = false; + FAS_DMB(); + if (fas_engine) { + fas_engine->manageSteppers(); + } +} +#endif + +// ==================================================================== +// Allocation — dynamic slot assignment for any GPIO step pin +// ==================================================================== +StepperQueue* StepperQueue::tryAllocateQueue( + FastAccelStepperEngine* engine, uint8_t step_pin) { + (void)engine; + + // Validate step pin before any hardware access + if (step_pin == PIN_UNDEFINED) return nullptr; + if ((step_pin & PIN_EXTERNAL_FLAG)) return nullptr; // External pins not supported + if (!digitalPinToPort(step_pin)) return nullptr; // Invalid pin → NULL port + + int8_t idx = findFreeSlot(); + if (idx < 0) return nullptr; + + fas_queue[idx]._initVars(); + fas_queue[idx].init((uint8_t)idx, step_pin); + stepper_allocated_mask |= (1 << idx); + return &fas_queue[idx]; +} + +// ==================================================================== +// freeQueue — Release stepper slot for reallocation +// +// Stops the queue (if running), clears the allocated bit, and resets +// channel mapping so the slot can be reused by another step pin. +// ==================================================================== +void StepperQueue::freeQueue(void) { + uint32_t prim = __get_PRIMASK(); + __disable_irq(); + + if (_isRunning) { + FAS_TIMER->DIER &= ~CCXIE_BIT(_timer_ch); + _isRunning = false; + } + + uint8_t ch = _timer_ch; + stepper_allocated_mask &= ~(1 << ch); + _ch_to_queue[ch] = NULL; + + // Clear all state to avoid stale values on reallocation + _step_pin = PIN_UNDEFINED; + _step_port = NULL; + _ccr_reg = NULL; + _dir_bsrr = NULL; + _last_command_ticks = 0; // prevent getActualTicksWithDirection() returning stale speed + _pulse_high = false; // reset pulse state + _dir_delay_active = false; // reset dir settle state + + if (!prim) __enable_irq(); +} + +// ==================================================================== +// Engine initialization +// ==================================================================== +void fas_init_engine(FastAccelStepperEngine* engine) { + fas_engine = engine; + + // Initialize Log2 timer frequency variables if using runtime fallback + // (SUPPORT_LOG2_TIMER_FREQ_VARIABLES path in RampCalculator.h) + init_ramp_module(); + + // Print clock error warning (if any) — user must have called Serial.begin() + // before engine.init() for this to appear. + fas_stm32_report_clock_error(); + + // PendSV at lowest priority — avoids blocking higher-priority interrupts + NVIC_SetPriority(PendSV_IRQn, 0xFF); +} + +#endif /* ARDUINO_ARCH_STM32 */ \ No newline at end of file diff --git a/src/pd_stm32/stm32_queue.h b/src/pd_stm32/stm32_queue.h new file mode 100644 index 00000000..8b410518 --- /dev/null +++ b/src/pd_stm32/stm32_queue.h @@ -0,0 +1,134 @@ +#ifndef PD_STM32_QUEUE_H +#define PD_STM32_QUEUE_H + +#include "FastAccelStepper.h" +#include "fas_queue/base.h" +#include "fas_arch/result_codes.h" + +// ---- Default pin mapping (overridable in sketch) ---- +#ifndef STEP_PIN_STEPPER_0 +#define STEP_PIN_STEPPER_0 PA0 +#endif +#ifndef STEP_PIN_STEPPER_1 +#define STEP_PIN_STEPPER_1 PA1 +#endif +#ifndef STEP_PIN_STEPPER_2 +#define STEP_PIN_STEPPER_2 PA2 +#endif +#ifndef STEP_PIN_STEPPER_3 +#define STEP_PIN_STEPPER_3 PA3 +#endif + +// ---- CC interrupt bit helpers ---- +#define CCXIE_BIT(ch) (TIM_DIER_CC1IE << (ch)) +#define CCXIF_BIT(ch) (TIM_SR_CC1IF << (ch)) + +// BSRR register layout (identical on ALL STM32 families): +// Bits [0:15] = set bits (write 1 → pin HIGH) +// Bits [16:31] = reset bits (write 1 → pin LOW) +// Clear is always done via BSRR high-half (mask << 16). +// Separate BRR register is NOT used — BSRR reset-half works everywhere. + +// ==================================================================== +// StepperQueue class — STM32-specific implementation +// ==================================================================== +class StepperQueue : public StepperQueueBase { + public: +#include "../fas_queue/protocol.h" + + volatile bool _isRunning; + // bool _initialized was removed — set but never read (fix_plan_v3 FIX #7) + + // Step pin GPIO + uint8_t _step_pin; + GPIO_TypeDef* _step_port; + uint32_t _step_set_mask; // BSRR set mask (write to BSRR low = set HIGH) + uint32_t _step_clr_mask; // BSRR clear mask (write to BSRR high = set LOW) = mask<<16 + + // Direction pin (atomic via BSRR) + volatile uint32_t* _dir_bsrr; // &GPIOx->BSRR + uint32_t _dir_set_mask; // BSRR low bits = set HIGH + uint32_t _dir_clr_mask; // BSRR high bits = set LOW (mask << 16) + + // Timer + volatile uint32_t* _ccr_reg; // &FAS_TIMER->CCR1/2/3/4 (TIM2 or TIM3 on C0) + uint8_t _timer_ch; // 0..3 + + // Pulse tracking + volatile bool _pulse_high; + volatile bool _dir_delay_active; // Direction settling in progress + + // Channel-to-queue mapping (static) + static StepperQueue* _ch_to_queue[4]; + + // ---- Inline methods ---- + inline void _pd_initVars() { + _step_pin = PIN_UNDEFINED; + _step_port = NULL; + _step_set_mask = 0; + _step_clr_mask = 0; + _dir_bsrr = NULL; + _dir_set_mask = 0; + _dir_clr_mask = 0; + _ccr_reg = NULL; + _timer_ch = 0; + _isRunning = false; + // _initialized = false was removed (fix_plan_v3 FIX #7) + _pulse_high = false; + _dir_delay_active = false; + max_speed_in_ticks = STEP_PULSE_WIDTH_TICKS * 4; + } + + inline bool isRunning() const { return _isRunning; } + inline bool isReadyForCommands() const { return true; } + + // setDirPin — configure direction pin for atomic BSRR access + // Validates digitalPinToPort() before dereferencing. + // If port is NULL (invalid pin), _dir_bsrr stays NULL → SET_DIRECTION_PIN_STATE + // will be a no-op (safe, queued direction change fails silently). + void setDirPin(uint8_t dir_pin, bool _dirHighCountsUp) { + dirPin = dir_pin; + dirHighCountsUp = _dirHighCountsUp; + if ((dir_pin != PIN_UNDEFINED) && ((dir_pin & PIN_EXTERNAL_FLAG) == 0)) { + GPIO_TypeDef* port = digitalPinToPort(dir_pin); + if (!port) { // Invalid pin → BSRR stays NULL, SET_DIRECTION_PIN_STATE becomes no-op + _dir_bsrr = NULL; + return; + } + uint32_t mask = digitalPinToBitMask(dir_pin); + _dir_bsrr = &port->BSRR; + _dir_set_mask = mask; + _dir_clr_mask = mask << 16; // BSRR high half = reset (works on ALL families) + } + } + + void adjustSpeedToStepperCount(uint8_t steppers); + void freeQueue(void); +}; + +// ---- Direction pin: atomic via BSRR/BRR ---- +#define SET_DIRECTION_PIN_STATE(q, high) \ + do { \ + if ((q)->_dir_bsrr) { \ + *(q->_dir_bsrr) = (high) ? (q)->_dir_set_mask \ + : (q)->_dir_clr_mask; \ + } \ + } while (0) + +// ---- Enable pin: simple digitalWrite ---- +#define SET_ENABLE_PIN_STATE(q, pin, high) \ + digitalWrite((pin), (high) ? HIGH : LOW) + +// ---- Direction-to-pulse delay ---- +// Guard allows override from build_flags (-DAFTER_SET_DIR_PIN_DELAY_US=50) +#ifndef AFTER_SET_DIR_PIN_DELAY_US +#define AFTER_SET_DIR_PIN_DELAY_US 30 +#endif + +// ---- Direction change delay (synchronized-with-commands via BSRR) ---- +// STM32 uses atomic BSRR writes in ISR between step pulses. +// Direction change is synchronized with command execution, no buffer delay needed. +#define BEFORE_DIR_CHANGE_DELAY_TICKS(q) 0 +#define AFTER_DIR_CHANGE_DELAY_TICKS(q) 0 + +#endif /* PD_STM32_QUEUE_H */