Stepper Motors (Part 8)

Part 12, 3, 4, 5, 6, 7, 8, 9

This post is about driving multiple stepper motors continuously sweeping various ranges of steps at various speeds. Each motor features a permanent sinusoidal motion (accelerated and decelerated). The complexity of the code lies in the next requirements: Each motor has its own sweeping frequency, its own full swing range (number of steps explored on each cycle). It must be possible to introduce a phase shift between each motor, they must stay synchronized over long periods of time and ultimately you may change the settings at any time.

That was a challenge ! The most confusing part of the project lies in the need for thinking in reverse mode. In this case, we do not have fixed intervals of time so we need to define how much time must elapse between to steps. Let’s put it differently: drawing a sin curve on a spreadsheet usually requires fixed time intervals on the x axis and computed ordinates for the y axis using this sort of formula: y = sin((x * frequency  * 2 * Pi())). Computing x from y requires the use of the inverse of sin function: sin^-1 = asin (arc sine).

The code below encapsulate the Interval function which is used for computing the time after which the next step will be issued by the stepper motor driver. Note that all direction pins are wire together whatever the number of drivers, while pulse must be directed to individual drivers (as per the _vStepPins vector)

#include <math.h>

const uint8_t DIR_CW = LOW;
const uint8_t DIR_CCW = HIGH;
const float _twoPi = 6.283185307179586476925286766559;

/* User settings. See explanations below */
/* _directionPin: this is the arduino pin to which the direction pins from the stepper drivers are
attached. Numbering is as per arduino standard pin namming convention */
const uint8_t _directionPin = 8;
/* _steppers: number of stepper motors. Check next vector contents accordingly */
const uint8_t _steppers = 4;
/* _vSteps: number of steps required to explore the full swing (half the _vBnFSteps) */
const uint16_t _vSteps[_steppers] = {200, 100, 50, 25};
/* _vStepPins: these are the arduino pins to which the step pins from the stepper drivers are 
attached. Numbering is as per arduino standard pin namming convention */
const uint8_t _vStepPins[_steppers] = {9, 10, 11, 12};  
/* _vFrequency; Frequency (in Hz) of sinusoidal motion. Each step interval is computed */
const float _vFrequency[_steppers] = {0.5, 1.0, 2.0, 4.0}; 
/* _vPhaseShift: Phase shift in ms versus the absolute start time which is identical for all tepper 
motors. This value is self constrained. if f = 1 Hz, the phase shift might range from 0 to 1000 ms. 
A 500ms value corresponds to 1 x Pi (so as to say half a cycle) phase shift. */
uint16_t _vPhaseShift[_steppers] = {0, 0, 0, 0}; 
/* Do not change next vectors and variables */
uint16_t _vBnFSteps[_steppers]; /* Twice the steps */
uint16_t _vHalfSteps[_steppers];  /* Half the steps */
int16_t _vStepsCounter[_steppers]; 
uint32_t _vNextStepTime[_steppers];
uint32_t _vLoopsCounter[_steppers];
uint32_t _vStartTime[_steppers];
const uint16_t _startTimeOffset = 100; /* In milli seconds */
const uint8_t _pulseDelay = 2; /* In micro seconds */

void setup(void)
{ 
	/* Console for diag */
	Serial.begin(115200);
	Serial.println("Ready");
	/* Direction pin */
	pinMode(_directionPin, OUTPUT);
	digitalWrite(_directionPin, DIR_CW); /* Set default direction */
	/* Step pins */
	for (uint8_t i = 0; i < _steppers; i++) {
		pinMode(_vStepPins[i], OUTPUT); /* Set direction */
		digitalWrite(_vStepPins[i], LOW); /* Set default state */
	}	
	uint32_t now = millis();
	for (uint8_t i = 0; i < _steppers; i++) {
		_vBnFSteps[i] = (_vSteps[i] << 1); /* Precalculate data */
		_vHalfSteps[i] = (_vSteps[i] >> 1); /* Precalculate data */
		_vStepsCounter[i] = 0; /* Reset steps counter */
		_vPhaseShift[i] %= uint16_t(1000.0 / _vFrequency[i]); /* Constrain phase shift */
		_vStartTime[i] = (now + _vPhaseShift[i]); /* Compute start time */
		_vStartTime[i] +=_startTimeOffset;
		_vNextStepTime[i] = (_vStartTime[i] + Interval(i)); /* Set next step time */
		_vLoopsCounter[i] = 0; /* Reset loop counter */
	}
}

void loop(void)
{
	uint32_t now = millis();
	for (int8_t i = 0; i < _steppers; i++) {
		if (now > _vNextStepTime[i]) {
			/* Set direction */
			int8_t dir;
			if (_vStepsCounter[i] < _vSteps[i]) {
				dir = DIR_CW;
			} else {
				dir = DIR_CCW;
			}
			digitalWrite(_directionPin, dir);
			/* Generate pulse */
			digitalWrite(_vStepPins[i], HIGH);
			delayMicroseconds(_pulseDelay); /* Minimal pulse delay */
			digitalWrite(_vStepPins[i], LOW);
			/* Increment step and constrain computed step value */
			_vStepsCounter[i] += 1; 
			if (_vStepsCounter[i] >= _vBnFSteps[i]) { 
				_vLoopsCounter[i] += 1; /* Increment loop counter */
				_vStepsCounter[i] = 0; /* Reset steps counter */
				_vNextStepTime[i] = (_vStartTime[i] + int32_t(_vLoopsCounter[i] * (1000.0 / _vFrequency[i])));
			} else {
				_vNextStepTime[i] = (now + Interval(i));
			}
		}
	}	
}

uint16_t Interval(uint16_t stepper)
{
	/* Compute next stepper time */
	int16_t i0 = ((_vStepsCounter[stepper] % _vSteps[stepper]) - _vHalfSteps[stepper]);
	int16_t i1 = (i0 + 1);
	float y0 = ((float)i0 / _vHalfSteps[stepper]);
	float y1 = ((float)i1 / _vHalfSteps[stepper]);
	int16_t result = (int16_t)(((asin(y1) - asin(y0)) * 1000.0) / (_twoPi * _vFrequency[stepper]));
	/* Returned value */
	return(result);
}

Next post on same subject

Leave a Reply

You must be logged in to post a comment.