Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/build_arduino_examples_matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ on:
pull_request:
branches: [ master ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
strategy:
Expand Down Expand Up @@ -80,6 +76,11 @@ jobs:
- atmelsam
- rpipico
- rpipico2
- bluepill_f103c8
- nucleo_g070rb
- blackpill_f401cc
- nucleo_h743zi
- nucleo_l476rg

runs-on: ubuntu-latest

Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/build_idf_examples_matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ on:
pull_request:
branches: [ master ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
strategy:
Expand Down
122 changes: 122 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,128 @@ 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.

### 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 | TIM2 32-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 **32-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
Expand Down
139 changes: 139 additions & 0 deletions collect_source_v2.py
Original file line number Diff line number Diff line change
@@ -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()
34 changes: 34 additions & 0 deletions extras/StepperPins_stm32.h
Original file line number Diff line number Diff line change
@@ -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 */
36 changes: 36 additions & 0 deletions extras/ci/build_matrix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -429,6 +460,11 @@ workflows:
- atmelsam
- rpipico
- rpipico2
- bluepill_f103c8
- nucleo_g070rb
- blackpill_f401cc
- nucleo_h743zi
- nucleo_l476rg
script: build-platformio.sh

idf:
Expand Down
Loading
Loading