├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── stale.yml └── workflows │ ├── arduino.yml │ └── platformio.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── _config.yml ├── arduino-cli.yaml ├── examples ├── AccelTest │ └── AccelTest.ino ├── BasicStepperDriver │ └── BasicStepperDriver.ino ├── ClockStepper │ └── ClockStepper.ino ├── MicroStepping │ └── MicroStepping.ino ├── MultiAxis │ └── MultiAxis.ino ├── NonBlocking │ └── NonBlocking.ino ├── SpeedProfile │ └── SpeedProfile.ino └── UnitTest │ ├── UnitTest.ino │ ├── adafruit_feather_m0.txt │ └── esp8266_nodemcu.txt ├── keywords.txt ├── library.properties ├── platformio.ini ├── src ├── A4988.cpp ├── A4988.h ├── BasicStepperDriver.cpp ├── BasicStepperDriver.h ├── DRV8825.cpp ├── DRV8825.h ├── DRV8834.cpp ├── DRV8834.h ├── DRV8880.cpp ├── DRV8880.h ├── MultiDriver.cpp ├── MultiDriver.h ├── SyncDriver.cpp └── SyncDriver.h └── test └── README /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | # To fully customize the contents of this image, use the following Dockerfile instead: 7 | # https://github.com/microsoft/vscode-dev-containers/tree/v0.112.0/containers/ubuntu-18.04-git/.devcontainer/Dockerfile 8 | FROM mcr.microsoft.com/vscode/devcontainers/base:0-ubuntu-18.04 9 | 10 | # ** [Optional] Uncomment this section to install additional packages. ** 11 | # 12 | ENV DEBIAN_FRONTEND=noninteractive 13 | RUN apt-get update \ 14 | && apt-get -y install --no-install-recommends make curl clang clang-tools \ 15 | # 16 | # Clean up 17 | && apt-get autoremove -y \ 18 | && apt-get clean -y \ 19 | && rm -rf /var/lib/apt/lists/* 20 | ENV DEBIAN_FRONTEND=dialog 21 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.112.0/containers/ubuntu-18.04-git 3 | { 4 | "name": "Ubuntu 18.04 & Git", 5 | "dockerFile": "Dockerfile", 6 | "workspaceFolder": "/workspaces/StepperDriver", 7 | "mounts": [ 8 | "source=arduino-cli,target=/workspaces/StepperDriver/.arduino,type=volume" 9 | ], 10 | 11 | // Set *default* container specific settings.json values on container create. 12 | "settings": { 13 | "terminal.integrated.shell.linux": "/bin/bash" 14 | }, 15 | 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": [ 18 | "vsciot-vscode.vscode-arduino", 19 | "ms-vscode.cpptools", 20 | "vector-of-bool.gitflow", 21 | "eamodio.gitlens" 22 | ], 23 | 24 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 25 | // "forwardPorts": [], 26 | 27 | // Use 'postCreateCommand' to run commands after the container is created. 28 | "postCreateCommand": "sudo chown vscode /workspaces/StepperDriver/.arduino; make setup", 29 | 30 | // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker. 31 | // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], 32 | 33 | // Uncomment when using a ptrace-based debugger like C++, Go, and Rust 34 | // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], 35 | 36 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 37 | "remoteUser": "vscode" 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Bug report 4 | title: '' 5 | labels: bug 6 | assignees: laurb9 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Example sketch code (smallest code that reproduces the problem) 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Platform Setup (please complete the following information):** 21 | - Arduino IDE version (try with latest version) 22 | - Board type (Uno, Feather M0 etc) 23 | - Stepper driver type (A4988, DRV8834, etc) 24 | - Wiring (if needed) - please note that hardware issues are outside of the scope of this project 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an improvement idea for this project 4 | title: '' 5 | labels: improvement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 90 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - improvement 8 | - help wanted 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/arduino.yml: -------------------------------------------------------------------------------- 1 | name: Arduino 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | pull_request: 9 | release: 10 | types: 11 | - created 12 | 13 | jobs: 14 | Boards: 15 | timeout-minutes: 10 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | board: 20 | - arduino:avr:uno 21 | - arduino:avr:mega 22 | - adafruit:samd:adafruit_feather_m0 23 | - esp8266:esp8266:nodemcu 24 | 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Set up Python 29 | uses: actions/setup-python@v1 30 | - name: Install arduino-cli 31 | run: | 32 | make setup 33 | - name: Build 34 | run: | 35 | make setup 36 | make all TARGET=${{ matrix.board }} 37 | -------------------------------------------------------------------------------- /.github/workflows/platformio.yml: -------------------------------------------------------------------------------- 1 | name: PlatformIO 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | pull_request: 9 | release: 10 | types: 11 | - created 12 | 13 | jobs: 14 | Boards: 15 | needs: Examples 16 | timeout-minutes: 10 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | board: 21 | - esp32dev 22 | - nodemcuv2 23 | - teensylc 24 | - adafruit_feather_m0 25 | - uno 26 | 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Set up Python 31 | uses: actions/setup-python@v1 32 | with: 33 | python-version: 3 34 | - name: Install PlatformIO 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install platformio 38 | - name: Build 39 | run: | 40 | platformio run -e ${{ matrix.board }} 41 | 42 | Examples: 43 | timeout-minutes: 5 44 | strategy: 45 | fail-fast: true 46 | matrix: 47 | board: 48 | - adafruit_feather_m0 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v2 52 | - name: Set up Python 3 53 | uses: actions/setup-python@v1 54 | with: 55 | python-version: 3 56 | - name: Install PlatformIO 57 | run: | 58 | python -m pip install --upgrade pip 59 | pip install platformio 60 | - name: Build 61 | run: | 62 | for sketch in examples/*; do 63 | platformio ci --lib src --keep-build-dir --board ${{ matrix.board }} ${sketch} || exit 1; 64 | done 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # Eclipse 31 | .*project 32 | 33 | # VScode 34 | .vscode 35 | 36 | # PyCharm/Jetbrains editors 37 | .idea 38 | 39 | # Mac 40 | .DS_Store 41 | 42 | # Arduino 43 | *.hex 44 | *.bin 45 | *.elf 46 | .arduino 47 | 48 | # PlatformIO 49 | .pio -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | idf_component_register( 3 | SRC_DIRS "src" # Add all source files here 4 | INCLUDE_DIRS "src" 5 | REQUIRES "arduino" # Add include directories here 6 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Laurentiu Badea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Default build architecture and board 2 | TARGET ?= arduino:avr:uno 3 | CORE = $(shell echo $(TARGET) | cut -d: -f1,2) 4 | 5 | # Where to save the Arduino support files, this should match what is in arduino-cli.yaml 6 | ARDUINO_DIR ?= .arduino 7 | 8 | default: 9 | ################################################################################################# 10 | # Initial setup: make .arduino/arduino-cli setup 11 | # 12 | # Build all the examples: make all TARGET=adafruit:samd:adafruit_feather_m0 13 | # 14 | # Install more cores: make core TARGET=adafruit:samd:adafruit_feather_m0 15 | # (edit arduino-cli.yaml and add repository if needed) 16 | ################################################################################################# 17 | 18 | # See https://arduino.github.io/arduino-cli/installation/ 19 | ARDUINO_CLI_URL = https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_64bit.tar.gz 20 | ARDUINO_CLI ?= $(ARDUINO_DIR)/arduino-cli --config-file arduino-cli.yaml 21 | EXAMPLES := $(shell ls examples) 22 | 23 | COMPILE = $(ARDUINO_CLI) compile --warnings all --fqbn $(TARGET) 24 | 25 | all: # Build all example sketches 26 | all: $(EXAMPLES:%=%.hex) 27 | ls -l build 28 | 29 | %.hex: # Generic rule for compiling sketch to uploadable hex file 30 | %.hex: examples/% core 31 | $(ARDUINO_CLI) compile --warnings all --fqbn $(TARGET) --output-dir build $< 32 | 33 | # Remove built objects 34 | clean: 35 | rm -rfv build 36 | 37 | core: $(ARDUINO_DIR)/arduino-cli 38 | $(ARDUINO_CLI) core install $(CORE) 39 | 40 | $(ARDUINO_DIR)/arduino-cli: # Download and install arduino-cli 41 | $(ARDUINO_DIR)/arduino-cli: 42 | mkdir -p $(ARDUINO_DIR) 43 | cd $(ARDUINO_DIR) 44 | curl -L -s $(ARDUINO_CLI_URL) \ 45 | | tar xfz - -C $(ARDUINO_DIR) arduino-cli 46 | chmod 755 $@ 47 | $(ARDUINO_CLI) version 48 | 49 | setup: # Configure cores and libraries for arduino-cli (which it will download if missing) 50 | setup: $(ARDUINO_DIR)/arduino-cli 51 | mkdir -p $(ARDUINO_DIR)/libraries 52 | ln -sf $(CURDIR) $(ARDUINO_DIR)/libraries/ 53 | $(ARDUINO_CLI) config dump 54 | $(ARDUINO_CLI) core update-index 55 | $(ARDUINO_CLI) core list 56 | 57 | .PHONY: clean %.hex all setup 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![arduino-library-badge](https://www.ardu-badge.com/badge/StepperDriver.svg?)](https://www.ardu-badge.com/StepperDriver) 2 | [![Actions Status](https://github.com/laurb9/StepperDriver/workflows/PlatformIO/badge.svg)](https://github.com/laurb9/StepperDriver/actions) 3 | [![Actions Status](https://github.com/laurb9/StepperDriver/workflows/Arduino/badge.svg)](https://github.com/laurb9/StepperDriver/actions) 4 | 5 | StepperDriver 6 | ============= 7 | 8 | A4988, DRV8825, DRV8834, DRV8880 and generic two-pin stepper motor driver library. 9 | Features: 10 | - Constant speed mode (low rpms) 11 | - Linear (accelerated) speed mode, with separate acceleration and deceleration settings. 12 | - Non-blocking mode (yields back to caller after each pulse) 13 | - Early brake / increase runtime in non-blocking mode 14 | 15 | Hardware currently supported: 16 | - DRV8834 Low-Voltage Stepper Motor Driver 17 | up to 1:32 18 | - A4988 Stepper Motor Driver up to 1:16 19 | - DRV8825 up to 1:32 20 | - DRV8880 up to 1:16, with current/torque control 21 | - any other 2-pin stepper via DIR and STEP pins, microstepping up to 1:128 externally set 22 | 23 | Microstepping 24 | ============= 25 | 26 | The library can set microstepping and generate the signals for each of the support driver boards. 27 | 28 | High RPM plus high microstep combinations may not work correctly on slower MCUs, there is a maximum speed 29 | achieveable for each board, especially with acceleration on multiple motors at the same time. 30 | 31 | Motors 32 | ====== 33 | 34 | - 4-wire bipolar stepper motor or 35 | - some 6-wire unipolar in 4-wire configuration (leaving centers out) or 36 | - 28BYJ-48 (commonly available) with a small modification (search for "convert 28byj-48 to 4-wire"). 37 | 38 | Connections 39 | =========== 40 | 41 | Minimal configuration from Pololu DRV8834 page: 42 | 43 | 44 | 45 | Wiring 46 | ====== 47 | 48 | This is suggested wiring for running the examples unmodified. All the pins below can be changed. 49 | 50 | - Arduino to driver board: 51 | - DIR - D8 52 | - STEP - D9 53 | - GND - Arduino GND 54 | - GND - Motor power GND 55 | - VMOT - Motor power (check driver-specific voltage range) 56 | - A4988/DRV8825 microstep control 57 | - MS1/MODE0 - D10 58 | - MS2/MODE1 - D11 59 | - MS3/MODE2 - D12 60 | - DRV8834/DRV8880 microstep control 61 | - M0 - D10 62 | - M1 - D11 63 | - ~SLEEP (optional) D13 64 | 65 | - driver board to motor (this varies from motor to motor, check motor coils schematic). 66 | - 100uF capacitor between GND - VMOT 67 | - Make sure to set the max current on the driver board to the motor limit (see below). 68 | - Have a motor power supply that can deliver that current. 69 | - Make sure the motor power supply voltage is within the range supported by the driver board. 70 | 71 | Set Max Current 72 | =============== 73 | 74 | The max current is set via the potentiometer on board. 75 | Turn it while measuring voltage at the passthrough next to it. 76 | The formula is V = I*5*R where I=max current, R=current sense resistor installed onboard 77 | 78 | - DRV8834 or DRV8825 Pololu boards, R=0.1 and V = 0.5 * max current(A). 79 | For example, for 1A you will set it to 0.5V. 80 | 81 | For latest info, see the Pololu board information pages. 82 | 83 | Code 84 | ==== 85 | 86 | See the BasicStepperDriver example for a generic driver that should work with any board 87 | supporting the DIR/STEP indexing mode. 88 | 89 | The Microstepping example works with a DRV8834 board. 90 | 91 | For example, to show what is possible, here is the ClockStepper example that moves a 92 | stepper motor like the seconds hand of a watch: 93 | 94 | ```C++ 95 | #include 96 | #include "A4988.h" 97 | 98 | // using a 200-step motor (most common) 99 | #define MOTOR_STEPS 200 100 | // configure the pins connected 101 | #define DIR 8 102 | #define STEP 9 103 | #define MS1 10 104 | #define MS2 11 105 | #define MS3 12 106 | A4988 stepper(MOTOR_STEPS, DIR, STEP, MS1, MS2, MS3); 107 | 108 | void setup() { 109 | // Set target motor RPM to 1RPM and microstepping to 1 (full step mode) 110 | stepper.begin(1, 1); 111 | } 112 | 113 | void loop() { 114 | // Tell motor to rotate 360 degrees. That's it. 115 | stepper.rotate(360); 116 | } 117 | ``` 118 | 119 | Hardware 120 | ======== 121 | - Arduino-compatible board 122 | - A stepper motor driver, for example DRV8834, DRV8825, DRV8824, A4988. 123 | - A Stepper Motor. 124 | - 1 x 100uF capacitor 125 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-modernist -------------------------------------------------------------------------------- /arduino-cli.yaml: -------------------------------------------------------------------------------- 1 | directories: 2 | data: .arduino 3 | downloads: .arduino/staging 4 | user: .arduino 5 | board_manager: 6 | additional_urls: 7 | - http://arduino.esp8266.com/stable/package_esp8266com_index.json 8 | - https://dl.espressif.com/dl/package_esp32_index.json 9 | - https://adafruit.github.io/arduino-board-index/package_adafruit_index.json 10 | telemetry: 11 | enabled: false 12 | -------------------------------------------------------------------------------- /examples/AccelTest/AccelTest.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Using accelerated motion ("linear speed") in nonblocking mode 3 | * 4 | * Copyright (C)2015-2017 Laurentiu Badea 5 | * 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | #include 10 | 11 | // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step 12 | #define MOTOR_STEPS 200 13 | // Target RPM for cruise speed 14 | #define RPM 120 15 | // Acceleration and deceleration values are always in FULL steps / s^2 16 | #define MOTOR_ACCEL 2000 17 | #define MOTOR_DECEL 1000 18 | 19 | // Microstepping mode. If you hardwired it to save pins, set to the same value here. 20 | #define MICROSTEPS 16 21 | 22 | #define DIR 8 23 | #define STEP 9 24 | #define SLEEP 13 // optional (just delete SLEEP from everywhere if not used) 25 | 26 | /* 27 | * Choose one of the sections below that match your board 28 | */ 29 | 30 | #include "DRV8834.h" 31 | #define M0 10 32 | #define M1 11 33 | DRV8834 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1); 34 | 35 | // #include "A4988.h" 36 | // #define MS1 10 37 | // #define MS2 11 38 | // #define MS3 12 39 | // A4988 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MS1, MS2, MS3); 40 | 41 | // #include "DRV8825.h" 42 | // #define MODE0 10 43 | // #define MODE1 11 44 | // #define MODE2 12 45 | // DRV8825 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MODE0, MODE1, MODE2); 46 | 47 | // #include "DRV8880.h" 48 | // #define M0 10 49 | // #define M1 11 50 | // #define TRQ0 6 51 | // #define TRQ1 7 52 | // DRV8880 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1, TRQ0, TRQ1); 53 | 54 | // #include "BasicStepperDriver.h" // generic 55 | // BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP); 56 | 57 | void setup() { 58 | Serial.begin(115200); 59 | 60 | stepper.begin(RPM, MICROSTEPS); 61 | // if using enable/disable on ENABLE pin (active LOW) instead of SLEEP uncomment next line 62 | // stepper.setEnableActiveState(LOW); 63 | stepper.enable(); 64 | // set current level (for DRV8880 only). Valid percent values are 25, 50, 75 or 100. 65 | // stepper.setCurrent(100); 66 | 67 | /* 68 | * Set LINEAR_SPEED (accelerated) profile. 69 | */ 70 | stepper.setSpeedProfile(stepper.LINEAR_SPEED, MOTOR_ACCEL, MOTOR_DECEL); 71 | 72 | Serial.println("START"); 73 | /* 74 | * Using non-blocking mode to print out the step intervals. 75 | * We could have just as easily replace everything below this line with 76 | * stepper.rotate(360); 77 | */ 78 | stepper.startRotate(360); 79 | } 80 | 81 | void loop() { 82 | static int step = 0; 83 | unsigned wait_time = stepper.nextAction(); 84 | if (wait_time){ 85 | Serial.print(" step="); Serial.print(step++); 86 | Serial.print(" dt="); Serial.print(wait_time); 87 | Serial.print(" rpm="); Serial.print(stepper.getCurrentRPM()); 88 | Serial.println(); 89 | } else { 90 | stepper.disable(); 91 | Serial.println("END"); 92 | delay(3600000); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/BasicStepperDriver/BasicStepperDriver.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple demo, should work with any driver board 3 | * 4 | * Connect STEP, DIR as indicated 5 | * 6 | * Copyright (C)2015-2017 Laurentiu Badea 7 | * 8 | * This file may be redistributed under the terms of the MIT license. 9 | * A copy of this license has been included with this distribution in the file LICENSE. 10 | */ 11 | #include 12 | #include "BasicStepperDriver.h" 13 | 14 | // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step 15 | #define MOTOR_STEPS 200 16 | #define RPM 120 17 | 18 | // Since microstepping is set externally, make sure this matches the selected mode 19 | // If it doesn't, the motor will move at a different RPM than chosen 20 | // 1=full step, 2=half step etc. 21 | #define MICROSTEPS 1 22 | 23 | // All the wires needed for full functionality 24 | #define DIR 8 25 | #define STEP 9 26 | //Uncomment line to use enable/disable functionality 27 | //#define SLEEP 13 28 | 29 | // 2-wire basic config, microstepping is hardwired on the driver 30 | BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP); 31 | 32 | //Uncomment line to use enable/disable functionality 33 | //BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP, SLEEP); 34 | 35 | void setup() { 36 | stepper.begin(RPM, MICROSTEPS); 37 | // if using enable/disable on ENABLE pin (active LOW) instead of SLEEP uncomment next line 38 | // stepper.setEnableActiveState(LOW); 39 | } 40 | 41 | void loop() { 42 | 43 | // energize coils - the motor will hold position 44 | // stepper.enable(); 45 | 46 | /* 47 | * Moving motor one full revolution using the degree notation 48 | */ 49 | stepper.rotate(360); 50 | 51 | /* 52 | * Moving motor to original position using steps 53 | */ 54 | stepper.move(-MOTOR_STEPS*MICROSTEPS); 55 | 56 | // pause and allow the motor to be moved by hand 57 | // stepper.disable(); 58 | 59 | delay(5000); 60 | } 61 | -------------------------------------------------------------------------------- /examples/ClockStepper/ClockStepper.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Clock Microstepping demo 3 | * 4 | * Moves the stepper motor like the seconds hand of a watch. 5 | * 6 | * Copyright (C)2015-2017 Laurentiu Badea 7 | * 8 | * This file may be redistributed under the terms of the MIT license. 9 | * A copy of this license has been included with this distribution in the file LICENSE. 10 | */ 11 | #include 12 | 13 | // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step 14 | #define MOTOR_STEPS 200 15 | 16 | // Microstepping mode. If you hardwired it to save pins, set to the same value here. 17 | #define MICROSTEPS 1 18 | 19 | #define DIR 8 20 | #define STEP 9 21 | #define SLEEP 13 // optional (just delete SLEEP from everywhere if not used) 22 | 23 | /* 24 | * Choose one of the sections below that match your board 25 | */ 26 | 27 | #include "DRV8834.h" 28 | #define M0 10 29 | #define M1 11 30 | DRV8834 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1); 31 | 32 | // #include "A4988.h" 33 | // #define MS1 10 34 | // #define MS2 11 35 | // #define MS3 12 36 | // A4988 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MS1, MS2, MS3); 37 | 38 | // #include "DRV8825.h" 39 | // #define MODE0 10 40 | // #define MODE1 11 41 | // #define MODE2 12 42 | // DRV8825 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MODE0, MODE1, MODE2); 43 | 44 | // #include "DRV8880.h" 45 | // #define M0 10 46 | // #define M1 11 47 | // #define TRQ0 6 48 | // #define TRQ1 7 49 | // DRV8880 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1, TRQ0, TRQ1); 50 | 51 | // #include "BasicStepperDriver.h" // generic 52 | // BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP); 53 | 54 | void setup() { 55 | /* 56 | * Set target motor RPM=1 57 | */ 58 | stepper.begin(1, MICROSTEPS); 59 | 60 | // if using enable/disable on ENABLE pin (active LOW) instead of SLEEP uncomment next line 61 | // stepper.setEnableActiveState(LOW); 62 | stepper.enable(); 63 | } 64 | 65 | void loop() { 66 | /* 67 | * The easy way is just tell the motor to rotate 360 degrees at 1rpm 68 | */ 69 | stepper.rotate(360); 70 | } 71 | -------------------------------------------------------------------------------- /examples/MicroStepping/MicroStepping.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Microstepping demo 3 | * 4 | * This requires that microstep control pins be connected in addition to STEP,DIR 5 | * 6 | * Copyright (C)2015 Laurentiu Badea 7 | * 8 | * This file may be redistributed under the terms of the MIT license. 9 | * A copy of this license has been included with this distribution in the file LICENSE. 10 | */ 11 | #include 12 | 13 | // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step 14 | #define MOTOR_STEPS 200 15 | #define RPM 120 16 | 17 | #define DIR 8 18 | #define STEP 9 19 | #define SLEEP 13 // optional (just delete SLEEP from everywhere if not used) 20 | 21 | /* 22 | * Choose one of the sections below that match your board 23 | */ 24 | 25 | #include "DRV8834.h" 26 | #define M0 10 27 | #define M1 11 28 | DRV8834 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1); 29 | 30 | // #include "A4988.h" 31 | // #define MS1 10 32 | // #define MS2 11 33 | // #define MS3 12 34 | // A4988 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MS1, MS2, MS3); 35 | 36 | // #include "DRV8825.h" 37 | // #define MODE0 10 38 | // #define MODE1 11 39 | // #define MODE2 12 40 | // DRV8825 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MODE0, MODE1, MODE2); 41 | 42 | // #include "DRV8880.h" 43 | // #define M0 10 44 | // #define M1 11 45 | // #define TRQ0 6 46 | // #define TRQ1 7 47 | // DRV8880 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1, TRQ0, TRQ1); 48 | 49 | // #include "BasicStepperDriver.h" // generic 50 | // BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP); 51 | 52 | void setup() { 53 | /* 54 | * Set target motor RPM. 55 | */ 56 | stepper.begin(RPM); 57 | // if using enable/disable on ENABLE pin (active LOW) instead of SLEEP uncomment next line 58 | // stepper.setEnableActiveState(LOW); 59 | stepper.enable(); 60 | 61 | // set current level (for DRV8880 only). 62 | // Valid percent values are 25, 50, 75 or 100. 63 | // stepper.setCurrent(100); 64 | } 65 | 66 | void loop() { 67 | delay(1000); 68 | 69 | /* 70 | * Moving motor in full step mode is simple: 71 | */ 72 | stepper.setMicrostep(1); // Set microstep mode to 1:1 73 | 74 | // One complete revolution is 360° 75 | stepper.rotate(360); // forward revolution 76 | stepper.rotate(-360); // reverse revolution 77 | 78 | // One complete revolution is also MOTOR_STEPS steps in full step mode 79 | stepper.move(MOTOR_STEPS); // forward revolution 80 | stepper.move(-MOTOR_STEPS); // reverse revolution 81 | 82 | /* 83 | * Microstepping mode: 1, 2, 4, 8, 16 or 32 (where supported by driver) 84 | * Mode 1 is full speed. 85 | * Mode 32 is 32 microsteps per step. 86 | * The motor should rotate just as fast (at the set RPM), 87 | * but movement precision is increased, which may become visually apparent at lower RPMs. 88 | */ 89 | stepper.setMicrostep(8); // Set microstep mode to 1:8 90 | 91 | // In 1:8 microstepping mode, one revolution takes 8 times as many microsteps 92 | stepper.move(8 * MOTOR_STEPS); // forward revolution 93 | stepper.move(-8 * MOTOR_STEPS); // reverse revolution 94 | 95 | // One complete revolution is still 360° regardless of microstepping mode 96 | // rotate() is easier to use than move() when no need to land on precise microstep position 97 | stepper.rotate(360); 98 | stepper.rotate(-360); 99 | 100 | delay(5000); 101 | } 102 | -------------------------------------------------------------------------------- /examples/MultiAxis/MultiAxis.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Multi-motor control (experimental) 3 | * 4 | * Move two or three motors at the same time. 5 | * This module is still work in progress and may not work well or at all. 6 | * 7 | * Copyright (C)2017 Laurentiu Badea 8 | * 9 | * This file may be redistributed under the terms of the MIT license. 10 | * A copy of this license has been included with this distribution in the file LICENSE. 11 | */ 12 | #include 13 | #include "BasicStepperDriver.h" 14 | #include "MultiDriver.h" 15 | #include "SyncDriver.h" 16 | 17 | // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step 18 | #define MOTOR_STEPS 200 19 | // Target RPM for X axis motor 20 | #define MOTOR_X_RPM 30 21 | // Target RPM for Y axis motor 22 | #define MOTOR_Y_RPM 90 23 | 24 | // X motor 25 | #define DIR_X 8 26 | #define STEP_X 9 27 | 28 | // Y motor 29 | #define DIR_Y 6 30 | #define STEP_Y 7 31 | 32 | // If microstepping is set externally, make sure this matches the selected mode 33 | // 1=full step, 2=half step etc. 34 | #define MICROSTEPS 32 35 | 36 | // 2-wire basic config, microstepping is hardwired on the driver 37 | // Other drivers can be mixed and matched but must be configured individually 38 | BasicStepperDriver stepperX(MOTOR_STEPS, DIR_X, STEP_X); 39 | BasicStepperDriver stepperY(MOTOR_STEPS, DIR_Y, STEP_Y); 40 | 41 | // Pick one of the two controllers below 42 | // each motor moves independently, trajectory is a hockey stick 43 | // MultiDriver controller(stepperX, stepperY); 44 | // OR 45 | // synchronized move, trajectory is a straight line 46 | SyncDriver controller(stepperX, stepperY); 47 | 48 | void setup() { 49 | /* 50 | * Set target motors RPM. 51 | */ 52 | stepperX.begin(MOTOR_X_RPM, MICROSTEPS); 53 | stepperY.begin(MOTOR_Y_RPM, MICROSTEPS); 54 | // if using enable/disable on ENABLE pin (active LOW) instead of SLEEP uncomment next two lines 55 | // stepperX.setEnableActiveState(LOW); 56 | // stepperY.setEnableActiveState(LOW); 57 | } 58 | 59 | void loop() { 60 | 61 | controller.rotate(90*5, 60*15); 62 | delay(1000); 63 | controller.rotate(-90*5, -30*15); 64 | delay(1000); 65 | controller.rotate(0, -30*15); 66 | delay(30000); 67 | } 68 | -------------------------------------------------------------------------------- /examples/NonBlocking/NonBlocking.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Example using non-blocking mode to move until a switch is triggered. 3 | * 4 | * Copyright (C)2015-2017 Laurentiu Badea 5 | * 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | #include 10 | 11 | // this pin should connect to Ground when want to stop the motor 12 | #define STOPPER_PIN 4 13 | 14 | // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step 15 | #define MOTOR_STEPS 200 16 | #define RPM 120 17 | // Microstepping mode. If you hardwired it to save pins, set to the same value here. 18 | #define MICROSTEPS 16 19 | 20 | #define DIR 8 21 | #define STEP 9 22 | #define SLEEP 13 // optional (just delete SLEEP from everywhere if not used) 23 | 24 | /* 25 | * Choose one of the sections below that match your board 26 | */ 27 | 28 | #include "DRV8834.h" 29 | #define M0 10 30 | #define M1 11 31 | DRV8834 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1); 32 | 33 | // #include "A4988.h" 34 | // #define MS1 10 35 | // #define MS2 11 36 | // #define MS3 12 37 | // A4988 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MS1, MS2, MS3); 38 | 39 | // #include "DRV8825.h" 40 | // #define MODE0 10 41 | // #define MODE1 11 42 | // #define MODE2 12 43 | // DRV8825 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MODE0, MODE1, MODE2); 44 | 45 | // #include "DRV8880.h" 46 | // #define M0 10 47 | // #define M1 11 48 | // #define TRQ0 6 49 | // #define TRQ1 7 50 | // DRV8880 stepper(MOTORS_STEPS, DIR, STEP, SLEEP, M0, M1, TRQ0, TRQ1); 51 | 52 | // #include "BasicStepperDriver.h" // generic 53 | // BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP); 54 | 55 | void setup() { 56 | Serial.begin(115200); 57 | 58 | // Configure stopper pin to read HIGH unless grounded 59 | pinMode(STOPPER_PIN, INPUT_PULLUP); 60 | 61 | stepper.begin(RPM, MICROSTEPS); 62 | // if using enable/disable on ENABLE pin (active LOW) instead of SLEEP uncomment next line 63 | // stepper.setEnableActiveState(LOW); 64 | stepper.enable(); 65 | 66 | // set current level (for DRV8880 only). Valid percent values are 25, 50, 75 or 100. 67 | // stepper.setCurrent(100); 68 | 69 | Serial.println("START"); 70 | 71 | // set the motor to move continuously for a reasonable time to hit the stopper 72 | // let's say 100 complete revolutions (arbitrary number) 73 | stepper.startMove(100 * MOTOR_STEPS * MICROSTEPS); // in microsteps 74 | // stepper.startRotate(100 * 360); // or in degrees 75 | } 76 | 77 | void loop() { 78 | // first, check if stopper was hit 79 | if (digitalRead(STOPPER_PIN) == LOW){ 80 | Serial.println("STOPPER REACHED"); 81 | 82 | /* 83 | * Choosing stop() vs startBrake(): 84 | * 85 | * constant speed mode, they are the same (stop immediately) 86 | * linear (accelerated) mode with brake, the motor will go past the stopper a bit 87 | */ 88 | 89 | stepper.stop(); 90 | // stepper.startBrake(); 91 | } 92 | 93 | // motor control loop - send pulse and return how long to wait until next pulse 94 | unsigned wait_time_micros = stepper.nextAction(); 95 | 96 | // 0 wait time indicates the motor has stopped 97 | if (wait_time_micros <= 0) { 98 | stepper.disable(); // comment out to keep motor powered 99 | delay(3600000); 100 | } 101 | 102 | // (optional) execute other code if we have enough time 103 | if (wait_time_micros > 100){ 104 | // other code here 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/SpeedProfile/SpeedProfile.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This is not an example sketch, it is used to visualize the motor speed. 3 | * 4 | * Usage: upload and start Tool -> Serial Plotter 5 | * 6 | * All driver tests are done with microstep 1. Increasing microstep halves max rpm with each level. 7 | * The maximum usable RPM can be determined from the output. 8 | * The max RPM at a different microstep can be calculated with formula "max rpm / microstep" 9 | * 10 | * Copyright (C)2020 Laurentiu Badea 11 | * 12 | * This file may be redistributed under the terms of the MIT license. 13 | * A copy of this license has been included with this distribution in the file LICENSE. 14 | */ 15 | #include 16 | 17 | #include "BasicStepperDriver.h" 18 | #include "MultiDriver.h" 19 | #include "SyncDriver.h" 20 | 21 | #define RPM 150 22 | #define MICROSTEP 1 23 | 24 | #define MOTOR_STEPS 200 25 | 26 | // Do not use with a real motor, the step timing is very delayed due to serial printing. 27 | BasicStepperDriver s1(MOTOR_STEPS, 12, 13); 28 | BasicStepperDriver s2(MOTOR_STEPS, 12, 13); 29 | BasicStepperDriver s3(MOTOR_STEPS, 12, 13); 30 | 31 | void setup() { 32 | Serial.begin(115200); 33 | delay(4000); 34 | 35 | s1.setSpeedProfile(BasicStepperDriver::LINEAR_SPEED, 2000, 2000); 36 | s2.setSpeedProfile(BasicStepperDriver::LINEAR_SPEED, 500, 500); 37 | s3.setSpeedProfile(BasicStepperDriver::CONSTANT_SPEED); 38 | 39 | s1.begin(RPM, MICROSTEP); 40 | s2.begin(RPM, MICROSTEP); 41 | s3.begin(RPM, MICROSTEP); 42 | 43 | s1.startMove(500); 44 | s2.startMove(500); 45 | s3.startMove(500); 46 | } 47 | 48 | void loop() { 49 | unsigned w1, w2, w3; 50 | 51 | w1 = s1.nextAction(); 52 | w2 = s2.nextAction(); 53 | w3 = s3.nextAction(); 54 | 55 | if (w1 > 0 || w2 > 0 || w3 > 0){ 56 | // uncomment to see step delays instead of speed 57 | /* 58 | Serial.print(w1); 59 | Serial.print("\t"); 60 | Serial.print(w2); 61 | Serial.print("\t"); 62 | Serial.print(w2); 63 | Serial.println(); 64 | */ 65 | 66 | // graph current rpm 67 | Serial.print(s1.getCurrentRPM()); 68 | Serial.print("\t"); 69 | Serial.print(s2.getCurrentRPM()); 70 | Serial.print("\t"); 71 | Serial.print(s3.getCurrentRPM()); 72 | Serial.println(); 73 | } else { 74 | delay(100000); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/UnitTest/UnitTest.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This is not an example sketch, it is used to validate code changes 3 | * and determine maximum workable RPM/microsteps parameters for a given board. 4 | * 5 | * Usage: run with serial terminal open 6 | * 7 | * All driver tests are done with microstep 1. Increasing microstep halves max rpm with each level. 8 | * The maximum usable RPM can be determined from the output. 9 | * The max RPM at a different microstep can be calculated with formula "max rpm / microstep" 10 | * 11 | * Copyright (C)2020 Laurentiu Badea 12 | * 13 | * This file may be redistributed under the terms of the MIT license. 14 | * A copy of this license has been included with this distribution in the file LICENSE. 15 | */ 16 | #include 17 | 18 | #include "BasicStepperDriver.h" 19 | #include "MultiDriver.h" 20 | #include "SyncDriver.h" 21 | 22 | // RPMS contains the list of RPMS to test at, assuming microstep=1 23 | const float RPMS[] = {6000, 600, 60, 6}; 24 | const int RPMS_COUNT = sizeof(RPMS)/sizeof(*RPMS); 25 | const long DURATION_CONSTANT[] = {10000, 100000, 1000000, 10000000}; 26 | const long DURATION_LINEAR[] = {365148, 365148, 1033246, 10000000}; 27 | // STEPS is how many steps for each test. More has better accuracy but slower 28 | #define STEPS 200 29 | // ALLOWED_DEVIATION is the error tolerance. 0.10 considers 90% - 110% range aceptable 30 | #define ALLOWED_DEVIATION 0.10 31 | 32 | /* 33 | * Verify that the expected time calculation is correct at different rpms and two microstep levels 34 | */ 35 | bool test_calculations(BasicStepperDriver stepper, const long duration[]){ 36 | bool pass = true; 37 | char t[128]; 38 | for (int i = 0; i < RPMS_COUNT; i++){ 39 | float rpm = RPMS[i]; 40 | for (int microstep = 1; microstep <= 16; microstep <<= 4){ 41 | long expected_micros = duration[i]; 42 | stepper.begin(rpm, microstep); 43 | long estimated_micros = stepper.getTimeForMove(STEPS*microstep); 44 | sprintf(t, " rpm=%-4d microstep=%-2d expected=%10luµs estimated %10luµs", 45 | int(rpm), microstep, expected_micros, estimated_micros); 46 | Serial.print(t); 47 | float ratio = float(estimated_micros) / float(expected_micros); 48 | if (ratio > 1.01 or ratio < 0.99) { 49 | Serial.print(" FAIL"); 50 | pass = false; 51 | } 52 | Serial.println(); 53 | } 54 | } 55 | return pass; 56 | } 57 | 58 | /* 59 | * Pass/fail the result and print it out in a one-line format 60 | */ 61 | bool result(float rpm, int microstep, int steps, long elapsed_micros, long expected_micros){ 62 | bool pass = true; 63 | char t[128]; 64 | float error = float(elapsed_micros) / float(expected_micros); 65 | unsigned step_micros = expected_micros / steps; 66 | unsigned error_micros = labs(elapsed_micros - expected_micros) / steps; 67 | sprintf(t, " rpm=%-4d expected=%10luµs elapsed=%10luµs step_err=%6uµs avgstep=%6uµs", 68 | int(rpm), expected_micros, elapsed_micros, error_micros, step_micros); 69 | Serial.print(t); 70 | if (error >= 1.0f + ALLOWED_DEVIATION || error <= 1.0f - ALLOWED_DEVIATION) { 71 | pass = false; 72 | Serial.print(" FAIL"); 73 | } 74 | Serial.println(); 75 | return pass; 76 | } 77 | 78 | /* 79 | * Run the tests for BasicStepperDriver 80 | */ 81 | bool test_basic(BasicStepperDriver stepper){ 82 | bool pass = true; 83 | for (int i = 0; i < RPMS_COUNT; i++){ 84 | float rpm = RPMS[i]; 85 | stepper.begin(rpm, 1); 86 | unsigned long start_time_micros = micros(); 87 | stepper.move(STEPS); 88 | long elapsed_micros = micros() - start_time_micros; 89 | pass &= result(rpm, 1, STEPS, elapsed_micros, stepper.getTimeForMove(STEPS)); 90 | } 91 | return pass; 92 | } 93 | 94 | /* 95 | * Run the tests for MultiDriver with 3 motors 96 | */ 97 | bool test_multi(BasicStepperDriver s1, BasicStepperDriver s2, BasicStepperDriver s3){ 98 | MultiDriver controller(s1, s2, s3); 99 | bool pass = true; 100 | for (int i = 0; i < RPMS_COUNT; i++){ 101 | float rpm = RPMS[i]; 102 | s1.begin(rpm, 1); 103 | s2.begin(rpm, 1); 104 | s3.begin(rpm, 1); 105 | unsigned long start_time_micros = micros(); 106 | controller.move(STEPS, 2*STEPS/3, -STEPS/2); 107 | long elapsed_micros = micros() - start_time_micros; 108 | pass &= result(rpm, 1, STEPS, elapsed_micros, s1.getTimeForMove(STEPS)); 109 | } 110 | return pass; 111 | } 112 | 113 | /* 114 | * Run the tests for SyncDriver with 3 motors 115 | */ 116 | bool test_sync(BasicStepperDriver s1, BasicStepperDriver s2, BasicStepperDriver s3){ 117 | SyncDriver controller(s1, s2, s3); 118 | bool pass = true; 119 | for (int i = 0; i < RPMS_COUNT; i++){ 120 | float rpm = RPMS[i]; 121 | s1.begin(rpm, 1); 122 | s2.begin(rpm, 1); 123 | s3.begin(rpm, 1); 124 | unsigned long start_time_micros = micros(); 125 | controller.move(STEPS, 2*STEPS/3, -STEPS/2); 126 | long elapsed_micros = micros() - start_time_micros; 127 | pass &= result(rpm, 1, STEPS, elapsed_micros, s1.getTimeForMove(STEPS)); 128 | } 129 | return pass; 130 | } 131 | 132 | #define TEST_RESULT(result, func, ...) #func "(" #__VA_ARGS__ "): " result 133 | #define RUN_TEST(desc, func, ...) Serial.println(desc); Serial.println(func(__VA_ARGS__) ? TEST_RESULT("OK", func, __VA_ARGS__) : TEST_RESULT("FAIL", func, __VA_ARGS__)) 134 | 135 | void setup() { 136 | 137 | BasicStepperDriver s1(200, 12, 13); 138 | BasicStepperDriver s2(200, 12, 13); 139 | BasicStepperDriver s3(200, 12, 13); 140 | 141 | Serial.begin(115200); 142 | delay(2000); 143 | #ifdef ARDUINO_BOARD 144 | Serial.println(ARDUINO_BOARD); 145 | #endif 146 | RUN_TEST("Timing Calculation test, constant speed", test_calculations, s1, DURATION_CONSTANT); 147 | RUN_TEST("BasicStepperDriver test, constant speed", test_basic, s1); 148 | RUN_TEST("MultiDriver test, constant speed", test_multi, s1, s2, s3); 149 | RUN_TEST("SyncDriver test, constant speed", test_sync, s1, s2, s3); 150 | 151 | s1.setSpeedProfile(s1.LINEAR_SPEED, 6000, 6000); 152 | s2.setSpeedProfile(s2.LINEAR_SPEED, 6000, 6000); 153 | s3.setSpeedProfile(s3.LINEAR_SPEED, 6000, 6000); 154 | 155 | RUN_TEST("Timing Calculation test, linear speed", test_calculations, s1, DURATION_LINEAR); 156 | RUN_TEST("BasicStepperDriver test, linear speed", test_basic, s1); 157 | RUN_TEST("MultiDriver test, linear speed", test_multi, s1, s2, s3); 158 | RUN_TEST("SyncDriver test, linear speed", test_sync, s1, s2, s3); 159 | } 160 | 161 | void loop() { 162 | delay(1); 163 | } 164 | -------------------------------------------------------------------------------- /examples/UnitTest/adafruit_feather_m0.txt: -------------------------------------------------------------------------------- 1 | Timing Calculation test, constant speed 2 | rpm=6000 microstep=1 expected= 10000µs estimated 10000µs 3 | rpm=6000 microstep=16 expected= 10000µs estimated 10000µs 4 | rpm=600 microstep=1 expected= 100000µs estimated 100000µs 5 | rpm=600 microstep=16 expected= 100000µs estimated 100000µs 6 | rpm=60 microstep=1 expected= 1000000µs estimated 1000000µs 7 | rpm=60 microstep=16 expected= 1000000µs estimated 1000000µs 8 | rpm=6 microstep=1 expected= 10000000µs estimated 10000000µs 9 | rpm=6 microstep=16 expected= 10000000µs estimated 10000000µs 10 | test_calculations(s1, DURATION_CONSTANT): OK 11 | BasicStepperDriver test, constant speed 12 | rpm=6000 expected= 10000µs elapsed= 11336µs step_err= 6µs avgstep= 50µs FAIL 13 | rpm=600 expected= 100000µs elapsed= 100728µs step_err= 3µs avgstep= 500µs 14 | rpm=60 expected= 1000000µs elapsed= 996284µs step_err= 18µs avgstep= 5000µs 15 | rpm=6 expected= 10000000µs elapsed= 9951257µs step_err= 243µs avgstep= 50000µs 16 | test_basic(s1): FAIL 17 | MultiDriver test, constant speed 18 | rpm=6000 expected= 10000µs elapsed= 18210µs step_err= 41µs avgstep= 50µs FAIL 19 | rpm=600 expected= 100000µs elapsed= 108452µs step_err= 42µs avgstep= 500µs 20 | rpm=60 expected= 1000000µs elapsed= 1008295µs step_err= 41µs avgstep= 5000µs 21 | rpm=6 expected= 10000000µs elapsed= 10008300µs step_err= 41µs avgstep= 50000µs 22 | test_multi(s1, s2, s3): FAIL 23 | SyncDriver test, constant speed 24 | rpm=6000 expected= 10000µs elapsed= 19284µs step_err= 46µs avgstep= 50µs FAIL 25 | rpm=600 expected= 100000µs elapsed= 109244µs step_err= 46µs avgstep= 500µs 26 | rpm=60 expected= 1000000µs elapsed= 1009259µs step_err= 46µs avgstep= 5000µs 27 | rpm=6 expected= 10000000µs elapsed= 10009274µs step_err= 46µs avgstep= 50000µs 28 | test_sync(s1, s2, s3): FAIL 29 | Timing Calculation test, linear speed 30 | rpm=6000 microstep=1 expected= 365148µs estimated 365148µs 31 | rpm=6000 microstep=16 expected= 365148µs estimated 365148µs 32 | rpm=600 microstep=1 expected= 365148µs estimated 365148µs 33 | rpm=600 microstep=16 expected= 365148µs estimated 365148µs 34 | rpm=60 microstep=1 expected= 1033246µs estimated 1033246µs 35 | rpm=60 microstep=16 expected= 1033246µs estimated 1033333µs 36 | rpm=6 microstep=1 expected= 10000000µs estimated 10000000µs 37 | rpm=6 microstep=16 expected= 10000000µs estimated 10000000µs 38 | test_calculations(s1, DURATION_LINEAR): OK 39 | BasicStepperDriver test, linear speed 40 | rpm=6000 expected= 365148µs elapsed= 343357µs step_err= 108µs avgstep= 1825µs 41 | rpm=600 expected= 365148µs elapsed= 343361µs step_err= 108µs avgstep= 1825µs 42 | rpm=60 expected= 1033246µs elapsed= 1010367µs step_err= 114µs avgstep= 5166µs 43 | rpm=6 expected= 10000000µs elapsed= 2457426µs step_err= 37712µs avgstep= 50000µs FAIL 44 | test_basic(s1): FAIL 45 | MultiDriver test, linear speed 46 | rpm=6000 expected= 365148µs elapsed= 364538µs step_err= 3µs avgstep= 1825µs 47 | rpm=600 expected= 365148µs elapsed= 364552µs step_err= 2µs avgstep= 1825µs 48 | rpm=60 expected= 1033246µs elapsed= 1030380µs step_err= 14µs avgstep= 5166µs 49 | rpm=6 expected= 10000000µs elapsed= 2477104µs step_err= 37614µs avgstep= 50000µs FAIL 50 | test_multi(s1, s2, s3): FAIL 51 | SyncDriver test, linear speed 52 | rpm=6000 expected= 365148µs elapsed= 365655µs step_err= 2µs avgstep= 1825µs 53 | rpm=600 expected= 365148µs elapsed= 365591µs step_err= 2µs avgstep= 1825µs 54 | rpm=60 expected= 1033246µs elapsed= 1245906µs step_err= 1063µs avgstep= 5166µs FAIL 55 | rpm=6 expected= 10000000µs elapsed= 2478551µs step_err= 37607µs avgstep= 50000µs FAIL 56 | test_sync(s1, s2, s3): FAIL 57 | -------------------------------------------------------------------------------- /examples/UnitTest/esp8266_nodemcu.txt: -------------------------------------------------------------------------------- 1 | 2 | ESP8266_NODEMCU 3 | Timing Calculation test, constant speed 4 | rpm=6000 microstep=1 expected= 10000µs estimated 10000µs 5 | rpm=6000 microstep=16 expected= 10000µs estimated 10000µs 6 | rpm=600 microstep=1 expected= 100000µs estimated 100000µs 7 | rpm=600 microstep=16 expected= 100000µs estimated 100000µs 8 | rpm=60 microstep=1 expected= 1000000µs estimated 1000000µs 9 | rpm=60 microstep=16 expected= 1000000µs estimated 1000000µs 10 | rpm=6 microstep=1 expected= 10000000µs estimated 10000000µs 11 | rpm=6 microstep=16 expected= 10000000µs estimated 10000000µs 12 | test_calculations(s1, DURATION_CONSTANT): OK 13 | BasicStepperDriver test, constant speed 14 | rpm=6000 expected= 10000µs elapsed= 10346µs step_err= 1µs avgstep= 50µs 15 | rpm=600 expected= 100000µs elapsed= 99760µs step_err= 1µs avgstep= 500µs 16 | rpm=60 expected= 1000000µs elapsed= 995291µs step_err= 23µs avgstep= 5000µs 17 | rpm=6 expected= 10000000µs elapsed= 9950300µs step_err= 248µs avgstep= 50000µs 18 | test_basic(s1): OK 19 | MultiDriver test, constant speed 20 | rpm=6000 expected= 10000µs elapsed= 12420µs step_err= 12µs avgstep= 50µs FAIL 21 | rpm=600 expected= 100000µs elapsed= 105190µs step_err= 25µs avgstep= 500µs 22 | rpm=60 expected= 1000000µs elapsed= 1006547µs step_err= 32µs avgstep= 5000µs 23 | rpm=6 expected= 10000000µs elapsed= 10017375µs step_err= 86µs avgstep= 50000µs 24 | test_multi(s1, s2, s3): FAIL 25 | SyncDriver test, constant speed 26 | rpm=6000 expected= 10000µs elapsed= 14511µs step_err= 22µs avgstep= 50µs FAIL 27 | rpm=600 expected= 100000µs elapsed= 105689µs step_err= 28µs avgstep= 500µs 28 | rpm=60 expected= 1000000µs elapsed= 1006867µs step_err= 34µs avgstep= 5000µs 29 | rpm=6 expected= 10000000µs elapsed= 10018707µs step_err= 93µs avgstep= 50000µs 30 | test_sync(s1, s2, s3): FAIL 31 | Timing Calculation test, linear speed 32 | rpm=6000 microstep=1 expected= 365148µs estimated 365148µs 33 | rpm=6000 microstep=16 expected= 365148µs estimated 365148µs 34 | rpm=600 microstep=1 expected= 365148µs estimated 365148µs 35 | rpm=600 microstep=16 expected= 365148µs estimated 365148µs 36 | rpm=60 microstep=1 expected= 1033246µs estimated 1033246µs 37 | rpm=60 microstep=16 expected= 1033246µs estimated 1033333µs 38 | rpm=6 microstep=1 expected= 10000000µs estimated 10000000µs 39 | rpm=6 microstep=16 expected= 10000000µs estimated 10000000µs 40 | test_calculations(s1, DURATION_LINEAR): OK 41 | BasicStepperDriver test, linear speed 42 | rpm=6000 expected= 365148µs elapsed= 342337µs step_err= 114µs avgstep= 1825µs 43 | rpm=600 expected= 365148µs elapsed= 342328µs step_err= 114µs avgstep= 1825µs 44 | rpm=60 expected= 1033246µs elapsed= 1009343µs step_err= 119µs avgstep= 5166µs 45 | rpm=6 expected= 10000000µs elapsed= 2456407µs step_err= 37717µs avgstep= 50000µs FAIL 46 | test_basic(s1): FAIL 47 | MultiDriver test, linear speed 48 | rpm=6000 expected= 365148µs elapsed= 360765µs step_err= 21µs avgstep= 1825µs 49 | rpm=600 expected= 365148µs elapsed= 360705µs step_err= 22µs avgstep= 1825µs 50 | rpm=60 expected= 1033246µs elapsed= 1028486µs step_err= 23µs avgstep= 5166µs 51 | rpm=6 expected= 10000000µs elapsed= 2477224µs step_err= 37613µs avgstep= 50000µs FAIL 52 | test_multi(s1, s2, s3): FAIL 53 | SyncDriver test, linear speed 54 | rpm=6000 expected= 365148µs elapsed= 361367µs step_err= 18µs avgstep= 1825µs 55 | rpm=600 expected= 365148µs elapsed= 361408µs step_err= 18µs avgstep= 1825µs 56 | rpm=60 expected= 1033246µs elapsed= 1242580µs step_err= 1046µs avgstep= 5166µs FAIL 57 | rpm=6 expected= 10000000µs elapsed= 2477912µs step_err= 37610µs avgstep= 50000µs FAIL 58 | test_sync(s1, s2, s3): FAIL 59 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | StepperDriver KEYWORD1 2 | 3 | BasicStepperDriver KEYWORD1 4 | DRV8880 KEYWORD1 5 | DRV8834 KEYWORD1 6 | DRV8824 KEYWORD1 7 | DRV8825 KEYWORD1 8 | A4988 KEYWORD1 9 | MultiDriver KEYWORD1 10 | SyncDriver KEYWORD1 11 | 12 | setMicrostep KEYWORD2 13 | setSpeedProfile KEYWORD2 14 | move KEYWORD2 15 | rotate KEYWORD2 16 | setRPM KEYWORD2 17 | getRPM KEYWORD2 18 | setCurrent KEYWORD2 19 | enable KEYWORD2 20 | disable KEYWORD2 21 | startMove KEYWORD2 22 | startRotate KEYWORD2 23 | nextAction KEYWORD2 24 | stop KEYWORD2 25 | startBrake KEYWORD2 26 | 27 | CONSTANT_SPEED LITERAL1 28 | LINEAR_SPEED LITERAL1 29 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=StepperDriver 2 | version=1.4.1 3 | author=Laurentiu Badea 4 | maintainer=Laurentiu Badea 5 | sentence=A4988, DRV8825 and generic two-pin stepper motor driver library. 6 | paragraph=Control steppers via a driver board providing STEP+DIR like the ones from Pololu. Microstepping is supported. Acceleration is supported. Supported drivers are A4988, DRV8824, DRV8825, DRV8834, DRV8880. 7 | category=Device Control 8 | url=https://github.com/laurb9/StepperDriver 9 | architectures=* 10 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | src_dir = examples/UnitTest 13 | lib_dir = . 14 | default_envs = 15 | nodemcuv2 16 | adafruit_feather_m0 17 | esp32dev 18 | teensylc 19 | 20 | [env] 21 | framework = arduino 22 | monitor_filters = 23 | colorize 24 | send_on_enter 25 | monitor_speed = 115200 26 | 27 | [env:nodemcuv2] 28 | platform = espressif8266 29 | board = nodemcuv2 30 | upload_speed = 1000000 31 | 32 | [env:adafruit_feather_m0] 33 | platform = atmelsam 34 | board = adafruit_feather_m0 35 | 36 | [env:esp32dev] 37 | board = esp32dev 38 | platform = espressif32 39 | 40 | [env:teensylc] 41 | platform = teensy 42 | board = teensylc 43 | 44 | [env:uno] 45 | board = uno 46 | platform = atmelavr 47 | 48 | [env:native] 49 | platform = native 50 | -------------------------------------------------------------------------------- /src/A4988.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * A4988 - Stepper Motor Driver Driver 3 | * Indexer mode only. 4 | 5 | * Copyright (C)2015 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #include "A4988.h" 11 | 12 | /* 13 | * Microstepping resolution truth table (Page 6 of A4988 pdf) 14 | * 0bMS3,MS2,MS1 for 1,2,4,8,16 microsteps 15 | */ 16 | const uint8_t A4988::MS_TABLE[] = {0b000, 0b001, 0b010, 0b011, 0b111}; 17 | 18 | /* 19 | * Basic connection: only DIR, STEP are connected. 20 | * Microstepping controls should be hardwired. 21 | */ 22 | A4988::A4988(short steps, short dir_pin, short step_pin) 23 | :BasicStepperDriver(steps, dir_pin, step_pin) 24 | {} 25 | 26 | A4988::A4988(short steps, short dir_pin, short step_pin, short enable_pin) 27 | :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin) 28 | {} 29 | 30 | /* 31 | * Fully wired. 32 | * All the necessary control pins for A4988 are connected. 33 | */ 34 | A4988::A4988(short steps, short dir_pin, short step_pin, short ms1_pin, short ms2_pin, short ms3_pin) 35 | :BasicStepperDriver(steps, dir_pin, step_pin), 36 | ms1_pin(ms1_pin), ms2_pin(ms2_pin), ms3_pin(ms3_pin) 37 | {} 38 | 39 | A4988::A4988(short steps, short dir_pin, short step_pin, short enable_pin, short ms1_pin, short ms2_pin, short ms3_pin) 40 | :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), 41 | ms1_pin(ms1_pin), ms2_pin(ms2_pin), ms3_pin(ms3_pin) 42 | {} 43 | 44 | void A4988::begin(float rpm, short microsteps){ 45 | BasicStepperDriver::begin(rpm, microsteps); 46 | 47 | if (!IS_CONNECTED(ms1_pin) || !IS_CONNECTED(ms2_pin) || !IS_CONNECTED(ms3_pin)){ 48 | return; 49 | } 50 | 51 | pinMode(ms1_pin, OUTPUT); 52 | pinMode(ms2_pin, OUTPUT); 53 | pinMode(ms3_pin, OUTPUT); 54 | } 55 | 56 | /* 57 | * Set microstepping mode (1:divisor) 58 | * Allowed ranges for A4988 are 1:1 to 1:16 59 | * If the control pins are not connected, we recalculate the timing only 60 | */ 61 | short A4988::setMicrostep(short microsteps){ 62 | BasicStepperDriver::setMicrostep(microsteps); 63 | 64 | if (!IS_CONNECTED(ms1_pin) || !IS_CONNECTED(ms2_pin) || !IS_CONNECTED(ms3_pin)){ 65 | return this->microsteps; 66 | } 67 | 68 | const uint8_t* ms_table = getMicrostepTable(); 69 | size_t ms_table_size = getMicrostepTableSize(); 70 | 71 | unsigned short i = 0; 72 | while (i < ms_table_size){ 73 | if (this->microsteps & (1<microsteps; 83 | } 84 | 85 | const uint8_t* A4988::getMicrostepTable(){ 86 | return A4988::MS_TABLE; 87 | } 88 | 89 | size_t A4988::getMicrostepTableSize(){ 90 | return sizeof(A4988::MS_TABLE); 91 | } 92 | 93 | short A4988::getMaxMicrostep(){ 94 | return A4988::MAX_MICROSTEP; 95 | } 96 | -------------------------------------------------------------------------------- /src/A4988.h: -------------------------------------------------------------------------------- 1 | /* 2 | * A4988 - Stepper Motor Driver Driver 3 | * Indexer mode only. 4 | * 5 | * Copyright (C)2015 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #ifndef A4988_H 11 | #define A4988_H 12 | #include 13 | #include "BasicStepperDriver.h" 14 | 15 | class A4988 : public BasicStepperDriver { 16 | protected: 17 | static const uint8_t MS_TABLE[]; 18 | short ms1_pin = PIN_UNCONNECTED; 19 | short ms2_pin = PIN_UNCONNECTED; 20 | short ms3_pin = PIN_UNCONNECTED; 21 | // tA STEP minimum, HIGH pulse width (1us) 22 | static const int step_high_min = 1; 23 | // tB STEP minimum, LOW pulse width (1us) 24 | static const int step_low_min = 1; 25 | // wakeup time, nSLEEP inactive to STEP (1000us) 26 | static const int wakeup_time = 1000; 27 | // also 200ns between ENBL/DIR/MSx changes and STEP HIGH 28 | 29 | // Get the microstep table 30 | virtual const uint8_t* getMicrostepTable(); 31 | virtual size_t getMicrostepTableSize(); 32 | 33 | // Get max microsteps supported by the device 34 | short getMaxMicrostep() override; 35 | 36 | private: 37 | // microstep range (1, 16, 32 etc) 38 | static const short MAX_MICROSTEP = 16; 39 | 40 | public: 41 | /* 42 | * Basic connection: only DIR, STEP are connected. 43 | * Microstepping controls should be hardwired. 44 | */ 45 | A4988(short steps, short dir_pin, short step_pin); 46 | A4988(short steps, short dir_pin, short step_pin, short enable_pin); 47 | 48 | void begin(float rpm=60, short microsteps=1); 49 | /* 50 | * Fully wired. All the necessary control pins for A4988 are connected. 51 | */ 52 | A4988(short steps, short dir_pin, short step_pin, short ms1_pin, short ms2_pin, short ms3_pin); 53 | A4988(short steps, short dir_pin, short step_pin, short enable_pin, short ms1_pin, short ms2_pin, short ms3_pin); 54 | short setMicrostep(short microsteps) override; 55 | }; 56 | #endif // A4988_H 57 | -------------------------------------------------------------------------------- /src/BasicStepperDriver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Generic Stepper Motor Driver Driver 3 | * Indexer mode only. 4 | 5 | * Copyright (C)2015-2019 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | * 10 | * Linear speed profile calculations based on 11 | * - Generating stepper-motor speed profiles in real time - David Austin, 2004 12 | * - Atmel AVR446: Linear speed control of stepper motor, 2006 13 | */ 14 | #include "BasicStepperDriver.h" 15 | 16 | 17 | /* 18 | * Min/Max functions which avoid evaluating the arguments multiple times. 19 | * See also https://github.com/arduino/Arduino/issues/2069 20 | */ 21 | template 22 | constexpr const T& stepperMin(const T& a, const T& b) 23 | { 24 | return b < a ? b : a; 25 | } 26 | 27 | template 28 | constexpr const T& stepperMax(const T& a, const T& b) 29 | { 30 | return a < b ? b : a; 31 | } 32 | 33 | /* 34 | * Basic connection: only DIR, STEP are connected. 35 | * Microstepping controls should be hardwired. 36 | */ 37 | BasicStepperDriver::BasicStepperDriver(short steps, short dir_pin, short step_pin) 38 | :BasicStepperDriver(steps, dir_pin, step_pin, PIN_UNCONNECTED) 39 | { 40 | } 41 | 42 | BasicStepperDriver::BasicStepperDriver(short steps, short dir_pin, short step_pin, short enable_pin) 43 | :motor_steps(steps), dir_pin(dir_pin), step_pin(step_pin), enable_pin(enable_pin) 44 | { 45 | steps_to_cruise = 0; 46 | steps_remaining = 0; 47 | dir_state = 0; 48 | steps_to_brake = 0; 49 | step_pulse = 0; 50 | cruise_step_pulse = 0; 51 | rest = 0; 52 | step_count = 0; 53 | } 54 | 55 | /* 56 | * Initialize pins, calculate timings etc 57 | */ 58 | void BasicStepperDriver::begin(float rpm, short microsteps){ 59 | pinMode(dir_pin, OUTPUT); 60 | digitalWrite(dir_pin, HIGH); 61 | 62 | pinMode(step_pin, OUTPUT); 63 | digitalWrite(step_pin, LOW); 64 | 65 | if IS_CONNECTED(enable_pin){ 66 | pinMode(enable_pin, OUTPUT); 67 | disable(); 68 | } 69 | 70 | this->rpm = rpm; 71 | setMicrostep(microsteps); 72 | 73 | enable(); 74 | } 75 | 76 | /* 77 | * Set target motor RPM (1-200 is a reasonable range) 78 | */ 79 | void BasicStepperDriver::setRPM(float rpm){ 80 | if (this->rpm == 0){ // begin() has not been called (old 1.0 code) 81 | begin(rpm, microsteps); 82 | } 83 | this->rpm = rpm; 84 | } 85 | 86 | /* 87 | * Set stepping mode (1:microsteps) 88 | * Allowed ranges for BasicStepperDriver are 1:1 to 1:128 89 | */ 90 | short BasicStepperDriver::setMicrostep(short microsteps){ 91 | for (short ms=1; ms <= getMaxMicrostep(); ms<<=1){ 92 | if (microsteps == ms){ 93 | this->microsteps = microsteps; 94 | break; 95 | } 96 | } 97 | return this->microsteps; 98 | } 99 | 100 | /* 101 | * Set speed profile - CONSTANT_SPEED, LINEAR_SPEED (accelerated) 102 | * accel and decel are given in [full steps/s^2] 103 | */ 104 | void BasicStepperDriver::setSpeedProfile(Mode mode, short accel, short decel){ 105 | profile.mode = mode; 106 | profile.accel = accel; 107 | profile.decel = decel; 108 | } 109 | void BasicStepperDriver::setSpeedProfile(struct Profile profile){ 110 | this->profile = profile; 111 | } 112 | 113 | /* 114 | * Move the motor a given number of steps. 115 | * positive to move forward, negative to reverse 116 | */ 117 | void BasicStepperDriver::move(long steps){ 118 | startMove(steps); 119 | while (nextAction()); 120 | } 121 | /* 122 | * Move the motor a given number of degrees (1-360) 123 | */ 124 | void BasicStepperDriver::rotate(long deg){ 125 | move(calcStepsForRotation(deg)); 126 | } 127 | /* 128 | * Move the motor with sub-degree precision. 129 | * Note that using this function even once will add 1K to your program size 130 | * due to inclusion of float support. 131 | */ 132 | void BasicStepperDriver::rotate(double deg){ 133 | move(calcStepsForRotation(deg)); 134 | } 135 | 136 | /* 137 | * Set up a new move (calculate and save the parameters) 138 | */ 139 | void BasicStepperDriver::startMove(long steps, long time){ 140 | float speed; 141 | // set up new move 142 | dir_state = (steps >= 0) ? HIGH : LOW; 143 | last_action_end = 0; 144 | steps_remaining = labs(steps); 145 | step_count = 0; 146 | rest = 0; 147 | switch (profile.mode){ 148 | case LINEAR_SPEED: 149 | // speed is in [steps/s] 150 | speed = rpm * motor_steps / 60; 151 | if (time > 0){ 152 | // Calculate a new speed to finish in the time requested 153 | float t = time / (1e+6); // convert to seconds 154 | float d = steps_remaining / microsteps; // convert to full steps 155 | float a2 = 1.0 / profile.accel + 1.0 / profile.decel; 156 | float sqrt_candidate = t*t - 2 * a2 * d; // in √b^2-4ac 157 | if (sqrt_candidate >= 0){ 158 | speed = stepperMin(speed, (t - (float)sqrt(sqrt_candidate)) / a2); 159 | }; 160 | } 161 | // how many microsteps from 0 to target speed 162 | steps_to_cruise = microsteps * (speed * speed / (2 * profile.accel)); 163 | // how many microsteps are needed from cruise speed to a full stop 164 | steps_to_brake = steps_to_cruise * profile.accel / profile.decel; 165 | if (steps_remaining < steps_to_cruise + steps_to_brake){ 166 | // cannot reach max speed, will need to brake early 167 | steps_to_cruise = steps_remaining * profile.decel / (profile.accel + profile.decel); 168 | steps_to_brake = steps_remaining - steps_to_cruise; 169 | } 170 | // Initial pulse (c0) including error correction factor 0.676 [us] 171 | step_pulse = (1e+6)*0.676*sqrt(2.0f/profile.accel/microsteps); 172 | // Save cruise timing since we will no longer have the calculated target speed later 173 | cruise_step_pulse = 1e+6 / speed / microsteps; 174 | break; 175 | 176 | case CONSTANT_SPEED: 177 | default: 178 | steps_to_cruise = 0; 179 | steps_to_brake = 0; 180 | step_pulse = cruise_step_pulse = STEP_PULSE(motor_steps, microsteps, rpm); 181 | if (time > steps_remaining * step_pulse){ 182 | step_pulse = (float)time / steps_remaining; 183 | } 184 | } 185 | } 186 | /* 187 | * Alter a running move by adding/removing steps 188 | * FIXME: This is a naive implementation and it only works well in CRUISING state 189 | */ 190 | void BasicStepperDriver::alterMove(long steps){ 191 | switch (getCurrentState()){ 192 | case ACCELERATING: // this also works but will keep the original speed target 193 | case CRUISING: 194 | if (steps >= 0){ 195 | steps_remaining += steps; 196 | } else { 197 | steps_remaining = stepperMax(steps_to_brake, steps_remaining+steps); 198 | }; 199 | break; 200 | case DECELERATING: 201 | // would need to start accelerating again -- NOT IMPLEMENTED 202 | break; 203 | case STOPPED: 204 | startMove(steps); 205 | break; 206 | } 207 | } 208 | /* 209 | * Brake early. 210 | */ 211 | void BasicStepperDriver::startBrake(void){ 212 | switch (getCurrentState()){ 213 | case CRUISING: // this applies to both CONSTANT_SPEED and LINEAR_SPEED modes 214 | steps_remaining = steps_to_brake; 215 | break; 216 | 217 | case ACCELERATING: 218 | steps_remaining = step_count * profile.accel / profile.decel; 219 | break; 220 | 221 | default: 222 | break; // nothing to do if already stopped or braking 223 | } 224 | } 225 | /* 226 | * Stop movement immediately and return remaining steps. 227 | */ 228 | long BasicStepperDriver::stop(void){ 229 | long retval = steps_remaining; 230 | steps_remaining = 0; 231 | return retval; 232 | } 233 | /* 234 | * Return calculated time to complete the given move 235 | */ 236 | long BasicStepperDriver::getTimeForMove(long steps){ 237 | float t; 238 | long cruise_steps; 239 | float speed; 240 | if (steps == 0){ 241 | return 0; 242 | } 243 | switch (profile.mode){ 244 | case LINEAR_SPEED: 245 | startMove(steps); 246 | cruise_steps = steps_remaining - steps_to_cruise - steps_to_brake; 247 | speed = rpm * motor_steps / 60; // full steps/s 248 | t = (cruise_steps / (microsteps * speed)) + 249 | sqrt(2.0 * steps_to_cruise / profile.accel / microsteps) + 250 | sqrt(2.0 * steps_to_brake / profile.decel / microsteps); 251 | t *= (1e+6); // seconds -> micros 252 | break; 253 | case CONSTANT_SPEED: 254 | default: 255 | t = steps * STEP_PULSE(motor_steps, microsteps, rpm); 256 | } 257 | return round(t); 258 | } 259 | /* 260 | * Move the motor an integer number of degrees (360 = full rotation) 261 | * This has poor precision for small amounts, since step is usually 1.8deg 262 | */ 263 | void BasicStepperDriver::startRotate(long deg){ 264 | startMove(calcStepsForRotation(deg)); 265 | } 266 | /* 267 | * Move the motor with sub-degree precision. 268 | * Note that calling this function will increase program size substantially 269 | * due to inclusion of float support. 270 | */ 271 | void BasicStepperDriver::startRotate(double deg){ 272 | startMove(calcStepsForRotation(deg)); 273 | } 274 | 275 | /* 276 | * calculate the interval til the next pulse 277 | */ 278 | void BasicStepperDriver::calcStepPulse(void){ 279 | if (steps_remaining <= 0){ // this should not happen, but avoids strange calculations 280 | return; 281 | } 282 | steps_remaining--; 283 | step_count++; 284 | 285 | if (profile.mode == LINEAR_SPEED){ 286 | switch (getCurrentState()){ 287 | case ACCELERATING: 288 | if (step_count < steps_to_cruise){ 289 | step_pulse = step_pulse - (2*step_pulse+rest)/(4*step_count+1); 290 | rest = (2*step_pulse+rest) % (4*step_count+1); 291 | } else { 292 | // The series approximates target, set the final value to what it should be instead 293 | step_pulse = cruise_step_pulse; 294 | rest = 0; 295 | } 296 | break; 297 | 298 | case DECELERATING: 299 | step_pulse = step_pulse - (2*step_pulse+rest)/(-4*steps_remaining+1); 300 | rest = (2*step_pulse+rest) % (-4*steps_remaining+1); 301 | break; 302 | 303 | default: 304 | break; // no speed changes 305 | } 306 | } 307 | } 308 | /* 309 | * Yield to step control 310 | * Toggle step and return time until next change is needed (micros) 311 | */ 312 | long BasicStepperDriver::nextAction(void){ 313 | if (steps_remaining > 0){ 314 | delayMicros(next_action_interval, last_action_end); 315 | /* 316 | * DIR pin is sampled on rising STEP edge, so it is set first 317 | */ 318 | digitalWrite(dir_pin, dir_state); 319 | digitalWrite(step_pin, HIGH); 320 | unsigned m = micros(); 321 | unsigned long pulse = step_pulse; // save value because calcStepPulse() will overwrite it 322 | calcStepPulse(); 323 | // We should pull HIGH for at least 1-2us (step_high_min) 324 | delayMicros(step_high_min); 325 | digitalWrite(step_pin, LOW); 326 | // account for calcStepPulse() execution time; sets ceiling for max rpm on slower MCUs 327 | last_action_end = micros(); 328 | m = last_action_end - m; 329 | next_action_interval = (pulse > m) ? pulse - m : 1; 330 | } else { 331 | // end of move 332 | last_action_end = 0; 333 | next_action_interval = 0; 334 | } 335 | return next_action_interval; 336 | } 337 | 338 | enum BasicStepperDriver::State BasicStepperDriver::getCurrentState(void){ 339 | enum State state; 340 | if (steps_remaining <= 0){ 341 | state = STOPPED; 342 | } else { 343 | if (steps_remaining <= steps_to_brake){ 344 | state = DECELERATING; 345 | } else if (step_count <= steps_to_cruise){ 346 | state = ACCELERATING; 347 | } else { 348 | state = CRUISING; 349 | } 350 | } 351 | return state; 352 | } 353 | /* 354 | * Configure which logic state on ENABLE pin means active 355 | * when using SLEEP (default) this is active HIGH 356 | */ 357 | void BasicStepperDriver::setEnableActiveState(short state){ 358 | enable_active_state = state; 359 | } 360 | /* 361 | * Enable/Disable the motor by setting a digital flag 362 | */ 363 | void BasicStepperDriver::enable(void){ 364 | if IS_CONNECTED(enable_pin){ 365 | digitalWrite(enable_pin, enable_active_state); 366 | }; 367 | delayMicros(2); 368 | } 369 | 370 | void BasicStepperDriver::disable(void){ 371 | if IS_CONNECTED(enable_pin){ 372 | digitalWrite(enable_pin, (enable_active_state == HIGH) ? LOW : HIGH); 373 | } 374 | } 375 | 376 | short BasicStepperDriver::getMaxMicrostep(){ 377 | return BasicStepperDriver::MAX_MICROSTEP; 378 | } 379 | -------------------------------------------------------------------------------- /src/BasicStepperDriver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generic Stepper Motor Driver Driver 3 | * Indexer mode only. 4 | * 5 | * Copyright (C)2015-2018 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #ifndef STEPPER_DRIVER_BASE_H 11 | #define STEPPER_DRIVER_BASE_H 12 | #include 13 | 14 | // used internally by the library to mark unconnected pins 15 | #define PIN_UNCONNECTED -1 16 | #define IS_CONNECTED(pin) (pin != PIN_UNCONNECTED) 17 | 18 | /* 19 | * calculate the step pulse in microseconds for a given rpm value. 20 | * 60[s/min] * 1000000[us/s] / microsteps / steps / rpm 21 | */ 22 | #define STEP_PULSE(steps, microsteps, rpm) (60.0*1000000L/steps/microsteps/rpm) 23 | 24 | // don't call yield if we have a wait shorter than this 25 | #define MIN_YIELD_MICROS 50 26 | 27 | /* 28 | * Basic Stepper Driver class. 29 | * Microstepping level should be externally controlled or hardwired. 30 | */ 31 | class BasicStepperDriver { 32 | public: 33 | enum Mode {CONSTANT_SPEED, LINEAR_SPEED}; 34 | enum State {STOPPED, ACCELERATING, CRUISING, DECELERATING}; 35 | struct Profile { 36 | Mode mode = CONSTANT_SPEED; 37 | short accel = 1000; // acceleration [steps/s^2] 38 | short decel = 1000; // deceleration [steps/s^2] 39 | }; 40 | static inline void delayMicros(unsigned long delay_us, unsigned long start_us = 0){ 41 | if (delay_us){ 42 | if (!start_us){ 43 | start_us = micros(); 44 | } 45 | if (delay_us > MIN_YIELD_MICROS){ 46 | yield(); 47 | } 48 | // See https://www.gammon.com.au/millis 49 | while (micros() - start_us < delay_us); 50 | } 51 | } 52 | 53 | private: 54 | // calculation remainder to be fed into successive steps to increase accuracy (Atmel DOC8017) 55 | long rest; 56 | unsigned long last_action_end = 0; 57 | unsigned long next_action_interval = 0; 58 | 59 | protected: 60 | /* 61 | * Motor Configuration 62 | */ 63 | short motor_steps; // motor steps per revolution (usually 200) 64 | 65 | /* 66 | * Driver Configuration 67 | */ 68 | short dir_pin; 69 | short step_pin; 70 | short enable_pin = PIN_UNCONNECTED; 71 | short enable_active_state = HIGH; 72 | // Get max microsteps supported by the device 73 | virtual short getMaxMicrostep(); 74 | // current microstep level (1,2,4,8,...), must be < getMaxMicrostep() 75 | short microsteps = 1; 76 | // tWH(STEP) pulse duration, STEP high, min value (us) 77 | static const int step_high_min = 1; 78 | // tWL(STEP) pulse duration, STEP low, min value (us) 79 | static const int step_low_min = 1; 80 | // tWAKE wakeup time, nSLEEP inactive to STEP (us) 81 | static const int wakeup_time = 0; 82 | 83 | float rpm = 0; 84 | 85 | /* 86 | * Movement state 87 | */ 88 | struct Profile profile; 89 | 90 | long step_count; // current position 91 | long steps_remaining; // to complete the current move (absolute value) 92 | long steps_to_cruise; // steps to reach cruising (max) rpm 93 | long steps_to_brake; // steps needed to come to a full stop 94 | long step_pulse; // step pulse duration (microseconds) 95 | long cruise_step_pulse; // step pulse duration for constant speed section (max rpm) 96 | 97 | // DIR pin state 98 | short dir_state; 99 | 100 | void calcStepPulse(void); 101 | 102 | // this is internal because one can call the start methods while CRUISING to get here 103 | void alterMove(long steps); 104 | 105 | private: 106 | // microstep range (1, 16, 32 etc) 107 | static const short MAX_MICROSTEP = 128; 108 | 109 | public: 110 | /* 111 | * Basic connection: DIR, STEP are connected. 112 | */ 113 | BasicStepperDriver(short steps, short dir_pin, short step_pin); 114 | BasicStepperDriver(short steps, short dir_pin, short step_pin, short enable_pin); 115 | /* 116 | * Initialize pins, calculate timings etc 117 | */ 118 | void begin(float rpm=60, short microsteps=1); 119 | /* 120 | * Set current microstep level, 1=full speed, 32=fine microstepping 121 | * Returns new level or previous level if value out of range 122 | */ 123 | virtual short setMicrostep(short microsteps); 124 | short getMicrostep(void){ 125 | return microsteps; 126 | } 127 | short getSteps(void){ 128 | return motor_steps; 129 | } 130 | /* 131 | * Set target motor RPM (1-200 is a reasonable range) 132 | */ 133 | void setRPM(float rpm); 134 | float getRPM(void){ 135 | return rpm; 136 | }; 137 | float getCurrentRPM(void){ 138 | return (60.0*1000000L / step_pulse / microsteps / motor_steps); 139 | } 140 | /* 141 | * Set speed profile - CONSTANT_SPEED, LINEAR_SPEED (accelerated) 142 | * accel and decel are given in [full steps/s^2] 143 | */ 144 | void setSpeedProfile(Mode mode, short accel=1000, short decel=1000); 145 | void setSpeedProfile(struct Profile profile); 146 | struct Profile getSpeedProfile(void){ 147 | return profile; 148 | } 149 | short getAcceleration(void){ 150 | return profile.accel; 151 | } 152 | short getDeceleration(void){ 153 | return profile.decel; 154 | } 155 | /* 156 | * Move the motor a given number of steps. 157 | * positive to move forward, negative to reverse 158 | */ 159 | void move(long steps); 160 | /* 161 | * Rotate the motor a given number of degrees (1-360) 162 | */ 163 | void rotate(long deg); 164 | inline void rotate(int deg){ 165 | rotate((long)deg); 166 | }; 167 | /* 168 | * Rotate using a float or double for increased movement precision. 169 | */ 170 | void rotate(double deg); 171 | /* 172 | * Configure which logic state on ENABLE pin means active 173 | * when using SLEEP (default) this is active HIGH 174 | */ 175 | void setEnableActiveState(short state); 176 | /* 177 | * Turn off/on motor to allow the motor to be moved by hand/hold the position in place 178 | */ 179 | virtual void enable(void); 180 | virtual void disable(void); 181 | /* 182 | * Methods for non-blocking mode. 183 | * They use more code but allow doing other operations between impulses. 184 | * The flow has two parts - start/initiate followed by looping with nextAction. 185 | * See NonBlocking example. 186 | */ 187 | /* 188 | * Initiate a move over known distance (calculate and save the parameters) 189 | * Pick just one based on move type and distance type. 190 | * If time (microseconds) is given, the driver will attempt to execute the move in exactly that time 191 | * by altering rpm for this move only (up to preset rpm). 192 | */ 193 | void startMove(long steps, long time=0); 194 | inline void startRotate(int deg){ 195 | startRotate((long)deg); 196 | }; 197 | void startRotate(long deg); 198 | void startRotate(double deg); 199 | /* 200 | * Toggle step at the right time and return time until next change is needed (micros) 201 | */ 202 | long nextAction(void); 203 | /* 204 | * Optionally, call this to begin braking (and then stop) early 205 | * For constant speed, this is the same as stop() 206 | */ 207 | void startBrake(void); 208 | /* 209 | * Immediate stop 210 | * Returns the number of steps remaining. 211 | */ 212 | long stop(void); 213 | /* 214 | * State querying 215 | */ 216 | enum State getCurrentState(void); 217 | /* 218 | * Get the number of completed steps so far. 219 | * This is always a positive number 220 | */ 221 | long getStepsCompleted(void){ 222 | return step_count; 223 | } 224 | /* 225 | * Get the number of steps remaining to complete the move 226 | * This is always a positive number 227 | */ 228 | long getStepsRemaining(void){ 229 | return steps_remaining; 230 | } 231 | /* 232 | * Get movement direction: forward +1, back -1 233 | */ 234 | int getDirection(void){ 235 | return (dir_state == HIGH) ? 1 : -1; 236 | } 237 | /* 238 | * Return calculated time to complete the given move 239 | */ 240 | long getTimeForMove(long steps); 241 | /* 242 | * Calculate steps needed to rotate requested angle, given in degrees 243 | */ 244 | long calcStepsForRotation(long deg){ 245 | return deg * motor_steps * (long)microsteps / 360; 246 | } 247 | long calcStepsForRotation(double deg){ 248 | return deg * motor_steps * microsteps / 360; 249 | } 250 | }; 251 | #endif // STEPPER_DRIVER_BASE_H 252 | -------------------------------------------------------------------------------- /src/DRV8825.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * DRV8825 - Stepper Motor Driver Driver 3 | * Indexer mode only. 4 | 5 | * Copyright (C)2015 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #include "DRV8825.h" 11 | 12 | /* 13 | * Microstepping resolution truth table (Page 13 of DRV8825 pdf) 14 | * 0bMODE2,MODE1,MODE0 for 1,2,4,8,16,32 microsteps 15 | */ 16 | const uint8_t DRV8825::MS_TABLE[] = {0b000, 0b001, 0b010, 0b011, 0b100, 0b111}; 17 | 18 | DRV8825::DRV8825(short steps, short dir_pin, short step_pin) 19 | :A4988(steps, dir_pin, step_pin) 20 | {} 21 | 22 | DRV8825::DRV8825(short steps, short dir_pin, short step_pin, short enable_pin) 23 | :A4988(steps, dir_pin, step_pin, enable_pin) 24 | {} 25 | 26 | /* 27 | * A4988-DRV8825 Compatibility map: MS1-MODE0, MS2-MODE1, MS3-MODE2 28 | */ 29 | DRV8825::DRV8825(short steps, short dir_pin, short step_pin, short mode0_pin, short mode1_pin, short mode2_pin) 30 | :A4988(steps, dir_pin, step_pin, mode0_pin, mode1_pin, mode2_pin) 31 | {} 32 | 33 | DRV8825::DRV8825(short steps, short dir_pin, short step_pin, short enable_pin, short mode0_pin, short mode1_pin, short mode2_pin) 34 | :A4988(steps, dir_pin, step_pin, enable_pin, mode0_pin, mode1_pin, mode2_pin) 35 | {} 36 | 37 | const uint8_t* DRV8825::getMicrostepTable() 38 | { 39 | return (uint8_t*)DRV8825::MS_TABLE; 40 | } 41 | 42 | size_t DRV8825::getMicrostepTableSize() 43 | { 44 | return sizeof(DRV8825::MS_TABLE); 45 | } 46 | 47 | short DRV8825::getMaxMicrostep(){ 48 | return DRV8825::MAX_MICROSTEP; 49 | } 50 | -------------------------------------------------------------------------------- /src/DRV8825.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DRV8825 - Stepper Motor Driver Driver (A4988-compatible) 3 | * Indexer mode only. 4 | * 5 | * Copyright (C)2015 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #ifndef DRV8825_H 11 | #define DRV8825_H 12 | #include 13 | #include "A4988.h" 14 | 15 | class DRV8825 : public A4988 { 16 | protected: 17 | static const uint8_t MS_TABLE[]; 18 | // tWH(STEP) pulse duration, STEP high, min value (1.9us) 19 | static const int step_high_min = 2; 20 | // tWL(STEP) pulse duration, STEP low, min value (1.9us) 21 | static const int step_low_min = 2; 22 | // tWAKE wakeup time, nSLEEP inactive to STEP (1000us) 23 | static const int wakeup_time = 1700; 24 | // also 650ns between ENBL/DIR/MODEx changes and STEP HIGH 25 | 26 | // Get the microstep table 27 | const uint8_t* getMicrostepTable() override; 28 | size_t getMicrostepTableSize() override; 29 | 30 | // Get max microsteps supported by the device 31 | short getMaxMicrostep() override; 32 | 33 | private: 34 | // microstep range (1, 16, 32 etc) 35 | static const short MAX_MICROSTEP = 32; 36 | 37 | public: 38 | DRV8825(short steps, short dir_pin, short step_pin); 39 | DRV8825(short steps, short dir_pin, short step_pin, short enable_pin); 40 | DRV8825(short steps, short dir_pin, short step_pin, short mode0_pin, short mode1_pin, short mode2_pin); 41 | DRV8825(short steps, short dir_pin, short step_pin, short enable_pin, short mode0_pin, short mode1_pin, short mode2_pin); 42 | }; 43 | #endif // DRV8825_H 44 | -------------------------------------------------------------------------------- /src/DRV8834.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * DRV8834 - LV Stepper Motor Driver Driver (A4988-compatible - mostly) 3 | * Indexer mode only. 4 | 5 | * Copyright (C)2015 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #include "DRV8834.h" 11 | 12 | /* 13 | * Basic connection: only DIR, STEP are connected. 14 | * Microstepping controls should be hardwired. 15 | */ 16 | DRV8834::DRV8834(short steps, short dir_pin, short step_pin) 17 | :BasicStepperDriver(steps, dir_pin, step_pin) 18 | {} 19 | 20 | DRV8834::DRV8834(short steps, short dir_pin, short step_pin, short enable_pin) 21 | :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin) 22 | {} 23 | 24 | /* 25 | * Fully wired. All the necessary control pins for DRV8834 are connected. 26 | */ 27 | DRV8834::DRV8834(short steps, short dir_pin, short step_pin, short m0_pin, short m1_pin) 28 | :BasicStepperDriver(steps, dir_pin, step_pin), m0_pin(m0_pin), m1_pin(m1_pin) 29 | {} 30 | 31 | DRV8834::DRV8834(short steps, short dir_pin, short step_pin, short enable_pin, short m0_pin, short m1_pin) 32 | :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), m0_pin(m0_pin), m1_pin(m1_pin) 33 | {} 34 | 35 | /* 36 | * Set microstepping mode (1:divisor) 37 | * Allowed ranges for DRV8834 are 1:1 to 1:32 38 | * If the control pins are not connected, we recalculate the timing only 39 | * 40 | */ 41 | short DRV8834::setMicrostep(short microsteps){ 42 | BasicStepperDriver::setMicrostep(microsteps); 43 | 44 | if (!IS_CONNECTED(m0_pin) || !IS_CONNECTED(m1_pin)){ 45 | return this->microsteps; 46 | } 47 | 48 | /* 49 | * Step mode truth table 50 | * M1 M0 step mode 51 | * 0 0 1 52 | * 0 1 2 53 | * 0 Z 4 54 | * 1 0 8 55 | * 1 1 16 56 | * 1 Z 32 57 | * 58 | * Z = high impedance mode (M0 is tri-state) 59 | */ 60 | 61 | pinMode(m1_pin, OUTPUT); 62 | digitalWrite(m1_pin, (this->microsteps < 8) ? LOW : HIGH); 63 | 64 | switch(this->microsteps){ 65 | case 1: 66 | case 8: 67 | pinMode(m0_pin, OUTPUT); 68 | digitalWrite(m0_pin, LOW); 69 | break; 70 | case 2: 71 | case 16: 72 | pinMode(m0_pin, OUTPUT); 73 | digitalWrite(m0_pin, HIGH); 74 | break; 75 | case 4: 76 | case 32: 77 | pinMode(m0_pin, INPUT); // Z - high impedance 78 | break; 79 | } 80 | return this->microsteps; 81 | } 82 | 83 | short DRV8834::getMaxMicrostep(){ 84 | return DRV8834::MAX_MICROSTEP; 85 | } 86 | -------------------------------------------------------------------------------- /src/DRV8834.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DRV8834 - LV Stepper Motor Driver Driver (A4988-compatible - mostly) 3 | * Indexer mode only. 4 | * 5 | * Copyright (C)2015 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #ifndef DRV8834_H 11 | #define DRV8834_H 12 | #include 13 | #include "BasicStepperDriver.h" 14 | 15 | class DRV8834 : public BasicStepperDriver { 16 | protected: 17 | short m0_pin = PIN_UNCONNECTED; 18 | short m1_pin = PIN_UNCONNECTED; 19 | // tWH(STEP) pulse duration, STEP high, min value (1.9us) 20 | static const int step_high_min = 2; 21 | // tWL(STEP) pulse duration, STEP low, min value (1.9us) 22 | static const int step_low_min = 2; 23 | // tWAKE wakeup time, nSLEEP inactive to STEP (1000us) 24 | static const int wakeup_time = 1000; 25 | // also 200ns between ENBL/DIR/Mx changes and STEP HIGH 26 | 27 | // Get max microsteps supported by the device 28 | short getMaxMicrostep() override; 29 | 30 | private: 31 | // microstep range (1, 16, 32 etc) 32 | static const short MAX_MICROSTEP = 32; 33 | 34 | public: 35 | /* 36 | * Basic connection: only DIR, STEP are connected. 37 | * Microstepping controls should be hardwired. 38 | */ 39 | DRV8834(short steps, short dir_pin, short step_pin); 40 | DRV8834(short steps, short dir_pin, short step_pin, short enable_pin); 41 | /* 42 | * Fully wired. All the necessary control pins for DRV8834 are connected. 43 | */ 44 | DRV8834(short steps, short dir_pin, short step_pin, short m0_pin, short m1_pin); 45 | DRV8834(short steps, short dir_pin, short step_pin, short enable_pin, short m0_pin, short m1_pin); 46 | short setMicrostep(short microsteps) override; 47 | }; 48 | #endif // DRV8834_H 49 | -------------------------------------------------------------------------------- /src/DRV8880.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * DRV8880 - 2A Stepper Motor Driver with AutoTune and Torque Control 3 | * 4 | * Copyright (C)2017 Laurentiu Badea 5 | * 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | #include "DRV8880.h" 10 | 11 | /* 12 | * Basic connection: only DIR, STEP are connected. 13 | * Microstepping controls should be hardwired. 14 | */ 15 | DRV8880::DRV8880(short steps, short dir_pin, short step_pin) 16 | :BasicStepperDriver(steps, dir_pin, step_pin) 17 | {} 18 | 19 | DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short enable_pin) 20 | :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin) 21 | {} 22 | 23 | /* 24 | * Fully wired. All the necessary control pins for DRV8880 are connected. 25 | */ 26 | DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short m0, short m1) 27 | :BasicStepperDriver(steps, dir_pin, step_pin), m0(m0), m1(m1) 28 | {} 29 | 30 | DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short enable_pin, short m0, short m1) 31 | :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), m0(m0), m1(m1) 32 | {} 33 | 34 | DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short m0, short m1, short trq0, short trq1) 35 | :BasicStepperDriver(steps, dir_pin, step_pin), m0(m0), m1(m1), trq0(trq0), trq1(trq1) 36 | {} 37 | 38 | DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short enable_pin, short m0, short m1, short trq0, short trq1) 39 | :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), m0(m0), m1(m1), trq0(trq0), trq1(trq1) 40 | {} 41 | 42 | void DRV8880::begin(float rpm, short microsteps){ 43 | BasicStepperDriver::begin(rpm, microsteps); 44 | setCurrent(100); 45 | } 46 | 47 | short DRV8880::getMaxMicrostep(){ 48 | return DRV8880::MAX_MICROSTEP; 49 | } 50 | 51 | /* 52 | * Set microstepping mode (1:divisor) 53 | * Allowed ranges for DRV8880 are 1:1 to 1:16 54 | * If the control pins are not connected, we recalculate the timing only 55 | */ 56 | short DRV8880::setMicrostep(short microsteps){ 57 | BasicStepperDriver::setMicrostep(microsteps); 58 | 59 | if (!IS_CONNECTED(m0) || !IS_CONNECTED(m1)){ 60 | return this->microsteps; 61 | } 62 | 63 | /* 64 | * Step mode truth table 65 | * M1 M0 step mode 66 | * 0 0 1 67 | * 1 0 2 68 | * 1 1 4 69 | * 0 Z 8 70 | * 1 Z 16 71 | * 72 | * 0 1 2 (non-circular, not implemented) 73 | * Z = high impedance mode (M0 is tri-state) 74 | */ 75 | 76 | pinMode(m1, OUTPUT); 77 | pinMode(m0, OUTPUT); 78 | switch(this->microsteps){ 79 | case 1: 80 | digitalWrite(m1, LOW); 81 | digitalWrite(m0, LOW); 82 | break; 83 | case 2: 84 | digitalWrite(m1, HIGH); 85 | digitalWrite(m0, LOW); 86 | break; 87 | case 4: 88 | digitalWrite(m1, HIGH); 89 | digitalWrite(m0, HIGH); 90 | break; 91 | case 8: 92 | digitalWrite(m1, LOW); 93 | pinMode(m0, INPUT); // Z - high impedance 94 | break; 95 | case 16: 96 | digitalWrite(m1, HIGH); 97 | pinMode(m0, INPUT); // Z - high impedance 98 | break; 99 | } 100 | return this->microsteps; 101 | } 102 | 103 | void DRV8880::setCurrent(short percent){ 104 | /* 105 | * Torque DAC Settings table 106 | * TRQ1 TRQ0 Current scalar 107 | * 1 1 25% 108 | * 1 0 50% 109 | * 0 1 75% 110 | * 0 0 100% 111 | */ 112 | if (!IS_CONNECTED(trq1) || !IS_CONNECTED(trq0)){ 113 | return; 114 | } 115 | pinMode(trq1, OUTPUT); 116 | pinMode(trq0, OUTPUT); 117 | percent = (100-percent)/25; 118 | digitalWrite(trq1, percent & 2); 119 | digitalWrite(trq0, percent & 1); 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/DRV8880.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DRV8880 - 2A Stepper Motor Driver with AutoTune and Torque Control 3 | * 4 | * Copyright (C)2017 Laurentiu Badea 5 | * 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | #ifndef DRV8880_H 10 | #define DRV8880_H 11 | #include 12 | #include "BasicStepperDriver.h" 13 | 14 | class DRV8880 : public BasicStepperDriver { 15 | protected: 16 | short m0 = PIN_UNCONNECTED; 17 | short m1 = PIN_UNCONNECTED; 18 | short trq0 = PIN_UNCONNECTED; 19 | short trq1 = PIN_UNCONNECTED; 20 | // tWH(STEP) pulse duration, STEP high, min value 21 | static const int step_high_min = 0; // 0.47us 22 | // tWL(STEP) pulse duration, STEP low, min value 23 | static const int step_low_min = 0; // 0.47us 24 | // tWAKE wakeup time, nSLEEP inactive to STEP 25 | static const int wakeup_time = 1500; 26 | // also 200ns between ENBL/DIR/Mx changes and STEP HIGH 27 | 28 | // Get max microsteps supported by the device 29 | short getMaxMicrostep() override; 30 | 31 | private: 32 | // microstep range (1, 16, 32 etc) 33 | static const short MAX_MICROSTEP = 16; 34 | 35 | public: 36 | /* 37 | * Basic connection: only DIR, STEP are connected. 38 | * Microstepping controls should be hardwired. 39 | */ 40 | DRV8880(short steps, short dir_pin, short step_pin); 41 | DRV8880(short steps, short dir_pin, short step_pin, short enable_pin); 42 | /* 43 | * DIR, STEP and microstep control M0, M1 44 | */ 45 | DRV8880(short steps, short dir_pin, short step_pin, short m0, short m1); 46 | DRV8880(short steps, short dir_pin, short step_pin, short enable_pin, short m0, short m1); 47 | /* 48 | * Fully Wired - DIR, STEP, microstep and current control 49 | */ 50 | DRV8880(short steps, short dir_pin, short step_pin, short m0, short m1, short trq0, short trq1); 51 | DRV8880(short steps, short dir_pin, short step_pin, short enable_pin, short m0, short m1, short trq0, short trq1); 52 | 53 | void begin(float rpm=60, short microsteps=1); 54 | 55 | short setMicrostep(short microsteps) override; 56 | 57 | /* 58 | * Torque DAC Control 59 | * current percent value must be 25, 50, 75 or 100. 60 | */ 61 | void setCurrent(short percent=100); 62 | }; 63 | #endif // DRV8880_H 64 | -------------------------------------------------------------------------------- /src/MultiDriver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Multi-motor group driver 3 | * 4 | * Copyright (C)2017-2019 Laurentiu Badea 5 | * 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | #include "MultiDriver.h" 10 | 11 | #define FOREACH_MOTOR(action) for (short i=count-1; i >= 0; i--){action;} 12 | 13 | /* 14 | * Initialize motor parameters 15 | */ 16 | void MultiDriver::startMove(long steps1, long steps2, long steps3){ 17 | long steps[3] = {steps1, steps2, steps3}; 18 | /* 19 | * Initialize state for all active motors 20 | */ 21 | FOREACH_MOTOR( 22 | if (steps[i]){ 23 | motors[i]->startMove(steps[i]); 24 | event_timers[i] = 1; 25 | } else { 26 | event_timers[i] = 0; 27 | } 28 | ); 29 | ready = false; 30 | last_action_end = 0; 31 | next_action_interval = 1; 32 | } 33 | /* 34 | * Trigger next step action 35 | */ 36 | long MultiDriver::nextAction(void){ 37 | Motor::delayMicros(next_action_interval, last_action_end); 38 | 39 | // TODO: unroll these loops 40 | // Trigger all the motors that need it 41 | FOREACH_MOTOR( 42 | if (event_timers[i] <= next_action_interval){ 43 | event_timers[i] = motors[i]->nextAction(); 44 | } else { 45 | event_timers[i] -= next_action_interval; 46 | } 47 | ); 48 | last_action_end = micros(); 49 | 50 | next_action_interval = 0; 51 | // Find the time when the next pulse needs to fire 52 | // this is the smallest non-zero timer value from all active motors 53 | FOREACH_MOTOR( 54 | if (event_timers[i] > 0 && (event_timers[i] < next_action_interval || next_action_interval == 0)){ 55 | next_action_interval = event_timers[i]; 56 | } 57 | ); 58 | ready = (next_action_interval == 0); 59 | 60 | return next_action_interval; 61 | } 62 | /* 63 | * Optionally, call this to begin braking to stop early 64 | */ 65 | void MultiDriver::startBrake(void){ 66 | FOREACH_MOTOR( 67 | if (event_timers[i] > 0){ 68 | motors[i]->startBrake(); 69 | } 70 | ) 71 | } 72 | /* 73 | * Immediate stop 74 | * Returns the number of steps remaining. 75 | */ 76 | MultiDriver::Steps MultiDriver::stop(void){ 77 | Steps retval = Steps(); 78 | FOREACH_MOTOR( 79 | if (event_timers[i] > 0){ 80 | retval.steps[i] = motors[i]->stop(); 81 | } 82 | ) 83 | return retval; 84 | } 85 | /* 86 | * State querying 87 | */ 88 | bool MultiDriver::isRunning(void){ 89 | bool running = false; 90 | FOREACH_MOTOR( 91 | if (motors[i]->getCurrentState() != Motor::STOPPED){ 92 | running = true; 93 | break; 94 | } 95 | ) 96 | return running; 97 | } 98 | 99 | /* 100 | * Initialize pins, calculate timings etc 101 | */ 102 | void MultiDriver::begin(float rpm, short microsteps){ 103 | FOREACH_MOTOR( 104 | motors[i]->begin(rpm, microsteps); 105 | ) 106 | } 107 | 108 | /* 109 | * Move each motor the requested number of steps, in parallel 110 | * positive to move forward, negative to reverse, 0 to remain still 111 | */ 112 | void MultiDriver::move(long steps1, long steps2, long steps3){ 113 | startMove(steps1, steps2, steps3); 114 | while (!ready){ 115 | nextAction(); 116 | } 117 | } 118 | 119 | #define CALC_STEPS(i, deg) ((motors[i] && deg) ? motors[i]->calcStepsForRotation(deg) : 0) 120 | void MultiDriver::rotate(long deg1, long deg2, long deg3){ 121 | move(CALC_STEPS(0, deg1), CALC_STEPS(1, deg2), CALC_STEPS(2, deg3)); 122 | } 123 | 124 | void MultiDriver::rotate(double deg1, double deg2, double deg3){ 125 | move(CALC_STEPS(0, deg1), CALC_STEPS(1, deg2), CALC_STEPS(2, deg3)); 126 | } 127 | 128 | void MultiDriver::startRotate(long deg1, long deg2, long deg3){ 129 | startMove(CALC_STEPS(0, deg1), CALC_STEPS(1, deg2), CALC_STEPS(2, deg3)); 130 | } 131 | 132 | void MultiDriver::startRotate(double deg1, double deg2, double deg3){ 133 | startMove(CALC_STEPS(0, deg1), CALC_STEPS(1, deg2), CALC_STEPS(2, deg3)); 134 | } 135 | 136 | void MultiDriver::setMicrostep(unsigned microsteps){ 137 | FOREACH_MOTOR(motors[i]->setMicrostep(microsteps)); 138 | } 139 | 140 | void MultiDriver::setRPM(float rpm){ 141 | FOREACH_MOTOR(motors[i]->setRPM(rpm)); 142 | } 143 | 144 | void MultiDriver::enable(void){ 145 | FOREACH_MOTOR(motors[i]->enable()); 146 | } 147 | void MultiDriver::disable(void){ 148 | FOREACH_MOTOR(motors[i]->disable()); 149 | } 150 | -------------------------------------------------------------------------------- /src/MultiDriver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Multi-motor group driver 3 | * 4 | * Copyright (C)2017 Laurentiu Badea 5 | * 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | #ifndef MULTI_DRIVER_H 10 | #define MULTI_DRIVER_H 11 | #include 12 | #include "BasicStepperDriver.h" 13 | 14 | #define MAX_MOTORS 3 // a reasonable but arbitrary limit 15 | #define Motor BasicStepperDriver 16 | /* 17 | * Multi-motor group driver class. 18 | */ 19 | class MultiDriver { 20 | protected: 21 | /* 22 | * Configuration 23 | */ 24 | unsigned short count; 25 | Motor* const *motors; 26 | /* 27 | * Generic initializer, will be called by the others 28 | */ 29 | MultiDriver(const unsigned short count, Motor* const *motors) 30 | :count(count), motors(motors) 31 | {}; 32 | 33 | /* 34 | * Movement state 35 | */ 36 | // ready to start a new move 37 | bool ready = true; 38 | // when next state change is due for each motor 39 | unsigned long event_timers[MAX_MOTORS]; 40 | unsigned long next_action_interval = 0; 41 | unsigned long last_action_end = 0; 42 | 43 | public: 44 | struct Steps { 45 | long steps[3]; 46 | }; 47 | /* 48 | * Two-motor setup 49 | */ 50 | MultiDriver(Motor& motor1, Motor& motor2) 51 | :MultiDriver(2, new Motor* const[2]{&motor1, &motor2}) 52 | {}; 53 | /* 54 | * Three-motor setup (X, Y, Z for example) 55 | */ 56 | MultiDriver(Motor& motor1, Motor& motor2, Motor& motor3) 57 | :MultiDriver(3, new Motor* const[3]{&motor1, &motor2, &motor3}) 58 | {}; 59 | unsigned short getCount(void){ 60 | return count; 61 | } 62 | Motor& getMotor(short index){ 63 | return *motors[index]; 64 | } 65 | /* 66 | * Initialize pins, calculate timings etc 67 | */ 68 | void begin(float rpm=60, short microsteps=1); 69 | /* 70 | * Move the motors a given number of steps. 71 | * positive to move forward, negative to reverse 72 | */ 73 | void move(long steps1, long steps2, long steps3=0); 74 | void rotate(int deg1, int deg2, int deg3=0){ 75 | rotate((long)deg1, (long)deg2, (long)deg3); 76 | }; 77 | void rotate(long deg1, long deg2, long deg3=0); 78 | void rotate(double deg1, double deg2, double deg3=0); 79 | 80 | /* 81 | * Motor movement with external control of timing 82 | */ 83 | virtual void startMove(long steps1, long steps2, long steps3=0); 84 | void startRotate(int deg1, int deg2, int deg3=0){ 85 | startRotate((long)deg1, (long)deg2, (long)deg3); 86 | }; 87 | void startRotate(long deg1, long deg2, long deg3=0); 88 | void startRotate(double deg1, double deg2, double deg3=0); 89 | /* 90 | * Toggle step and return time until next change is needed (micros) 91 | */ 92 | virtual long nextAction(void); 93 | /* 94 | * Optionally, call this to begin braking to stop early 95 | */ 96 | void startBrake(void); 97 | /* 98 | * Immediate stop 99 | * Returns the number of steps remaining. 100 | */ 101 | Steps stop(void); 102 | /* 103 | * State querying 104 | */ 105 | bool isRunning(void); 106 | 107 | /* 108 | * Set the same microstepping level on all motors 109 | */ 110 | void setMicrostep(unsigned microsteps); 111 | /* 112 | * Set all motors RPM (1-200 is a reasonable range) 113 | */ 114 | void setRPM(float rpm); 115 | /* 116 | * Turn all motors on or off 117 | */ 118 | void enable(void); 119 | void disable(void); 120 | }; 121 | #endif // MULTI_DRIVER_H 122 | -------------------------------------------------------------------------------- /src/SyncDriver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Synchronous Multi-motor group driver 3 | * All motors reach their target at the same time. 4 | * 5 | * Copyright (C)2017-2019 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #include "SyncDriver.h" 11 | 12 | #define FOREACH_MOTOR(action) for (short i=count-1; i >= 0; i--){action;} 13 | 14 | /* 15 | * Initialize motor parameters 16 | */ 17 | void SyncDriver::startMove(long steps1, long steps2, long steps3){ 18 | long steps[3] = {steps1, steps2, steps3}; 19 | /* 20 | * find which motor would take the longest to finish, 21 | */ 22 | long move_time = 0; 23 | FOREACH_MOTOR( 24 | long m = motors[i]->getTimeForMove(labs(steps[i])); 25 | if (m > move_time){ 26 | move_time = m; 27 | } 28 | ); 29 | /* 30 | * Initialize state for all active motors to complete with micros 31 | */ 32 | FOREACH_MOTOR( 33 | if (steps[i]){ 34 | motors[i]->startMove(steps[i], move_time); 35 | event_timers[i] = 1; 36 | } else { 37 | event_timers[i] = 0; 38 | } 39 | ); 40 | ready = false; 41 | last_action_end = 0; 42 | next_action_interval = 1; 43 | } 44 | -------------------------------------------------------------------------------- /src/SyncDriver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Synchronous Multi-motor group driver 3 | * All motors reach their target at the same time. 4 | * 5 | * Copyright (C)2017-2019 Laurentiu Badea 6 | * 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | #ifndef SYNC_DRIVER_H 11 | #define SYNC_DRIVER_H 12 | #include 13 | #include "MultiDriver.h" 14 | 15 | /* 16 | * Synchronous Multi-motor group driver class. 17 | * This driver sets up timing so all motors reach their target at the same time. 18 | */ 19 | class SyncDriver : public MultiDriver { 20 | using MultiDriver::MultiDriver; 21 | 22 | public: 23 | 24 | void startMove(long steps1, long steps2, long steps3=0) override; 25 | }; 26 | #endif // SYNC_DRIVER_H 27 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------