diff --git a/.gitignore b/.gitignore index 6ce7e78..102f64e 100644 --- a/.gitignore +++ b/.gitignore @@ -144,3 +144,113 @@ dkms.conf /Release/objects.mk /Release/rotary-controller-f4.list /Release/sources.mk +/rotary-controller-f4.bin +/Makefile +/CMakeCache.txt +/cmake_install.cmake +/CMakeFiles/4.0.2/CompilerIdC/CMakeCCompilerId.c +/CMakeFiles/4.0.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +/CMakeFiles/4.0.2/CMakeASMCompiler.cmake +/CMakeFiles/4.0.2/CMakeCCompiler.cmake +/CMakeFiles/4.0.2/CMakeCXXCompiler.cmake +/CMakeFiles/4.0.2/CMakeDetermineCompilerABI_C.bin +/CMakeFiles/4.0.2/CMakeDetermineCompilerABI_CXX.bin +/CMakeFiles/4.0.2/CMakeSystem.cmake +/CMakeFiles/4.0.3-dirty/CompilerIdC/CMakeCCompilerId.c +/CMakeFiles/4.0.3-dirty/CompilerIdCXX/CMakeCXXCompilerId.cpp +/CMakeFiles/4.0.3-dirty/CMakeASMCompiler.cmake +/CMakeFiles/4.0.3-dirty/CMakeCCompiler.cmake +/CMakeFiles/4.0.3-dirty/CMakeCXXCompiler.cmake +/CMakeFiles/4.0.3-dirty/CMakeDetermineCompilerABI_C.bin +/CMakeFiles/4.0.3-dirty/CMakeDetermineCompilerABI_CXX.bin +/CMakeFiles/4.0.3-dirty/CMakeSystem.cmake +/CMakeFiles/4.1.2/CompilerIdC/CMakeCCompilerId.c +/CMakeFiles/4.1.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +/CMakeFiles/4.1.2/CMakeASMCompiler.cmake +/CMakeFiles/4.1.2/CMakeCCompiler.cmake +/CMakeFiles/4.1.2/CMakeCXXCompiler.cmake +/CMakeFiles/4.1.2/CMakeDetermineCompilerABI_C.bin +/CMakeFiles/4.1.2/CMakeDetermineCompilerABI_CXX.bin +/CMakeFiles/4.1.2/CMakeSystem.cmake +/CMakeFiles/4.2.1/CompilerIdC/CMakeCCompilerId.c +/CMakeFiles/4.2.1/CompilerIdCXX/CMakeCXXCompilerId.cpp +/CMakeFiles/4.2.1/CMakeASMCompiler.cmake +/CMakeFiles/4.2.1/CMakeCCompiler.cmake +/CMakeFiles/4.2.1/CMakeCXXCompiler.cmake +/CMakeFiles/4.2.1/CMakeDetermineCompilerABI_C.bin +/CMakeFiles/4.2.1/CMakeDetermineCompilerABI_CXX.bin +/CMakeFiles/4.2.1/CMakeSystem.cmake +/CMakeFiles/rotary-controller-f4.elf.dir/ASM.includecache +/CMakeFiles/rotary-controller-f4.elf.dir/build.make +/CMakeFiles/rotary-controller-f4.elf.dir/cmake_clean.cmake +/CMakeFiles/rotary-controller-f4.elf.dir/compiler_depend.make +/CMakeFiles/rotary-controller-f4.elf.dir/compiler_depend.ts +/CMakeFiles/rotary-controller-f4.elf.dir/depend.internal +/CMakeFiles/rotary-controller-f4.elf.dir/depend.make +/CMakeFiles/rotary-controller-f4.elf.dir/DependInfo.cmake +/CMakeFiles/rotary-controller-f4.elf.dir/flags.make +/CMakeFiles/rotary-controller-f4.elf.dir/link.txt +/CMakeFiles/rotary-controller-f4.elf.dir/progress.make +/CMakeFiles/clion-environment.txt +/CMakeFiles/cmake.check_cache +/CMakeFiles/CMakeConfigureLog.yaml +/CMakeFiles/CMakeDirectoryInformation.cmake +/CMakeFiles/InstallScripts.json +/CMakeFiles/Makefile.cmake +/CMakeFiles/Makefile2 +/CMakeFiles/progress.marks +/CMakeFiles/TargetDirectories.txt +/cmake-build-release/.cmake/api/v1/query/cache-v2 +/cmake-build-release/.cmake/api/v1/query/cmakeFiles-v1 +/cmake-build-release/.cmake/api/v1/query/codemodel-v2 +/cmake-build-release/.cmake/api/v1/query/toolchains-v1 +/cmake-build-release/.cmake/api/v1/reply/cache-v2-75ccddff1f9462878f34.json +/cmake-build-release/.cmake/api/v1/reply/cmakeFiles-v1-efb40c770de9d5bf104a.json +/cmake-build-release/.cmake/api/v1/reply/codemodel-v2-1d0bfa3e0f2ca88c0356.json +/cmake-build-release/.cmake/api/v1/reply/directory-.-Release-f5ebdc15457944623624.json +/cmake-build-release/.cmake/api/v1/reply/index-2025-07-25T20-06-13-0400.json +/cmake-build-release/.cmake/api/v1/reply/target-rotary-controller-f4.elf-Release-23665a0d8e0985d66bdc.json +/cmake-build-release/.cmake/api/v1/reply/toolchains-v1-516567ce22d77594ccde.json +/cmake-build-release/CMakeFiles/3.31.6/CompilerIdC/CMakeCCompilerId.c +/cmake-build-release/CMakeFiles/3.31.6/CompilerIdCXX/CMakeCXXCompilerId.cpp +/cmake-build-release/CMakeFiles/3.31.6/CMakeASMCompiler.cmake +/cmake-build-release/CMakeFiles/3.31.6/CMakeCCompiler.cmake +/cmake-build-release/CMakeFiles/3.31.6/CMakeCXXCompiler.cmake +/cmake-build-release/CMakeFiles/3.31.6/CMakeDetermineCompilerABI_C.bin +/cmake-build-release/CMakeFiles/3.31.6/CMakeDetermineCompilerABI_CXX.bin +/cmake-build-release/CMakeFiles/3.31.6/CMakeSystem.cmake +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/ASM.includecache +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/build.make +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/cmake_clean.cmake +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/compiler_depend.make +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/compiler_depend.ts +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/depend.internal +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/depend.make +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/DependInfo.cmake +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/flags.make +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/link.txt +/cmake-build-release/CMakeFiles/rotary-controller-f4.elf.dir/progress.make +/cmake-build-release/CMakeFiles/clion-environment.txt +/cmake-build-release/CMakeFiles/clion-Release-log.txt +/cmake-build-release/CMakeFiles/cmake.check_cache +/cmake-build-release/CMakeFiles/CMakeConfigureLog.yaml +/cmake-build-release/CMakeFiles/CMakeDirectoryInformation.cmake +/cmake-build-release/CMakeFiles/Makefile.cmake +/cmake-build-release/CMakeFiles/Makefile2 +/cmake-build-release/CMakeFiles/progress.marks +/cmake-build-release/CMakeFiles/TargetDirectories.txt +/cmake-build-release/cmake_install.cmake +/cmake-build-release/CMakeCache.txt +/cmake-build-release/Makefile +/cmake-build-release/rotary-controller-f4.bin +/.cmake/api/v1/query/cache-v2 +/.cmake/api/v1/query/cmakeFiles-v1 +/.cmake/api/v1/query/codemodel-v2 +/.cmake/api/v1/query/toolchains-v1 +/.cmake/api/v1/reply/cache-v2-f5e9ca3708de3f124d35.json +/.cmake/api/v1/reply/cmakeFiles-v1-281ea38263c07c7344ed.json +/.cmake/api/v1/reply/codemodel-v2-f943dd8c247e07984a15.json +/.cmake/api/v1/reply/directory-.-Release-9ad1a58d4644cf6543fa.json +/.cmake/api/v1/reply/index-2026-01-26T01-20-50-0476.json +/.cmake/api/v1/reply/target-rotary-controller-f4.elf-Release-abbd90adbbfbd406bb3c.json +/.cmake/api/v1/reply/toolchains-v1-516567ce22d77594ccde.json diff --git a/CMakeLists.txt b/CMakeLists.txt index bf03640..2b77574 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ #THIS FILE IS AUTO GENERATED FROM THE TEMPLATE! DO NOT CHANGE! set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_VERSION 1) -cmake_minimum_required(VERSION 3.27) +cmake_minimum_required(VERSION 3.30) # specify cross-compilers and tools set(CMAKE_C_COMPILER arm-none-eabi-gcc) diff --git a/Core/Inc/Ramps.h b/Core/Inc/Ramps.h index 4520aa2..03f038a 100644 --- a/Core/Inc/Ramps.h +++ b/Core/Inc/Ramps.h @@ -88,6 +88,21 @@ typedef struct { uint16_t servoMode; // Servo modes: 0=disabled, 1=sync/index, 2=jog } fastData_t; + +typedef struct { + uint16_t threadRequest; // host writes 1 to request latch+wait + uint16_t threadReset; // host writes 1 to reset the threading state + uint16_t spindleScaleIndex; // host sets which scale to watch + uint16_t threadPhaseActive; // 0 = not latched yet, 1 = latched + uint16_t threadEnabled; // firmware uses as waiting flag + uint16_t spindlePhaseTolerance; // encoder-count tolerance for phase matching + int32_t threadRemainingSteps; // SIGNED distance remaining + uint32_t threadStartSteps; // servo.currentSteps at latch + uint32_t spindleCountsPerRev; // spindle scale counts per revolution + int32_t threadPhaseRef; // firmware stores latched phase + int32_t currentThreadPhase; // current phase for debug purposes +} assistedThreadingData_t; + typedef struct { uint32_t executionInterval; uint32_t executionIntervalPrevious; @@ -95,6 +110,7 @@ typedef struct { uint32_t executionCycles; servo_t servo; input_t scales[SCALES_COUNT]; + assistedThreadingData_t assistedThreadingData; fastData_t fastData; } rampsSharedData_t; diff --git a/Core/Src/Ramps.c b/Core/Src/Ramps.c index 6d034cf..a046647 100644 --- a/Core/Src/Ramps.c +++ b/Core/Src/Ramps.c @@ -224,12 +224,124 @@ static inline void updateJogPosition(rampsHandler_t *data) { shared->servo.desiredSteps += positionIncrement; } +static inline int32_t getSpindlePhase( + int32_t encoderPosition, + uint32_t countsPerRev +) { + if (countsPerRev == 0) { + return 0; + } + + int32_t phase = encoderPosition % (int32_t)countsPerRev; + if (phase < 0) { + phase += countsPerRev; + } + + return phase; +} + +static inline int32_t computeServoStepTolerance(const rampsSharedData_t *shared) +{ + // Maximum possible step movement in ONE control cycle + int32_t maxStepThisCycle = + (int32_t)( + fabsf(shared->servo.currentSpeed) * + (float)shared->executionInterval / + 100000000.0f + ); + + // +1 to account for truncation / integer rounding + return maxStepThisCycle + 1; +} + +static inline void applyAssistedThreading(rampsSharedData_t *shared) +{ + uint16_t sp = shared->assistedThreadingData.spindleScaleIndex; + if (sp >= SCALES_COUNT) sp = 0; + input_t *spindleScale = &shared->scales[sp]; + + shared->assistedThreadingData.currentThreadPhase = + getSpindlePhase( + spindleScale->position, + shared->assistedThreadingData.spindleCountsPerRev + ); + + // Check for thread reset + if (shared->assistedThreadingData.threadReset == 1) { + shared->assistedThreadingData.threadPhaseActive = 0; + shared->assistedThreadingData.threadEnabled = 0; + shared->assistedThreadingData.threadReset = 2; // set to 2 to indicate reset done + spindleScale->syncEnable = 0; + } + + // Threading-phase request handling + if (shared->assistedThreadingData.threadRequest == 1 && shared->assistedThreadingData.threadEnabled == 0) { + // latch only if we don't already have a reference + if (shared->assistedThreadingData.threadPhaseActive == 0) { + shared->assistedThreadingData.threadPhaseRef = + getSpindlePhase( + spindleScale->position, + shared->assistedThreadingData.spindleCountsPerRev + ); + shared->assistedThreadingData.threadPhaseActive = 1; + } + + shared->assistedThreadingData.threadEnabled = 1; // firmware is now waiting for match + shared->assistedThreadingData.threadRequest = 0; // clear request so host sees acknowledgement + } + + // If firmware is waiting on the phase, check for match + if (shared->assistedThreadingData.threadEnabled == 1) { + int32_t delta = + shared->assistedThreadingData.currentThreadPhase - + shared->assistedThreadingData.threadPhaseRef; + + // wrap-aware delta + int32_t half = shared->assistedThreadingData.spindleCountsPerRev / 2; + if (delta > half) delta -= shared->assistedThreadingData.spindleCountsPerRev; + if (delta < -half) delta += shared->assistedThreadingData.spindleCountsPerRev; + + int32_t tol = (int32_t)(shared->assistedThreadingData.spindlePhaseTolerance); // host-configurable tolerance (counts) + + if (delta == 0 || (delta <= tol && delta >= -tol)) { + // matched -> start requested threading move + spindleScale->syncEnable = 1; // enable sync motion for the spindle scale + + shared->assistedThreadingData.threadStartSteps = shared->servo.currentSteps; + + shared->assistedThreadingData.threadPhaseActive = 1; + + // clear waiting flags + shared->assistedThreadingData.threadEnabled = 0; + // clear the request so host can detect completion + shared->assistedThreadingData.threadRequest = 0; + } + } + + if (shared->assistedThreadingData.threadEnabled == 0 && shared->assistedThreadingData.threadPhaseActive == 1) { + int32_t traveled = shared->servo.currentSteps - shared->assistedThreadingData.threadStartSteps; + + int32_t remaining = + shared->assistedThreadingData.threadRemainingSteps - traveled; + + int32_t tol = computeServoStepTolerance(shared); + + if (remaining == 0 || (remaining <= tol && remaining >= -tol)) { + // completed move, clear phase reference + shared->assistedThreadingData.threadPhaseActive = 0; + spindleScale->syncEnable = 0; + } + } +} + + void SynchroRefreshTimerIsr(rampsHandler_t *data) { // HAL_GPIO_TogglePin(SPARE_1_GPIO_PORT, SPARE_1_PIN); // HAL_GPIO_WritePin(SPARE_2_GPIO_PORT, SPARE_1_PIN, GPIO_PIN_SET); uint32_t start = DWT->CYCCNT; // Reset the step pin as soon as possible HAL_GPIO_WritePin(STEP_GPIO_PORT, STEP_PIN, GPIO_PIN_RESET); + HAL_GPIO_WritePin(STEP_GPIO_PORT, SPARE_2_PIN, GPIO_PIN_RESET); rampsSharedData_t *shared = &(data->shared); shared->executionIntervalPrevious = shared->executionIntervalCurrent; shared->executionIntervalCurrent = DWT->CYCCNT; @@ -259,7 +371,13 @@ void SynchroRefreshTimerIsr(rampsHandler_t *data) { shared->fastData.scaleCurrent[i] = shared->scales[i].position; } - if (shared->fastData.servoMode == 1) updateIndexingPosition(data); + if (shared->fastData.servoMode == 1){ + if (shared->assistedThreadingData.threadEnabled == 1 || shared->assistedThreadingData.threadRequest == 1 || shared->assistedThreadingData.threadPhaseActive == 1) { + applyAssistedThreading(shared); + } + updateIndexingPosition(data); + } + if (shared->fastData.servoMode == 2) updateJogPosition(data); if (shared->fastData.servoMode != 0 && servoCyclesCounter == 0) { @@ -278,6 +396,7 @@ void SynchroRefreshTimerIsr(rampsHandler_t *data) { if (direction == data->servoPreviousDirection && change != 0) { HAL_GPIO_WritePin(STEP_GPIO_PORT, STEP_PIN, GPIO_PIN_SET); + HAL_GPIO_WritePin(STEP_GPIO_PORT, SPARE_2_PIN, GPIO_PIN_SET); shared->servo.currentSteps += direction; } @@ -292,12 +411,17 @@ void SynchroRefreshTimerIsr(rampsHandler_t *data) { _Noreturn void userLedTask(__attribute__((unused)) void *argument) { uint16_t oldInCnt = 0; + uint8_t blinkInterval = 0; for (;;) { osDelay(50); + blinkInterval = (blinkInterval + 1) % 10; + if (blinkInterval == 0) { + HAL_GPIO_TogglePin(USR_LED_GPIO_Port, USR_LED_Pin); + } + if (oldInCnt != RampsModbusData.u16InCnt) { oldInCnt = RampsModbusData.u16InCnt; - HAL_GPIO_TogglePin(USR_LED_GPIO_Port, USR_LED_Pin); HAL_GPIO_WritePin(USR_LED_GPIO_Port, USR_LED_Pin, GPIO_PIN_RESET); osDelay(25); HAL_GPIO_WritePin(USR_LED_GPIO_Port, USR_LED_Pin, GPIO_PIN_SET); diff --git a/README.md b/README.md index 0f56606..7a2992d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,119 @@ -# Rotary Controller +# Rotary Controller (F4) -This repository contains the source code for the rotary controller board based -on the STM32F411 microcontroller from ST. +[![Discord](https://img.shields.io/discord/1386014070632878100?style=social)](https://discord.gg/EDtgj7Yayr) [![Shop at Provvedo](https://img.shields.io/badge/Shop-Provvedo-blue?logo=shopify&style=flat-square)](https://www.provvedo.com/shop) + +This repository contains the **firmware** for a rotary controller board based on the **STM32F411** microcontroller ([github.com][1]). It provides Digital Read Out (DRO) and single-axis control for CNC-style rotary tables. + +πŸ›’ **Purchase all boards from our shop:** [Provvedo Shop](https://www.provvedo.com/shop) + +--- + +## βš™οΈ Features + +* Utilizes **STM32CubeMX** for hardware configuration (.ioc file included) +* Modular firmware structure with FreeRTOS support +* Supports ST‑Link V2 and Raspberry Pi + OpenOCD programming +* Optimized for high-speed encoder + stepper motor control + +--- + +## πŸ› οΈ Build & Flash + +### Requirements + +* CMake & C/C++ toolchain (e.g. `arm-none-eabi-gcc`, `make`) +* ST-Link v2 or Raspberry Pi with OpenOCD + +### Build + +```bash +git clone https://github.com/bartei/rotary-controller-f4.git +cd rotary-controller-f4 +cmake -DCMAKE_BUILD_TYPE=Release . +make -j$(nproc) +``` + +### Clean + +```bash +make clean +``` + +### Flash + +* **ST‑Link V2**: + + ```bash + st-flash --format ihex write rotary-controller-f4.hex + ``` + +* **Raspberry Pi + OpenOCD**: + + ```bash + openocd -f ./raspberry.cfg + ``` + + The default `raspberry.cfg` configures SWD over GPIO pins 24/25 + GND. Ensure GND wiring is the **same length** as SWCLK/SWDIO for reliability. Modify the GPIO pins in `raspberry.cfg` if needed. + +--- + +## πŸ”§ Hardware Configuration + +* `.ioc` file for use with STM32CubeMX included +* Pin assignments for encoder, buttons, LEDs, SWD, etc. reviewed and tested +* Memory layout defined by `STM32F411CEUX_FLASH.ld` and `STM32F411CEUX_RAM.ld` + +--- + +## 🧩 PCB & Schematic + +Firmware integrates with hardware design available at: + +* **PCB repo**: bartei/rotary-controller-pcb β€” includes Proteus schematic, BOM (with pricing), and fab files; KiCad version in progress ([github.com][2], [github.com][3], [github.com][1]) + +Together, they form a complete controller + UI system when paired with: + +* `rotary-controller-python` β€” a Raspberry Pi Kivy-based DRO + control UI + +--- + +## πŸ›ŽοΈ Usage Notes + +* Works as a **single-axis rotary DRO** +* FreeRTOS scheduler handles encoder sampling loop +* GPIO/button routines support nudge and rotary button functions +* SWD pins must be appropriately wired and matched in length + +--- + +## πŸ“˜ Resources & Links + +* [Firmware repo](https://github.com/bartei/rotary-controller-f4) +* [PCB repo (Proteus/KiCad)](https://github.com/bartei/rotary-controller-pcb) +* [Raspberry Pi UI with Kivy](https://github.com/bartei/rotary-controller-python) +* Join the community on **Discord** + +--- + +## βœ… Next Steps + +1. Test hardware interface in CubeMX; verify pin assignments +2. Build and flash firmware, connect to DRO UI app +3. Utilize FreeRTOS for real-time sampling and control +4. Contribute improvements β€” e.g. KiCad support, UI features, multi-axis + +--- + +## πŸ“ Contact & Support + +Need help? Join our **Discord** community for support, discussions, and updates. + + +--- + +Let me know if you'd like additions like block diagrams, pinout tables, or usage screenshots! + +[1]: https://github.com/bartei/rotary-controller-f4" +[2]: https://github.com/bartei/rotary-controller-pcb" +[3]: https://github.com/bartei/rotary-controller-python" diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..47ce76e --- /dev/null +++ b/build.bat @@ -0,0 +1,7 @@ +mkdir build +cd build +cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. +ninja +st-flash --format ihex write rotary-controller-f4.hex +cd .. +rmdir build /S /Q \ No newline at end of file