OK, here’s the whole thing. In the photos, one of the photos shows the joystick control.
This code had previously been used to control a “classic” NEMA-17 stepper, using the A4988 board (also from Pololu). It worked perfectly. So we modified the code (the command table previously only went to 1/16, and it had MS1, MS2 and MS3 members; the new command table has more entries and uses the symbols from HighPowerStepper.h). So this code has been tested and works with another board. However, various attempts to cause the 36V4 to control the NEMA-23 stepper motor have been fruitless.
#include <SPI.h>
#include <HighPowerStepperDriver.h>
HighPowerStepperDriver driver;
//#undef F
//#define F(x) x
static const int vrxAPin = A7;
static const int vryAPin = A6;
static const int swAPin = 7;
static const int vrxBPin = A4;
static const int vryBPin = A1;
static const int swBPin = 6;
static const int DriverSelectPin = 5;
static const int potPin = A0;
static int fractionStep = 8;// step interval is 1/fractionStep
static int stepsPerRevolution =200* fractionStep;
static const int moveFast = 500;
static const int moveSlow = 800;
static const int moveVariable = -1;
static char stepMode = ' ';
static int moveDirection = 1;
static int minWait = 50;
static int maxWait = 3000;
//static const int driftX = 8;
//static const int driftY = 8;
static bool useJoystick = true;
static const int MAXTEST = 100;
/*************************************************************************
* The nominal zero positions of the joysticks. These will be recomputed
* by the calibrate() procedure
*************************************************************************/
static int vrxAZero = 508;
static int vryAZero = 516;
/**************************************************************************
* class Joystick
**************************************************************************/
class Joystick {
public:
int XPin; //analog input pin
int YPin; //analog input pin
int swPin; // switch pin
int XZero; // X Zero value
int YZero; //Y Zero value
int Xdrift;
int Ydrift;
Joystick ( int XP,int YP,int SPi,int XZ = 508,int YZ = 516,int XD = 8, int YD = 8)
{
XPin = XP;
YPin = YP;
swPin = SPi;
XZero = XZ;
YZero = YZ;
Xdrift = XD;
Ydrift = YD;
}
void calibrate()
{
XZero = average(XPin);
YZero = average(YPin);
}
void showcalibrate()
{
Serial.print(F("x = "));
Serial.print(XZero);
Serial.print(F(" y = "));
Serial.print(YZero);
}
/*******************************************************************
* Joystick::getXWait
* Inputs:
* bool & nothing: Set 'true' if there is nothing to do
* bool & direction: Set 'true' for positive motion, false for negative motion
*
* Effect:
* Computes the "wait time" to control the speed of the X-axis
* This is done by reading the X-position potentiometer, and computing
* the absolute value from the zero point
* X0
* 0 | 1023
* V
* -X0...................1023-X0
* |
*********************************************************************/
int getXWait(bool & nothing, bool & direction)
{
int vrx = analogRead(XPin);
int speed = map( vrx, 0, 1023, - XZero, 1023 - XZero);
int abspeed = abs(speed);
nothing = abspeed == 0;
int limit = vrx < XZero ? XZero : 1023 - XZero;
int wait = map (abspeed, 0, limit, maxWait, minWait);
direction = speed > 0;
return wait;
}
int getX ()
{
return analogRead(XPin);
}
int getY ()
{
return analogRead(YPin);
}
int getsw ()
{
return analogRead(swPin);
}
/************************************************
* inDriftX, inDriftY
* Inputs:
* int x (or y): The x- (or y-) coordinate read from the joystick
* Result: bool
* true if the value is within the nominal
* "drift" of the zero point
************************************************/
bool inDriftX(int x )
{
return (abs(x - XZero) <= Xdrift);
}
bool inDriftY ( int y )
{
return (abs(y - YZero) <= Ydrift);
}
protected:
int average(int Pin)
{
long Center = 0;
for(int i = 0; i < MAXTEST; ++ i)
{
Center += analogRead(Pin);
}
return Center / MAXTEST;
}
}; // class Joystick
Joystick JSleft(vrxAPin,vryAPin,swAPin);
Joystick JSright(vrxBPin,vryBPin,swBPin);
static bool debug = false;
static bool showStep = false;
/***********************************************************
* uSteps
*
* This is the table of microstep values and the corresponding
* command names. This table will be printed out as part of the
* 'help' message
***********************************************************/
typedef struct {
char name; // command letter
int minWait; // minimum delay in microseconds
HPSDStepMode Mode; // the value to use in setStepMode
int fraction; // human-readable fraction number
} MSTABLE;
MSTABLE uSteps[] = {
//cmd |minWait| Mode | fraction
//-----+-------|---------------------------+----------
{'w', 530, HPSDStepMode::MicroStep1, 1}, // [0]
{'h', 220, HPSDStepMode::MicroStep2, 2}, // [1]
{'q', 70, HPSDStepMode::MicroStep4, 4}, // [2]
{'e', 0, HPSDStepMode::MicroStep8, 8}, // [3]
{'s', 0, HPSDStepMode::MicroStep16, 16}, // [5]
{'V', 0, HPSDStepMode::MicroStep32, 32}, // [6]
{'X', 0, HPSDStepMode::MicroStep64, 64}, // [7]
{'Y', 0, HPSDStepMode::MicroStep128, 128}, // [8]
{'Z', 0, HPSDStepMode::MicroStep256, 256}, // [9]
{0} // EOT // [10]
};
static int offset = 0;
/*************************************************************
* calibrate
*
* Effect:
* Computes the "at rest" position of the joystick potentiometer
* It is nominally 512, but there is no guarantee
* Notes:
* This is currently called by explicit user command. In a
* user-input-free environment, this should probably be called
* from setup() to establish the current best-guess value
**************************************************************/
void calibrate()
{
Serial.println(F("CALIBRATION STARTING. DO NOT TOUCH JOYSTICK"));
JSleft.calibrate();
//JSright.calibrate();
Serial.print(F("Calibration complete"));
Serial.println();
JSleft.showcalibrate();
//JSright.showcalibrate();
Serial.println();
} // calibrate
/*******************************************************************
* setStep
*
* Inputs:
* char step: The step interval, one of the commands in the table
* Result: bool
* true if the step mode was set successfully, or is already correct
* false if the step mode is not recognized
********************************************************************/
bool setStep(char step)
{
if(step == stepMode)
return true; // not changed because already set
// Look up the command in the table
for(int i = 0; uSteps [i].name != 0 ; i++)
{ /* search step table */
if(uSteps[i].name == step)
{ /* valid command */
driver.setStepMode(uSteps[i]. Mode);
stepMode = step;
fractionStep = uSteps[i].fraction;
stepsPerRevolution = 200 * fractionStep;
minWait = uSteps [i].minWait + offset;
minWait = max(minWait,0);
if(debug)
{ /* debug output */
Serial.print(F("step size set to '"));
Serial.print(step);
Serial.print(F("' 1/"));
Serial.print(fractionStep);
Serial.println();
} /* debug output */
return true; // successfully changed
} /* valid command */
} /* search step table */
return false;
} // setStep
/****************************************************************************
* getVariableWait
* Inputs:
* bool & nothing: set to 'true' if nothing to be done
* bool & direction: 'true' for 'forward', 'false' for 'backward'
* Result: int
* a variable Wait interval,in microseconds, based on the potentiometer
* a negative value indicates a reverse direction
* Notes:
* In potentiometer mode, direction is set by the 'r' command and is ignored
* In potentiometer mode, nothing is meaningless and is not set
*
* 'forward' and 'backward' are defined by how the motor is wired in
* (phase and phase orientation matter) and therefore cannot be said to
* be "clockwise" or "counterclockwise", but simply two directions which
* are opposite
*****************************************************************************/
int getVariableWait(bool & nothing, bool & direction)
{
if (!useJoystick)
return map( analogRead(potPin), 0, 1023, minWait, maxWait);
// For the joystick, read the joystick x,y values
return JSleft.getXWait(nothing, direction);
} // getVariableWait
/**********************************************************************
* move
* inputs:
* int motor: the motor number(1 = x, 2 = y,3 =z)
* long distance: number of steps to move ( + = cw - = ccw)
* int wait: The delay between steps
* If < 0, reads the potentiometer or joystick
* If > 0, the actual delay time in ms
* Notes:
* At the moment, since there is only one motor, the motor parameter
* is ignored.
**********************************************************************/
void move(int motor, long distance, int Wait)
{
driver.setDirection( distance >= 0 ? true : false);
// Serial.println(distance);
for (long i = 0; i < abs(distance); i++) {
if (Serial.available() )
return; // If user has typed a command, abort the loop
bool nothing;
bool direction;
int loopWait = Wait > 0 ? Wait : getVariableWait(nothing, direction);
if( useJoystick)
{ /* joystick motion */
static int lastloopWait = 0;
static int lastnothing =-1;
static int lastdirection = -1;
if (debug && (nothing != lastnothing ||
direction != lastdirection ||
loopWait != lastloopWait))
{ /* trace */
Serial.print(F("loopWait = "));
Serial.print(loopWait);
lastloopWait = loopWait;
Serial.print(F(" nothing = "));
Serial.print(nothing);
lastnothing = nothing;
Serial.print(F(" direction = "));
Serial.print (direction);
lastdirection = direction;
Serial.println();
} /* trace */
if (nothing)
return;
driver.setDirection( direction ? true : false);
} /* joystick motion */
driver.step();
if (showStep)
{
static int count = 0; // Keep lines short
count++;
if(count % 50 == 0) // if value modulo is zero, we have printed out enough on this line
Serial.println();
Serial.print(direction ? F(">") : F("<"));
}
}
} // move
/******************************************************************
* setup
* Initializes the various states required
*
******************************************************************/
void setup() {
SPI.begin(); // Required for Pololu 36V4 driver board
Serial.begin(115200);
pinMode(DriverSelectPin,OUTPUT);
delay(1);
//Serial.print(1);
driver.setChipSelectPin(DriverSelectPin);
//Serial.print(2);
driver.resetSettings();
driver.clearStatus();
//Serial.print(3);
driver.setCurrentMilliamps36v4(4000);
//Serial.print(4);
driver.enableDriver();
pinMode(vrxAPin,INPUT);
pinMode(vryAPin,INPUT);
pinMode(swAPin,INPUT_PULLUP);
Serial.println(F("type m for help"));
} // setup
static const int Wait = 500;
char ticket = 's';
bool changed = false;
bool query = false;
static const long shortRevs = 2;
static const long longRevs = 100;
static const int dt = 10;
/********************************************************************
* help
*
* Effect:
* Prints out the help message to remind us of the commands
********************************************************************/
void help()
{
Serial.println(F(__FILE__));
Serial.println(F(__DATE__ __TIME__));
Serial.println(F("m this message"));
Serial.println("? current status");
Serial.print(F("+ add "));
Serial.print(dt);
Serial.println(F("us to step wait time"));
Serial.print(F("- subtract "));
Serial.print(dt);
Serial.println(F("us from step wait time"));
Serial.println(F("c calibrate joystick"));
Serial.println(F("d toggle debug Mode"));
Serial.println(F("D toggle show-step Mode"));
Serial.println(F("F clear faults"));
if(! useJoystick)
Serial.println ( F("j set Joystick mode"));
if(useJoystick)
Serial.println ( F("p set potentiometer mode "));
if(! useJoystick)
Serial.println(F("r reverse direction"));
Serial.println(F("S clear status"));
Serial.print(F("t set revolutions to "));
Serial.println(shortRevs);
Serial.print(F("T set revolutions to "));
Serial.println(longRevs);
for(int i = 0; uSteps [i].name != 0 ; i++)
{
Serial.print(uSteps [i].name);
Serial.print(F(" set 1/"));
Serial.print(uSteps [i].fraction);
Serial.println(F(" step"));
}
} // help
/********************************************************************
* showStatus
* Inputs:
* uint8_t status: The status bits from readStatus or readFaults
* Effect:
* displays the bits as symbolic status
********************************************************************/
void showStatus(uint8_t status)
{
if(status != 0)
{
/********************************************************************
* STATUS
* Inputs:
* x: The status flag shift value HPSDStatusBit::x
* s: The textual explanation of the bit
* Effect:
* Generates the string
* 0xDD NAME explanation
* for the bit in the status flag value 'status' at position x
* DD is the status bit in hexadecimal
* NAME is the name of the status bit, e.g., "OTS"
* explanation is the description of the status bit
* Notes:
* The string provided as the s argument must not use F(); that
* is provided in the body of the macro
* The \ ***MUST*** be the last character on the line, nothing may
* follow it, even a space.
*********************************************************************/
#define STATUS(x, s) if(status & (1 << (uint8_t)(HPSDStatusBit::x))) \
{\
Serial.print(F(" 0x"));\
Serial.print(1 << (uint8_t)HPSDStatusBit::x,HEX);\
Serial.println(F(" " #x " " s));\
}
STATUS(OTS,"overtemperature shutdown");
STATUS(AOCP,"channel A overcurrent shutdown");
STATUS(BOCP,"channel B overcurrent shutdown");
STATUS(APDF,"channel A predriver fault");
STATUS(BPDF,"channel B predriver fault");
STATUS(UVLO,"undervoltage lockout");
// Note that for faults, these two bits will always be zero
STATUS(STD,"stall detected");
STATUS(STDLAT,"latched stall detected");
}
}
static long revs = 100;
/**************************************************************
* loop
*
* Effect:
* The main polling loop. If there are characters at the serial
* port, process them first.
***************************************************************/
void loop()
{
if(Serial.available())
{ /* serial pending */
char ch = Serial.read();
if( ch <= ' ')
return; // ignore spaces and control characters
changed = true;
query = false;
switch(ch)
{ /* ch */
case'?':
query = true;
break;
case '+':
offset += dt;
return;
case '-':
offset -= dt;
return;
case 'c':
calibrate();
return;
case 'd':
debug = !debug;
Serial.print(F("debug "));
Serial.println(debug ? F("ON"): F("OFF"));
return;
case 'D':
showStep = !showStep;
Serial.print(F("show-step "));
Serial.println(showStep ? F("ON"): F("OFF"));
return;
case 'F':
{
driver. clearFaults();
uint8_t faults = driver.readFaults(); // verify that they were cleared and not stuck
Serial.println(F("Faults cleared"));
showStatus(faults);
}
return;
case 'j':
useJoystick = true;
return;
case 'p':
useJoystick = false;
return;
case 'r':
if(! useJoystick)
{
moveDirection = - moveDirection;
changed = true;
}
return;
case 'S':
driver.clearStatus();
Serial.println(F("status cleared"));
{
uint8_t status = driver.readStatus();
showStatus(status);
}
return;
case't':
revs = shortRevs;
return;
case'T':
revs = longRevs;
return;
case 'm':
help();
return;
default:
// It might be a step request, or complete nonsense
ticket = ch;
break;
} /* ch */
} /* serial pending */
if(!query && !setStep(ticket))
{ /* complete nonsense */
Serial.print(F("unknown command'"));
Serial.print(ticket);
Serial.print(F("'"));
Serial.println();
return;
} /* complete nonsense */
if(query || (debug && changed))
{ /* report status */
Serial.print(query ? F("status '") : F("changed '"));
Serial.print(stepMode);
Serial.print(F("' mw="));
Serial.print(minWait);
Serial.print(F(" vw="));
bool nothing;
bool direction;
Serial.print(getVariableWait(nothing, direction));
Serial.print(F(" direction = "));
Serial.print(direction);
Serial.print(useJoystick ? F(" Joystick mode") : F(" potentiometer mode"));
uint8_t status = driver.readStatus();
Serial.print(F(" status = 0x"));
Serial.print(status,HEX);
uint8_t faults = driver.readFaults();
Serial.print(F(" faults = 0x"));
Serial.print(faults,HEX);
Serial.println();
if(status != 0)
{ /* has status */
Serial.println(F("Status/Faults"));
showStatus(status);
} /* has status */
} /* report status */
// Clear the changed and query flags
changed = false;
query = false;
if (useJoystick)
{ /* joystick mode */
static int lastvrxA = 0;
static int lastvryA = 0;
static int lastswA = -1;
int vrxA = JSleft.getX();
int vryA = JSleft.getY();
int swA = JSleft.getsw();
if(JSleft.inDriftX(vrxA)&&
JSleft.inDriftY(vryA))
return; // ignore values within the "drift range" of the joystick
if (debug && (vrxA != lastvrxA ||
vryA != lastvryA ||
swA != lastswA))
{ /* debug trace */
Serial.print(F("vrxA = "));
Serial.print(vrxA);
lastvrxA = vrxA;
Serial.print (F(" vryA = "));
Serial.print ( vryA);
lastvryA = vryA;
Serial.print (F(" swA = "));
Serial.print (swA);
lastswA = swA;
Serial.println();
} /* debug trace */
move(0, 1, moveVariable);
} /* joystick mode */
else
{ /* potentiomenter mode */
move(0,moveDirection * revs * stepsPerRevolution, moveVariable);// will cause it to turn revs.
} /* potentiomenter mode */
} // loop
The photos are attached. My student took several. The power supply shown is 36V @ 10A. The NEMA-23 motor is rated 24V-48V @ 4.2A/phase. We can see the program uploaded; the ? command tells us that the status flags are 0x00. There are C and F commands to clear status and clear faults. We had to use “m” to get the help because “h” was already allocated to “half-step”.