/* Stepper motor demonstration program written by Arthur Jones, 4 November 2018. Implements a simplistic and ineffective ramping algorithm but provides framework for implementation of LeibRamp algorithm described by Aryeh Eiderman, http://hwml.com/LeibRamp.pdf Makes use of background work and some aspects of code developed by Choaran Wang, 2017-18. This in turn incorporates some ideas used in the AccelStepper library: https://www.airspayce.com/mikem/arduino/AccelStepper/ Serial input aspects are based closely upon: http://forum.arduino.cc/index.php?topic=396450 Example 4 - Receive a number as text and convert it to an int Modified to read a long */ // #define USEINTERRUPTS const int stepPin = 13; const int dirPin = 9; const bool FWDS = true; const bool BWDS = false; #ifdef USEINTERRUPTS const long ticksPerSec = 16000000; // Clock speed of Arduino #else const long ticksPerSec = 1000000; // microseconds in this case #endif /* Define permissible parameters for motor */ // For testing by watching LED: try movements in order of 100 steps //float accelSteps=20; /* leave this as a variable as we may over-write it */ //const float minSpeed = 2.0; //const float maxPermissSpeed = 20.0; //const float maxAccel = 10.0; //const long stepLengthMus = 10000; // For lab testing with real motor: try movements in the order of 3000 steps float accelSteps=1000; /* leave this as a variable as we may over-write it */ const float minSpeed=10.0; const float maxPermissSpeed=1000.0; const float maxAccel=500.0; const long stepLengthMus=100; /* Intervals in milliseconds for user-defined timed loops */ const long printInterval = 1000; /* Global variables used for loop timing */ unsigned long prevMillisPrint = 0; /* stores last time values were printed */ /* Global variables used in serial input */ enum {numChars = 32}; char receivedChars[numChars]; /* an array to store the received data */ long dataNumber = 0; /* Value read from serial monitor input */ boolean newData = false; /* Global variables relating to stepper motor position counting etc. */ long stepsToGo; /* Number of steps left to make in present movement */ long targetPosition; /* Intended destination of motor for given movement */ volatile long currentPosition = 0; /* Position in steps of motor relative to startup position */ double maxSpeed; /* Maximum speed in present movement (not nec. max permitted) */ bool direction; /* Direction of present movement: FWDS or BWDS */ volatile float p; /* Step interval in clock ticks or microseconds */ float p1, ps; /* Minimum and maximum step periods */ double deltaP; /* You'll be able to get rid of this later */ double R; /* Multiplying constant used in Eiderman's algorithm */ /* Global variable used for noting previous time of a step in timed loop */ long prevStepTime; void setup() { long stepsToGo = 0; currentPosition = 0; goToPosition(dataNumber); pinMode(stepPin, OUTPUT); pinMode(dirPin, OUTPUT); Serial.begin(9600); Serial.println("Enter target position in number of steps and hit return"); // If USEINTERRUPTS is defined at the start of the program this section will be used #ifdef USEINTERRUPTS cli(); TCCR1A = 0; // No output compare TCCR1B = 0; TCCR1B |= (1 << WGM12); //CTC mode OCR1A = 0; // Set to zero for the present time: catch this to switch interrupt off TCCR1B |= (1 << CS12); // 256 prescaler: overwritten in ISR TIMSK1 |= (1 << OCIE1A); //enable timer compare interrupt sei(); #else // // Use built-in Arduino function micros() to get the time in microseconds since the program started running. // Documentation: https://www.arduino.cc/reference/en/language/functions/time/micros/ prevStepTime = micros(); // This is a built-in Arduino function. #endif } void loop() { unsigned long currentMillis = millis(); unsigned long currentMicros; recvWithEndMarker(); stepsToGo = computeStepsToGo(); if (convertNewNumber()) { /* Only get to this stage if there was new data to convert */ if (stepsToGo <= 0) { /* Only get to this stage if not busy, otherwise will have thrown away input */ goToPosition(dataNumber); /* Delete these and replace with Leib Ramp formulae */ double maxInterval = ((double)ticksPerSec) / minSpeed; ps = ((double)ticksPerSec) / maxPermissSpeed; deltaP = (maxInterval - ps) / accelSteps; /* End of section requiring redefinitions */ stepsToGo = computeStepsToGo(); maxSpeed = maxPermissSpeed; if (2 * accelSteps > stepsToGo) { /* Definiiton of S where ther is no constant speed period - check it is still applicable */ accelSteps = (long)(stepsToGo / 2); /* Need to redefine maxSpeed here as we never fully accelerate */ } /* Will need to over-write these with correct initial value of p and p1, along with R */ p = maxInterval; p1 = (double)ticksPerSec/minSpeed; /* End of section requiring redefinitions */ ps = ((double)ticksPerSec) / maxSpeed; /* Eq 7 in paper: this is OK */ #ifdef USEINTERRUPTS if (p != 0) { // Re-enable interrupts if non-zero steps TIMSK1 |= (1 << OCIE1A); } #endif } } #ifndef USEINTERRUPTS /* Timed loop for stepping, and associated coding */ currentMicros = micros(); if (currentMicros - prevStepTime >= p) { moveOneStep(); prevStepTime = currentMicros; computeNewSpeed(); } #endif /* Timed loop for printing */ if (currentMillis - prevMillisPrint >= printInterval) { /* Save the last time output was printed */ prevMillisPrint = currentMillis; printLoop(); } } void moveOneStep() /* Move a single step, holding pulse high for delayMicroSeconds */ { if (p != 0) /* p=0 is code for "don't make steps" */ { digitalWrite(stepPin, HIGH); if (direction == FWDS) { /* Is something missing here? */ currentPosition++; } else { /* Is something missing here? */ currentPosition--; } delayMicroseconds(stepLengthMus); digitalWrite(stepPin, LOW); } } void computeNewSpeed() /* Calcuate new value of step interval p based on constants defined in loop() */ { /* You may need to declare some temporary variables for this function... */ stepsToGo = computeStepsToGo(); if (stepsToGo == 0) { p = 0; // Not actually a zero step interval, used to switch stepping off return; } else if (stepsToGo > accelSteps && (long)p > long(ps)) /* Speeding up */ { /* Delete this simplistic change to p and replace with something else */ p -= deltaP; } else if (stepsToGo <= accelSteps) /* Slowing down */ { /* Delete this simplistic change to p and replace with something else */ p += deltaP; } else /* Running at constant speed */ { /* For simpplistic approach, p=p so do nothing to it. But you will need to put something here for Leib ramp ... */ } /* Update to step interval based on Leib ramp algorithm, using temporary variables */ /* Need to ensure rounding error does not cause drift outside acceptable interval range: replace p with relevant bound if it strays outside - so need to write some code here */ } long computeStepsToGo() /* Work out how far the stepper motor still needs to move */ { if (direction == FWDS) { return targetPosition - currentPosition; } else { return currentPosition - targetPosition; } } void goToPosition(long newPosition) /* Set the target position and determine direction of intended movement */ { targetPosition = newPosition; if (targetPosition - currentPosition > 0) { direction = FWDS; } else { direction = BWDS; } } void recvWithEndMarker() /* Receive data from serial port finishing with "newline" character. Based on http://forum.arduino.cc/index.php?topic=396450 Example 4 */ { static byte ndx = 0; char endMarker = '\n'; char rc; if (Serial.available() > 0) { rc = Serial.read(); if (rc != endMarker) { receivedChars[ndx] = rc; ndx++; if (ndx >= numChars) { ndx = numChars - 1; } } else { receivedChars[ndx] = '\0'; // terminate the string ndx = 0; newData = true; } } } bool convertNewNumber() /* Converts character string to long integer only if there are new data to convert, otherwise returns false */ { if (newData) { dataNumber = 0.0; // new for this version dataNumber = atol(receivedChars); // new for this version newData = false; return true; } else { return false; } } void printLoop() /* Print current position of stepper using timed loop */ { /* Sample all counters one after the other to avoid delay-related offsets */ Serial.print("Current position = "); Serial.print(currentPosition); Serial.print("\r\n"); } #ifdef USEINTERRUPTS ISR(TIMER1_COMPA_vect) /* Interrupt service routine which essentially just calls moveOneStep and computeNewSpeed. However, it also changes the prescale value on-the-fly so that the full range of possible step rates can be exploited, from around 0.25 Hz upwards, limited by step pulse width. */ { if (p == 0) { // Disable interrupt to avoid endless calling of interrupt if not needed TIMSK1 &= !(1 << OCIE1A); } moveOneStep(); /* Adapt prescaler to keep OCR1A as large as possible within acceptable range */ if (p < 65536) { // Prescaler 1 OCR1A = (long)p - 1; TCCR1B = (TCCR1B & 0xF8) | 0x01; } else if (p < 524288) { // Prescaler 8 OCR1A = ((long)p >> 3) - 1; TCCR1B = (TCCR1B & 0xF8) | 0x02; } else if (p < 4194304) { // Prescaler 64 OCR1A = ((long)p >> 6) - 1; TCCR1B = (TCCR1B & 0xF8) | 0x03; } else if (p < 16777216) { // Prescaler 256 OCR1A = ((long)p >> 8) - 1; TCCR1B = (TCCR1B & 0xF8) | 0x04; } else { // Prescaler 1024 OCR1A = ((long)p >> 10) - 1; TCCR1B = (TCCR1B & 0xF8) | 0x05; } computeNewSpeed(); } #endif