diff --git a/.cli-config.yml b/.cli-config.yml new file mode 100644 index 0000000..77a4bc6 --- /dev/null +++ b/.cli-config.yml @@ -0,0 +1,7 @@ +sketchbook_path: .arduino +arduino_data: .arduino +board_manager: + additional_urls: + - http://arduino.esp8266.com/stable/package_esp8266com_index.json + - https://dl.espressif.com/dl/package_esp32_index.json + - https://adafruit.github.io/arduino-board-index/package_adafruit_index.json diff --git a/.gitignore b/.gitignore index ad7c00a..7eea01b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,14 @@ # VScode .vscode +# PyCharm/Jetbrains editors +.idea + # Mac .DS_Store + +# Arduino +*.hex +*.bin +*.elf +.arduino diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6ed3b0b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: minimal +git: + depth: false +addons: + apt: + packages: + - python +env: + matrix: + - TARGET=arduino:avr:uno + - TARGET=adafruit:samd:adafruit_feather_m0 + - TARGET=arduino:avr:mega + - TARGET=esp8266:esp8266:nodemcu +before_install: + - make setup +install: true +script: + - make all +cache: + directories: + - .arduino diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..54937ce --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +# Default build architecture and board +TARGET ?= arduino:avr:uno + +# Default list of cores to install with `make setup` +CORES ?= arduino:avr adafruit:samd esp8266:esp8266 esp32:esp32 + +# Where to save the Arduino support files, this should match what is in .cli-config.yml +ARDUINO_DIR ?= .arduino + +default: + ################################################################################################# + # Initial setup: make .arduino/arduino-cli setup + # + # Build all the examples: make all TARGET=adafruit:samd:adafruit_feather_m0 + # + # Install more cores: make setup CORES=arduino:samd + # (edit .cli-config.yml and add repository if needed) + ################################################################################################# + +ARDUINO_CLI_URL = http://downloads.arduino.cc/arduino-cli/arduino-cli-latest-linux64.tar.bz2 +ARDUINO_CLI ?= $(ARDUINO_DIR)/arduino-cli --config-file .cli-config.yml +EXAMPLES := $(shell ls examples) + +all: # Build all example sketches +all: $(EXAMPLES:%=%.hex) + +%.hex: # Generic rule for compiling sketch to uploadable hex file +%.hex: examples/% + $(ARDUINO_CLI) compile --warnings all --fqbn $(TARGET) $< --output $@ + ls -l $@* + +# Remove built objects +clean: + rm -fv $(EXAMPLES:%=%.{hex,elf}*) + +$(ARDUINO_DIR)/arduino-cli: # Download and install arduino-cli +$(ARDUINO_DIR)/arduino-cli: + mkdir -p $(ARDUINO_DIR) + cd $(ARDUINO_DIR) + curl -s $(ARDUINO_CLI_URL) \ + | tar xfj - -O -C $(ARDUINO_DIR) \ + > $@ + chmod 755 $@ + +setup: # Configure cores and libraries for arduino-cli (which it will download if missing) +setup: $(ARDUINO_DIR)/arduino-cli + mkdir -p $(ARDUINO_DIR)/libraries + ln -sf $(CURDIR) $(ARDUINO_DIR)/libraries/ + $(ARDUINO_CLI) config dump + $(ARDUINO_CLI) core update-index + $(ARDUINO_CLI) core install $(CORES) + $(ARDUINO_CLI) core list + +.PHONY: clean %.hex all setup diff --git a/README.md b/README.md index 740665d..49b5c95 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.com/laurb9/StepperDriver.svg?branch=master)](https://travis-ci.com/laurb9/StepperDriver) + StepperDriver ============= diff --git a/src/BasicStepperDriver.cpp b/src/BasicStepperDriver.cpp index 2829b0e..4ccec48 100644 --- a/src/BasicStepperDriver.cpp +++ b/src/BasicStepperDriver.cpp @@ -18,15 +18,8 @@ * Microstepping controls should be hardwired. */ BasicStepperDriver::BasicStepperDriver(short steps, short dir_pin, short step_pin) -:motor_steps(steps), dir_pin(dir_pin), step_pin(step_pin) +:BasicStepperDriver(steps, dir_pin, step_pin, PIN_UNCONNECTED) { - steps_to_cruise = 0; - steps_remaining = 0; - dir_state = 0; - steps_to_brake = 0; - step_pulse = 0; - rest = 0; - step_count = 0; } BasicStepperDriver::BasicStepperDriver(short steps, short dir_pin, short step_pin, short enable_pin) @@ -37,6 +30,7 @@ BasicStepperDriver::BasicStepperDriver(short steps, short dir_pin, short step_pi dir_state = 0; steps_to_brake = 0; step_pulse = 0; + cruise_step_pulse = 0; rest = 0; step_count = 0; } @@ -44,7 +38,7 @@ BasicStepperDriver::BasicStepperDriver(short steps, short dir_pin, short step_pi /* * Initialize pins, calculate timings etc */ -void BasicStepperDriver::begin(short rpm, short microsteps){ +void BasicStepperDriver::begin(float rpm, short microsteps){ pinMode(dir_pin, OUTPUT); digitalWrite(dir_pin, HIGH); @@ -65,7 +59,7 @@ void BasicStepperDriver::begin(short rpm, short microsteps){ /* * Set target motor RPM (1-200 is a reasonable range) */ -void BasicStepperDriver::setRPM(short rpm){ +void BasicStepperDriver::setRPM(float rpm){ if (this->rpm == 0){ // begin() has not been called (old 1.0 code) begin(rpm, microsteps); } @@ -125,8 +119,8 @@ void BasicStepperDriver::rotate(double deg){ /* * Set up a new move (calculate and save the parameters) */ -void BasicStepperDriver::startMove(long steps){ - long speed; +void BasicStepperDriver::startMove(long steps, long time){ + float speed; // set up new move dir_state = (steps >= 0) ? HIGH : LOW; last_action_end = 0; @@ -137,9 +131,19 @@ void BasicStepperDriver::startMove(long steps){ case LINEAR_SPEED: // speed is in [steps/s] speed = rpm * motor_steps / 60; - // how many steps from 0 to target rpm - steps_to_cruise = speed * speed * microsteps / (2 * profile.accel); - // how many steps are needed from target rpm to a full stop + if (time > 0){ + // Calculate a new speed to finish in the time requested + float t = time / (1e+6); // convert to seconds + float d = steps_remaining / microsteps; // convert to full steps + float a2 = 1.0 / profile.accel + 1.0 / profile.decel; + float sqrt_candidate = t*t - 2 * a2 * d; // in √b^2-4ac + if (sqrt_candidate >= 0){ + speed = min(speed, (t - (float)sqrt(sqrt_candidate)) / a2); + }; + } + // how many microsteps from 0 to target speed + steps_to_cruise = microsteps * (speed * speed / (2 * profile.accel)); + // how many microsteps are needed from cruise speed to a full stop steps_to_brake = steps_to_cruise * profile.accel / profile.decel; if (steps_remaining < steps_to_cruise + steps_to_brake){ // cannot reach max speed, will need to brake early @@ -147,14 +151,19 @@ void BasicStepperDriver::startMove(long steps){ steps_to_brake = steps_remaining - steps_to_cruise; } // Initial pulse (c0) including error correction factor 0.676 [us] - step_pulse = (1e+6)*0.676*sqrt(2.0f/(profile.accel*microsteps)); + step_pulse = (1e+6)*0.676*sqrt(2.0f/profile.accel/microsteps); + // Save cruise timing since we will no longer have the calculated target speed later + cruise_step_pulse = 1e+6 / speed / microsteps; break; case CONSTANT_SPEED: default: - step_pulse = STEP_PULSE(rpm, motor_steps, microsteps); steps_to_cruise = 0; steps_to_brake = 0; + step_pulse = cruise_step_pulse = STEP_PULSE(rpm, motor_steps, microsteps); + if (time > steps_remaining * step_pulse){ + step_pulse = (float)time / steps_remaining; + } } } /* @@ -208,19 +217,27 @@ long BasicStepperDriver::stop(void){ * Return calculated time to complete the given move */ long BasicStepperDriver::getTimeForMove(long steps){ - long t; + float t; + if (steps == 0){ + return 0; + } switch (profile.mode){ case LINEAR_SPEED: startMove(steps); - t = sqrt(2 * steps_to_cruise / profile.accel) + - (steps_remaining - steps_to_cruise - steps_to_brake) * STEP_PULSE(rpm, motor_steps, microsteps) + - sqrt(2 * steps_to_brake / profile.decel); + if (steps_remaining >= steps_to_cruise + steps_to_brake){ + float speed = rpm * motor_steps / 60; // full steps/s + t = (steps / (microsteps * speed)) + (speed / (2 * profile.accel)) + (speed / (2 * profile.decel)); // seconds + } else { + t = sqrt(2.0 * steps_to_cruise / profile.accel / microsteps) + + sqrt(2.0 * steps_to_brake / profile.decel / microsteps); + } + t *= (1e+6); // seconds -> micros break; case CONSTANT_SPEED: default: t = steps * STEP_PULSE(rpm, motor_steps, microsteps); } - return t; + return round(t); } /* * Move the motor an integer number of degrees (360 = full rotation) @@ -245,15 +262,19 @@ void BasicStepperDriver::calcStepPulse(void){ if (steps_remaining <= 0){ // this should not happen, but avoids strange calculations return; } - steps_remaining--; step_count++; if (profile.mode == LINEAR_SPEED){ switch (getCurrentState()){ case ACCELERATING: - step_pulse = step_pulse - (2*step_pulse+rest)/(4*step_count+1); - rest = (step_count < steps_to_cruise) ? (2*step_pulse+rest) % (4*step_count+1) : 0; + if (step_count < steps_to_cruise){ + step_pulse = step_pulse - (2*step_pulse+rest)/(4*step_count+1); + rest = (step_count < steps_to_cruise) ? (2*step_pulse+rest) % (4*step_count+1) : 0; + } else { + // The series approximates target, set the final value to what it should be instead + step_pulse = cruise_step_pulse; + } break; case DECELERATING: @@ -281,15 +302,12 @@ long BasicStepperDriver::nextAction(void){ unsigned m = micros(); unsigned long pulse = step_pulse; // save value because calcStepPulse() will overwrite it calcStepPulse(); - m = micros() - m; - // We should pull HIGH for 1-2us (step_high_min) - if (m < step_high_min){ // fast MCPU or CONSTANT_SPEED - delayMicros(step_high_min-m); - m = step_high_min; - }; + // We should pull HIGH for at least 1-2us (step_high_min) + delayMicros(step_high_min); digitalWrite(step_pin, LOW); // account for calcStepPulse() execution time; sets ceiling for max rpm on slower MCUs last_action_end = micros(); + m = last_action_end - m; next_action_interval = (pulse > m) ? pulse - m : 1; } else { // end of move diff --git a/src/BasicStepperDriver.h b/src/BasicStepperDriver.h index 835a160..20c3414 100644 --- a/src/BasicStepperDriver.h +++ b/src/BasicStepperDriver.h @@ -19,7 +19,7 @@ * calculate the step pulse in microseconds for a given rpm value. * 60[s/min] * 1000000[us/s] / microsteps / steps / rpm */ -#define STEP_PULSE(steps, microsteps, rpm) (60*1000000L/steps/microsteps/rpm) +#define STEP_PULSE(steps, microsteps, rpm) (60.0*1000000L/steps/microsteps/rpm) // don't call yield if we have a wait shorter than this #define MIN_YIELD_MICROS 50 @@ -80,7 +80,7 @@ class BasicStepperDriver { // tWAKE wakeup time, nSLEEP inactive to STEP (us) static const int wakeup_time = 0; - short rpm = 0; + float rpm = 0; /* * Movement state @@ -92,6 +92,7 @@ class BasicStepperDriver { long steps_to_cruise; // steps to reach cruising (max) rpm long steps_to_brake; // steps needed to come to a full stop long step_pulse; // step pulse duration (microseconds) + long cruise_step_pulse; // step pulse duration for constant speed section (max rpm) // DIR pin state short dir_state; @@ -114,7 +115,7 @@ class BasicStepperDriver { /* * Initialize pins, calculate timings etc */ - void begin(short rpm=60, short microsteps=1); + void begin(float rpm=60, short microsteps=1); /* * Set current microstep level, 1=full speed, 32=fine microstepping * Returns new level or previous level if value out of range @@ -129,12 +130,12 @@ class BasicStepperDriver { /* * Set target motor RPM (1-200 is a reasonable range) */ - void setRPM(short rpm); - short getRPM(void){ + void setRPM(float rpm); + float getRPM(void){ return rpm; }; - short getCurrentRPM(void){ - return (short)(60*1000000L / step_pulse / microsteps / motor_steps); + float getCurrentRPM(void){ + return (60.0*1000000L / step_pulse / microsteps / motor_steps); } /* * Set speed profile - CONSTANT_SPEED, LINEAR_SPEED (accelerated) @@ -186,9 +187,11 @@ class BasicStepperDriver { /* * Initiate a move over known distance (calculate and save the parameters) * Pick just one based on move type and distance type. + * If time (microseconds) is given, the driver will attempt to execute the move in exactly that time + * by altering rpm for this move only (up to preset rpm). */ - void startMove(long steps); - inline void startRotate(int deg){ + void startMove(long steps, long time=0); + inline void startRotate(int deg){ startRotate((long)deg); }; void startRotate(long deg); diff --git a/src/MultiDriver.cpp b/src/MultiDriver.cpp index 7ea00b1..92789f5 100644 --- a/src/MultiDriver.cpp +++ b/src/MultiDriver.cpp @@ -67,7 +67,7 @@ long MultiDriver::nextAction(void){ */ void MultiDriver::startBrake(void){ FOREACH_MOTOR( - if (event_timers[i] >= 0){ + if (event_timers[i] > 0){ motors[i]->startBrake(); } ) @@ -91,10 +91,9 @@ bool MultiDriver::isRunning(void){ * positive to move forward, negative to reverse, 0 to remain still */ void MultiDriver::move(long steps1, long steps2, long steps3){ - unsigned long next_event; startMove(steps1, steps2, steps3); while (!ready){ - next_event = nextAction(); + nextAction(); } } diff --git a/src/MultiDriver.h b/src/MultiDriver.h index 97ec61b..07e4e54 100644 --- a/src/MultiDriver.h +++ b/src/MultiDriver.h @@ -36,7 +36,7 @@ class MultiDriver { // ready to start a new move bool ready = true; // when next state change is due for each motor - long event_timers[MAX_MOTORS]; + unsigned long event_timers[MAX_MOTORS]; unsigned long next_action_interval = 0; unsigned long last_action_end = 0; diff --git a/src/SyncDriver.cpp b/src/SyncDriver.cpp index 8a50305..527dd61 100644 --- a/src/SyncDriver.cpp +++ b/src/SyncDriver.cpp @@ -2,7 +2,7 @@ * Synchronous Multi-motor group driver * All motors reach their target at the same time. * - * Copyright (C)2017 Laurentiu Badea + * Copyright (C)2017-2018 Laurentiu Badea * * This file may be redistributed under the terms of the MIT license. * A copy of this license has been included with this distribution in the file LICENSE. @@ -16,37 +16,22 @@ */ void SyncDriver::startMove(long steps1, long steps2, long steps3){ long steps[3] = {steps1, steps2, steps3}; - long timing[MAX_MOTORS]; /* * find which motor would take the longest to finish, */ long move_time = 0; FOREACH_MOTOR( long m = motors[i]->getTimeForMove(abs(steps[i])); - timing[i] = m; if (m > move_time){ move_time = m; } ); /* - * Stretch timing for all others by adjusting rpm proportionally - */ - if (move_time){ - FOREACH_MOTOR( - if (steps[i]){ - rpms[i] = motors[i]->getRPM(); - motors[i]->setRPM(rpms[i] * timing[i] / move_time); - } else { - rpms[i] = 0; - } - ); - } - /* - * Initialize state for all active motors + * Initialize state for all active motors to complete with micros */ FOREACH_MOTOR( if (steps[i]){ - motors[i]->startMove(steps[i]); + motors[i]->startMove(steps[i], move_time); }; event_timers[i] = 0; );