diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b75678 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.clangd diff --git a/README.md b/README.md index fed57a4..675e3b9 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,34 @@ Compile through Arduino IDE or equivalent. > Add [kissStepper](https://github.com/risitt/kissStepper) to your project > librairies. +## Development + +Required dependeinces if not using +[Arduino IDE](https://www.arduino.cc/en/software/): + +- [Arduino CLI](https://docs.arduino.cc/arduino-cli/): + - Install: + ```sh + # linux + curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh + + # macos + brew update + brew install arduino-cli + + # windows + winget install --id=ArduinoSA.CLI -e + ``` + - Configure: + ```sh + arduino-cli config init + arduino-cli core update-index + arduino-cli core install arduino:avr + ``` + +If you use `clangd` as lsp run `python ./generate_clangd.py` to load arduino +config and libraries. + ## Contributing Before `git commit`, run `git clang-format --staged` to format stagged files and diff --git a/generate_clangd.py b/generate_clangd.py new file mode 100644 index 0000000..c8d4f83 --- /dev/null +++ b/generate_clangd.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import os +import platform +import yaml + +def write_clangd_file(flags): + clangd_config = { + "CompileFlags": { + "Add": flags + } + } + + with open(".clangd", "w") as f: + yaml.dump(clangd_config, f, default_flow_style=False) + +def main(): + arduino_base_path = os.path.expanduser("~/AppData/Local") if platform.system() == "Windows" else os.path.expanduser('~') + + flags = [ + "-x", "c++", + "-std=gnu++11", + "-fpermissive", + "-fno-exceptions", + "-ffunction-sections", + "-fdata-sections", + "-fno-threadsafe-statics", + "-Wno-error=narrowing", + "-flto", + "-E", + "-CC", + "-D__AVR__", + "-D__AVR_ATmega328P__", + "-DF_CPU=16000000L", + "-DARDUINO=10607", + "-DARDUINO_AVR_UNO", + "-DARDUINO_ARCH_AVR", + "-I./include", + f"-I{arduino_base_path}/Arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino", + f"-I{arduino_base_path}/Arduino15/packages/arduino/hardware/avr/1.8.6/variants/standard", + f"-I{arduino_base_path}/Arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/avr/include", + f"-I{arduino_base_path}/Arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/lib/gcc/avr/7.3.0/include", + ] + + write_clangd_file(flags) + +if __name__ == "__main__": + main() diff --git a/include/kissStepper.h b/include/kissStepper.h new file mode 100644 index 0000000..14cbafd --- /dev/null +++ b/include/kissStepper.h @@ -0,0 +1,312 @@ +/* +kissStepper - a lightweight library for the Easy Driver, Big Easy Driver, Allegro stepper motor drivers and others that use a Step/Dir interface +Written by Rylee Isitt. September 21, 2015 +License: GNU Lesser General Public License (LGPL) V2.1 + +Despite the existence of several excellent libraries for driving stepper motors, I created this one to fulfill the following needs: +- Simplicity +- Handling of enable, step, and dir pins +- Based around an external loop +- Approximately linear acceleration using a fast algorithm +- High step frequency (or reasonably so, given the overhead involved) +- Use AVR/ARM libraries and port access to increase performance while keeping the API Arduino-friendly +- Teensy (Teensyduino) compatibility + +Acceleration approximation math is based on Aryeh Eiderman's "Real Time Stepper Motor Linear Ramping Just by Addition and Multiplication", available at http://hwml.com/LeibRamp.pdf +*/ + +#ifndef kissStepper_H +#define kissStepper_H + +#include + +// determine port register size +#if defined(__AVR__) || defined(__avr__) + typedef uint8_t regint; +#elif defined(TEENSYDUINO) + #if defined(__AVR_ATmega32U4__) || defined(__AVR_AT90USB1286__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) + typedef uint8_t regint; + #else + typedef uint32_t regint; + #endif +#else + typedef uint32_t regint; +#endif + + +// the order of enums allows some simple tests: +// if > STATE_STARTING, motor is in motion +// if > STATE_RUN, motor is accelerating or decelerating +enum kissState_t: uint8_t +{ + STATE_STOPPED = 0, + STATE_STARTING = 1, + STATE_RUN = 2, + STATE_ACCEL = 3, + STATE_DECEL = 4 +}; + +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- +// kissStepper without acceleration +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- + +class kissStepperNoAccel +{ +public: + kissStepperNoAccel(uint8_t PIN_DIR, uint8_t PIN_STEP, uint8_t PIN_ENABLE = 255, bool invertDir = false); + kissStepperNoAccel(uint8_t PIN_DIR, uint8_t PIN_STEP, bool invertDir = false); + ~kissStepperNoAccel(void) {}; + + bool prepareMove(int32_t target); + kissState_t move(void); + void stop(void); + + uint16_t getCurSpeed(void) + { + if (m_kissState == STATE_RUN) + return m_maxSpeed; + else + return 0; + } + kissState_t getState(void) + { + return m_kissState; + } + int32_t getPos(void) + { + if (m_forwards) + return m_pos + m_distMoved; + else + return m_pos - m_distMoved; + } + bool isEnabled(void) + { + return m_enabled; + } + bool isMovingForwards(void) + { + return m_forwards; + } + void begin(void); + void enable(void); + void disable(void); + + void setPos(int32_t pos) + { + if (m_kissState == STATE_STOPPED) + m_pos = constrain(pos, m_reverseLimit, m_forwardLimit); + } + int32_t getTarget(void) + { + if (m_kissState == STATE_STOPPED) + return m_pos; + else if (m_forwards) + return m_pos + m_distTotal; + else + return m_pos - m_distTotal; + } + uint32_t getDistRemaining(void) + { + return m_distTotal - m_distMoved; + } + void setForwardLimit(int32_t forwardLimit) + { + m_forwardLimit = forwardLimit; + } + void setReverseLimit(int32_t reverseLimit) + { + m_reverseLimit = reverseLimit; + } + int32_t getForwardLimit(void) + { + return m_forwardLimit; + } + int32_t getReverseLimit(void) + { + return m_reverseLimit; + } + void setMaxSpeed(uint16_t maxSpeed) + { + if (m_kissState == STATE_STOPPED) m_maxSpeed = maxSpeed; + } + uint16_t getMaxSpeed(void) + { + return m_maxSpeed; + } + +protected: + void setDir(bool forwards) + { + m_forwards = forwards; + digitalWrite(PIN_DIR, forwards == m_invertDir); + } + void updatePos(void) + { + if (m_forwards) + m_pos += m_distMoved; + else + m_pos -= m_distMoved; + m_distMoved = 0; + } + static const uint32_t ONE_SECOND = 1000000UL; + static const uint8_t PULSE_WIDTH_US = 2; // desired width of step pulse (high) in us + static const int32_t DEFAULT_FORWARD_LIMIT = 2147483647L; + static const int32_t DEFAULT_REVERSE_LIMIT = -2147483648L; + static const uint16_t DEFAULT_SPEED = 1600; + static const uint16_t INTERVAL_CORRECTION_INCREMENT = 255; + + int32_t m_forwardLimit; + int32_t m_reverseLimit; + uint16_t m_maxSpeed; + + const uint8_t PIN_DIR; + const uint8_t PIN_STEP; + const uint8_t PIN_ENABLE; + + kissState_t m_kissState; + uint32_t m_distTotal, m_distMoved; + bool m_forwards; + int32_t m_pos; + + const regint m_stepBit; + regint volatile * const m_stepOut; + + uint32_t m_stepIntervalWhole; + uint16_t m_stepIntervalRemainder; + uint16_t m_stepIntervalCorrectionCounter; + bool m_enabled; + uint32_t m_lastStepTime; + bool m_invertDir; + bool m_init; +}; + +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- +// kissStepper WITH acceleration +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- + +class kissStepper: public kissStepperNoAccel +{ +public: + kissStepper(uint8_t PIN_DIR, uint8_t PIN_STEP, uint8_t PIN_ENABLE = 255, bool invertDir = false); + kissStepper(uint8_t PIN_DIR, uint8_t PIN_STEP, bool invertDir = false); + ~kissStepper(void) {}; + bool prepareMove(int32_t target); + kissState_t move(void); + void stop(void); + + uint16_t getCurSpeed(void) + { + if (m_kissState == STATE_RUN) + return m_maxSpeed; + else if (m_kissState > STATE_STARTING) + { + uint32_t curSpeed = ONE_SECOND / m_stepIntervalWhole; + if (curSpeed > m_maxSpeed) curSpeed = m_maxSpeed; + return curSpeed; + } + else + return 0; + } + + void decelerate(void); + uint32_t calcMaxAccelDist(void) + { + if (m_accel > 0) + return ((uint32_t)m_maxSpeed * m_maxSpeed) / (2UL * m_accel); + else + return 0; + } + uint32_t getAccelDist(void) + { + return m_distAccel; + } + uint32_t getRunDist(void) + { + return m_distRun - m_distAccel; + } + uint32_t getDecelDist(void) + { + return m_distTotal - m_distRun; + } + void setAccel(uint16_t accel) + { + if (m_kissState == STATE_STOPPED) m_accel = accel; + } + uint16_t getAccel(void) + { + return m_accel; + } + uint16_t getTopSpeed(void); + +protected: + + static const uint16_t DEFAULT_ACCEL = 1600; + uint32_t m_distAccel, m_distRun; + uint32_t m_topSpeedStepInterval; + uint32_t m_minSpeedStepInterval; + float m_stepInterval; + float m_constMult; + uint16_t m_accel; + +private: + + /* + ---------------------------------------------------------------------------------------------------- + + To strike a balance between accuracy and performance, this library uses a set of approximations + for calculating stepInterval when accelerating/decelerating. Although this does use floating point + math, it is a drastic improvement over exact calculations and better than anything else I've tried. + + There is probably room for further improvement (fixed point or integer math?) but this is good enough. + + exact: + stepInterval = ONE_SECOND / newSpeed + curSpeed = ONE_SECOND / stepInterval + newSpeed = sqrt(curSpeed^2 + 2a) + stepInterval = ONE_SECOND / sqrt(curSpeed^2 + 2a) + + approximations: + constMult = accel / (ONE_SECOND * ONE_SECOND) + q = constMult*stepInterval*stepInterval + set q to negative if accelerating + + good precision, fast: stepInterval *= 1.0 + q + better precision, slower: stepInterval *= 1.0 + q + q*q + best precision, slowest: stepInterval *= 1.0 + q + 1.5*q*q + + ---------------------------------------------------------------------------------------------------- + */ + + float accelStep(float stepInterval, float constMult) + { + float newStepInterval; + float q = -constMult*stepInterval*stepInterval; + newStepInterval = stepInterval * (1.0 + q); + // newStepInterval = stepInterval * (1.0 + q + q*q); // better accuracy + // newStepInterval = stepInterval * (1.0 + q + 1.5*q*q); // best accuracy + if (newStepInterval < m_topSpeedStepInterval) newStepInterval = m_topSpeedStepInterval; + return newStepInterval; + } + + float decelStep(float stepInterval, float constMult) + { + float newStepInterval; + float q = constMult*stepInterval*stepInterval; + newStepInterval = stepInterval * (1.0 + q); + // newStepInterval = stepInterval * (1.0 + q + q*q); // better accuracy + // newStepInterval = stepInterval * (1.0 + q + 1.5*q*q); // best accuracy + if (newStepInterval > m_minSpeedStepInterval) newStepInterval = m_minSpeedStepInterval; + return newStepInterval; + } + +}; + +#endif