├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 5-to_do.yml │ ├── 4-documentation_update.yml │ ├── 3-feature_request.yml │ ├── 1-support_request.yml │ ├── 2-bug_report.yml │ └── 6-beta_test_results.yml └── workflows │ ├── build-test-workflow.yml │ └── new-items.yml ├── standard_steppers.h ├── platformio.ini ├── EEPROMFunctions.h ├── README.md ├── IOFunctions.h ├── TurntableFunctions.h ├── EEPROMFunctions.cpp ├── defines.h ├── EX-Turntable.ino ├── version.h ├── config.example.h ├── config.traverser.h ├── IOFunctions.cpp ├── AccelStepper.cpp ├── TurntableFunctions.cpp ├── LICENSE └── AccelStepper.h /.gitignore: -------------------------------------------------------------------------------- 1 | Release/* 2 | .ino.cpp 3 | .pioenvs 4 | .piolibdeps 5 | .clang_complete 6 | .gcc-flags.json 7 | .pio/ 8 | .vscode/ 9 | config.h 10 | .vscode/* 11 | 12 | config.h 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for the template chooser 2 | # 3 | # This file needs to exist in the https://github.com/DCC-EX/.github repository in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | blank_issues_enabled: false 6 | contact_links: 7 | - name: DCC-EX Discord server 8 | url: https://discord.gg/y2sB4Fp 9 | about: For the best support experience, join our Discord server 10 | - name: DCC-EX Contact and Support page 11 | url: https://dcc-ex.com/support/index.html 12 | about: For other support options, refer to our Contact & Support page -------------------------------------------------------------------------------- /.github/workflows/build-test-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Install Python Wheel 13 | run: pip install wheel 14 | - name: Install PlatformIO Core 15 | run: pip install -U platformio 16 | - name: Copy generic turntable config over 17 | run: cp config.example.h config.h 18 | - name: Compile Turntable-EX (turntable mode) 19 | run: python -m platformio run 20 | - name: Copy generic traverser config over 21 | run: cp config.traverser.h config.h 22 | - name: Compile Turntable-EX (traverser mode) 23 | run: python -m platformio run 24 | -------------------------------------------------------------------------------- /standard_steppers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Peter Cole 3 | * 4 | * These are the standard stepper controller and motor definitions. 5 | */ 6 | 7 | #ifndef STANDARD_STEPPERS_h 8 | #define STANDARD_STEPPERS_h 9 | 10 | #include 11 | #include "AccelStepper.h" 12 | 13 | #define UNUSED_PIN 127 14 | 15 | #define FULLSTEPS 4096 16 | 17 | #define ULN2003_HALF_CW AccelStepper(AccelStepper::HALF4WIRE, A3, A1, A2, A0) 18 | #define ULN2003_HALF_CCW AccelStepper(AccelStepper::HALF4WIRE, A0, A2, A1, A3) 19 | #define ULN2003_FULL_CW AccelStepper(AccelStepper::FULL4WIRE, A3, A1, A2, A0) 20 | #define ULN2003_FULL_CCW AccelStepper(AccelStepper::FULL4WIRE, A0, A2, A1, A3) 21 | #define A4988 AccelStepper(AccelStepper::DRIVER, A0, A1) 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/5-to_do.yml: -------------------------------------------------------------------------------- 1 | # General To Do item GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: To Do 6 | description: Create a general To Do item 7 | title: "[To Do]: " 8 | labels: 9 | - To Do 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to create an issue for a general task that needs to be done. 15 | 16 | This is handy for capturing ad-hoc items that don't necessarily require code to be written or updated. 17 | 18 | - type: textarea 19 | id: description 20 | attributes: 21 | label: Task description 22 | description: Provide the details of what needs to be done. 23 | validations: 24 | required: true -------------------------------------------------------------------------------- /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 | default_envs = 13 | nanoatmega328new 14 | uno 15 | src_dir = . 16 | include_dir = . 17 | 18 | [env:nanoatmega328new] 19 | platform = atmelavr 20 | board = nanoatmega328new 21 | framework = arduino 22 | monitor_speed = 115200 23 | monitor_echo = yes 24 | 25 | [env:nanoatmega328] 26 | platform = atmelavr 27 | board = nanoatmega328 28 | framework = arduino 29 | monitor_speed = 115200 30 | monitor_echo = yes 31 | 32 | [env:uno] 33 | platform = atmelavr 34 | board = uno 35 | framework = arduino 36 | monitor_speed = 115200 37 | monitor_echo = yes 38 | -------------------------------------------------------------------------------- /EEPROMFunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * 4 | * This file is part of EX-Turntable 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with EX-Turntable. If not, see . 18 | */ 19 | 20 | #ifndef EEPROMFUNCTIONS_H 21 | #define EEPROMFUNCTIONS_H 22 | 23 | #include 24 | #include "defines.h" 25 | #include "TurntableFunctions.h" 26 | 27 | long getSteps(); 28 | void writeEEPROM(long steps); 29 | void clearEEPROM(); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/4-documentation_update.yml: -------------------------------------------------------------------------------- 1 | # Documentation update GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Documentation Update 6 | description: Submit a request for documentation updates, or to report broken links or inaccuracies 7 | title: "[Documentation Update]: " 8 | labels: 9 | - Needs Documentation 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to submit a request for updates to our documentation. 15 | 16 | This can be used for general documentation requests if information is missing or lacking, or to correct issues with our existing documentation such as broken links, or inaccurate information. 17 | 18 | - type: textarea 19 | id: details 20 | attributes: 21 | label: Documentation details 22 | description: Provide the details of what needs to be documented or corrected. 23 | validations: 24 | required: true 25 | 26 | - type: input 27 | id: page 28 | attributes: 29 | label: Page with issues 30 | description: If reporting broken links or inaccuracies, please provide the link to the page here. 31 | placeholder: https://dcc-ex.com/index.html -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-feature_request.yml: -------------------------------------------------------------------------------- 1 | # Feature Request GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Feature Request 6 | description: Suggest a new feature 7 | title: "[Feature Request]: " 8 | labels: 9 | - Enhancement 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to suggest a new feature for EX-Turntable. 15 | 16 | - type: textarea 17 | id: description 18 | attributes: 19 | label: Problem/idea statement 20 | description: Please provide the problem you're trying to solve, or share the idea you have. 21 | placeholder: A clear and concise description of the problem you're trying to solve, or the idea you have. For example, I'm always frustrated when... 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: alternatives 27 | attributes: 28 | label: Alternatives or workarounds 29 | description: Please provide any alternatives or workarounds you currently use. 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: context 35 | attributes: 36 | label: Additional context 37 | description: Add any other context, screenshots, or files related to the feature request here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-support_request.yml: -------------------------------------------------------------------------------- 1 | # Support Request GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Support Request 6 | description: Request support or assistance 7 | title: "[Support Request]: " 8 | labels: 9 | - Support Request 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to request support or assistance with EX-Turntable. 15 | 16 | For a better support experience, feel free to join our [Discord server](https://discord.gg/y2sB4Fp), as you will typically get a faster response, and will be able to communicate directly with the DCC-EX team. 17 | 18 | - type: input 19 | id: version 20 | attributes: 21 | label: Version 22 | description: Please provide the version of the software in use. 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: description 28 | attributes: 29 | label: Issue description 30 | description: Please describe the issue being encountered as accurately and detailed as possible. 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | id: hardware 36 | attributes: 37 | label: Hardware 38 | description: If appropriate, please provide details of the hardware in use. 39 | placeholder: | 40 | Arduino Nano 41 | ULN2003 stepper controller 42 | 28BYJ-48 stepper motor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EX-Turntable 2 | 3 | This README is a very brief summary, for the full documentation relating to EX-Turntable, please refer to the [DCC-EX Website](https://dcc-ex.com/). 4 | 5 | **AccelStepper.h credit:** This project would not be effective without the excellent work by Mike McCauley on the AccelStepper.h library that enables us to have somewhat prototypical acceleration and deceleration of the turntable. More details on the library can be found on the official [AccelStepper](http://www.airspayce.com/mikem/arduino/AccelStepper/) web page, and we have included the library within the EX-Turntable software so you don't need to find or download it. 6 | 7 | **NmraDcc.h credit:** Also, while not directly used in this software, Alex Shephard's "DCCInterface_TurntableControl" was the inspiration for the initial turntable logic for another DCC driven turntable that translated into the beginnings of EX-Turntable. You can see this code as part of the [NmraDcc Arduino library](https://github.com/mrrwa/NmraDcc). 8 | 9 | EX-Turntable is a fully integrated turntable controller for DCC-EX, using an Arduino microcontroller to drive a stepper controller and motor to rotate a turntable bridge or drive a horizontal or vertical traverser. 10 | 11 | The integration includes: 12 | 13 | - I2C device driver 14 | - EXRAIL automation support 15 | - Interactive commands via the serial console for debugging and testing 16 | - Out-of-the-box support for several common stepper motor drivers 17 | - DCC signal phase switching to align bridge track phase with layout phase 18 | - Operates in either turntable or traverser mode 19 | -------------------------------------------------------------------------------- /IOFunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * 4 | * This file is part of EX-Turntable 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with EX-Turntable. If not, see . 18 | */ 19 | 20 | #ifndef IOFUNCTIONS_H 21 | #define IOFUNCTIONS_H 22 | 23 | #include 24 | #include 25 | #include "defines.h" 26 | #include "TurntableFunctions.h" 27 | #include "EEPROMFunctions.h" 28 | #include "version.h" 29 | 30 | extern bool testCommandSent; // Flag a test command has been sent via serial. 31 | extern uint8_t testActivity; // Activity sent via serial. 32 | extern uint8_t testStepsMSB; 33 | extern uint8_t testStepsLSB; 34 | extern bool debug; 35 | extern bool sensorTesting; 36 | 37 | void setupWire(); 38 | void processSerialInput(); 39 | void serialCommandC(); 40 | void serialCommandD(); 41 | void serialCommandE(); 42 | void serialCommandH(); 43 | void serialCommandM(long steps); 44 | void serialCommandR(); 45 | void serialCommandT(); 46 | void serialCommandV(); 47 | void displayTTEXConfig(); 48 | void receiveEvent(int received); 49 | void requestEvent(); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /TurntableFunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * 4 | * This file is part of EX-Turntable 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with EX-Turntable. If not, see . 18 | */ 19 | 20 | /*============================================================= 21 | * This file contains all functions pertinent to turntable 22 | * operation including stepper movements, relay phase switching, 23 | * and LED/accessory related functions. 24 | =============================================================*/ 25 | 26 | #ifndef TURNTABLEFUNCTIONS_H 27 | #define TURNTABLEFUNCTIONS_H 28 | 29 | #include 30 | #include "defines.h" 31 | #include "AccelStepper.h" 32 | #include "standard_steppers.h" 33 | 34 | extern const long sanitySteps; 35 | extern bool calibrating; 36 | extern uint8_t homed; 37 | extern AccelStepper stepper; 38 | extern long fullTurnSteps; 39 | extern long phaseSwitchStartSteps; 40 | extern long phaseSwitchStopSteps; 41 | extern long lastTarget; 42 | extern bool homeSensorState; 43 | extern bool limitSensorState; 44 | 45 | void startupConfiguration(); 46 | void setupStepperDriver(); 47 | void moveHome(); 48 | void moveToPosition(long steps, uint8_t phaseSwitch); 49 | void setPhase(uint8_t phase); 50 | void processLED(); 51 | void calibration(); 52 | void processAutoPhaseSwitch(); 53 | bool getHomeState(); 54 | bool getLimitState(); 55 | void initiateHoming(); 56 | void initiateCalibration(); 57 | void setLEDActivity(uint8_t activity); 58 | void setAccessory(bool state); 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /EEPROMFunctions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * 4 | * This file is part of EX-Turntable 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with EX-Turntable. If not, see . 18 | */ 19 | 20 | #include "EEPROMFunctions.h" 21 | #include 22 | #include "IOFunctions.h" 23 | 24 | char eepromFlag[4] = {'T', 'T', 'E', 'X'}; // EEPROM location 0 to 3 should contain TTEX if we have stored steps. 25 | const uint8_t eepromVersion = EEPROM_VERSION; // Version of stored EEPROM data to invalidate stored steps if config changes. 26 | 27 | // Function to retrieve step count from EEPROM. 28 | // Looks for identifier "TTEX" at 0 to 3. 29 | // Looks for version in 4. 30 | // MSB -> LSB of steps stored in 5 - 8. 31 | long getSteps() { 32 | char data[4]; 33 | long eepromSteps; 34 | bool stepsSet = true; 35 | for (uint8_t i = 0; i < 4; i ++) { 36 | data[i] = EEPROM.read(i); 37 | if (data[i] != eepromFlag[i]) { 38 | stepsSet = false; 39 | break; 40 | } 41 | } 42 | uint8_t version = EEPROM.read(4); 43 | if (version != eepromVersion) { 44 | Serial.println(F("EEPROM version outdated, calibration required")); 45 | stepsSet = false; 46 | } 47 | if (stepsSet) { 48 | eepromSteps = ((long)EEPROM.read(5) << 24) + ((long)EEPROM.read(6) << 16) + ((long)EEPROM.read(7) << 8) + (long)EEPROM.read(8); 49 | if (eepromSteps <= sanitySteps) { 50 | if (debug) { 51 | Serial.print(F("DEBUG: TTEX steps defined in EEPROM: ")); 52 | Serial.println(eepromSteps); 53 | } 54 | return eepromSteps; 55 | } else { 56 | if (debug) { 57 | Serial.print(F("DEBUG: TTEX steps defined in EEPROM are invalid: ")); 58 | Serial.println(eepromSteps); 59 | } 60 | calibrating = true; 61 | return 0; 62 | } 63 | } else { 64 | if (debug) { 65 | Serial.println(F("DEBUG: TTEX steps not defined in EEPROM")); 66 | } 67 | calibrating = true; 68 | return 0; 69 | } 70 | } 71 | 72 | // Function to write step count with "TTEX" identifier to EEPROM. 73 | void writeEEPROM(long steps) { 74 | (void) EEPROM; 75 | for (uint8_t i = 0; i < 4; i++) { 76 | EEPROM.write(i, eepromFlag[i]); 77 | } 78 | EEPROM.write(4, eepromVersion); 79 | EEPROM.write(5, (steps >> 24) & 0xFF); 80 | EEPROM.write(6, (steps >> 16) & 0xFF); 81 | EEPROM.write(7, (steps >> 8) & 0xFF); 82 | EEPROM.write(8, steps & 0xFF); 83 | } 84 | 85 | // Function to clear step count and identifier from EEPROM. 86 | void clearEEPROM() { 87 | for (uint8_t i = 0; i < 9; i++) { 88 | EEPROM.write(i, 0); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-bug_report.yml: -------------------------------------------------------------------------------- 1 | # Bug report GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Bug Report 6 | description: Submit a bug report 7 | labels: 8 | - Bug 9 | title: "Bug Report: " 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Thanks for taking the time to submit a bug report to the DCC-EX team! 15 | 16 | In order to help us to validate the bug and ascertain what's causing it, please provide as much information as possible in this form. 17 | 18 | - type: input 19 | id: version 20 | attributes: 21 | label: EX-Turntable Version 22 | description: Please provide the version of EX-Turntable in use. 23 | validations: 24 | required: true 25 | 26 | - type: dropdown 27 | id: mode 28 | attributes: 29 | label: EX-Turntable Mode 30 | description: Please provide the mode that EX-Turntable is configured for. 31 | options: 32 | - Turntable 33 | - Traverser 34 | validations: 35 | required: true 36 | 37 | - type: input 38 | id: cs-version 39 | attributes: 40 | label: EX-CommandStation Version 41 | description: Please provide the version of EX-CommandStation in use. 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: description 47 | attributes: 48 | label: Bug description 49 | description: Please provide a clear and concise description of what the symptoms of the bug are. 50 | placeholder: | 51 | When issuing a diagnostic command, the turntable spins erratically and launches itself into the air. 52 | validations: 53 | required: true 54 | 55 | - type: textarea 56 | id: reproduction 57 | attributes: 58 | label: Steps to reproduce the bug 59 | description: Please provide the steps to reproduce the behaviour. 60 | placeholder: | 61 | 1. Turn on EX-Turntable and the CommandStation. 62 | 2. Execute the command ``. 63 | validations: 64 | required: true 65 | 66 | - type: textarea 67 | id: expectation 68 | attributes: 69 | label: Expected behaviour 70 | description: Please provide a clear and concise description of what you expected to happen. 71 | placeholder: | 72 | The turntable should rotate 300 steps from home. 73 | validations: 74 | required: true 75 | 76 | - type: textarea 77 | id: screenshots 78 | attributes: 79 | label: Screenshots 80 | description: If applicable, upload any screenshots here. 81 | 82 | - type: textarea 83 | id: hardware 84 | attributes: 85 | label: Hardware in use 86 | description: Please provide details of hardware in use including microcontroller, stepper details, and any other relevant information. 87 | placeholder: | 88 | Arduino Nano 89 | ULN2003 stepper controller 90 | 28BYJ-48 stepper motor 91 | A3144 hall effect sensor 92 | validations: 93 | required: true 94 | 95 | - type: textarea 96 | id: extra-context 97 | attributes: 98 | label: Additional context 99 | description: Please provide any other relevant information that could help us resolve this issue, for example a customised config.h file. -------------------------------------------------------------------------------- /defines.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * 4 | * This file is part of EX-Turntable 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with EX-Turntable. If not, see . 18 | */ 19 | 20 | #ifndef DEFINES_H 21 | #define DEFINES_H 22 | 23 | // Ensure AUTO and MANUAL phase switching has a value to test. 24 | #define AUTO 1 25 | #define MANUAL 0 26 | 27 | // Ensure TURNTABLE and TRAVERSER modes also have a value to test. 28 | #define TURNTABLE 0 29 | #define TRAVERSER 1 30 | 31 | // If we haven't got a custom config.h, use the example. 32 | #if __has_include ( "config.h") 33 | #include "config.h" 34 | #else 35 | #warning config.h not found. Using defaults from config.example.h 36 | #include "config.example.h" 37 | #endif 38 | 39 | // Define global variables here. 40 | #ifndef TURNTABLE_EX_MODE 41 | #define TURNTABLE_EX_MODE TURNTABLE // If the mode isn't defined, put it in turntable mode. 42 | #endif 43 | 44 | #ifndef STEPPER_MAX_SPEED 45 | #define STEPPER_MAX_SPEED 200 // Set default max speed if not defined. 46 | #endif 47 | 48 | #ifndef STEPPER_ACCELERATION 49 | #define STEPPER_ACCELERATION 25 // Set default acceleration if not defined. 50 | #endif 51 | 52 | #ifndef SANITY_STEPS 53 | #define SANITY_STEPS 10000 // Define sanity steps if not in config.h. 54 | #endif 55 | 56 | #ifndef HOME_SENSITIVITY 57 | #define HOME_SENSITIVITY 300 // Define homing sensitivity if not in config.h. 58 | #endif 59 | 60 | #ifndef PHASE_SWITCHING 61 | #define PHASE_SWITCHING AUTO // Define automatic phase switching if not in config.h 62 | #endif 63 | 64 | #ifndef PHASE_SWITCH_ANGLE 65 | #define PHASE_SWITCH_ANGLE 45 // Define phase switch at 45 degrees if not in config.h 66 | #endif 67 | 68 | #ifndef DEBOUNCE_DELAY // Define debounce delay in ms if not in config.h 69 | #if TURNTABLE_EX_MODE == TRAVERSER 70 | #define DEBOUNCE_DELAY 10 // If we're a traverser, use a delay because switches likely in use 71 | #elif TURNTABLE_EX_MODE == TURNTABLE 72 | #define DEBOUNCE_DELAY 0 // If we're a turntable, use 0 because hall effect sensor likely in use 73 | #endif 74 | #endif 75 | 76 | #ifndef STEPPER_GEARING_FACTOR 77 | #define STEPPER_GEARING_FACTOR 1 // Define the gearing factor to default of 1 if not in config.h 78 | #endif 79 | 80 | // Define current version of EEPROM configuration 81 | #define EEPROM_VERSION 2 82 | 83 | #if defined(ROTATE_FORWARD_ONLY) && defined(ROTATE_REVERSE_ONLY) 84 | #error Both ROTATE_FORWARD_ONLY and ROTATE_REVERSE_ONLY defined, please only define one or the other 85 | #endif 86 | 87 | #if (TURNTABLE_EX_MODE == TRAVERSER && defined(ROTATE_FORWARD_ONLY)) || (TURNTABLE_EX_MODE == TRAVERSER && defined(ROTATE_REVERSE_ONLY)) 88 | #error Traverser mode cannot operate with ROTATE_FORWARD_ONLY or ROTATE_REVERSE_ONLY 89 | #endif 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /EX-Turntable.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * © 2022 Peter Cole 4 | * 5 | * This file is part of EX-Turntable 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with EX-Turntable. If not, see . 19 | */ 20 | 21 | // Include required libraries. 22 | #include 23 | #include "defines.h" 24 | 25 | // Include local files 26 | #include "IOFunctions.h" 27 | #include "TurntableFunctions.h" 28 | 29 | bool lastRunningState; // Stores last running state to allow turning the stepper off after moves. 30 | 31 | void setup() { 32 | // Run startup configuration 33 | startupConfiguration(); 34 | 35 | // Set up the stepper driver 36 | setupStepperDriver(); 37 | 38 | // If we're not sensor testing, start Wire() 39 | if (!sensorTesting) setupWire(); 40 | 41 | // Display EX-Turntable configuration 42 | displayTTEXConfig(); 43 | } 44 | 45 | void loop() { 46 | // If we're only testing sensors, don't do anything else. 47 | if (sensorTesting) { 48 | bool testHomeSensorState = getHomeState(); 49 | if (testHomeSensorState != homeSensorState) { 50 | if (testHomeSensorState == HOME_SENSOR_ACTIVE_STATE) { 51 | Serial.println(F("Home sensor ACTIVATED")); 52 | } else { 53 | Serial.println(F("Home sensor DEACTIVATED")); 54 | } 55 | homeSensorState = testHomeSensorState; 56 | } 57 | getHomeState(); 58 | 59 | #if TURNTABLE_EX_MODE == TRAVERSER 60 | bool testLimitSensorState = getLimitState(); 61 | if (testLimitSensorState != limitSensorState) { 62 | if (testLimitSensorState == LIMIT_SENSOR_ACTIVE_STATE) { 63 | Serial.println(F("Limit sensor ACTIVATED")); 64 | } else { 65 | Serial.println(F("Limit sensor DEACTIVATED")); 66 | } 67 | limitSensorState = testLimitSensorState; 68 | } 69 | #endif 70 | } else { 71 | #if TURNTABLE_EX_MODE == TRAVERSER 72 | // If we hit our limit switch when not calibrating, stop! 73 | if (getLimitState() == LIMIT_SENSOR_ACTIVE_STATE && !calibrating && stepper.isRunning() && stepper.targetPosition() < 0) { 74 | Serial.println(F("ALERT! Limit sensor activitated, halting stepper")); 75 | if (!homed) { 76 | homed = 1; 77 | } 78 | stepper.stop(); 79 | stepper.setCurrentPosition(stepper.currentPosition()); 80 | } 81 | 82 | // If we hit our home switch when not homing, stop! 83 | if (getHomeState() == HOME_SENSOR_ACTIVE_STATE && homed && !calibrating && stepper.isRunning() && stepper.distanceToGo() > 0) { 84 | Serial.println(F("ALERT! Home sensor activitated, halting stepper")); 85 | stepper.stop(); 86 | stepper.setCurrentPosition(0); 87 | } 88 | #endif 89 | 90 | // If we haven't successfully homed yet, do it. 91 | if (homed == 0) { 92 | moveHome(); 93 | } 94 | 95 | // If flag is set for calibrating, do it. 96 | if (calibrating) { 97 | calibration(); 98 | } 99 | 100 | // Process the stepper object continuously. 101 | stepper.run(); 102 | 103 | // Process our LED. 104 | processLED(); 105 | 106 | // If disabling on idle is enabled, disable the stepper. 107 | #if defined(DISABLE_OUTPUTS_IDLE) 108 | if (stepper.isRunning() != lastRunningState) { 109 | lastRunningState = stepper.isRunning(); 110 | if (!lastRunningState) { 111 | stepper.disableOutputs(); 112 | } 113 | } 114 | #endif 115 | } 116 | // Receive and process and serial input for test commands. 117 | processSerialInput(); 118 | } 119 | -------------------------------------------------------------------------------- /version.h: -------------------------------------------------------------------------------- 1 | #ifndef version_h 2 | #define version_h 3 | 4 | #define VERSION "0.7.0" 5 | 6 | // 0.7.0: 7 | // - Fix bug where enabling sensor testing prevents compiling 8 | // - Add interactive serial command C to initiate calibration 9 | // - Add interactive serial command D to enable/disable debug output in serial console 10 | // - Add interactive serial command E to erase calibration steps stored in EEPROM 11 | // - Add interactive serial command H to initiate homing 12 | // - Change movement command from to 13 | // - Add interactive serial command R to reset the EX-Turntable microcontroller 14 | // - Add interactive serial command T to enable/disable sensor testing mode 15 | // - Add interactive serial command V to display version and other system info 16 | // - Add ability to force single direction rotation only for steppers with a lot of slop 17 | // - Update AccelStepper library to v1.64 and remove need for modification 18 | // - Enable inversion of direction, steps, and enable via config.h defines 19 | // 0.6.0: 20 | // - Rewrite into multiple files 21 | // - Convert steps to long to match AccelStepper and cater for large step counts 22 | // - Add gearing factor to cope with steps per revolution larger than 32767 23 | // 0.5.1-Beta: 24 | // - Corrected AccelStepper.cpp modification to apply to DRIVER instead of FULL2WIRE. 25 | // 0.5.0-Beta: 26 | // - Rename to EX-Turntable in line with re-branding of DCC-EX. 27 | // - Add test commands via serial console to replicate diagnostic command for local testing. 28 | // - Disable phase switching during homing. 29 | // - Increase HOME_SENSITIVITY default to 300 steps due to not moving far enough away from home sensor during calibration. 30 | // - Correct stepper configuration for A4988/DRV8825 to use AccelStepper::DRIVER. 31 | // 0.4.0-Beta: 32 | // - Introduced support for traversers and turntables that rotate less than 360 degrees. 33 | // - Added sensor testing mode that disables all operations and only reports sensor changes to the serial console. 34 | // 0.3.1-Beta: 35 | // - Introduce automatic phase switching as the default behaviour. 36 | // - Default phase switching happens at 45 degrees from home, and reverts 180 degrees later, this is configurable via config.h. 37 | // - Added option to manually define the steps per rotation via config.h. 38 | // 0.3.0-Beta: 39 | // - No actual changes, resetting version number for intial Beta release. 40 | // 0.2.3 includes: 41 | // - Bug fix where two wire drivers were not enabling outputs during calibration, and stepper wasn't rotating. 42 | // 0.2.2 includes: 43 | // - Bug fix where active low relays are activated during the calibration sequence. 44 | // 0.2.1 includes: 45 | // - Bug fix where failed homing puts calibration into infinite loop. 46 | // - Calibration no longer initiated if homing fails. 47 | // 0.2.0 includes: 48 | // - Refactor stepper support to enable easy configuration and support for multiple driver types. 49 | // - Eliminate defining step count, and use homing to determine rotational steps which are stored in EEPROM. 50 | // - Calibration sequence rewrite to erase EEPROM (if used) and use homing to count rotational steps. 51 | // - Start up checks for EEPROM and initiates calibration if not set. 52 | // - Add support for A4988 and DRV8825 as part of the supported stepper options. 53 | // 0.1.2 includes: 54 | // - Fix typo in TURNTABLE_EX() macro in README (activit). 55 | // - RAM optimisation with Serial.print(F()) and remove Serial.print((String)). 56 | // - Enabled RELAY_ACTIVE_STATE to cater for active low or high relays. 57 | // 0.1.1 includes: 58 | // - Missed updating stepper pin definitions for COUNTER_CLOCKWISE. 59 | // - Home sensor pin still incorrectly mentions pin 2. 60 | // 0.1.0 includes: 61 | // - "Breaking" change with revised pin allocations. This has been done to allow the potential future use of pin D2 for 62 | // interrupt driven activities if required (potentially a DCC controlled option rather than I2C). 63 | // - Also caters for GC9A01 round display if this proves viable. 64 | // - There is also a simplification of the calibration sequence. 65 | // - Also a clean up of debug outputs by using a DEBUG flag. 66 | // 0.0.9 includes: 67 | // - Added calibration sequence to validate step count for 360 degree rotation is accurate. 68 | // 0.0.8 includes: 69 | // - Added LED and accessory output support, with LED on/blink slow/blink fast/off, and accessory on/off. 70 | // 0.0.7 includes: 71 | // - Revise return status to 0 (stopped) or 1 (moving) only, return status of 2 unused and causing issues with EX-RAIL RESERVE/FREE. 72 | // 0.0.6 includes: 73 | // - Move to statically defined home sensor and relay pins. 74 | // 0.0.5 includes: 75 | // - Returns status to the TurntableEX.h device driver when requested. 76 | // 0.0.4 includes: 77 | // - Improvement to homing, limiting rotations to two full turns if homing fails. 78 | // 0.0.3 includes: 79 | // - Bug fix for two homing issues and cleaner debug output. 80 | // - Also removed ability for users to enable/disable relay outputs to keep things simple. 81 | // If users don't wish to use the relay outputs, they can simply leave them unused. 82 | // 0.0.2 includes: 83 | // - Steps sent by CommandStation SERVO command, no local position/step configuration. 84 | // - Phase switch also by SERVO command. 85 | // 0.0.1 includes: 86 | // - Basic functionality with positions defined in code via array of structs. 87 | // - Phase switching via external dual relay. 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /.github/workflows/new-items.yml: -------------------------------------------------------------------------------- 1 | # This workflow is to be used for all repositories to integrate with the DCC++ EX Beta Project. 2 | # It will add all issues and pull requests for a repository to the project, and put in the correct status. 3 | # 4 | # Ensure "REPO_LABEL" is updated with the correct label for the repo stream of work. 5 | name: Add Issue or Pull Request to Project 6 | 7 | env: 8 | REPO_LABEL: ${{ secrets.PROJECT_STREAM_LABEL }} 9 | PROJECT_NUMBER: 7 10 | ORG: DCC-EX 11 | 12 | on: 13 | issues: 14 | types: 15 | - opened 16 | pull_request_target: 17 | types: 18 | - ready_for_review 19 | - opened 20 | - review_requested 21 | 22 | jobs: 23 | add_to_project: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Add labels 27 | uses: andymckay/labeler@master 28 | with: 29 | add-labels: ${{ env.REPO_LABEL }} 30 | 31 | - name: Generate token 32 | id: generate_token 33 | uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 34 | with: 35 | app_id: ${{ secrets.PROJECT_APP_ID }} 36 | private_key: ${{ secrets. PROJECT_APP_KEY }} 37 | 38 | - name: Get project data 39 | env: 40 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 41 | run: | 42 | gh api graphql -f query=' 43 | query($org: String!, $number: Int!) { 44 | organization(login: $org){ 45 | projectV2(number: $number) { 46 | id 47 | fields(first:20) { 48 | nodes { 49 | ... on ProjectV2Field { 50 | id 51 | name 52 | } 53 | ... on ProjectV2SingleSelectField { 54 | id 55 | name 56 | options { 57 | id 58 | name 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | }' -f org=$ORG -F number=$PROJECT_NUMBER > project_data.json 66 | 67 | echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV 68 | echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV 69 | echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV 70 | echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV 71 | echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV 72 | echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV 73 | 74 | - name: Add issue to project 75 | env: 76 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 77 | ITEM_ID: ${{ github.event.issue.node_id }} 78 | if: github.event_name == 'issues' 79 | run: | 80 | project_item_id="$( gh api graphql -f query=' 81 | mutation($project:ID!, $item:ID!) { 82 | addProjectV2ItemById(input: {projectId: $project, contentId: $item}) { 83 | item { 84 | id 85 | } 86 | } 87 | }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')" 88 | echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV 89 | 90 | - name: Add PR to project 91 | env: 92 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 93 | ITEM_ID: ${{ github.event.pull_request.node_id }} 94 | if: github.event_name == 'pull_request' 95 | run: | 96 | project_item_id="$( gh api graphql -f query=' 97 | mutation($project:ID!, $item:ID!) { 98 | addProjectV2ItemById(input: {projectId: $project, contentId: $item}) { 99 | item { 100 | id 101 | } 102 | } 103 | }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')" 104 | echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV 105 | 106 | - name: Set status - To Do 107 | env: 108 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 109 | if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Bug') || contains(github.event.*.labels.*.name, 'Support Request')) 110 | run: | 111 | gh api graphql -f query=' 112 | mutation( 113 | $project: ID! 114 | $item: ID! 115 | $status_field: ID! 116 | $status_value: String! 117 | ){ 118 | set_status: updateProjectV2ItemFieldValue(input: { 119 | projectId: $project 120 | itemId: $item 121 | fieldId: $status_field 122 | value: { 123 | singleSelectOptionId: $status_value 124 | } 125 | }) { 126 | projectV2Item { 127 | id 128 | } 129 | } 130 | }' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TO_DO_OPTION_ID }} --silent 131 | 132 | - name: Set status - Review 133 | env: 134 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 135 | if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Unit Tested') || contains(github.event.*.labels.*.name, 'Regression Tested') || contains(github.event.*.labels.*.name, 'Needs Review')) || github.event_name == 'pull_request' 136 | run: | 137 | gh api graphql -f query=' 138 | mutation( 139 | $project: ID! 140 | $item: ID! 141 | $status_field: ID! 142 | $status_value: String! 143 | ){ 144 | set_status: updateProjectV2ItemFieldValue(input: { 145 | projectId: $project 146 | itemId: $item 147 | fieldId: $status_field 148 | value: { 149 | singleSelectOptionId: $status_value 150 | } 151 | }) { 152 | projectV2Item { 153 | id 154 | } 155 | } 156 | }' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.NEEDS_REVIEW_OPTION_ID }} --silent 157 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/6-beta_test_results.yml: -------------------------------------------------------------------------------- 1 | # Beta Test Results GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Beta Test Results 6 | description: Use this template to submit the results of EX-Turntable Beta testing 7 | title: "[Beta Test Results]: " 8 | labels: 9 | - Beta Testing 10 | - Regression Tested 11 | - Needs Review 12 | body: 13 | - type: markdown 14 | attributes: 15 | value: | 16 | Thanks for Beta testing EX-Turntable! 17 | 18 | This test results form is to be used in conjunction with the [EX-Turntable regression testing process](https://github.com/DCC-EX/Support-Planning/wiki/EX-Turntable_Tests). 19 | 20 | Please provide the test results below, and we encourage feedback on the ease of use, reliability, and overall usefullness also. 21 | 22 | - type: textarea 23 | id: test-config 24 | attributes: 25 | label: Testing Configuration 26 | description: Please provide details of your testing configuration. 27 | value: | 28 | EX-CommandStation version: 29 | EX-Turntable version: 30 | EX-Turntable microcontroller: 31 | Stepper driver: 32 | Stepper motor: 33 | Homing sensor: 34 | Limit sensor (if in traverser mode): 35 | Dual relay board (if in use): 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: config-files 41 | attributes: 42 | label: Configuration files 43 | description: Please upload relevant config files here. 44 | placeholder: For example, config.h, myHal.cpp, myAutomation.h 45 | validations: 46 | required: true 47 | 48 | - type: dropdown 49 | id: test1 50 | attributes: 51 | label: Test 1 - Startup Sequence 52 | options: 53 | - Pass 54 | - Fail 55 | validations: 56 | required: true 57 | 58 | - type: textarea 59 | id: test1-comments 60 | attributes: 61 | label: Observations or comments 62 | description: Please provide any observations or comments on the test. 63 | 64 | - type: dropdown 65 | id: test2 66 | attributes: 67 | label: Test 2 - EX-Turntable is Available 68 | options: 69 | - Pass 70 | - Fail 71 | validations: 72 | required: true 73 | 74 | - type: textarea 75 | id: test2-comments 76 | attributes: 77 | label: Observations or comments 78 | description: Please provide any observations or comments on the test. 79 | 80 | - type: dropdown 81 | id: test3 82 | attributes: 83 | label: Test 3 - Automatic Phase Switching Enabled 84 | options: 85 | - Pass 86 | - Fail 87 | - Not Tested 88 | validations: 89 | required: true 90 | 91 | - type: textarea 92 | id: test3-comments 93 | attributes: 94 | label: Observations or comments 95 | description: Please provide any observations or comments on the test. 96 | 97 | - type: dropdown 98 | id: test3-1 99 | attributes: 100 | label: Test 3, Part 1 - Clockwise 101 | options: 102 | - Pass 103 | - Fail 104 | validations: 105 | required: true 106 | 107 | - type: textarea 108 | id: test3-1-comments 109 | attributes: 110 | label: Observations or comments 111 | description: Please provide any observations or comments on the test. 112 | 113 | - type: dropdown 114 | id: test3-2 115 | attributes: 116 | label: Test 3, Part 2 - Counter Clockwise 117 | options: 118 | - Pass 119 | - Fail 120 | validations: 121 | required: true 122 | 123 | - type: textarea 124 | id: test3-2-comments 125 | attributes: 126 | label: Observations or comments 127 | description: Please provide any observations or comments on the test. 128 | 129 | - type: dropdown 130 | id: test4 131 | attributes: 132 | label: Test 4 - Homing 133 | options: 134 | - Pass 135 | - Fail 136 | validations: 137 | required: true 138 | 139 | - type: textarea 140 | id: test4-comments 141 | attributes: 142 | label: Observations or comments 143 | description: Please provide any observations or comments on the test. 144 | 145 | - type: dropdown 146 | id: test5 147 | attributes: 148 | label: Test 5 - Calibration 149 | options: 150 | - Pass 151 | - Fail 152 | validations: 153 | required: true 154 | 155 | - type: textarea 156 | id: test5-comments 157 | attributes: 158 | label: Observations or comments 159 | description: Please provide any observations or comments on the test. 160 | 161 | - type: dropdown 162 | id: test6 163 | attributes: 164 | label: Test 6 - LED Output Testing 165 | options: 166 | - Pass 167 | - Fail 168 | - Not Tested 169 | validations: 170 | required: true 171 | 172 | - type: textarea 173 | id: test6-comments 174 | attributes: 175 | label: Observations or comments 176 | description: Please provide any observations or comments on the test. 177 | 178 | - type: dropdown 179 | id: test7 180 | attributes: 181 | label: Test 7 - Accessory Output Testing 182 | options: 183 | - Pass 184 | - Fail 185 | - Not Tested 186 | validations: 187 | required: true 188 | 189 | - type: textarea 190 | id: test7-comments 191 | attributes: 192 | label: Observations or comments 193 | description: Please provide any observations or comments on the test. 194 | 195 | - type: dropdown 196 | id: test8 197 | attributes: 198 | label: Test 8 - Manual Phase Switching 199 | options: 200 | - Pass 201 | - Fail 202 | - Not Tested 203 | validations: 204 | required: true 205 | 206 | - type: textarea 207 | id: test8-comments 208 | attributes: 209 | label: Observations or comments 210 | description: Please provide any observations or comments on the test. 211 | 212 | - type: dropdown 213 | id: test9 214 | attributes: 215 | label: Test 9 - Sensor Testing 216 | options: 217 | - Pass 218 | - Fail 219 | - Not Tested 220 | validations: 221 | required: true 222 | 223 | - type: textarea 224 | id: test9-comments 225 | attributes: 226 | label: Observations or comments 227 | description: Please provide any observations or comments on the test. 228 | 229 | - type: dropdown 230 | id: test10 231 | attributes: 232 | label: Test 10 - Traverser Mode 233 | options: 234 | - Pass 235 | - Fail 236 | - Not Tested 237 | validations: 238 | required: true 239 | 240 | - type: textarea 241 | id: test10-comments 242 | attributes: 243 | label: Observations or comments 244 | description: Please provide any observations or comments on the test. 245 | 246 | - type: textarea 247 | id: feedback 248 | attributes: 249 | label: General comments or feedback 250 | description: Please provide any additional comments or feedback here. -------------------------------------------------------------------------------- /config.example.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Peter Cole 3 | * 4 | * This is the configuration file for Turntable-EX. 5 | */ 6 | 7 | ///////////////////////////////////////////////////////////////////////////////////// 8 | // Define a valid (and free) I2C address, 0x60 is the default. 9 | // 10 | #define I2C_ADDRESS 0x60 11 | 12 | ///////////////////////////////////////////////////////////////////////////////////// 13 | // Define the mode for Turntable-EX. 14 | // TURNTABLE : Use this for normal, 360 degree rotation turntables (Default). 15 | // TRAVERSER : Use this for vertical or horizontal traversers, or turntables that do 16 | // do not rotate a full 360 degrees. 17 | // 18 | #define TURNTABLE_EX_MODE TURNTABLE 19 | // #define TURNTABLE_EX_MODE TRAVERSER 20 | 21 | ///////////////////////////////////////////////////////////////////////////////////// 22 | // Enable sensor testing only, prevents all Turntable-EX operations. 23 | // Uncomment this line to disable all normal Turntable-EX operations in order to test 24 | // and validate that homing and limit sensors activate and deactivate correctly. 25 | // Note that you can enable sensor testing interactively in the serial console with 26 | // 27 | // #define SENSOR_TESTING 28 | 29 | ///////////////////////////////////////////////////////////////////////////////////// 30 | // Define the active state for the homing sensor. 31 | // LOW = When activated, the input is pulled down (ground or 0V). 32 | // HIGH = When activated, the input is pulled up (typically 5V). 33 | // 34 | #define HOME_SENSOR_ACTIVE_STATE LOW 35 | 36 | ///////////////////////////////////////////////////////////////////////////////////// 37 | // REQUIRED FOR TRAVERSER MODE ONLY 38 | // Define the active state for the limit sensor. 39 | // LOW = When activated, the input is pulled down (ground or 0V). 40 | // HIGH = When activated, the input is pulled up (typically 5V). 41 | // 42 | #define LIMIT_SENSOR_ACTIVE_STATE LOW 43 | 44 | ///////////////////////////////////////////////////////////////////////////////////// 45 | // Define the active state for the phase switching relays. 46 | // LOW = When activated, the input is pulled down (ground or 0V). 47 | // HIGH = When activated, the input is pulled up (typically 5V). 48 | // 49 | #define RELAY_ACTIVE_STATE HIGH 50 | 51 | ///////////////////////////////////////////////////////////////////////////////////// 52 | // Define phase switching behaviour. 53 | // 54 | // PHASE_SWITCHING options: 55 | // AUTO : When defined, phase will invert at PHASE_SWITCH_START_ANGLE, and revert 56 | // at PHASE_SWITCH_STOP_ANGLE (see below). 57 | // MANUAL : When defined, phase will only invert using the Turn_PInvert command. 58 | // 59 | // Refer to the documentation for the full explanation on phase switching, and when 60 | // it is recommended to change these options. 61 | // 62 | #define PHASE_SWITCHING AUTO 63 | // #define PHASE_SWITCHING MANUAL 64 | 65 | ///////////////////////////////////////////////////////////////////////////////////// 66 | // Define automatic phase switching angle. 67 | // 68 | // If PHASE_SWITCHING is set to AUTO (see above), then when the turntable rotates 69 | // PHASE_SWITCH_ANGLE degrees from home, the phase will automatically invert. 70 | // Once the turntable reaches a further 180 degrees, the phase will automatically 71 | // revert. 72 | // 73 | // Refer to the documentation for the full explanation on phase switching, and how to 74 | // define the angle that's relevant for your layout. 75 | // 76 | #define PHASE_SWITCH_ANGLE 45 77 | 78 | ///////////////////////////////////////////////////////////////////////////////////// 79 | // Define the stepper controller in use according to those available below, refer to the 80 | // documentation for further details on which to select for your application. 81 | // 82 | // ULN2003_HALF_CW : ULN2003 in half step mode, clockwise 83 | // ULN2003_HALF_CCW : ULN2003 in half step mode, counter clockwise 84 | // ULN2003_FULL_CW : ULN2003 in full step mode, clockwise 85 | // ULN2003_FULL_CCW : ULN2003 in full step mode, counter clockwise 86 | // A4988 : Two wire drivers (eg. A4988, DRV8825, TMC2208) 87 | // 88 | // NOTE: If you are using a different controller than those already defined, refer to 89 | // the documentation to define the appropriate configuration variables. Note there are 90 | // some controllers that are pin-compatible with an existing defined controller, and 91 | // in those instances, no custom configuration would be required. 92 | // 93 | 94 | #define STEPPER_DRIVER ULN2003_HALF_CW 95 | // #define STEPPER_DRIVER ULN2003_HALF_CCW 96 | // #define STEPPER_DRIVER ULN2003_FULL_CW 97 | // #define STEPPER_DRIVER ULN2003_FULL_CCW 98 | // #define STEPPER_DRIVER A4988 99 | // 100 | // When using a two wire driver (eg. A4988, DRV8825, TMC2208), it may be necessary to invert 101 | // the direction pin. This is likely required when using a TMC2208. This has no effect on 102 | // ULN2003. 103 | // #define INVERT_DIRECTION 104 | // 105 | // When using a two wire driver (eg. A4988, DRV8825, TMC2208), it may be necessary to invert 106 | // the step pin. If so, uncomment this line. This has no effect on ULN2003. 107 | // #define INVERT_STEP 108 | // 109 | // When using a two wire driver (eg. A4988, DRV8825, TMC2208), it may be necessary to invert 110 | // the enable pin behaviour if you wish to have the stepper driver disabled when not moving. 111 | // This has no effect on ULN2003. 112 | // #define INVERT_ENABLE 113 | 114 | ///////////////////////////////////////////////////////////////////////////////////// 115 | // Define the various stepper configuration items below if the defaults don't suit 116 | // 117 | // Disable the stepper controller when idling, comment out to leave on. Note that this 118 | // is handy to prevent controllers overheating, so this is a recommended setting. 119 | #define DISABLE_OUTPUTS_IDLE 120 | // 121 | // Define the acceleration and speed settings. 122 | #define STEPPER_MAX_SPEED 200 // Maximum possible speed the stepper will reach 123 | #define STEPPER_ACCELERATION 25 // Acceleration and deceleration rate 124 | // 125 | // If using a gearing or microstep setup with larger than 32767 steps, you need to set the 126 | // gearing factor appropriately. 127 | // Step counts sent from EX-CommandStation will be multiplied by this number. 128 | #define STEPPER_GEARING_FACTOR 1 129 | 130 | ///////////////////////////////////////////////////////////////////////////////////// 131 | // If dealing with steppers that have a lot of slop, it can be beneficial to force 132 | // rotating in one direction only. Enable one (and one only) of the below options if 133 | // a single rotation direction is required. 134 | // NOTE this does not apply in TRAVERSER mode. 135 | // 136 | // #define ROTATE_FORWARD_ONLY 137 | // #define ROTATE_REVERSE_ONLY 138 | 139 | ///////////////////////////////////////////////////////////////////////////////////// 140 | // Define the LED blink rates for fast and slow blinking in milliseconds. 141 | // 142 | // The LED will alternative on/off for these durations. 143 | #define LED_FAST 100 144 | #define LED_SLOW 500 145 | 146 | ///////////////////////////////////////////////////////////////////////////////////// 147 | // ADVANCED OPTIONS 148 | // In normal circumstances, the settings below should not need to be adjusted unless 149 | // requested by support ticket, or if Tinkerers or Engineers are working with alternative 150 | // stepper drivers and motors. 151 | // 152 | // Enable debug outputs if required during troubleshooting. 153 | // Note you can enable debug output interactively in the serial console with 154 | // 155 | // #define DEBUG 156 | // 157 | // Define the maximum number of steps homing and calibration will perform before marking 158 | // these activities as failed. This step count must exceed a single full rotation in order 159 | // to be useful. 160 | // #define SANITY_STEPS 10000 161 | // 162 | // Define the minimum number of steps the turntable needs to move before the homing sensor 163 | // deactivates, which is required during the calibration sequence. For high step count 164 | // setups, this may need to be increased. 165 | // #define HOME_SENSITIVITY 300 166 | // 167 | // Override the step count determined by automatic calibration by uncommenting the line 168 | // below, and manually defining a specific step count. 169 | // #define FULL_STEP_COUNT 4096 170 | // 171 | // Override the default debounce delay (in ms) if using mechanical home/limit switches that have 172 | // "noisy" switch bounce issues. 173 | // In TRAVERSER mode, default is 10ms as these would typically use mechanical switches. 174 | // In TURNTABLE mode, default is 0ms as these would typically use hall effect sensors. 175 | // #define DEBOUNCE_DELAY 10 176 | -------------------------------------------------------------------------------- /config.traverser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Peter Cole 3 | * 4 | * This is the configuration file for Turntable-EX. 5 | */ 6 | 7 | ///////////////////////////////////////////////////////////////////////////////////// 8 | // Define a valid (and free) I2C address, 0x60 is the default. 9 | // 10 | #define I2C_ADDRESS 0x60 11 | 12 | ///////////////////////////////////////////////////////////////////////////////////// 13 | // Define the mode for Turntable-EX. 14 | // TURNTABLE : Use this for normal, 360 degree rotation turntables (Default). 15 | // TRAVERSER : Use this for vertical or horizontal traversers, or turntables that do 16 | // do not rotate a full 360 degrees. 17 | // 18 | // #define TURNTABLE_EX_MODE TURNTABLE 19 | #define TURNTABLE_EX_MODE TRAVERSER 20 | 21 | ///////////////////////////////////////////////////////////////////////////////////// 22 | // Enable sensor testing only, prevents all Turntable-EX operations. 23 | // Uncomment this line to disable all normal Turntable-EX operations in order to test 24 | // and validate that homing and limit sensors activate and deactivate correctly. 25 | // Note that you can enable sensor testing interactively in the serial console with 26 | // 27 | // #define SENSOR_TESTING 28 | 29 | ///////////////////////////////////////////////////////////////////////////////////// 30 | // Define the active state for the homing sensor. 31 | // LOW = When activated, the input is pulled down (ground or 0V). 32 | // HIGH = When activated, the input is pulled up (typically 5V). 33 | // 34 | #define HOME_SENSOR_ACTIVE_STATE LOW 35 | 36 | ///////////////////////////////////////////////////////////////////////////////////// 37 | // REQUIRED FOR TRAVERSER MODE ONLY 38 | // Define the active state for the limit sensor. 39 | // LOW = When activated, the input is pulled down (ground or 0V). 40 | // HIGH = When activated, the input is pulled up (typically 5V). 41 | // 42 | #define LIMIT_SENSOR_ACTIVE_STATE LOW 43 | 44 | ///////////////////////////////////////////////////////////////////////////////////// 45 | // Define the active state for the phase switching relays. 46 | // LOW = When activated, the input is pulled down (ground or 0V). 47 | // HIGH = When activated, the input is pulled up (typically 5V). 48 | // 49 | #define RELAY_ACTIVE_STATE HIGH 50 | 51 | ///////////////////////////////////////////////////////////////////////////////////// 52 | // Define phase switching behaviour. 53 | // 54 | // PHASE_SWITCHING options: 55 | // AUTO : When defined, phase will invert at PHASE_SWITCH_START_ANGLE, and revert 56 | // at PHASE_SWITCH_STOP_ANGLE (see below). 57 | // MANUAL : When defined, phase will only invert using the Turn_PInvert command. 58 | // 59 | // Refer to the documentation for the full explanation on phase switching, and when 60 | // it is recommended to change these options. 61 | // 62 | // #define PHASE_SWITCHING AUTO 63 | #define PHASE_SWITCHING MANUAL 64 | 65 | ///////////////////////////////////////////////////////////////////////////////////// 66 | // Define automatic phase switching angle. 67 | // 68 | // If PHASE_SWITCHING is set to AUTO (see above), then when the turntable rotates 69 | // PHASE_SWITCH_ANGLE degrees from home, the phase will automatically invert. 70 | // Once the turntable reaches a further 180 degrees, the phase will automatically 71 | // revert. 72 | // 73 | // Refer to the documentation for the full explanation on phase switching, and how to 74 | // define the angle that's relevant for your layout. 75 | // 76 | #define PHASE_SWITCH_ANGLE 45 77 | 78 | ///////////////////////////////////////////////////////////////////////////////////// 79 | // Define the stepper controller in use according to those available below, refer to the 80 | // documentation for further details on which to select for your application. 81 | // 82 | // ULN2003_HALF_CW : ULN2003 in half step mode, clockwise 83 | // ULN2003_HALF_CCW : ULN2003 in half step mode, counter clockwise 84 | // ULN2003_FULL_CW : ULN2003 in full step mode, clockwise 85 | // ULN2003_FULL_CCW : ULN2003 in full step mode, counter clockwise 86 | // A4988 : Two wire drivers (eg. A4988, DRV8825, TMC2208) 87 | // 88 | // NOTE: If you are using a different controller than those already defined, refer to 89 | // the documentation to define the appropriate configuration variables. Note there are 90 | // some controllers that are pin-compatible with an existing defined controller, and 91 | // in those instances, no custom configuration would be required. 92 | // 93 | 94 | #define STEPPER_DRIVER ULN2003_HALF_CW 95 | // #define STEPPER_DRIVER ULN2003_HALF_CCW 96 | // #define STEPPER_DRIVER ULN2003_FULL_CW 97 | // #define STEPPER_DRIVER ULN2003_FULL_CCW 98 | // #define STEPPER_DRIVER A4988 99 | // 100 | // When using a two wire driver (eg. A4988, DRV8825, TMC2208), it may be necessary to invert 101 | // the direction pin. This is likely required when using a TMC2208. This has no effect on 102 | // ULN2003. 103 | // #define INVERT_DIRECTION 104 | // 105 | // When using a two wire driver (eg. A4988, DRV8825, TMC2208), it may be necessary to invert 106 | // the step pin. If so, uncomment this line. This has no effect on ULN2003. 107 | // #define INVERT_STEP 108 | // 109 | // When using a two wire driver (eg. A4988, DRV8825, TMC2208), it may be necessary to invert 110 | // the enable pin behaviour if you wish to have the stepper driver disabled when not moving. 111 | // This has no effect on ULN2003. 112 | // #define INVERT_ENABLE 113 | 114 | ///////////////////////////////////////////////////////////////////////////////////// 115 | // Define the various stepper configuration items below if the defaults don't suit 116 | // 117 | // Disable the stepper controller when idling, comment out to leave on. Note that this 118 | // is handy to prevent controllers overheating, so this is a recommended setting. 119 | #define DISABLE_OUTPUTS_IDLE 120 | // 121 | // Define the acceleration and speed settings. 122 | #define STEPPER_MAX_SPEED 200 // Maximum possible speed the stepper will reach 123 | #define STEPPER_ACCELERATION 25 // Acceleration and deceleration rate 124 | // 125 | // If using a gearing or microstep setup with larger than 32767 steps, you need to set the 126 | // gearing factor appropriately. 127 | // Step counts sent from EX-CommandStation will be multiplied by this number. 128 | #define STEPPER_GEARING_FACTOR 1 129 | 130 | ///////////////////////////////////////////////////////////////////////////////////// 131 | // If dealing with steppers that have a lot of slop, it can be beneficial to force 132 | // rotating in one direction only. Enable one (and one only) of the below options if 133 | // a single rotation direction is required. 134 | // NOTE this does not apply in TRAVERSER mode. 135 | // 136 | // #define ROTATE_FORWARD_ONLY 137 | // #define ROTATE_REVERSE_ONLY 138 | 139 | ///////////////////////////////////////////////////////////////////////////////////// 140 | // Define the LED blink rates for fast and slow blinking in milliseconds. 141 | // 142 | // The LED will alternative on/off for these durations. 143 | #define LED_FAST 100 144 | #define LED_SLOW 500 145 | 146 | ///////////////////////////////////////////////////////////////////////////////////// 147 | // ADVANCED OPTIONS 148 | // In normal circumstances, the settings below should not need to be adjusted unless 149 | // requested by support ticket, or if Tinkerers or Engineers are working with alternative 150 | // stepper drivers and motors. 151 | // 152 | // Enable debug outputs if required during troubleshooting. 153 | // Note you can enable debug output interactively in the serial console with 154 | // 155 | // #define DEBUG 156 | // 157 | // Define the maximum number of steps homing and calibration will perform before marking 158 | // these activities as failed. This step count must exceed a single full rotation in order 159 | // to be useful. 160 | // #define SANITY_STEPS 10000 161 | // 162 | // Define the minimum number of steps the turntable needs to move before the homing sensor 163 | // deactivates, which is required during the calibration sequence. For high step count 164 | // setups, this may need to be increased. 165 | // #define HOME_SENSITIVITY 300 166 | // 167 | // Override the step count determined by automatic calibration by uncommenting the line 168 | // below, and manually defining a specific step count. 169 | // #define FULL_STEP_COUNT 4096 170 | // 171 | // Override the default debounce delay (in ms) if using mechanical home/limit switches that have 172 | // "noisy" switch bounce issues. 173 | // In TRAVERSER mode, default is 10ms as these would typically use mechanical switches. 174 | // In TURNTABLE mode, default is 0ms as these would typically use hall effect sensors. 175 | // #define DEBOUNCE_DELAY 10 176 | -------------------------------------------------------------------------------- /IOFunctions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * 4 | * This file is part of EX-Turntable 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with EX-Turntable. If not, see . 18 | */ 19 | 20 | #include "IOFunctions.h" 21 | #include "EEPROMFunctions.h" 22 | #include 23 | 24 | unsigned long gearingFactor = STEPPER_GEARING_FACTOR; 25 | const byte numChars = 20; 26 | char serialInputChars[numChars]; 27 | bool newSerialData = false; 28 | bool testCommandSent = false; 29 | uint8_t testActivity = 0; 30 | uint8_t testStepsMSB = 0; 31 | uint8_t testStepsLSB = 0; 32 | #ifdef DEBUG 33 | bool debug = true; 34 | #else 35 | bool debug = false; 36 | #endif 37 | #ifdef SENSOR_TESTING 38 | bool sensorTesting = true; 39 | #else 40 | bool sensorTesting = false; 41 | #endif 42 | 43 | // Function to setup Wire library and functions 44 | void setupWire() { 45 | Wire.begin(I2C_ADDRESS); 46 | Wire.onReceive(receiveEvent); 47 | Wire.onRequest(requestEvent); 48 | } 49 | 50 | // Function to read and process serial input for valid test commands 51 | void processSerialInput() { 52 | static bool serialInProgress = false; 53 | static byte serialIndex = 0; 54 | char startMarker = '<'; 55 | char endMarker = '>'; 56 | char serialChar; 57 | while (Serial.available() > 0 && newSerialData == false) { 58 | serialChar = Serial.read(); 59 | if (serialInProgress == true) { 60 | if (serialChar != endMarker) { 61 | serialInputChars[serialIndex] = serialChar; 62 | serialIndex++; 63 | if (serialIndex >= numChars) { 64 | serialIndex = numChars - 1; 65 | } 66 | } else { 67 | serialInputChars[serialIndex] = '\0'; 68 | serialInProgress = false; 69 | serialIndex = 0; 70 | newSerialData = true; 71 | } 72 | } else if (serialChar == startMarker) { 73 | serialInProgress = true; 74 | } 75 | } 76 | if (newSerialData == true) { 77 | newSerialData = false; 78 | char * strtokIndex; 79 | strtokIndex = strtok(serialInputChars," "); 80 | char command = strtokIndex[0]; // first parameter is activity 81 | strtokIndex = strtok(NULL," "); // space separator 82 | long steps; 83 | if (command == 'M') { 84 | steps = atol(strtokIndex); 85 | strtokIndex = strtok(NULL," "); 86 | testActivity = atoi(strtokIndex); 87 | } 88 | switch (command) { 89 | case 'C': 90 | serialCommandC(); 91 | break; 92 | 93 | case 'D': 94 | serialCommandD(); 95 | break; 96 | 97 | case 'E': 98 | serialCommandE(); 99 | break; 100 | 101 | case 'H': 102 | serialCommandH(); 103 | break; 104 | 105 | case 'M': 106 | serialCommandM(steps); 107 | break; 108 | 109 | case 'R': 110 | serialCommandR(); 111 | break; 112 | 113 | case 'T': 114 | serialCommandT(); 115 | break; 116 | 117 | case 'V': 118 | serialCommandV(); 119 | break; 120 | 121 | default: 122 | break; 123 | } 124 | } 125 | } 126 | 127 | // C command to initiate calibration 128 | void serialCommandC() { 129 | if (stepper.isRunning()) { 130 | Serial.println(F("Stepper is running, ignoring ")); 131 | return; 132 | } 133 | if (!calibrating || homed == 2) { 134 | initiateCalibration(); 135 | } 136 | } 137 | 138 | // D command to enable debug output 139 | void serialCommandD() { 140 | if (debug) { 141 | Serial.println(F("Disabling debug output")); 142 | debug = false; 143 | } else { 144 | Serial.println(F("Enabling debug output")); 145 | debug = true; 146 | } 147 | } 148 | 149 | // E command to erase EEPROM 150 | void serialCommandE() { 151 | if (stepper.isRunning()) { 152 | Serial.println(F("Stepper is running, ignoring ")); 153 | return; 154 | } 155 | Serial.println(F("Erasing full step count from EEPROM")); 156 | clearEEPROM(); 157 | #ifndef FULL_STEP_COUNT 158 | Serial.println(F("Resetting full step count to 0")); 159 | fullTurnSteps = 0; 160 | #endif 161 | } 162 | 163 | // H command to initiate homing 164 | void serialCommandH() { 165 | if (stepper.isRunning()) { 166 | Serial.println(F("Stepper is running, ignoring ")); 167 | return; 168 | } 169 | if (!calibrating || homed == 2) { 170 | initiateHoming(); 171 | } 172 | } 173 | 174 | // M command to move 175 | void serialCommandM(long steps) { 176 | if (stepper.isRunning()) { 177 | Serial.println(F("Stepper is running, ignoring ")); 178 | return; 179 | } 180 | if (steps < 0) { 181 | Serial.println(F("Cannot provide a negative step count")); 182 | } else if (steps > 32767) { 183 | Serial.println(F("Step count too large, refer to the documentation for large step counts > 32767")); 184 | } else { 185 | Serial.print(F("Test move ")); 186 | Serial.print(steps); 187 | Serial.print(F(" steps, activity ID ")); 188 | Serial.println(testActivity); 189 | testStepsMSB = steps >> 8; 190 | testStepsLSB = steps & 0xFF; 191 | testCommandSent = true; 192 | receiveEvent(3); 193 | } 194 | } 195 | 196 | void serialCommandR() { 197 | wdt_enable(WDTO_15MS); 198 | delay(50); 199 | } 200 | 201 | // T command to perform sensor testing 202 | void serialCommandT() { 203 | if (stepper.isRunning()) { 204 | Serial.println(F("Stepper is running, ignoring ")); 205 | return; 206 | } 207 | if (sensorTesting) { 208 | Serial.println(F("Disabling sensor testing mode, reboot required")); 209 | sensorTesting = false; 210 | } else { 211 | Serial.println(F("Enabling sensor testing mode, taking EX-Turntable offline")); 212 | Wire.end(); 213 | sensorTesting = true; 214 | } 215 | } 216 | 217 | // V command to display version and other info 218 | void serialCommandV() { 219 | displayTTEXConfig(); 220 | } 221 | 222 | // Function to display the defined stepper motor config. 223 | void displayTTEXConfig() { 224 | // Basic setup, display what this is. 225 | Serial.begin(115200); 226 | while(!Serial); 227 | Serial.println(F("License GPLv3 fsf.org (c) dcc-ex.com")); 228 | Serial.print(F("EX-Turntable version ")); 229 | Serial.println(VERSION); 230 | Serial.print(F("Available at I2C address 0x")); 231 | Serial.println(I2C_ADDRESS, HEX); 232 | if (fullTurnSteps == 0) { 233 | Serial.println(F("EX-Turntable has not been calibrated yet")); 234 | } else { 235 | #ifdef FULL_STEP_COUNT 236 | Serial.print(F("Manual override has been set for ")); 237 | #else 238 | Serial.print(F("EX-Turntable has been calibrated for ")); 239 | #endif 240 | Serial.print(fullTurnSteps); 241 | Serial.println(F(" steps per revolution")); 242 | } 243 | Serial.print(F("Gearing factor set to ")); 244 | Serial.println(gearingFactor); 245 | #if PHASE_SWITCHING == AUTO 246 | Serial.print(F("Automatic phase switching enabled at ")); 247 | Serial.print(PHASE_SWITCH_ANGLE); 248 | Serial.println(F(" degrees")); 249 | Serial.print(F("Phase will switch at ")); 250 | Serial.print(phaseSwitchStartSteps); 251 | Serial.print(F(" steps from home, and revert at ")); 252 | Serial.print(phaseSwitchStopSteps); 253 | Serial.println(F(" steps from home")); 254 | #else 255 | Serial.println(F("Manual phase switching enabled")); 256 | #endif 257 | #if TURNTABLE_EX_MODE == TRAVERSER 258 | Serial.println(F("EX-Turntable in TRAVERSER mode")); 259 | #else 260 | Serial.println(F("EX-Turntable in TURNTABLE mode")); 261 | #endif 262 | 263 | #if defined(ROTATE_FORWARD_ONLY) 264 | Serial.println(F("Rotating FORWARD only")); 265 | #elif defined(ROTATE_REVERSE_ONLY) 266 | Serial.println(F("Rotating REVERSE only")); 267 | #else 268 | Serial.println(F("Rotating SHORTEST DIRECTION")); 269 | #endif 270 | 271 | #if defined(INVERT_DIRECTION) 272 | Serial.println(F("INVERT_DIRECTION enabled")); 273 | #endif 274 | #if defined(INVERT_STEP) 275 | Serial.println(F("INVERT_STEP enabled")); 276 | #endif 277 | #if defined(INVERT_ENABLE) 278 | Serial.println(F("INVERT_ENABLE enabled")); 279 | #endif 280 | 281 | Serial.print(F("STEPPER_MAX_SPEED ")); 282 | Serial.println(STEPPER_MAX_SPEED); 283 | Serial.print(F("STEPPER_ACCELERATION ")); 284 | Serial.println(STEPPER_ACCELERATION); 285 | 286 | if (debug) { 287 | Serial.print(F("DEBUG: maxSpeed()|acceleration(): ")); 288 | Serial.print(stepper.maxSpeed()); 289 | Serial.print(F("|")); 290 | Serial.println(stepper.acceleration()); 291 | } 292 | 293 | // If in sensor testing mode, display this, don't enable stepper or I2C 294 | if (sensorTesting) { 295 | Serial.println(F("SENSOR TESTING ENABLED, EX-Turntable operations disabled")); 296 | Serial.print(F("Home/limit switch current state: ")); 297 | Serial.print(homeSensorState); 298 | Serial.print(F("/")); 299 | Serial.println(limitSensorState); 300 | Serial.print(F("Debounce delay: ")); 301 | Serial.println(DEBOUNCE_DELAY); 302 | } 303 | } 304 | 305 | // Function to define the action on a received I2C event. 306 | void receiveEvent(int received) { 307 | if (debug) { 308 | Serial.print(F("DEBUG: Received ")); 309 | Serial.print(received); 310 | Serial.println(F(" bytes")); 311 | } 312 | int16_t receivedSteps; 313 | long steps; 314 | uint8_t activity; 315 | uint8_t receivedStepsMSB; 316 | uint8_t receivedStepsLSB; 317 | // We need 3 received bytes in order to care about what's received. 318 | if (received == 3) { 319 | // Get our 3 bytes of data, bit shift into steps. 320 | if (testCommandSent == true) { 321 | receivedStepsMSB = testStepsMSB; 322 | receivedStepsLSB = testStepsLSB; 323 | activity = testActivity; 324 | testCommandSent = false; 325 | } else { 326 | receivedStepsMSB = Wire.read(); 327 | receivedStepsLSB = Wire.read(); 328 | activity = Wire.read(); 329 | } 330 | receivedSteps = (receivedStepsMSB << 8) + receivedStepsLSB; 331 | if (gearingFactor > 10) { 332 | gearingFactor = 10; 333 | } 334 | steps = receivedSteps * gearingFactor; 335 | if (debug) { 336 | Serial.print(F("DEBUG: receivedStepsMSB|receivedStepsLSB|activity: ")); 337 | Serial.print(receivedStepsMSB); 338 | Serial.print(F("|")); 339 | Serial.print(receivedStepsLSB); 340 | Serial.print(F("|")); 341 | Serial.println(activity); 342 | Serial.print(F("DEBUG: gearingFactor|receivedSteps|steps: ")); 343 | Serial.print(gearingFactor); 344 | Serial.print(F("|")); 345 | Serial.print(receivedSteps); 346 | Serial.print(F("|")); 347 | Serial.println(steps); 348 | } 349 | if (steps <= fullTurnSteps && activity < 2 && !stepper.isRunning() && !calibrating) { 350 | // Activities 0/1 require turning and setting phase, process only if stepper is not running. 351 | if (debug) { 352 | Serial.print(F("DEBUG: Requested valid step move to: ")); 353 | Serial.print(steps); 354 | Serial.print(F(" with phase switch: ")); 355 | Serial.println(activity); 356 | } 357 | moveToPosition(steps, activity); 358 | } else if (activity == 2 && !stepper.isRunning() && (!calibrating || homed == 2)) { 359 | // Activity 2 needs to reset our homed flag to initiate the homing process, only if stepper not running. 360 | if (debug) { 361 | Serial.println(F("DEBUG: Requested to home")); 362 | } 363 | initiateHoming(); 364 | } else if (activity == 3 && !stepper.isRunning() && (!calibrating || homed == 2)) { 365 | // Activity 3 will initiate calibration sequence, only if stepper not running. 366 | if (debug) { 367 | Serial.println(F("DEBUG: Calibration requested")); 368 | } 369 | initiateCalibration(); 370 | } else if (activity > 3 && activity < 8) { 371 | // Activities 4 through 7 set LED state. 372 | if (debug) { 373 | Serial.print(F("DEBUG: Set LED state to: ")); 374 | Serial.println(activity); 375 | } 376 | setLEDActivity(activity); 377 | } else if (activity == 8) { 378 | // Activity 8 turns accessory pin on at any time. 379 | if (debug) { 380 | Serial.println(F("DEBUG: Turn accessory pin on")); 381 | } 382 | setAccessory(HIGH); 383 | } else if (activity == 9) { 384 | // Activity 9 turns accessory pin off at any time. 385 | if (debug) { 386 | Serial.println(F("DEBUG: Turn accessory pin off")); 387 | } 388 | setAccessory(LOW); 389 | } else { 390 | if (debug) { 391 | Serial.print(F("DEBUG: Invalid step count or activity provided, or turntable still moving: ")); 392 | Serial.print(steps); 393 | Serial.print(F(" steps, activity: ")); 394 | Serial.println(activity); 395 | } 396 | } 397 | } else { 398 | // Even if we have nothing to do, we need to read and discard all the bytes to avoid timeouts in the CS. 399 | if (debug) { 400 | Serial.println(F("DEBUG: Incorrect number of bytes received, discarding")); 401 | } 402 | while (Wire.available()) { 403 | Wire.read(); 404 | } 405 | } 406 | } 407 | 408 | // Function to return the stepper status when requested by the IO_TurntableEX.h device driver. 409 | // 0 = Finished moving to the correct position. 410 | // 1 = Still moving. 411 | void requestEvent() { 412 | uint8_t stepperStatus; 413 | if (stepper.isRunning()) { 414 | stepperStatus = 1; 415 | } else { 416 | stepperStatus = 0; 417 | } 418 | Wire.write(stepperStatus); 419 | } 420 | -------------------------------------------------------------------------------- /AccelStepper.cpp: -------------------------------------------------------------------------------- 1 | // AccelStepper.cpp 2 | // 3 | // Copyright (C) 2009-2020 Mike McCauley 4 | // $Id: AccelStepper.cpp,v 1.24 2020/04/20 00:15:03 mikem Exp mikem $ 5 | 6 | #include "AccelStepper.h" 7 | 8 | #if 0 9 | // Some debugging assistance 10 | void dump(uint8_t* p, int l) 11 | { 12 | int i; 13 | 14 | for (i = 0; i < l; i++) 15 | { 16 | Serial.print(p[i], HEX); 17 | Serial.print(" "); 18 | } 19 | Serial.println(""); 20 | } 21 | #endif 22 | 23 | void AccelStepper::moveTo(long absolute) 24 | { 25 | if (_targetPos != absolute) 26 | { 27 | _targetPos = absolute; 28 | computeNewSpeed(); 29 | // compute new n? 30 | } 31 | } 32 | 33 | void AccelStepper::move(long relative) 34 | { 35 | moveTo(_currentPos + relative); 36 | } 37 | 38 | // Implements steps according to the current step interval 39 | // You must call this at least once per step 40 | // returns true if a step occurred 41 | boolean AccelStepper::runSpeed() 42 | { 43 | // Dont do anything unless we actually have a step interval 44 | if (!_stepInterval) 45 | return false; 46 | 47 | unsigned long time = micros(); 48 | if (time - _lastStepTime >= _stepInterval) 49 | { 50 | if (_direction == DIRECTION_CW) 51 | { 52 | // Clockwise 53 | _currentPos += 1; 54 | } 55 | else 56 | { 57 | // Anticlockwise 58 | _currentPos -= 1; 59 | } 60 | step(_currentPos); 61 | 62 | _lastStepTime = time; // Caution: does not account for costs in step() 63 | 64 | return true; 65 | } 66 | else 67 | { 68 | return false; 69 | } 70 | } 71 | 72 | long AccelStepper::distanceToGo() 73 | { 74 | return _targetPos - _currentPos; 75 | } 76 | 77 | long AccelStepper::targetPosition() 78 | { 79 | return _targetPos; 80 | } 81 | 82 | long AccelStepper::currentPosition() 83 | { 84 | return _currentPos; 85 | } 86 | 87 | // Useful during initialisations or after initial positioning 88 | // Sets speed to 0 89 | void AccelStepper::setCurrentPosition(long position) 90 | { 91 | _targetPos = _currentPos = position; 92 | _n = 0; 93 | _stepInterval = 0; 94 | _speed = 0.0; 95 | } 96 | 97 | // Subclasses can override 98 | unsigned long AccelStepper::computeNewSpeed() 99 | { 100 | long distanceTo = distanceToGo(); // +ve is clockwise from curent location 101 | 102 | long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16 103 | 104 | if (distanceTo == 0 && stepsToStop <= 1) 105 | { 106 | // We are at the target and its time to stop 107 | _stepInterval = 0; 108 | _speed = 0.0; 109 | _n = 0; 110 | return _stepInterval; 111 | } 112 | 113 | if (distanceTo > 0) 114 | { 115 | // We are anticlockwise from the target 116 | // Need to go clockwise from here, maybe decelerate now 117 | if (_n > 0) 118 | { 119 | // Currently accelerating, need to decel now? Or maybe going the wrong way? 120 | if ((stepsToStop >= distanceTo) || _direction == DIRECTION_CCW) 121 | _n = -stepsToStop; // Start deceleration 122 | } 123 | else if (_n < 0) 124 | { 125 | // Currently decelerating, need to accel again? 126 | if ((stepsToStop < distanceTo) && _direction == DIRECTION_CW) 127 | _n = -_n; // Start accceleration 128 | } 129 | } 130 | else if (distanceTo < 0) 131 | { 132 | // We are clockwise from the target 133 | // Need to go anticlockwise from here, maybe decelerate 134 | if (_n > 0) 135 | { 136 | // Currently accelerating, need to decel now? Or maybe going the wrong way? 137 | if ((stepsToStop >= -distanceTo) || _direction == DIRECTION_CW) 138 | _n = -stepsToStop; // Start deceleration 139 | } 140 | else if (_n < 0) 141 | { 142 | // Currently decelerating, need to accel again? 143 | if ((stepsToStop < -distanceTo) && _direction == DIRECTION_CCW) 144 | _n = -_n; // Start accceleration 145 | } 146 | } 147 | 148 | // Need to accelerate or decelerate 149 | if (_n == 0) 150 | { 151 | // First step from stopped 152 | _cn = _c0; 153 | _direction = (distanceTo > 0) ? DIRECTION_CW : DIRECTION_CCW; 154 | } 155 | else 156 | { 157 | // Subsequent step. Works for accel (n is +_ve) and decel (n is -ve). 158 | _cn = _cn - ((2.0 * _cn) / ((4.0 * _n) + 1)); // Equation 13 159 | _cn = max(_cn, _cmin); 160 | } 161 | _n++; 162 | _stepInterval = _cn; 163 | _speed = 1000000.0 / _cn; 164 | if (_direction == DIRECTION_CCW) 165 | _speed = -_speed; 166 | 167 | #if 0 168 | Serial.println(_speed); 169 | Serial.println(_acceleration); 170 | Serial.println(_cn); 171 | Serial.println(_c0); 172 | Serial.println(_n); 173 | Serial.println(_stepInterval); 174 | Serial.println(distanceTo); 175 | Serial.println(stepsToStop); 176 | Serial.println("-----"); 177 | #endif 178 | return _stepInterval; 179 | } 180 | 181 | // Run the motor to implement speed and acceleration in order to proceed to the target position 182 | // You must call this at least once per step, preferably in your main loop 183 | // If the motor is in the desired position, the cost is very small 184 | // returns true if the motor is still running to the target position. 185 | boolean AccelStepper::run() 186 | { 187 | if (runSpeed()) 188 | computeNewSpeed(); 189 | return _speed != 0.0 || distanceToGo() != 0; 190 | } 191 | 192 | AccelStepper::AccelStepper(uint8_t interface, uint8_t pin1, uint8_t pin2, uint8_t pin3, uint8_t pin4, bool enable) 193 | { 194 | _interface = interface; 195 | _currentPos = 0; 196 | _targetPos = 0; 197 | _speed = 0.0; 198 | _maxSpeed = 0.0; 199 | _acceleration = 0.0; 200 | _sqrt_twoa = 1.0; 201 | _stepInterval = 0; 202 | _minPulseWidth = 1; 203 | _enablePin = 0xff; 204 | _lastStepTime = 0; 205 | _pin[0] = pin1; 206 | _pin[1] = pin2; 207 | _pin[2] = pin3; 208 | _pin[3] = pin4; 209 | _enableInverted = false; 210 | 211 | // NEW 212 | _n = 0; 213 | _c0 = 0.0; 214 | _cn = 0.0; 215 | _cmin = 1.0; 216 | _direction = DIRECTION_CCW; 217 | 218 | int i; 219 | for (i = 0; i < 4; i++) 220 | _pinInverted[i] = 0; 221 | if (enable) 222 | enableOutputs(); 223 | // Some reasonable default 224 | setAcceleration(1); 225 | setMaxSpeed(1); 226 | } 227 | 228 | AccelStepper::AccelStepper(void (*forward)(), void (*backward)()) 229 | { 230 | _interface = 0; 231 | _currentPos = 0; 232 | _targetPos = 0; 233 | _speed = 0.0; 234 | _maxSpeed = 0.0; 235 | _acceleration = 0.0; 236 | _sqrt_twoa = 1.0; 237 | _stepInterval = 0; 238 | _minPulseWidth = 1; 239 | _enablePin = 0xff; 240 | _lastStepTime = 0; 241 | _pin[0] = 0; 242 | _pin[1] = 0; 243 | _pin[2] = 0; 244 | _pin[3] = 0; 245 | _forward = forward; 246 | _backward = backward; 247 | 248 | // NEW 249 | _n = 0; 250 | _c0 = 0.0; 251 | _cn = 0.0; 252 | _cmin = 1.0; 253 | _direction = DIRECTION_CCW; 254 | 255 | int i; 256 | for (i = 0; i < 4; i++) 257 | _pinInverted[i] = 0; 258 | // Some reasonable default 259 | setAcceleration(1); 260 | setMaxSpeed(1); 261 | } 262 | 263 | void AccelStepper::setMaxSpeed(float speed) 264 | { 265 | if (speed < 0.0) 266 | speed = -speed; 267 | if (_maxSpeed != speed) 268 | { 269 | _maxSpeed = speed; 270 | _cmin = 1000000.0 / speed; 271 | // Recompute _n from current speed and adjust speed if accelerating or cruising 272 | if (_n > 0) 273 | { 274 | _n = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16 275 | computeNewSpeed(); 276 | } 277 | } 278 | } 279 | 280 | float AccelStepper::maxSpeed() 281 | { 282 | return _maxSpeed; 283 | } 284 | 285 | void AccelStepper::setAcceleration(float acceleration) 286 | { 287 | if (acceleration == 0.0) 288 | return; 289 | if (acceleration < 0.0) 290 | acceleration = -acceleration; 291 | if (_acceleration != acceleration) 292 | { 293 | // Recompute _n per Equation 17 294 | _n = _n * (_acceleration / acceleration); 295 | // New c0 per Equation 7, with correction per Equation 15 296 | _c0 = 0.676 * sqrt(2.0 / acceleration) * 1000000.0; // Equation 15 297 | _acceleration = acceleration; 298 | computeNewSpeed(); 299 | } 300 | } 301 | 302 | float AccelStepper::acceleration() 303 | { 304 | return _acceleration; 305 | } 306 | 307 | void AccelStepper::setSpeed(float speed) 308 | { 309 | if (speed == _speed) 310 | return; 311 | speed = constrain(speed, -_maxSpeed, _maxSpeed); 312 | if (speed == 0.0) 313 | _stepInterval = 0; 314 | else 315 | { 316 | _stepInterval = fabs(1000000.0 / speed); 317 | _direction = (speed > 0.0) ? DIRECTION_CW : DIRECTION_CCW; 318 | } 319 | _speed = speed; 320 | } 321 | 322 | float AccelStepper::speed() 323 | { 324 | return _speed; 325 | } 326 | 327 | // Subclasses can override 328 | void AccelStepper::step(long step) 329 | { 330 | switch (_interface) 331 | { 332 | case FUNCTION: 333 | step0(step); 334 | break; 335 | 336 | case DRIVER: 337 | step1(step); 338 | break; 339 | 340 | case FULL2WIRE: 341 | step2(step); 342 | break; 343 | 344 | case FULL3WIRE: 345 | step3(step); 346 | break; 347 | 348 | case FULL4WIRE: 349 | step4(step); 350 | break; 351 | 352 | case HALF3WIRE: 353 | step6(step); 354 | break; 355 | 356 | case HALF4WIRE: 357 | step8(step); 358 | break; 359 | } 360 | } 361 | 362 | long AccelStepper::stepForward() 363 | { 364 | // Clockwise 365 | _currentPos += 1; 366 | step(_currentPos); 367 | _lastStepTime = micros(); 368 | return _currentPos; 369 | } 370 | 371 | long AccelStepper::stepBackward() 372 | { 373 | // Counter-clockwise 374 | _currentPos -= 1; 375 | step(_currentPos); 376 | _lastStepTime = micros(); 377 | return _currentPos; 378 | } 379 | 380 | // You might want to override this to implement eg serial output 381 | // bit 0 of the mask corresponds to _pin[0] 382 | // bit 1 of the mask corresponds to _pin[1] 383 | // .... 384 | void AccelStepper::setOutputPins(uint8_t mask) 385 | { 386 | uint8_t numpins = 2; 387 | if (_interface == FULL4WIRE || _interface == HALF4WIRE) 388 | numpins = 4; 389 | else if (_interface == FULL3WIRE || _interface == HALF3WIRE) 390 | numpins = 3; 391 | uint8_t i; 392 | for (i = 0; i < numpins; i++) 393 | digitalWrite(_pin[i], (mask & (1 << i)) ? (HIGH ^ _pinInverted[i]) : (LOW ^ _pinInverted[i])); 394 | } 395 | 396 | // 0 pin step function (ie for functional usage) 397 | void AccelStepper::step0(long step) 398 | { 399 | (void)(step); // Unused 400 | if (_speed > 0) 401 | _forward(); 402 | else 403 | _backward(); 404 | } 405 | 406 | // 1 pin step function (ie for stepper drivers) 407 | // This is passed the current step number (0 to 7) 408 | // Subclasses can override 409 | void AccelStepper::step1(long step) 410 | { 411 | (void)(step); // Unused 412 | 413 | // _pin[0] is step, _pin[1] is direction 414 | setOutputPins(_direction ? 0b10 : 0b00); // Set direction first else get rogue pulses 415 | setOutputPins(_direction ? 0b11 : 0b01); // step HIGH 416 | // Caution 200ns setup time 417 | // Delay the minimum allowed pulse width 418 | delayMicroseconds(_minPulseWidth); 419 | setOutputPins(_direction ? 0b10 : 0b00); // step LOW 420 | } 421 | 422 | 423 | // 2 pin step function 424 | // This is passed the current step number (0 to 7) 425 | // Subclasses can override 426 | void AccelStepper::step2(long step) 427 | { 428 | switch (step & 0x3) 429 | { 430 | case 0: /* 01 */ 431 | setOutputPins(0b10); 432 | break; 433 | 434 | case 1: /* 11 */ 435 | setOutputPins(0b11); 436 | break; 437 | 438 | case 2: /* 10 */ 439 | setOutputPins(0b01); 440 | break; 441 | 442 | case 3: /* 00 */ 443 | setOutputPins(0b00); 444 | break; 445 | } 446 | } 447 | // 3 pin step function 448 | // This is passed the current step number (0 to 7) 449 | // Subclasses can override 450 | void AccelStepper::step3(long step) 451 | { 452 | switch (step % 3) 453 | { 454 | case 0: // 100 455 | setOutputPins(0b100); 456 | break; 457 | 458 | case 1: // 001 459 | setOutputPins(0b001); 460 | break; 461 | 462 | case 2: //010 463 | setOutputPins(0b010); 464 | break; 465 | 466 | } 467 | } 468 | 469 | // 4 pin step function for half stepper 470 | // This is passed the current step number (0 to 7) 471 | // Subclasses can override 472 | void AccelStepper::step4(long step) 473 | { 474 | switch (step & 0x3) 475 | { 476 | case 0: // 1010 477 | setOutputPins(0b0101); 478 | break; 479 | 480 | case 1: // 0110 481 | setOutputPins(0b0110); 482 | break; 483 | 484 | case 2: //0101 485 | setOutputPins(0b1010); 486 | break; 487 | 488 | case 3: //1001 489 | setOutputPins(0b1001); 490 | break; 491 | } 492 | } 493 | 494 | // 3 pin half step function 495 | // This is passed the current step number (0 to 7) 496 | // Subclasses can override 497 | void AccelStepper::step6(long step) 498 | { 499 | switch (step % 6) 500 | { 501 | case 0: // 100 502 | setOutputPins(0b100); 503 | break; 504 | 505 | case 1: // 101 506 | setOutputPins(0b101); 507 | break; 508 | 509 | case 2: // 001 510 | setOutputPins(0b001); 511 | break; 512 | 513 | case 3: // 011 514 | setOutputPins(0b011); 515 | break; 516 | 517 | case 4: // 010 518 | setOutputPins(0b010); 519 | break; 520 | 521 | case 5: // 011 522 | setOutputPins(0b110); 523 | break; 524 | 525 | } 526 | } 527 | 528 | // 4 pin half step function 529 | // This is passed the current step number (0 to 7) 530 | // Subclasses can override 531 | void AccelStepper::step8(long step) 532 | { 533 | switch (step & 0x7) 534 | { 535 | case 0: // 1000 536 | setOutputPins(0b0001); 537 | break; 538 | 539 | case 1: // 1010 540 | setOutputPins(0b0101); 541 | break; 542 | 543 | case 2: // 0010 544 | setOutputPins(0b0100); 545 | break; 546 | 547 | case 3: // 0110 548 | setOutputPins(0b0110); 549 | break; 550 | 551 | case 4: // 0100 552 | setOutputPins(0b0010); 553 | break; 554 | 555 | case 5: //0101 556 | setOutputPins(0b1010); 557 | break; 558 | 559 | case 6: // 0001 560 | setOutputPins(0b1000); 561 | break; 562 | 563 | case 7: //1001 564 | setOutputPins(0b1001); 565 | break; 566 | } 567 | } 568 | 569 | // Prevents power consumption on the outputs 570 | void AccelStepper::disableOutputs() 571 | { 572 | if (! _interface) return; 573 | 574 | setOutputPins(0); // Handles inversion automatically 575 | if (_enablePin != 0xff) 576 | { 577 | pinMode(_enablePin, OUTPUT); 578 | digitalWrite(_enablePin, LOW ^ _enableInverted); 579 | } 580 | } 581 | 582 | void AccelStepper::enableOutputs() 583 | { 584 | if (! _interface) 585 | return; 586 | 587 | pinMode(_pin[0], OUTPUT); 588 | pinMode(_pin[1], OUTPUT); 589 | if (_interface == FULL4WIRE || _interface == HALF4WIRE) 590 | { 591 | pinMode(_pin[2], OUTPUT); 592 | pinMode(_pin[3], OUTPUT); 593 | } 594 | else if (_interface == FULL3WIRE || _interface == HALF3WIRE) 595 | { 596 | pinMode(_pin[2], OUTPUT); 597 | } 598 | 599 | if (_enablePin != 0xff) 600 | { 601 | pinMode(_enablePin, OUTPUT); 602 | digitalWrite(_enablePin, HIGH ^ _enableInverted); 603 | } 604 | } 605 | 606 | void AccelStepper::setMinPulseWidth(unsigned int minWidth) 607 | { 608 | _minPulseWidth = minWidth; 609 | } 610 | 611 | void AccelStepper::setEnablePin(uint8_t enablePin) 612 | { 613 | _enablePin = enablePin; 614 | 615 | // This happens after construction, so init pin now. 616 | if (_enablePin != 0xff) 617 | { 618 | pinMode(_enablePin, OUTPUT); 619 | digitalWrite(_enablePin, HIGH ^ _enableInverted); 620 | } 621 | } 622 | 623 | void AccelStepper::setPinsInverted(bool directionInvert, bool stepInvert, bool enableInvert) 624 | { 625 | _pinInverted[0] = stepInvert; 626 | _pinInverted[1] = directionInvert; 627 | _enableInverted = enableInvert; 628 | } 629 | 630 | void AccelStepper::setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert) 631 | { 632 | _pinInverted[0] = pin1Invert; 633 | _pinInverted[1] = pin2Invert; 634 | _pinInverted[2] = pin3Invert; 635 | _pinInverted[3] = pin4Invert; 636 | _enableInverted = enableInvert; 637 | } 638 | 639 | // Blocks until the target position is reached and stopped 640 | void AccelStepper::runToPosition() 641 | { 642 | while (run()) 643 | YIELD; // Let system housekeeping occur 644 | } 645 | 646 | boolean AccelStepper::runSpeedToPosition() 647 | { 648 | if (_targetPos == _currentPos) 649 | return false; 650 | if (_targetPos >_currentPos) 651 | _direction = DIRECTION_CW; 652 | else 653 | _direction = DIRECTION_CCW; 654 | return runSpeed(); 655 | } 656 | 657 | // Blocks until the new target position is reached 658 | void AccelStepper::runToNewPosition(long position) 659 | { 660 | moveTo(position); 661 | runToPosition(); 662 | } 663 | 664 | void AccelStepper::stop() 665 | { 666 | if (_speed != 0.0) 667 | { 668 | long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)) + 1; // Equation 16 (+integer rounding) 669 | if (_speed > 0) 670 | move(stepsToStop); 671 | else 672 | move(-stepsToStop); 673 | } 674 | } 675 | 676 | bool AccelStepper::isRunning() 677 | { 678 | return !(_speed == 0.0 && _targetPos == _currentPos); 679 | } 680 | -------------------------------------------------------------------------------- /TurntableFunctions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023 Peter Cole 3 | * 4 | * This file is part of EX-Turntable 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with EX-Turntable. If not, see . 18 | */ 19 | 20 | /*============================================================= 21 | * This file contains all functions pertinent to turntable 22 | * operation including stepper movements, relay phase switching, 23 | * and LED/accessory related functions. 24 | =============================================================*/ 25 | 26 | #include "TurntableFunctions.h" 27 | #include "IOFunctions.h" 28 | 29 | const long sanitySteps = SANITY_STEPS; // Define an arbitrary number of steps to prevent indefinite spinning if homing/calibrations fails. 30 | const uint8_t limitSensorPin = 2; // Define pin 2 for the traverser mode limit sensor. 31 | const uint8_t homeSensorPin = 5; // Define pin 5 for the home sensor. 32 | const uint8_t relay1Pin = 3; // Control pin for relay 1. 33 | const uint8_t relay2Pin = 4; // Control pin for relay 2. 34 | const uint8_t ledPin = 6; // Pin for LED output. 35 | const uint8_t accPin = 7; // Pin for accessory output. 36 | const long homeSensitivity = HOME_SENSITIVITY; // Define the minimum number of steps required before homing sensor deactivates. 37 | const int16_t totalMinutes = 21600; // Total minutes in one rotation (360 * 60) 38 | 39 | long lastStep = 0; // Holds the last step value we moved to (enables least distance moves). 40 | uint8_t homed = 0; // Flag to indicate homing state: 0 = not homed, 1 = homed, 2 = failed. 41 | long fullTurnSteps; // Assign our defined full turn steps from config.h. 42 | long halfTurnSteps; // Defines a half turn to enable moving the least distance. 43 | long phaseSwitchStartSteps; // Defines the step count at which phase should automatically invert. 44 | long phaseSwitchStopSteps; // Defines the step count at which phase should automatically revert. 45 | long lastTarget = sanitySteps; // Holds the last step target (prevents continuous rotation if homing fails). 46 | uint8_t ledState = 7; // Flag for the LED state: 4 on, 5 slow, 6 fast, 7 off. 47 | bool ledOutput = LOW; // Boolean for the actual state of the output LED pin. 48 | unsigned long ledMillis = 0; // Required for non blocking LED blink rate timing. 49 | bool calibrating = false; // Flag to prevent other rotation activities during calibration. 50 | uint8_t calibrationPhase = 0; // Flag for calibration phase. 51 | unsigned long calMillis = 0; // Required for non blocking calibration pauses. 52 | bool homeSensorState; // Stores the current home sensor state. 53 | bool limitSensorState; // Stores the current limit sensor state. 54 | bool lastHomeSensorState; // Stores the last home sensor state. 55 | bool lastLimitSensorState; // Stores the last limit sensor state. 56 | unsigned long lastLimitDebounce = 0; // Stores the last time the limit sensor switched for debouncing. 57 | unsigned long lastHomeDebounce = 0; // Stores the last time the home sensor switched for debouncing. 58 | #ifdef INVERT_DIRECTION 59 | bool invertDirection = true; 60 | #else 61 | bool invertDirection = false; 62 | #endif 63 | #ifdef INVERT_STEP 64 | bool invertStep = true; 65 | #else 66 | bool invertStep = false; 67 | #endif 68 | #ifdef INVERT_ENABLE 69 | bool invertEnable = true; 70 | #else 71 | bool invertEnable = false; 72 | #endif 73 | 74 | AccelStepper stepper = STEPPER_DRIVER; 75 | 76 | // Function configure sensor pins 77 | void startupConfiguration() { 78 | #if SELECTED_DRIVER == A4988_DRIVER 79 | if (debug) { 80 | Serial.println(F("DEBUG: invertDirection|invertStep|invertEnable: ")); 81 | Serial.print(invertDirection); 82 | Serial.print(F("|")); 83 | Serial.print(invertStep); 84 | Serial.print(F("|")); 85 | Serial.println(invertEnable); 86 | } 87 | stepper.setEnablePin(A2); 88 | stepper.setPinsInverted(invertDirection, invertStep, invertEnable); 89 | #endif 90 | #if HOME_SENSOR_ACTIVE_STATE == LOW 91 | pinMode(homeSensorPin, INPUT_PULLUP); 92 | #elif HOME_SENSOR_ACTIVE_STATE == HIGH 93 | pinMode(homeSensorPin, INPUT); 94 | #endif 95 | #if TURNTABLE_EX_MODE == TRAVERSER 96 | // Configure limit sensor pin in traverser mode 97 | #if LIMIT_SENSOR_ACTIVE_STATE == LOW 98 | pinMode(limitSensorPin, INPUT_PULLUP); 99 | #elif LIMIT_SENSOR_ACTIVE_STATE == HIGH 100 | pinMode(limitSensorPin, INPUT); 101 | #endif 102 | #endif 103 | // Get the current sensor state 104 | lastHomeSensorState = digitalRead(homeSensorPin); 105 | homeSensorState = getHomeState(); 106 | #if TURNTABLE_EX_MODE == TRAVERSER 107 | lastLimitSensorState = digitalRead(limitSensorPin); 108 | limitSensorState = getLimitState(); 109 | #endif 110 | 111 | // Configure relay output pins 112 | pinMode(relay1Pin, OUTPUT); 113 | pinMode(relay2Pin, OUTPUT); 114 | 115 | // Ensure relays are inactive on startup 116 | setPhase(0); 117 | 118 | // Configure LED and accessory output pins 119 | pinMode(ledPin, OUTPUT); 120 | pinMode(accPin, OUTPUT); 121 | 122 | // If step count explicitly defined, use that 123 | #ifdef FULL_STEP_COUNT 124 | fullTurnSteps = FULL_STEP_COUNT; 125 | #else 126 | // Else read steps from EEPROM 127 | fullTurnSteps = getSteps(); 128 | #endif 129 | halfTurnSteps = fullTurnSteps / 2; 130 | 131 | #if PHASE_SWITCHING == AUTO 132 | // Calculate phase invert/revert steps 133 | processAutoPhaseSwitch(); 134 | #endif 135 | } 136 | 137 | // Function to define the stepper parameters. 138 | void setupStepperDriver() { 139 | stepper.setMaxSpeed(STEPPER_MAX_SPEED); 140 | stepper.setAcceleration(STEPPER_ACCELERATION); 141 | } 142 | 143 | // Function to find the home position. 144 | void moveHome() { 145 | setPhase(0); 146 | if (getHomeState() == HOME_SENSOR_ACTIVE_STATE) { 147 | stepper.stop(); 148 | #if defined(DISABLE_OUTPUTS_IDLE) 149 | stepper.disableOutputs(); 150 | #endif 151 | stepper.setCurrentPosition(0); 152 | lastStep = 0; 153 | homed = 1; 154 | Serial.println(F("Turntable homed successfully")); 155 | if (debug) { 156 | Serial.print(F("DEBUG: Stored values for lastStep/lastTarget: ")); 157 | Serial.print(lastStep); 158 | Serial.print(F("/")); 159 | Serial.println(lastTarget); 160 | } 161 | } else if(!stepper.isRunning()) { 162 | if (debug) { 163 | Serial.print(F("DEBUG: Recorded/last actual target: ")); 164 | Serial.print(lastTarget); 165 | Serial.print(F("/")); 166 | Serial.println(stepper.targetPosition()); 167 | } 168 | if (stepper.targetPosition() == lastTarget) { 169 | stepper.setCurrentPosition(0); 170 | lastStep = 0; 171 | homed = 2; 172 | Serial.println(F("ERROR: Turntable failed to home, setting random home position")); 173 | } else { 174 | stepper.enableOutputs(); 175 | stepper.move(sanitySteps); 176 | lastTarget = stepper.targetPosition(); 177 | if (debug) { 178 | Serial.print(F("DEBUG: lastTarget: ")); 179 | Serial.println(lastTarget); 180 | } 181 | Serial.println(F("Homing started")); 182 | } 183 | } 184 | } 185 | 186 | // Function to move to the indicated position. 187 | void moveToPosition(long steps, uint8_t phaseSwitch) { 188 | if (steps != lastStep) { 189 | Serial.print(F("Received notification to move to step postion ")); 190 | Serial.println(steps); 191 | long moveSteps; 192 | Serial.print(F("Position steps: ")); 193 | Serial.print(steps); 194 | #if PHASE_SWITCHING == AUTO 195 | Serial.print(F(", Auto phase switch")); 196 | #else 197 | Serial.print(F(", Phase switch flag: ")); 198 | Serial.print(phaseSwitch); 199 | #endif 200 | #if TURNTABLE_EX_MODE == TRAVERSER 201 | // If we're in traverser mode, very simple logic, negative move to limit, positive move to home. 202 | moveSteps = lastStep - steps; 203 | #else 204 | // In turntable mode we can force always moving forwards or reverse, or (default) shortest distance 205 | #if defined(ROTATE_FORWARD_ONLY) 206 | if (debug) Serial.println(F("Force forward move only")); 207 | moveSteps = steps - lastStep; 208 | if (moveSteps < 0) { 209 | moveSteps += fullTurnSteps; 210 | } 211 | #elif defined(ROTATE_REVERSE_ONLY) 212 | if (debug) Serial.println(F("Force reverse move only")); 213 | moveSteps = steps - lastStep; 214 | if (moveSteps > 0) { 215 | moveSteps -= fullTurnSteps; 216 | } 217 | #else 218 | if ((steps - lastStep) > halfTurnSteps) { 219 | moveSteps = steps - fullTurnSteps - lastStep; 220 | } else if ((steps - lastStep) < -halfTurnSteps) { 221 | moveSteps = fullTurnSteps - lastStep + steps; 222 | } else { 223 | moveSteps = steps - lastStep; 224 | } 225 | #endif // Turntable forward/reverse/shortest distance 226 | #endif // Turntable/traverser 227 | Serial.print(F(" - moving ")); 228 | Serial.print(moveSteps); 229 | Serial.println(F(" steps")); 230 | #if PHASE_SWITCHING == AUTO 231 | if ((steps >= 0 && steps < phaseSwitchStartSteps) || (steps <= fullTurnSteps && steps >= phaseSwitchStopSteps)) { 232 | phaseSwitch = 0; 233 | } else { 234 | phaseSwitch = 1; 235 | } 236 | #endif 237 | Serial.print(F("Setting phase switch flag to: ")); 238 | Serial.println(phaseSwitch); 239 | setPhase(phaseSwitch); 240 | lastStep = steps; 241 | stepper.enableOutputs(); 242 | stepper.move(moveSteps); 243 | lastTarget = stepper.targetPosition(); 244 | if (debug) { 245 | Serial.print(F("DEBUG: Stored values for lastStep/lastTarget: ")); 246 | Serial.print(lastStep); 247 | Serial.print(F("/")); 248 | Serial.println(lastTarget); 249 | } 250 | } 251 | } 252 | 253 | // Function to set phase. 254 | void setPhase(uint8_t phase) { 255 | #if RELAY_ACTIVE_STATE == HIGH 256 | digitalWrite(relay1Pin, phase); 257 | digitalWrite(relay2Pin, phase); 258 | #elif RELAY_ACTIVE_STATE == LOW 259 | digitalWrite(relay1Pin, !phase); 260 | digitalWrite(relay2Pin, !phase); 261 | #endif 262 | } 263 | 264 | // Function to set/maintain our LED state for on/blink/off. 265 | // 4 = on, 5 = slow blink, 6 = fast blink, 7 = off. 266 | void processLED() { 267 | uint16_t currentMillis = millis(); 268 | if (ledState == 4 ) { 269 | ledOutput = 1; 270 | } else if (ledState == 7) { 271 | ledOutput = 0; 272 | } else if (ledState == 5 && currentMillis - ledMillis >= LED_SLOW) { 273 | ledOutput = !ledOutput; 274 | ledMillis = currentMillis; 275 | } else if (ledState == 6 && currentMillis - ledMillis >= LED_FAST) { 276 | ledOutput = !ledOutput; 277 | ledMillis = currentMillis; 278 | } 279 | digitalWrite(ledPin, ledOutput); 280 | } 281 | 282 | // The calibration function is used to determine the number of steps required for a single 360 degree rotation, 283 | // or, in traverser mode, the steps between the home and limit switches. 284 | // This should only be trigged when either there are no stored steps in EEPROM, the stored steps are invalid, 285 | // or the calibration command has been initiated by the CommandStation. 286 | // Logic: 287 | // - Move away from home if already homed and erase EEPROM. 288 | // - Perform initial home rotation, set to 0 steps when homed. 289 | // - Perform second home rotation, set steps to currentPosition(). 290 | // - Write steps to EEPROM. 291 | void calibration() { 292 | setPhase(0); 293 | #if TURNTABLE_EX_MODE == TRAVERSER 294 | if (calibrationPhase == 3 && getLimitState() != LIMIT_SENSOR_ACTIVE_STATE) { 295 | #else 296 | if (calibrationPhase == 2 && getHomeState() == HOME_SENSOR_ACTIVE_STATE && stepper.currentPosition() > homeSensitivity) { 297 | #endif 298 | stepper.stop(); 299 | #if defined(DISABLE_OUTPUTS_IDLE) 300 | stepper.disableOutputs(); 301 | #endif 302 | fullTurnSteps = stepper.currentPosition(); 303 | if (fullTurnSteps < 0) { 304 | fullTurnSteps = -fullTurnSteps; 305 | } 306 | halfTurnSteps = fullTurnSteps / 2; 307 | #if PHASE_SWITCHING == AUTO 308 | processAutoPhaseSwitch(); 309 | #endif 310 | calibrating = false; 311 | calibrationPhase = 0; 312 | writeEEPROM(fullTurnSteps); 313 | Serial.print(F("CALIBRATION: Completed, storing full turn step count: ")); 314 | Serial.println(fullTurnSteps); 315 | stepper.setCurrentPosition(stepper.currentPosition()); 316 | homed = 0; 317 | lastTarget = sanitySteps; 318 | displayTTEXConfig(); 319 | #if TURNTABLE_EX_MODE == TRAVERSER 320 | } else if (calibrationPhase == 2 && getLimitState() == LIMIT_SENSOR_ACTIVE_STATE) { 321 | // In TRAVERSER mode, we want our full step count to stop short of the limit switch, so need phase 3 to move away. 322 | stepper.stop(); 323 | stepper.setCurrentPosition(stepper.currentPosition()); 324 | Serial.println(F("CALIBRATION: Phase 3, counting limit steps...")); 325 | stepper.moveTo(0); 326 | lastStep = 0; 327 | calibrationPhase = 3; 328 | #endif 329 | #if TURNTABLE_EX_MODE == TRAVERSER 330 | } else if (calibrationPhase == 1 && lastStep == sanitySteps && getHomeState() == HOME_SENSOR_ACTIVE_STATE) { 331 | Serial.println(F("CALIBRATION: Phase 2, finding limit switch...")); 332 | #else 333 | } else if (calibrationPhase == 1 && lastStep == sanitySteps && getHomeState() == HOME_SENSOR_ACTIVE_STATE && stepper.currentPosition() > homeSensitivity) { 334 | Serial.println(F("CALIBRATION: Phase 2, counting full turn steps...")); 335 | #endif 336 | stepper.stop(); 337 | stepper.setCurrentPosition(0); 338 | calibrationPhase = 2; 339 | stepper.enableOutputs(); 340 | #if TURNTABLE_EX_MODE == TRAVERSER 341 | stepper.moveTo(-sanitySteps); 342 | lastStep = sanitySteps; 343 | #else 344 | stepper.moveTo(sanitySteps); 345 | lastStep = sanitySteps; 346 | #endif 347 | } else if (calibrationPhase == 0 && !stepper.isRunning() && homed == 1) { 348 | Serial.println(F("CALIBRATION: Phase 1, homing...")); 349 | calibrationPhase = 1; 350 | #if TURNTABLE_EX_MODE == TRAVERSER 351 | if (getHomeState() == HOME_SENSOR_ACTIVE_STATE) { 352 | Serial.println(F("Turntable already homed")); 353 | } else { 354 | stepper.enableOutputs(); 355 | stepper.moveTo(sanitySteps); 356 | } 357 | #else 358 | stepper.enableOutputs(); 359 | stepper.moveTo(sanitySteps); 360 | #endif 361 | lastStep = sanitySteps; 362 | } else if ((calibrationPhase == 2 || calibrationPhase == 1) && !stepper.isRunning() && stepper.currentPosition() == sanitySteps) { 363 | Serial.println(F("CALIBRATION: FAILED, could not home, could not determine step count")); 364 | #if defined(DISABLE_OUTPUTS_IDLE) 365 | stepper.disableOutputs(); 366 | #endif 367 | calibrating = false; 368 | calibrationPhase = 0; 369 | } 370 | } 371 | 372 | // If phase switching is set to auto, calculate the trigger point steps based on the angle. 373 | #if PHASE_SWITCHING == AUTO 374 | void processAutoPhaseSwitch() { 375 | if (PHASE_SWITCH_ANGLE + 180 >= 360) { 376 | Serial.print(F("ERROR: The defined phase switch angle of ")); 377 | Serial.print(PHASE_SWITCH_ANGLE); 378 | Serial.println(F(" degrees is invalid, setting to default 45 degrees")); 379 | } 380 | #if PHASE_SWITCH_ANGLE + 180 >= 360 381 | #undef PHASE_SWITCH_ANGLE 382 | #define PHASE_SWITCH_ANGLE 45 383 | #endif 384 | phaseSwitchStartSteps = fullTurnSteps / 360 * PHASE_SWITCH_ANGLE; 385 | phaseSwitchStopSteps = fullTurnSteps / 360 * (PHASE_SWITCH_ANGLE + 180); 386 | } 387 | #endif 388 | 389 | // Function to debounce and get the state of the homing sensor 390 | bool getHomeState() { 391 | bool newHomeSensorState = digitalRead(homeSensorPin); 392 | if (newHomeSensorState != lastHomeSensorState && (millis() - lastHomeDebounce) > DEBOUNCE_DELAY) { 393 | lastHomeDebounce = millis(); 394 | lastHomeSensorState = newHomeSensorState; 395 | } 396 | return lastHomeSensorState; 397 | } 398 | 399 | // Function to debounce and get the state of the limit sensor 400 | bool getLimitState() { 401 | bool newLimitSensorState = digitalRead(limitSensorPin); 402 | if (newLimitSensorState != lastLimitSensorState && (millis() - lastLimitDebounce) > DEBOUNCE_DELAY) { 403 | lastLimitDebounce = millis(); 404 | lastLimitSensorState = newLimitSensorState; 405 | } 406 | return lastLimitSensorState; 407 | } 408 | 409 | // Function to reset home state, triggering homing to happen 410 | void initiateHoming() { 411 | homed = 0; 412 | lastTarget = sanitySteps; 413 | } 414 | 415 | // Function to trigger calibration to begin 416 | void initiateCalibration() { 417 | calibrating = true; 418 | homed = 0; 419 | lastTarget = sanitySteps; 420 | clearEEPROM(); 421 | } 422 | 423 | // Function to set LED activity 424 | void setLEDActivity(uint8_t activity) { 425 | ledState = activity; 426 | } 427 | 428 | // Function to set the state of the accessory pin 429 | void setAccessory(bool state) { 430 | digitalWrite(accPin, state); 431 | } 432 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /AccelStepper.h: -------------------------------------------------------------------------------- 1 | // AccelStepper.h 2 | // 3 | /// \mainpage AccelStepper library for Arduino 4 | /// 5 | /// This is the Arduino AccelStepper library. 6 | /// It provides an object-oriented interface for 2, 3 or 4 pin stepper motors and motor drivers. 7 | /// 8 | /// The standard Arduino IDE includes the Stepper library 9 | /// (http://arduino.cc/en/Reference/Stepper) for stepper motors. It is 10 | /// perfectly adequate for simple, single motor applications. 11 | /// 12 | /// AccelStepper significantly improves on the standard Arduino Stepper library in several ways: 13 | /// \li Supports acceleration and deceleration 14 | /// \li Supports multiple simultaneous steppers, with independent concurrent stepping on each stepper 15 | /// \li Most API functions never delay() or block (unless otherwise stated) 16 | /// \li Supports 2, 3 and 4 wire steppers, plus 3 and 4 wire half steppers. 17 | /// \li Supports alternate stepping functions to enable support of AFMotor (https://github.com/adafruit/Adafruit-Motor-Shield-library) 18 | /// \li Supports stepper drivers such as the Sparkfun EasyDriver (based on 3967 driver chip) 19 | /// \li Very slow speeds are supported 20 | /// \li Extensive API 21 | /// \li Subclass support 22 | /// 23 | /// The latest version of this documentation can be downloaded from 24 | /// http://www.airspayce.com/mikem/arduino/AccelStepper 25 | /// The version of the package that this documentation refers to can be downloaded 26 | /// from http://www.airspayce.com/mikem/arduino/AccelStepper/AccelStepper-1.64.zip 27 | /// 28 | /// Example Arduino programs are included to show the main modes of use. 29 | /// 30 | /// You can also find online help and discussion at http://groups.google.com/group/accelstepper 31 | /// Please use that group for all questions and discussions on this topic. 32 | /// Do not contact the author directly, unless it is to discuss commercial licensing. 33 | /// Before asking a question or reporting a bug, please read 34 | /// - http://en.wikipedia.org/wiki/Wikipedia:Reference_desk/How_to_ask_a_software_question 35 | /// - http://www.catb.org/esr/faqs/smart-questions.html 36 | /// - http://www.chiark.greenend.org.uk/~shgtatham/bugs.html 37 | /// 38 | /// Beginners to C++ and stepper motors in general may find this helpful: 39 | /// - https://hackaday.io/project/183279-accelstepper-the-missing-manual 40 | /// - https://hackaday.io/project/183713-using-the-arduino-accelstepper-library 41 | /// 42 | /// Tested on Arduino Diecimila and Mega with arduino-0018 & arduino-0021 43 | /// on OpenSuSE 11.1 and avr-libc-1.6.1-1.15, 44 | /// cross-avr-binutils-2.19-9.1, cross-avr-gcc-4.1.3_20080612-26.5. 45 | /// Tested on Teensy http://www.pjrc.com/teensy including Teensy 3.1 built using Arduino IDE 1.0.5 with 46 | /// teensyduino addon 1.18 and later. 47 | /// 48 | /// \par Installation 49 | /// 50 | /// Install in the usual way: unzip the distribution zip file to the libraries 51 | /// sub-folder of your sketchbook. 52 | /// 53 | /// \par Theory 54 | /// 55 | /// This code uses speed calculations as described in 56 | /// "Generate stepper-motor speed profiles in real time" by David Austin 57 | /// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf or 58 | /// http://www.embedded.com/design/mcus-processors-and-socs/4006438/Generate-stepper-motor-speed-profiles-in-real-time or 59 | /// http://web.archive.org/web/20140705143928/http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf 60 | /// with the exception that AccelStepper uses steps per second rather than radians per second 61 | /// (because we dont know the step angle of the motor) 62 | /// An initial step interval is calculated for the first step, based on the desired acceleration 63 | /// On subsequent steps, shorter step intervals are calculated based 64 | /// on the previous step until max speed is achieved. 65 | /// 66 | /// \par Adafruit Motor Shield V2 67 | /// 68 | /// The included examples AFMotor_* are for Adafruit Motor Shield V1 and do not work with Adafruit Motor Shield V2. 69 | /// See https://github.com/adafruit/Adafruit_Motor_Shield_V2_Library for examples that work with Adafruit Motor Shield V2. 70 | /// 71 | /// \par Donations 72 | /// 73 | /// This library is offered under a free GPL license for those who want to use it that way. 74 | /// We try hard to keep it up to date, fix bugs 75 | /// and to provide free support. If this library has helped you save time or money, please consider donating at 76 | /// http://www.airspayce.com or here: 77 | /// 78 | /// \htmlonly
\endhtmlonly 79 | /// 80 | /// \par Trademarks 81 | /// 82 | /// AccelStepper is a trademark of AirSpayce Pty Ltd. The AccelStepper mark was first used on April 26 2010 for 83 | /// international trade, and is used only in relation to motor control hardware and software. 84 | /// It is not to be confused with any other similar marks covering other goods and services. 85 | /// 86 | /// \par Copyright 87 | /// 88 | /// This software is Copyright (C) 2010-2021 Mike McCauley. Use is subject to license 89 | /// conditions. The main licensing options available are GPL V3 or Commercial: 90 | /// 91 | /// \par Open Source Licensing GPL V3 92 | /// This is the appropriate option if you want to share the source code of your 93 | /// application with everyone you distribute it to, and you also want to give them 94 | /// the right to share who uses it. If you wish to use this software under Open 95 | /// Source Licensing, you must contribute all your source code to the open source 96 | /// community in accordance with the GPL Version 23 when your application is 97 | /// distributed. See https://www.gnu.org/licenses/gpl-3.0.html 98 | /// 99 | /// \par Commercial Licensing 100 | /// This is the appropriate option if you are creating proprietary applications 101 | /// and you are not prepared to distribute and share the source code of your 102 | /// application. To purchase a commercial license, contact info@airspayce.com 103 | /// 104 | /// \par Revision History 105 | /// \version 1.0 Initial release 106 | /// 107 | /// \version 1.1 Added speed() function to get the current speed. 108 | /// \version 1.2 Added runSpeedToPosition() submitted by Gunnar Arndt. 109 | /// \version 1.3 Added support for stepper drivers (ie with Step and Direction inputs) with _pins == 1 110 | /// \version 1.4 Added functional contructor to support AFMotor, contributed by Limor, with example sketches. 111 | /// \version 1.5 Improvements contributed by Peter Mousley: Use of microsecond steps and other speed improvements 112 | /// to increase max stepping speed to about 4kHz. New option for user to set the min allowed pulse width. 113 | /// Added checks for already running at max speed and skip further calcs if so. 114 | /// \version 1.6 Fixed a problem with wrapping of microsecond stepping that could cause stepping to hang. 115 | /// Reported by Sandy Noble. 116 | /// Removed redundant _lastRunTime member. 117 | /// \version 1.7 Fixed a bug where setCurrentPosition() did not always work as expected. 118 | /// Reported by Peter Linhart. 119 | /// \version 1.8 Added support for 4 pin half-steppers, requested by Harvey Moon 120 | /// \version 1.9 setCurrentPosition() now also sets motor speed to 0. 121 | /// \version 1.10 Builds on Arduino 1.0 122 | /// \version 1.11 Improvments from Michael Ellison: 123 | /// Added optional enable line support for stepper drivers 124 | /// Added inversion for step/direction/enable lines for stepper drivers 125 | /// \version 1.12 Announce Google Group 126 | /// \version 1.13 Improvements to speed calculation. Cost of calculation is now less in the worst case, 127 | /// and more or less constant in all cases. This should result in slightly beter high speed performance, and 128 | /// reduce anomalous speed glitches when other steppers are accelerating. 129 | /// However, its hard to see how to replace the sqrt() required at the very first step from 0 speed. 130 | /// \version 1.14 Fixed a problem with compiling under arduino 0021 reported by EmbeddedMan 131 | /// \version 1.15 Fixed a problem with runSpeedToPosition which did not correctly handle 132 | /// running backwards to a smaller target position. Added examples 133 | /// \version 1.16 Fixed some cases in the code where abs() was used instead of fabs(). 134 | /// \version 1.17 Added example ProportionalControl 135 | /// \version 1.18 Fixed a problem: If one calls the funcion runSpeed() when Speed is zero, it makes steps 136 | /// without counting. reported by Friedrich, Klappenbach. 137 | /// \version 1.19 Added MotorInterfaceType and symbolic names for the number of pins to use 138 | /// for the motor interface. Updated examples to suit. 139 | /// Replaced individual pin assignment variables _pin1, _pin2 etc with array _pin[4]. 140 | /// _pins member changed to _interface. 141 | /// Added _pinInverted array to simplify pin inversion operations. 142 | /// Added new function setOutputPins() which sets the motor output pins. 143 | /// It can be overridden in order to provide, say, serial output instead of parallel output 144 | /// Some refactoring and code size reduction. 145 | /// \version 1.20 Improved documentation and examples to show need for correctly 146 | /// specifying AccelStepper::FULL4WIRE and friends. 147 | /// \version 1.21 Fixed a problem where desiredSpeed could compute the wrong step acceleration 148 | /// when _speed was small but non-zero. Reported by Brian Schmalz. 149 | /// Precompute sqrt_twoa to improve performance and max possible stepping speed 150 | /// \version 1.22 Added Bounce.pde example 151 | /// Fixed a problem where calling moveTo(), setMaxSpeed(), setAcceleration() more 152 | /// frequently than the step time, even 153 | /// with the same values, would interfere with speed calcs. Now a new speed is computed 154 | /// only if there was a change in the set value. Reported by Brian Schmalz. 155 | /// \version 1.23 Rewrite of the speed algorithms in line with 156 | /// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf 157 | /// Now expect smoother and more linear accelerations and decelerations. The desiredSpeed() 158 | /// function was removed. 159 | /// \version 1.24 Fixed a problem introduced in 1.23: with runToPosition, which did never returned 160 | /// \version 1.25 Now ignore attempts to set acceleration to 0.0 161 | /// \version 1.26 Fixed a problem where certina combinations of speed and accelration could cause 162 | /// oscillation about the target position. 163 | /// \version 1.27 Added stop() function to stop as fast as possible with current acceleration parameters. 164 | /// Also added new Quickstop example showing its use. 165 | /// \version 1.28 Fixed another problem where certain combinations of speed and acceleration could cause 166 | /// oscillation about the target position. 167 | /// Added support for 3 wire full and half steppers such as Hard Disk Drive spindle. 168 | /// Contributed by Yuri Ivatchkovitch. 169 | /// \version 1.29 Fixed a problem that could cause a DRIVER stepper to continually step 170 | /// with some sketches. Reported by Vadim. 171 | /// \version 1.30 Fixed a problem that could cause stepper to back up a few steps at the end of 172 | /// accelerated travel with certain speeds. Reported and patched by jolo. 173 | /// \version 1.31 Updated author and distribution location details to airspayce.com 174 | /// \version 1.32 Fixed a problem with enableOutputs() and setEnablePin on Arduino Due that 175 | /// prevented the enable pin changing stae correctly. Reported by Duane Bishop. 176 | /// \version 1.33 Fixed an error in example AFMotor_ConstantSpeed.pde did not setMaxSpeed(); 177 | /// Fixed a problem that caused incorrect pin sequencing of FULL3WIRE and HALF3WIRE. 178 | /// Unfortunately this meant changing the signature for all step*() functions. 179 | /// Added example MotorShield, showing how to use AdaFruit Motor Shield to control 180 | /// a 3 phase motor such as a HDD spindle motor (and without using the AFMotor library. 181 | /// \version 1.34 Added setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert) 182 | /// to allow inversion of 2, 3 and 4 wire stepper pins. Requested by Oleg. 183 | /// \version 1.35 Removed default args from setPinsInverted(bool, bool, bool, bool, bool) to prevent ambiguity with 184 | /// setPinsInverted(bool, bool, bool). Reported by Mac Mac. 185 | /// \version 1.36 Changed enableOutputs() and disableOutputs() to be virtual so can be overridden. 186 | /// Added new optional argument 'enable' to constructor, which allows you toi disable the 187 | /// automatic enabling of outputs at construction time. Suggested by Guido. 188 | /// \version 1.37 Fixed a problem with step1 that could cause a rogue step in the 189 | /// wrong direction (or not, 190 | /// depending on the setup-time requirements of the connected hardware). 191 | /// Reported by Mark Tillotson. 192 | /// \version 1.38 run() function incorrectly always returned true. Updated function and doc so it returns true 193 | /// if the motor is still running to the target position. 194 | /// \version 1.39 Updated typos in keywords.txt, courtesey Jon Magill. 195 | /// \version 1.40 Updated documentation, including testing on Teensy 3.1 196 | /// \version 1.41 Fixed an error in the acceleration calculations, resulting in acceleration of haldf the intended value 197 | /// \version 1.42 Improved support for FULL3WIRE and HALF3WIRE output pins. These changes were in Yuri's original 198 | /// contribution but did not make it into production.
199 | /// \version 1.43 Added DualMotorShield example. Shows how to use AccelStepper to control 2 x 2 phase steppers using the 200 | /// Itead Studio Arduino Dual Stepper Motor Driver Shield model IM120417015.
201 | /// \version 1.44 examples/DualMotorShield/DualMotorShield.ino examples/DualMotorShield/DualMotorShield.pde 202 | /// was missing from the distribution.
203 | /// \version 1.45 Fixed a problem where if setAcceleration was not called, there was no default 204 | /// acceleration. Reported by Michael Newman.
205 | /// \version 1.45 Fixed inaccuracy in acceleration rate by using Equation 15, suggested by Sebastian Gracki.
206 | /// Performance improvements in runSpeed suggested by Jaakko Fagerlund.
207 | /// \version 1.46 Fixed error in documentation for runToPosition(). 208 | /// Reinstated time calculations in runSpeed() since new version is reported 209 | /// not to work correctly under some circumstances. Reported by Oleg V Gavva.
210 | /// \version 1.48 2015-08-25 211 | /// Added new class MultiStepper that can manage multiple AccelSteppers, 212 | /// and cause them all to move 213 | /// to selected positions at such a (constant) speed that they all arrive at their 214 | /// target position at the same time. Suitable for X-Y flatbeds etc.
215 | /// Added new method maxSpeed() to AccelStepper to return the currently configured maxSpeed.
216 | /// \version 1.49 2016-01-02 217 | /// Testing with VID28 series instrument stepper motors and EasyDriver. 218 | /// OK, although with light pointers 219 | /// and slow speeds like 180 full steps per second the motor movement can be erratic, 220 | /// probably due to some mechanical resonance. Best to accelerate through this speed.
221 | /// Added isRunning().
222 | /// \version 1.50 2016-02-25 223 | /// AccelStepper::disableOutputs now sets the enable pion to OUTPUT mode if the enable pin is defined. 224 | /// Patch from Piet De Jong.
225 | /// Added notes about the fact that AFMotor_* examples do not work with Adafruit Motor Shield V2.
226 | /// \version 1.51 2016-03-24 227 | /// Fixed a problem reported by gregor: when resetting the stepper motor position using setCurrentPosition() the 228 | /// stepper speed is reset by setting _stepInterval to 0, but _speed is not 229 | /// reset. this results in the stepper motor not starting again when calling 230 | /// setSpeed() with the same speed the stepper was set to before. 231 | /// \version 1.52 2016-08-09 232 | /// Added MultiStepper to keywords.txt. 233 | /// Improvements to efficiency of AccelStepper::runSpeed() as suggested by David Grayson. 234 | /// Improvements to speed accuracy as suggested by David Grayson. 235 | /// \version 1.53 2016-08-14 236 | /// Backed out Improvements to speed accuracy from 1.52 as it did not work correctly. 237 | /// \version 1.54 2017-01-24 238 | /// Fixed some warnings about unused arguments. 239 | /// \version 1.55 2017-01-25 240 | /// Fixed another warning in MultiStepper.cpp 241 | /// \version 1.56 2017-02-03 242 | /// Fixed minor documentation error with DIRECTION_CCW and DIRECTION_CW. Reported by David Mutterer. 243 | /// Added link to Binpress commercial license purchasing. 244 | /// \version 1.57 2017-03-28 245 | /// _direction moved to protected at the request of Rudy Ercek. 246 | /// setMaxSpeed() and setAcceleration() now correct negative values to be positive. 247 | /// \version 1.58 2018-04-13 248 | /// Add initialisation for _enableInverted in constructor. 249 | /// \version 1.59 2018-08-28 250 | /// Update commercial licensing, remove binpress. 251 | /// \version 1.60 2020-03-07 252 | /// Release under GPL V3 253 | /// \version 1.61 2020-04-20 254 | /// Added yield() call in runToPosition(), so that platforms like esp8266 dont hang/crash 255 | /// during long runs. 256 | /// \version 1.62 2022-05-22 257 | /// Added link to AccelStepper - The Missing Manual.
258 | /// Fixed a problem when setting the maxSpeed to 1.0 due to incomplete initialisation. 259 | /// Reported by Olivier Pécheux.
260 | /// \version 1.63 2022-06-30 261 | /// Added virtual destructor at the request of Jan.
262 | /// \version 1.64 2022-10-31 263 | /// Patch courtesy acwest: Changes to make AccelStepper more subclassable. These changes are 264 | /// largely oriented to implementing new step-scheduling algorithms. 265 | /// 266 | /// \author Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY: USE THE GOOGLE GROUP 267 | // Copyright (C) 2009-2020 Mike McCauley 268 | // $Id: AccelStepper.h,v 1.28 2020/04/20 00:15:03 mikem Exp mikem $ 269 | 270 | #ifndef AccelStepper_h 271 | #define AccelStepper_h 272 | 273 | #include 274 | #if ARDUINO >= 100 275 | #include 276 | #else 277 | #include 278 | #include 279 | #endif 280 | 281 | // These defs cause trouble on some versions of Arduino 282 | #undef round 283 | 284 | // Use the system yield() whenever possoible, since some platforms require it for housekeeping, especially 285 | // ESP8266 286 | #if (defined(ARDUINO) && ARDUINO >= 155) || defined(ESP8266) 287 | #define YIELD yield(); 288 | #else 289 | #define YIELD 290 | #endif 291 | 292 | ///////////////////////////////////////////////////////////////////// 293 | /// \class AccelStepper AccelStepper.h 294 | /// \brief Support for stepper motors with acceleration etc. 295 | /// 296 | /// This defines a single 2 or 4 pin stepper motor, or stepper moter with fdriver chip, with optional 297 | /// acceleration, deceleration, absolute positioning commands etc. Multiple 298 | /// simultaneous steppers are supported, all moving 299 | /// at different speeds and accelerations. 300 | /// 301 | /// \par Operation 302 | /// This module operates by computing a step time in microseconds. The step 303 | /// time is recomputed after each step and after speed and acceleration 304 | /// parameters are changed by the caller. The time of each step is recorded in 305 | /// microseconds. The run() function steps the motor once if a new step is due. 306 | /// The run() function must be called frequently until the motor is in the 307 | /// desired position, after which time run() will do nothing. 308 | /// 309 | /// \par Positioning 310 | /// Positions are specified by a signed long integer. At 311 | /// construction time, the current position of the motor is consider to be 0. Positive 312 | /// positions are clockwise from the initial position; negative positions are 313 | /// anticlockwise. The current position can be altered for instance after 314 | /// initialization positioning. 315 | /// 316 | /// \par Caveats 317 | /// This is an open loop controller: If the motor stalls or is oversped, 318 | /// AccelStepper will not have a correct 319 | /// idea of where the motor really is (since there is no feedback of the motor's 320 | /// real position. We only know where we _think_ it is, relative to the 321 | /// initial starting point). 322 | /// 323 | /// \par Performance 324 | /// The fastest motor speed that can be reliably supported is about 4000 steps per 325 | /// second at a clock frequency of 16 MHz on Arduino such as Uno etc. 326 | /// Faster processors can support faster stepping speeds. 327 | /// However, any speed less than that 328 | /// down to very slow speeds (much less than one per second) are also supported, 329 | /// provided the run() function is called frequently enough to step the motor 330 | /// whenever required for the speed set. 331 | /// Calling setAcceleration() is expensive, 332 | /// since it requires a square root to be calculated. 333 | /// 334 | /// Gregor Christandl reports that with an Arduino Due and a simple test program, 335 | /// he measured 43163 steps per second using runSpeed(), 336 | /// and 16214 steps per second using run(); 337 | class AccelStepper 338 | { 339 | public: 340 | /// \brief Symbolic names for number of pins. 341 | /// Use this in the pins argument the AccelStepper constructor to 342 | /// provide a symbolic name for the number of pins 343 | /// to use. 344 | typedef enum 345 | { 346 | FUNCTION = 0, ///< Use the functional interface, implementing your own driver functions (internal use only) 347 | DRIVER = 1, ///< Stepper Driver, 2 driver pins required 348 | FULL2WIRE = 2, ///< 2 wire stepper, 2 motor pins required 349 | FULL3WIRE = 3, ///< 3 wire stepper, such as HDD spindle, 3 motor pins required 350 | FULL4WIRE = 4, ///< 4 wire full stepper, 4 motor pins required 351 | HALF3WIRE = 6, ///< 3 wire half stepper, such as HDD spindle, 3 motor pins required 352 | HALF4WIRE = 8 ///< 4 wire half stepper, 4 motor pins required 353 | } MotorInterfaceType; 354 | 355 | /// Constructor. You can have multiple simultaneous steppers, all moving 356 | /// at different speeds and accelerations, provided you call their run() 357 | /// functions at frequent enough intervals. Current Position is set to 0, target 358 | /// position is set to 0. MaxSpeed and Acceleration default to 1.0. 359 | /// The motor pins will be initialised to OUTPUT mode during the 360 | /// constructor by a call to enableOutputs(). 361 | /// \param[in] interface Number of pins to interface to. Integer values are 362 | /// supported, but it is preferred to use the \ref MotorInterfaceType symbolic names. 363 | /// AccelStepper::DRIVER (1) means a stepper driver (with Step and Direction pins). 364 | /// If an enable line is also needed, call setEnablePin() after construction. 365 | /// You may also invert the pins using setPinsInverted(). 366 | /// Caution: DRIVER implements a blocking delay of minPulseWidth microseconds (default 1us) for each step. 367 | /// You can change this with setMinPulseWidth(). 368 | /// AccelStepper::FULL2WIRE (2) means a 2 wire stepper (2 pins required). 369 | /// AccelStepper::FULL3WIRE (3) means a 3 wire stepper, such as HDD spindle (3 pins required). 370 | /// AccelStepper::FULL4WIRE (4) means a 4 wire stepper (4 pins required). 371 | /// AccelStepper::HALF3WIRE (6) means a 3 wire half stepper, such as HDD spindle (3 pins required) 372 | /// AccelStepper::HALF4WIRE (8) means a 4 wire half stepper (4 pins required) 373 | /// Defaults to AccelStepper::FULL4WIRE (4) pins. 374 | /// \param[in] pin1 Arduino digital pin number for motor pin 1. Defaults 375 | /// to pin 2. For a AccelStepper::DRIVER (interface==1), 376 | /// this is the Step input to the driver. Low to high transition means to step) 377 | /// \param[in] pin2 Arduino digital pin number for motor pin 2. Defaults 378 | /// to pin 3. For a AccelStepper::DRIVER (interface==1), 379 | /// this is the Direction input the driver. High means forward. 380 | /// \param[in] pin3 Arduino digital pin number for motor pin 3. Defaults 381 | /// to pin 4. 382 | /// \param[in] pin4 Arduino digital pin number for motor pin 4. Defaults 383 | /// to pin 5. 384 | /// \param[in] enable If this is true (the default), enableOutputs() will be called to enable 385 | /// the output pins at construction time. 386 | AccelStepper(uint8_t interface = AccelStepper::FULL4WIRE, uint8_t pin1 = 2, uint8_t pin2 = 3, uint8_t pin3 = 4, uint8_t pin4 = 5, bool enable = true); 387 | 388 | /// Alternate Constructor which will call your own functions for forward and backward steps. 389 | /// You can have multiple simultaneous steppers, all moving 390 | /// at different speeds and accelerations, provided you call their run() 391 | /// functions at frequent enough intervals. Current Position is set to 0, target 392 | /// position is set to 0. MaxSpeed and Acceleration default to 1.0. 393 | /// Any motor initialization should happen before hand, no pins are used or initialized. 394 | /// \param[in] forward void-returning procedure that will make a forward step 395 | /// \param[in] backward void-returning procedure that will make a backward step 396 | AccelStepper(void (*forward)(), void (*backward)()); 397 | 398 | /// Set the target position. The run() function will try to move the motor (at most one step per call) 399 | /// from the current position to the target position set by the most 400 | /// recent call to this function. Caution: moveTo() also recalculates the speed for the next step. 401 | /// If you are trying to use constant speed movements, you should call setSpeed() after calling moveTo(). 402 | /// \param[in] absolute The desired absolute position. Negative is 403 | /// anticlockwise from the 0 position. 404 | void moveTo(long absolute); 405 | 406 | /// Set the target position relative to the current position. 407 | /// \param[in] relative The desired position relative to the current position. Negative is 408 | /// anticlockwise from the current position. 409 | void move(long relative); 410 | 411 | /// Poll the motor and step it if a step is due, implementing 412 | /// accelerations and decelerations to achieve the target position. You must call this as 413 | /// frequently as possible, but at least once per minimum step time interval, 414 | /// preferably in your main loop. Note that each call to run() will make at most one step, and then only when a step is due, 415 | /// based on the current speed and the time since the last step. 416 | /// \return true if the motor is still running to the target position. 417 | boolean run(); 418 | 419 | /// Poll the motor and step it if a step is due, implementing a constant 420 | /// speed as set by the most recent call to setSpeed(). You must call this as 421 | /// frequently as possible, but at least once per step interval, 422 | /// \return true if the motor was stepped. 423 | boolean runSpeed(); 424 | 425 | /// Sets the maximum permitted speed. The run() function will accelerate 426 | /// up to the speed set by this function. 427 | /// Caution: the maximum speed achievable depends on your processor and clock speed. 428 | /// The default maxSpeed is 1.0 steps per second. 429 | /// \param[in] speed The desired maximum speed in steps per second. Must 430 | /// be > 0. Caution: Speeds that exceed the maximum speed supported by the processor may 431 | /// Result in non-linear accelerations and decelerations. 432 | void setMaxSpeed(float speed); 433 | 434 | /// Returns the maximum speed configured for this stepper 435 | /// that was previously set by setMaxSpeed(); 436 | /// \return The currently configured maximum speed 437 | float maxSpeed(); 438 | 439 | /// Sets the acceleration/deceleration rate. 440 | /// \param[in] acceleration The desired acceleration in steps per second 441 | /// per second. Must be > 0.0. This is an expensive call since it requires a square 442 | /// root to be calculated. Dont call more ofthen than needed 443 | void setAcceleration(float acceleration); 444 | 445 | /// Returns the acceleration/deceleration rate configured for this stepper 446 | /// that was previously set by setAcceleration(); 447 | /// \return The currently configured acceleration/deceleration 448 | float acceleration(); 449 | 450 | /// Sets the desired constant speed for use with runSpeed(). 451 | /// \param[in] speed The desired constant speed in steps per 452 | /// second. Positive is clockwise. Speeds of more than 1000 steps per 453 | /// second are unreliable. Very slow speeds may be set (eg 0.00027777 for 454 | /// once per hour, approximately. Speed accuracy depends on the Arduino 455 | /// crystal. Jitter depends on how frequently you call the runSpeed() function. 456 | /// The speed will be limited by the current value of setMaxSpeed() 457 | void setSpeed(float speed); 458 | 459 | /// The most recently set speed. 460 | /// \return the most recent speed in steps per second 461 | float speed(); 462 | 463 | /// The distance from the current position to the target position. 464 | /// \return the distance from the current position to the target position 465 | /// in steps. Positive is clockwise from the current position. 466 | long distanceToGo(); 467 | 468 | /// The most recently set target position. 469 | /// \return the target position 470 | /// in steps. Positive is clockwise from the 0 position. 471 | long targetPosition(); 472 | 473 | /// The current motor position. 474 | /// \return the current motor position 475 | /// in steps. Positive is clockwise from the 0 position. 476 | long currentPosition(); 477 | 478 | /// Resets the current position of the motor, so that wherever the motor 479 | /// happens to be right now is considered to be the new 0 position. Useful 480 | /// for setting a zero position on a stepper after an initial hardware 481 | /// positioning move. 482 | /// Has the side effect of setting the current motor speed to 0. 483 | /// \param[in] position The position in steps of wherever the motor 484 | /// happens to be right now. 485 | void setCurrentPosition(long position); 486 | 487 | /// Moves the motor (with acceleration/deceleration) 488 | /// to the target position and blocks until it is at 489 | /// position. Dont use this in event loops, since it blocks. 490 | void runToPosition(); 491 | 492 | /// Executes runSpeed() unless the targetPosition is reached. 493 | /// This function needs to be called often just like runSpeed() or run(). 494 | /// Will step the motor if a step is required at the currently selected 495 | /// speed unless the target position has been reached. 496 | /// Does not implement accelerations. 497 | /// \return true if it stepped 498 | boolean runSpeedToPosition(); 499 | 500 | /// Moves the motor (with acceleration/deceleration) 501 | /// to the new target position and blocks until it is at 502 | /// position. Dont use this in event loops, since it blocks. 503 | /// \param[in] position The new target position. 504 | void runToNewPosition(long position); 505 | 506 | /// Sets a new target position that causes the stepper 507 | /// to stop as quickly as possible, using the current speed and acceleration parameters. 508 | void stop(); 509 | 510 | /// Disable motor pin outputs by setting them all LOW 511 | /// Depending on the design of your electronics this may turn off 512 | /// the power to the motor coils, saving power. 513 | /// This is useful to support Arduino low power modes: disable the outputs 514 | /// during sleep and then reenable with enableOutputs() before stepping 515 | /// again. 516 | /// If the enable Pin is defined, sets it to OUTPUT mode and clears the pin to disabled. 517 | virtual void disableOutputs(); 518 | 519 | /// Enable motor pin outputs by setting the motor pins to OUTPUT 520 | /// mode. Called automatically by the constructor. 521 | /// If the enable Pin is defined, sets it to OUTPUT mode and sets the pin to enabled. 522 | virtual void enableOutputs(); 523 | 524 | /// Sets the minimum pulse width allowed by the stepper driver. The minimum practical pulse width is 525 | /// approximately 20 microseconds. Times less than 20 microseconds 526 | /// will usually result in 20 microseconds or so. 527 | /// \param[in] minWidth The minimum pulse width in microseconds. 528 | void setMinPulseWidth(unsigned int minWidth); 529 | 530 | /// Sets the enable pin number for stepper drivers. 531 | /// 0xFF indicates unused (default). 532 | /// Otherwise, if a pin is set, the pin will be turned on when 533 | /// enableOutputs() is called and switched off when disableOutputs() 534 | /// is called. 535 | /// \param[in] enablePin Arduino digital pin number for motor enable 536 | /// \sa setPinsInverted 537 | void setEnablePin(uint8_t enablePin = 0xff); 538 | 539 | /// Sets the inversion for stepper driver pins 540 | /// \param[in] directionInvert True for inverted direction pin, false for non-inverted 541 | /// \param[in] stepInvert True for inverted step pin, false for non-inverted 542 | /// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted 543 | void setPinsInverted(bool directionInvert = false, bool stepInvert = false, bool enableInvert = false); 544 | 545 | /// Sets the inversion for 2, 3 and 4 wire stepper pins 546 | /// \param[in] pin1Invert True for inverted pin1, false for non-inverted 547 | /// \param[in] pin2Invert True for inverted pin2, false for non-inverted 548 | /// \param[in] pin3Invert True for inverted pin3, false for non-inverted 549 | /// \param[in] pin4Invert True for inverted pin4, false for non-inverted 550 | /// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted 551 | void setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert); 552 | 553 | /// Checks to see if the motor is currently running to a target 554 | /// \return true if the speed is not zero or not at the target position 555 | bool isRunning(); 556 | 557 | /// Virtual destructor to prevent warnings during delete 558 | virtual ~AccelStepper() {}; 559 | protected: 560 | 561 | /// \brief Direction indicator 562 | /// Symbolic names for the direction the motor is turning 563 | typedef enum 564 | { 565 | DIRECTION_CCW = 0, ///< Counter-Clockwise 566 | DIRECTION_CW = 1 ///< Clockwise 567 | } Direction; 568 | 569 | /// Forces the library to compute a new instantaneous speed and set that as 570 | /// the current speed. It is called by 571 | /// the library: 572 | /// \li after each step 573 | /// \li after change to maxSpeed through setMaxSpeed() 574 | /// \li after change to acceleration through setAcceleration() 575 | /// \li after change to target position (relative or absolute) through 576 | /// move() or moveTo() 577 | /// \return the new step interval 578 | virtual unsigned long computeNewSpeed(); 579 | 580 | /// Low level function to set the motor output pins 581 | /// bit 0 of the mask corresponds to _pin[0] 582 | /// bit 1 of the mask corresponds to _pin[1] 583 | /// You can override this to impment, for example serial chip output insted of using the 584 | /// output pins directly 585 | virtual void setOutputPins(uint8_t mask); 586 | 587 | /// Called to execute a step. Only called when a new step is 588 | /// required. Subclasses may override to implement new stepping 589 | /// interfaces. The default calls step1(), step2(), step4() or step8() depending on the 590 | /// number of pins defined for the stepper. 591 | /// \param[in] step The current step phase number (0 to 7) 592 | virtual void step(long step); 593 | 594 | /// Called to execute a clockwise(+) step. Only called when a new step is 595 | /// required. This increments the _currentPos and calls step() 596 | /// \return the updated current position 597 | long stepForward(); 598 | 599 | /// Called to execute a counter-clockwise(-) step. Only called when a new step is 600 | /// required. This decrements the _currentPos and calls step() 601 | /// \return the updated current position 602 | long stepBackward(); 603 | 604 | /// Called to execute a step using stepper functions (pins = 0) Only called when a new step is 605 | /// required. Calls _forward() or _backward() to perform the step 606 | /// \param[in] step The current step phase number (0 to 7) 607 | virtual void step0(long step); 608 | 609 | /// Called to execute a step on a stepper driver (ie where pins == 1). Only called when a new step is 610 | /// required. Subclasses may override to implement new stepping 611 | /// interfaces. The default sets or clears the outputs of Step pin1 to step, 612 | /// and sets the output of _pin2 to the desired direction. The Step pin (_pin1) is pulsed for 1 microsecond 613 | /// which is the minimum STEP pulse width for the 3967 driver. 614 | /// \param[in] step The current step phase number (0 to 7) 615 | virtual void step1(long step); 616 | 617 | /// Called to execute a step on a 2 pin motor. Only called when a new step is 618 | /// required. Subclasses may override to implement new stepping 619 | /// interfaces. The default sets or clears the outputs of pin1 and pin2 620 | /// \param[in] step The current step phase number (0 to 7) 621 | virtual void step2(long step); 622 | 623 | /// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is 624 | /// required. Subclasses may override to implement new stepping 625 | /// interfaces. The default sets or clears the outputs of pin1, pin2, 626 | /// pin3 627 | /// \param[in] step The current step phase number (0 to 7) 628 | virtual void step3(long step); 629 | 630 | /// Called to execute a step on a 4 pin motor. Only called when a new step is 631 | /// required. Subclasses may override to implement new stepping 632 | /// interfaces. The default sets or clears the outputs of pin1, pin2, 633 | /// pin3, pin4. 634 | /// \param[in] step The current step phase number (0 to 7) 635 | virtual void step4(long step); 636 | 637 | /// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is 638 | /// required. Subclasses may override to implement new stepping 639 | /// interfaces. The default sets or clears the outputs of pin1, pin2, 640 | /// pin3 641 | /// \param[in] step The current step phase number (0 to 7) 642 | virtual void step6(long step); 643 | 644 | /// Called to execute a step on a 4 pin half-stepper motor. Only called when a new step is 645 | /// required. Subclasses may override to implement new stepping 646 | /// interfaces. The default sets or clears the outputs of pin1, pin2, 647 | /// pin3, pin4. 648 | /// \param[in] step The current step phase number (0 to 7) 649 | virtual void step8(long step); 650 | 651 | /// Current direction motor is spinning in 652 | /// Protected because some peoples subclasses need it to be so 653 | boolean _direction; // 1 == CW 654 | 655 | /// The current interval between steps in microseconds. 656 | /// 0 means the motor is currently stopped with _speed == 0 657 | unsigned long _stepInterval; 658 | 659 | private: 660 | /// Number of pins on the stepper motor. Permits 2 or 4. 2 pins is a 661 | /// bipolar, and 4 pins is a unipolar. 662 | uint8_t _interface; // 0, 1, 2, 4, 8, See MotorInterfaceType 663 | 664 | /// Arduino pin number assignments for the 2 or 4 pins required to interface to the 665 | /// stepper motor or driver 666 | uint8_t _pin[4]; 667 | 668 | /// Whether the _pins is inverted or not 669 | uint8_t _pinInverted[4]; 670 | 671 | /// The current absolution position in steps. 672 | long _currentPos; // Steps 673 | 674 | /// The target position in steps. The AccelStepper library will move the 675 | /// motor from the _currentPos to the _targetPos, taking into account the 676 | /// max speed, acceleration and deceleration 677 | long _targetPos; // Steps 678 | 679 | /// The current motos speed in steps per second 680 | /// Positive is clockwise 681 | float _speed; // Steps per second 682 | 683 | /// The maximum permitted speed in steps per second. Must be > 0. 684 | float _maxSpeed; 685 | 686 | /// The acceleration to use to accelerate or decelerate the motor in steps 687 | /// per second per second. Must be > 0 688 | float _acceleration; 689 | float _sqrt_twoa; // Precomputed sqrt(2*_acceleration) 690 | 691 | /// The last step time in microseconds 692 | unsigned long _lastStepTime; 693 | 694 | /// The minimum allowed pulse width in microseconds 695 | unsigned int _minPulseWidth; 696 | 697 | /// Is the direction pin inverted? 698 | ///bool _dirInverted; /// Moved to _pinInverted[1] 699 | 700 | /// Is the step pin inverted? 701 | ///bool _stepInverted; /// Moved to _pinInverted[0] 702 | 703 | /// Is the enable pin inverted? 704 | bool _enableInverted; 705 | 706 | /// Enable pin for stepper driver, or 0xFF if unused. 707 | uint8_t _enablePin; 708 | 709 | /// The pointer to a forward-step procedure 710 | void (*_forward)(); 711 | 712 | /// The pointer to a backward-step procedure 713 | void (*_backward)(); 714 | 715 | /// The step counter for speed calculations 716 | long _n; 717 | 718 | /// Initial step size in microseconds 719 | float _c0; 720 | 721 | /// Last step size in microseconds 722 | float _cn; 723 | 724 | /// Min step size in microseconds based on maxSpeed 725 | float _cmin; // at max speed 726 | 727 | }; 728 | 729 | /// @example Random.pde 730 | /// Make a single stepper perform random changes in speed, position and acceleration 731 | 732 | /// @example Overshoot.pde 733 | /// Check overshoot handling 734 | /// which sets a new target position and then waits until the stepper has 735 | /// achieved it. This is used for testing the handling of overshoots 736 | 737 | /// @example MultipleSteppers.pde 738 | /// Shows how to multiple simultaneous steppers 739 | /// Runs one stepper forwards and backwards, accelerating and decelerating 740 | /// at the limits. Runs other steppers at the same time 741 | 742 | /// @example ConstantSpeed.pde 743 | /// Shows how to run AccelStepper in the simplest, 744 | /// fixed speed mode with no accelerations 745 | 746 | /// @example Blocking.pde 747 | /// Shows how to use the blocking call runToNewPosition 748 | /// Which sets a new target position and then waits until the stepper has 749 | /// achieved it. 750 | 751 | /// @example AFMotor_MultiStepper.pde 752 | /// Control both Stepper motors at the same time with different speeds 753 | /// and accelerations. 754 | 755 | /// @example AFMotor_ConstantSpeed.pde 756 | /// Shows how to run AccelStepper in the simplest, 757 | /// fixed speed mode with no accelerations 758 | 759 | /// @example ProportionalControl.pde 760 | /// Make a single stepper follow the analog value read from a pot or whatever 761 | /// The stepper will move at a constant speed to each newly set posiiton, 762 | /// depending on the value of the pot. 763 | 764 | /// @example Bounce.pde 765 | /// Make a single stepper bounce from one limit to another, observing 766 | /// accelrations at each end of travel 767 | 768 | /// @example Quickstop.pde 769 | /// Check stop handling. 770 | /// Calls stop() while the stepper is travelling at full speed, causing 771 | /// the stepper to stop as quickly as possible, within the constraints of the 772 | /// current acceleration. 773 | 774 | /// @example MotorShield.pde 775 | /// Shows how to use AccelStepper to control a 3-phase motor, such as a HDD spindle motor 776 | /// using the Adafruit Motor Shield http://www.ladyada.net/make/mshield/index.html. 777 | 778 | /// @example DualMotorShield.pde 779 | /// Shows how to use AccelStepper to control 2 x 2 phase steppers using the 780 | /// Itead Studio Arduino Dual Stepper Motor Driver Shield 781 | /// model IM120417015 782 | 783 | #endif 784 | --------------------------------------------------------------------------------