Stepper Motors (Part 7)

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

This post is about driving stepper motors. In the previous posts I described the various components involved in motorized assemblies featuring stepper motors. However, putting all these nuts and bolts together may lead to unexpected results. In my case, these defects where related to the use of stepper motors taken from my scrap box, most of them lacking clear identification or appropriate specifications. Thus the need for building a test bench for testing these motors and giving them a chance to leave a second life !

Next picture illustrates the test bench which is made of (starting from right to left):

  • An Arduino UNO baord (Should I introduce it to you ?)
    • A bread board featuring a switching voltage regulator
    • A stepper motor driver (in this case a A4988)
  • A stepper motor (Cheap 5 V, 2048 steps per turn stepper motor)
  • A 3D printed base plate and a 3D printed wheel
  • A 12 V DC power supply which current rating is compatible with the stepper motor (Do  not expect to feed the stepper through the USB 5 V!)

20160926_085802

Next picture illustrates the wiring diagram.

test_bench

After multiple attempts to run stepper motors from my scrap box, I realized how critical might be the voltage applied to the stepper through its driver. Too much voltage leads to excessive magnetic fields and unpredictable motions at certain frequencies (f = 1 / time_between_2_consecutive_pulses_applied_to_the_driver); excessive voltage also drives to motor overheating. So that this test bench features a well known, cheap and yet powerful voltage regulator. This is precisely a “buck” switching regulator, “buck” means that it lowers the input voltage (as opposed to “boost”) and switching means that a MOSFET transistor acts as a fast on-off electronic tap which leaves a known amount of current flowing from the input to the output of the module. The benefit from such design (switching regulator) is the low power dissipation across the switch at even pretty high current (up to 3 A for this module). These modules are available for a few €/$, some of them  feature a voltage display which might be convenient for those who lack measuring instrumentation.

lm2576hv

Next is an also well known module. The A4988 contains all the bits and pieces necessary for driving almost any stepper motor (please check previous posts on same topic). In this configuration, three out of the multiple configuration pins are used: Enable, Step and Direction. These pin are connected to any port and pin from an Arduino UNO board (or compatible)

a4988

The test bench is driven by a quite simple application which uses the Arduino’s IDE console as a Human Interface. The list of available commands is available at run time after typing the “?” character. In this list, the available range for the arguments as well as the actual values are printed, offering one sort of a dashboard to the user.

/*

	Stepper motor test bench, Arduino UNO compatible
	Copyright (C) 2016 Didier Longueville

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
	
*/
 
/*
	KEYWORDS: stepper motor, A4988, enable, direction, frequency, step, pulse
*/


/* Direction port and pin */
volatile uint8_t *_dirCtrlPort = &PORTB;
const uint8_t _directionPinMask = (1 << PINB0);
/* Step port and pin */
volatile uint8_t *_stepCtrlPort = &PORTB;
const uint8_t _stepPinMask = ( 1 << PINB1);  
/* Enable port and pin */
volatile uint8_t *_enablePort = &PORTB;
const uint8_t _enablePinMask = ( 1 << PINB2);  
/* Variables and constants */
const uint32_t _oneSecond = 1000000; /* In us */
uint32_t _interval = 0;
uint32_t _lastStepTime;
uint16_t _frequency;
const uint8_t _pulseDelay = 1; /* In micros */
uint32_t _pulseCounter;
uint32_t _pulses = 0;
uint16_t _argument;
uint8_t _opcode;
uint8_t _enabled;
uint8_t _direction;
uint8_t _running;

void CommandProcessor(void) 
{
	if (Serial.available()) {
		while (Serial.available()) {
			char inputChar = Serial.read();
			if (inputChar == 0x3F) {
				PrintCommands();
				_opcode = 0;
			} else if ((inputChar >= 0x41) && (inputChar <= 0x5A)) { /* Alpha character */
				/* Probably an _opcode */
				_opcode = uint8_t( inputChar);
			} else if ((inputChar >= 0x30) && (inputChar <= 0x39) && (_opcode != 0)) { /* Num character */
				_argument *= 10; /* Exponent previous value */
				_argument += uint8_t(inputChar - 48); /* Add currrent value */
			} else if ((inputChar = 0x0A) || (inputChar = 0x0D)){ /* Cr Nl */
				switch (_opcode) {
				case 'D': /* Direction */
					_direction = _argument;
					if (_direction == 0) {
						*(_dirCtrlPort) &= ~_directionPinMask;
					} else if (_direction == 1) {
						*(_dirCtrlPort) |= _directionPinMask;
					}
					break;
				case 'E': /* Enabled */
					_enabled = _argument;
					if (_enabled == 0) {
						*(_enablePort) |= _enablePinMask;
					} else if (_enabled == 1) {
						*(_enablePort) &= ~_enablePinMask;
					}
					break;
				case 'F': /* Frequency */
					_frequency = _argument;
					_interval = (_oneSecond / _frequency); /* Compute interval */
					_lastStepTime = micros();
					break;
				case 'I': /* Intervals */
					_interval = _argument;
					_frequency = (_oneSecond / _interval); /* Compute frequency */
					_lastStepTime = micros();
					break;
				case 'P': /* Pulses */
					_pulseCounter = 0;
					_pulses = _argument;
					break;
				case 'R': /* running state */
					_running = _argument; 
					break;
				}
				if (_opcode) {
					Serial.print(char(_opcode));
					Serial.print(" : ");
					Serial.print(_argument);
					Serial.println();
				}
				_opcode = 0; /* Reset opcode */
				_argument = 0; /* reset argument */
			}
		}
	}
}

void PrintCommands(void)
{
	Serial.print(F("D [0:1]: direction ("));
	Serial.print(_direction);
	Serial.println(F(")"));
	Serial.print(F("E [0:1]: enable ("));
	Serial.print(_enabled);
	Serial.println(F(")"));
	Serial.print(F("F [1:32767]: frequency in Hz ("));
	Serial.print(_frequency);
	Serial.println(F(")"));
	Serial.print(F("I [1:65535]: interval in us ("));
	Serial.print(_interval);
	Serial.println(F(")"));
	Serial.print(F("P [0:65535]: pulses and then idle, 0=infinite pulses ("));
	Serial.print(_pulses);
	Serial.println(F(")"));
	Serial.print(F("R [0:1]: running ("));
	Serial.print(_running);
	Serial.println(F(")"));
	Serial.print(F("? : help"));
	Serial.println();
}


void setup(void)
{ 
	/* Console for diag */
	Serial.begin(115200);
	/* Set direction ports and pins */
	*(_enablePort - 1) |= _enablePinMask; /* Enable pin */
	*(_enablePort) |= _enablePinMask; /* Disable driver */
	*(_dirCtrlPort - 1) |= _directionPinMask;
	*(_dirCtrlPort) |= _directionPinMask; /* Set default direction */
	*(_stepCtrlPort - 1) |= _stepPinMask; /* Set pin direction */
	*(_stepCtrlPort) &= ~_stepPinMask; /* Set default pin state to LOW */
	/* Compute default interval */
	_enabled = 0;
	_running = 0;
	_frequency = 100;
	_interval = (_oneSecond / _frequency);
	_lastStepTime = micros();
}


void loop(void)
{
	CommandProcessor();
	/* Send step pulse to the dirver */
	if ((micros() - _lastStepTime) > _interval) {
		_lastStepTime += _interval;
		if (_running) {
			/* 
			Generate pulse: A low-to-high transition on the STEP input sequences 
			the translator and advances the motor one increment 
			*/
			*(_stepCtrlPort) |= _stepPinMask;
			delayMicroseconds(_pulseDelay); 
			*(_stepCtrlPort) &= ~_stepPinMask;
			/* */
			if (_pulses != 0) {
				_pulseCounter += 1;
				if (_pulseCounter >= _pulses) {
					_pulseCounter = 0; /* Reset counter */
					_running = 0; /* Reset running state */
				}
			}
		}
	}
}

Please note that running the stepper requires that the following steps are completed: set frequency or interval time as appropriate (default frequency is 100 Hz), enable the driver (“E1”) and set running state (“R1”). Why this distinction between enabling the driver and setting a running state ? When the driver is enabled and no pulses are sent, the stepper motor coils are still fed with some current. In this way, the rotor is locked in a rest position. As it is important to check this idle current both stages (enabled and running) are individualized.

It appeared to me that setting the most appropriate current limit required that multiple operating conditions had to be checked: setting various frequencies and checking the forward and reverse motion for example. The power supply voltage controller happened to be very useful for checking unmarked steppers and setting the optimal minimal voltage in order to decrease the dissipated temperature (almost none from the driver, most from the stepper motor itself).

Are you interested in the 3D printed base ? Get the .stl file >here<

Next post on same subject

Leave a Reply

You must be logged in to post a comment.