Adapt code for bipolar stepper motor

Hi there,

I could use some help in changing the code I have been using for a while now. It was written for a unipolar stepper motor, but since I changed the hardware and started using a bipolar one, I need to adapt the code. Could someone point to the part of the code I need to change? I will post the code in two posts.

//------------------------------------------------------------------
// Ascom-Arduino Focusser
// Dave Wells
// Thanks for code snippets & inspiration:
//  o  Gina (Stargazers Lounge) for the stepper control basics
//  o  russellhq  (Stargazers Lounge) for the 1-wire code and info.
//------------------------------------------------------------------

//------ Change Log ------------------------------------------------
//  Version    Date        Change
//  0.0.2      26/05/2014  Initial - copied from Windows version
//  2.0.0      22/06/2014  Renumbered to match Ascom Driver Numbering
//  2.0.1      09/08/2014  Initial position set to 1000
//  2.0.2      16/08/2014  Halt function implemented with H# command
//                         New I# command to set an initial position
//
//  version numbering brought into line with driver numbering
//  2.2.0      03/10/2014  Implemented Temperature Sensing C# command
//  2.3.0      07/10/2014  Now stores position in EEPROM
//                         Changed some int variables to unsigned int
//                         Fixed minor bug in motor hi/lo speed detection
//                         Temperature now in 100ths of degree for internationlisation
//                         Block temperature requests during focuser movement
//------------------------------------------------------------------

// Include necessary libraries
#include <OneWire.h>                          // DS18B20 temp sensor
#include <DallasTemperature.h>                // DS18B20 temp sensor  
#include <EEPROM.h>                           // EEPROM Library
#define ONE_WIRE_BUS 6                        // DS18B20 DATA wire connected to Digital Pin 6

// EEPROM storage locations
#define EE_LOC_POS 0                          // Location of position (2 bytes)
#define EE_LOC_PSTAT 2                        // Location of Position Status (1 Byte)

// Position Statuses
#define POS_VALID 55                          // Stored position valid if this value otherwise assume invalid

const String programName = "Arduino Focuser";
const String programVersion = "2.3.0";

const int     motorPins[4] = {7,8,9,10};       // Declare pins to drive motor control board
const int     motorSpeedLo = 16000;            // Motor step delay for Lo speed (uS)
const int     motorSpeedHi = 2000;             // Motor step delay for Hi speed (uS)
const int     motorSpeedDefault = 16000;       // Default motor step speed (uS)(failsafe operation)
const int     speedThreshold = 25;             // motor speed Hi if steps to go is higher than this
int           motorSpeed = motorSpeedDefault;  // current delay for motor step speed (uS)
DeviceAddress tempSensor;                      // Temperature sensor
double        currentTemperature;              // current temperature
boolean       tempSensorPresent = false;       // Is there a temperature sensor installed?

// Default initial positions if not set using the Innnn# command by Ascom Driver
unsigned int           currentPosition = 1000;             // current position
unsigned int           targetPosition = 1000;              // target position

// Initialise the temp sensor
OneWire oneWire(ONE_WIRE_BUS);         // Setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire);   // Pass our oneWire reference to Dallas Temperature.
  
// lookup table to drive motor control board                                
const int stepPattern[8] = {B01000, B01100, B00100, B00110, B00010, B00011, B00001, B01001};

// For ASCOM connection
String inputString = "";         // string to hold incoming data
boolean stringComplete = false;  // whether the string is complete
boolean isMoving = false;        // is the motor currently moving

//------------------------------------------------------------------
// Move stepper anticlockwise 
//------------------------------------------------------------------
void anticlockwise()
{
  for(int i = 0; i < 8; i++)
  {
        setOutput(i);
        delayMicroseconds(motorSpeed);
  }
  clearOutput();
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Move stepper clockwise 
//------------------------------------------------------------------

void clockwise()
{
  for(int i = 7; i >= 0; i--)
  {
        setOutput(i);
        delayMicroseconds(motorSpeed);
  }
  clearOutput();
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Set output pins for stepper
//------------------------------------------------------------------
void setOutput(int out)
{
  for (int i=0; i<4; i++) {
    digitalWrite(motorPins[i], bitRead(stepPattern[out], i));
  }
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Clear output pins for stepper
// To ensure they are not left in an on state after movement
//------------------------------------------------------------------
void clearOutput()
{
  for (int i=0; i<4; i++) {
    digitalWrite(motorPins[i], 0);
  }
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Get Temperature 
//------------------------------------------------------------------
double getTemperature()
{
  sensors.requestTemperatures();                    // Get temperatures
  double tempC = sensors.getTempC(tempSensor);      // Get Temperature from our (single) Sensor
  
  if (tempC == -127.00) {
    // error getting temperature, don't change current temperature
  }
  else {
    currentTemperature = tempC;
  }
  return currentTemperature;
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Save position in EEPROM - split into 2 byte values. Also sets position valid
//------------------------------------------------------------------
void savePosition(unsigned int p)
{
  byte lowByte = ((p >> 0) & 0xFF);
  byte highByte = ((p >> 8) & 0xFF);
  
  EEPROM.write(EE_LOC_POS, lowByte);
  EEPROM.write(EE_LOC_POS + 1, highByte);
  
  EEPROM.write(EE_LOC_PSTAT, POS_VALID);   // stored position is valid
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Restore position from EEPROM
//------------------------------------------------------------------
unsigned int restorePosition(void)
{
  byte lowByte = EEPROM.read(EE_LOC_POS);
  byte highByte = EEPROM.read(EE_LOC_POS + 1);
  
  return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// Check if stored position in EEPROM is valid
//------------------------------------------------------------------
boolean storedPositionValid(void)
{
  byte status = EEPROM.read(EE_LOC_PSTAT);
  if (status == POS_VALID)
    return true;
  else
    return false;
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// ASCOM Serial Commands
//------------------------------------------------------------------
void serialCommand(String command) {

  switch(command.charAt(0)) {
  case '#':  // Confirm Connection
    Serial.print("OK!#");
    break;
  case 'T': // Set Target Position
    {
      int hashpos = command.indexOf('#');    // position of hash in string
      String targetPosS = command.substring(1,hashpos);
      unsigned int targetPosI = targetPosS.toInt();
      targetPosition = targetPosI;
      Serial.print("T" + targetPosS + ":OK#");
      break;
    }
  case 'C': // Get Temperature
    {
      double t;
      
      // if moving block temperature requests as they interfere with movement. Just return last reading.
      if (isMoving)
      {
        t = currentTemperature;
      }
      else
      {
        t = getTemperature();     
      }
      
      Serial.print("C");
      Serial.print(t * 100, 0);
      Serial.print(":OK#");
      break;
    }
  case 'I': // Set Initial Position. Sets Position without any movement
    {
      int hashpos = command.indexOf('#');    // position of hash in string
      String initPosS = command.substring(1,hashpos);
      unsigned int initPosI = initPosS.toInt();
      currentPosition = initPosI;
      targetPosition = initPosI;
      Serial.print("I" + initPosS + ":OK#");
      break;
    }
  case 'P': // Get Current Position
    {
      String currentPositionS = String(currentPosition);
      Serial.print("P" + currentPositionS + ":OK#");
      break;
    }
  case 'H': // Halt
    {
      targetPosition = currentPosition;
      String currentPositionS = String(currentPosition);
      Serial.print("H" + currentPositionS + ":OK#");
      break;
    }
  case 'M': // Is motor moving
    {
      if (isMoving) {
        Serial.print("M1:OK#");
      } else {
        Serial.print("M0:OK#");
      }
      break;
    }
  case 'V': // Get Version and abilities
    {
      String tempInstalled = (tempSensorPresent ? " | Temp. Sensor |" : "");
      String posValid = (storedPositionValid() ? " | Stored Position |" : "");
      
      Serial.print( programName + " V" + programVersion + tempInstalled + posValid + "#");
      break;
    }
  default:
    {
      motorSpeed = motorSpeedDefault;
      Serial.print("ERR#");
      break;
    }
  }
}
//------------------------------------------------------------------


//------------------------------------------------------------------
// Setup
//------------------------------------------------------------------
void setup() 
{
  // Declare the stepper motor pins as outputs
  for (int i=0; i<4; i++) {
    pinMode(motorPins[i], OUTPUT);
  }
  
  clearOutput();
      
  // initialize serial for ASCOM
  Serial.begin(9600);
  // reserve 200 bytes for the ASCOM driver inputString:
  inputString.reserve(200);

  //EEPROM.write(EE_LOC_PSTAT, 0); // FOR TESTING - invalidate stored position
  
  // Use position from EEPROM if it is valid, otherwise use default
  if (storedPositionValid())
  {
    currentPosition = restorePosition();
    targetPosition = currentPosition;
  }
  else
  {
    currentPosition = 1000;
    targetPosition = currentPosition;
  }
  
  // OneWire Libary setup
  oneWire.reset_search();                    // Reset search
  oneWire.search(tempSensor);                // Search for temp sensor and assign address to tempSensor

  // DallasTemperature Library setup
  sensors.begin();                           // Initialise 1-wire bus
  
  // Check if the temperature sensor is installed
  if (sensors.getDeviceCount() == 0)
  {
    tempSensorPresent = false;
  }
  else
  {
    // temperature sensor installed - set it up and get initial value
    tempSensorPresent = true;
    sensors.setResolution(tempSensor, 10);     // Set the resolution to 12 bit (9bit=0.50C; 10bit=0.25C; 11bit=0.125C; 12bit=0.0625C)
    sensors.requestTemperatures();             // Get the Temperatures
    currentTemperature = getTemperature();
  }
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Main Loop
//------------------------------------------------------------------
void loop()
{
  // process the command string when a hash arrives:
  if (stringComplete) {
    serialCommand(inputString); 
    
    // clear the command string:
    inputString = "";
    stringComplete = false;
  }
  
  // Move the position by a single step if target is different to current position
  if (targetPosition != currentPosition) {
    isMoving = true;
    
    // Adjust speed according to distance yet to travel
    int distance = currentPosition - targetPosition;
    if (abs(distance) > speedThreshold) {
      motorSpeed = motorSpeedHi;
    } else {
      motorSpeed = motorSpeedLo;
    }
    
    // Going Anticlockwise to lower position
    if (targetPosition < currentPosition) {
      anticlockwise();
      currentPosition--;
    }
    
    // Going Clockwise to higher position
    if (targetPosition > currentPosition) {
      clockwise();
      currentPosition++;
    }
       
    // save new position in EEPROM
    savePosition(currentPosition);

  } else {
    isMoving = false;
  }

}
//------------------------------------------------------------------

//------------------------------------------------------------------
// SerialEvent occurs whenever new data comes in the serial RX. 
//------------------------------------------------------------------
void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read(); 
    // add it to the inputString:
    inputString += inChar;
    // if incoming character is hash, set flag so main loop can action it
    if (inChar == '#') {
      stringComplete = true;
    } 
  }
}
//------------------------------------------------------------------

Why do you need to change it ? Isn't it the same number of windings (4) ? If you are not using a unipolar
motor then you don't connect the two wires that normally go to the power supply.

Why would you need to change the code ?

USING A UNIPOLAR MOTOR AS BIPOLAR

Yes, I am using only 4 wires. I have managed to get the bipolar motor working, but using another example code. I have changed the driver (previously I was using ULN2003 with the unipolar stepper) and now I am using L293D. Still, only 4 wires, no separate wire for the power supply, you are right.

The people who wrote the above code told me that I needed to change the code if I wanted to use a bipolar stepper motor. But, I am not sure which part should be changed.

I tried running the motor with this code, and it is not working.

It's going to take me some time to look at the code but think about it.

A unipolar stepper motor has one winding with center tap per phase. Each section of windings is switched on for each direction of magnetic field. Since in this arrangement a magnetic pole can be reversed without switching the direction of current, the commutation circuit can be made very simple (e.g., a single transistor) for each winding. Typically, given a phase, the center tap of each winding is made common: giving three leads per phase and six leads for a typical two phase motor. Often, these two phase commons are internally joined, so the motor has only five leads.

A micro controller or stepper motor controller can be used to activate the drive transistors in the right order, and this ease of operation makes unipolar motors popular with hobbyists; they are probably the cheapest way to get precise angular movements.

Unipolar stepper motor coils
(For the experimenter, the windings can be identified by touching the terminal wires together in PM motors. If the terminals of a coil are connected, the shaft becomes harder to turn. one way to distinguish the center tap (common wire) from a coil-end wire is by measuring the resistance. Resistance between common wire and coil-end wire is always half of what it is between coil-end and coil-end wires. This is because there is twice the length of coil between the ends and only half from center (common wire) to the end.) A quick way to determine if the stepper motor is working is to short circuit every two pairs and try turning the shaft, whenever a higher than normal resistance is felt, it indicates that the circuit to the particular winding is closed and that the phase is working.

Bipolar motor[edit]
Bipolar motors have a single winding per phase. The current in a winding needs to be reversed in order to reverse a magnetic pole, so the driving circuit must be more complicated, typically with an H-bridge arrangement (however there are several off-the-shelf driver chips available to make this a simple affair). There are two leads per phase, none are common.

Static friction effects using an H-bridge have been observed with certain drive topologies.[2]

In short, what this means is that you cannot use UNIPOLAR code for a bipolar motor because the bipolar
motor requires the polarity reversal, hence the H-BRIDGE. You need to substitute BIPOLAR code for the
unipolar code. (unless of course you are have enough S/W experience to "modify" it. "Modify" is a misleading word because it could be dirt simple or non-trivial. If you looked at the code close enough
you would have noticed it was written for an ASCOM driver and is looking for an "input string" . If you
DON'T have an ASCOM driver , you have no business using this code. Trash it and start from scratch and
look for or ask for BIPOLAR code and stop wasting time trying to drive a VW with FERRARI timing .
(crude analogy I admit).

I do understand the theory and the difference. I am using several apps on my PC which control the stepper motor via the nano and ASCOM driver.

I have to maintain the ASCOM function within the code in order for these apps to keep working.

I thought it would be easier to modify the code than to write it all over again.

So ASCOM is the astronomy software and not a motor controller ?

I would recommend using a specialized stepper motor driver for a bipolar stepper (for example a Pololu A4988) and I wrote stepper motor basics with that in mind.

But if you insist on using a h-bridge driver the simplest way is to use one of the stepper libraries such as AccelStepper. It takes care of the complexities of which coil needs to be energized and when.

...R

raschemmel:
So ASCOM is the astronomy software and not a motor controller ?

Yes, ASCOM is an astronomy tools platform, which includes communication drivers for mounts, CCD cameras, focusers... It uses its own communication protocol. The ASCOM part is not a problem. The problem is my lack of programming knowledge :slight_smile:

I will have a look at the AccelStepper library for start. Any additional help with this is more than welcome :smiley:

This is what you need to change:

 void setOutput(int out)
{
  for (int i=0; i<4; i++) {
    digitalWrite(motorPins[i], bitRead(stepPattern[out], i));
  }
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Clear output pins for stepper
// To ensure they are not left in an on state after movement
//------------------------------------------------------------------
void clearOutput()
{
  for (int i=0; i<4; i++) {
    digitalWrite(motorPins[i], 0);
  }

You need to learn how to drive your stepper motor without all of the other code in your program.
You have taken a program that has features you need but you have put the cart before the horse.
BEFORE you run a program with a bunch of other features, you need to set ALL of that aside and focus on one task and one task only, driving a stepper motor. You need to learn the basics of driving a stepper motor with an arduino, which is not difficult. We can't help if you keep looking at your other program. If you agree to learn the basics, we can help you.

If you look at the Stepper Library examples , one of them has this:

 #include <Stepper.h>

const int stepsPerRevolution = 200;  // change this to fit the number of steps per revolution
                                     // for your motor

// initialize the stepper library on pins 8 through 11:
Stepper myStepper(stepsPerRevolution, 8,9,10,11);            

int stepCount = 0;         // number of steps the motor has taken

and another example has this:

#include <Stepper.h>

const int stepsPerRevolution = 200;  // change this to fit the number of steps per revolution
// for your motor


// initialize the stepper library on pins 8 through 11:
Stepper myStepper(stepsPerRevolution, 8,9,10,11);            

int stepCount = 0;  // number of steps the motor has taken

void setup() {
  // nothing to do inside the setup
}

void loop() {
  // read the sensor value:
  int sensorReading = analogRead(A0);
  // map it to a range from 0 to 100:
  int motorSpeed = map(sensorReading, 0, 1023, 0, 100);
  // set the motor speed:
  if (motorSpeed > 0) {
    myStepper.setSpeed(motorSpeed);
    // step 1/100 of a revolution:
    myStepper.step(stepsPerRevolution/100);
  } 
}

SW is not my area of expertise, but I would suggest you start learning by looking at Andrew Stone's L298 STEPPER DRIVER.

and compare his code to THIS snippet from YOUR code:

 You should start with these:

[code]  const int     motorPins[4] = {7,8,9,10};       // Declare pins to drive motor control board
const int     motorSpeedLo = 16000;            // Motor step delay for Lo speed (uS)
const int     motorSpeedHi = 2000;             // Motor step delay for Hi speed (uS)
const int     motorSpeedDefault = 16000;       // Default motor step speed (uS)(failsafe operation)
const int     speedThreshold = 25;             // motor speed Hi if steps to go is higher than this
int           motorSpeed = motorSpeedDefault;  // cu

[/code]

Look at THESE functions in YOUR code:

 void anticlockwise()
{
  for(int i = 0; i < 8; i++)
  {
        setOutput(i);
        delayMicroseconds(motorSpeed);
  }
  clearOutput();
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Move stepper clockwise 
//------------------------------------------------------------------

void clockwise()
{
  for(int i = 7; i >= 0; i--)
  {
        setOutput(i);
        delayMicroseconds(motorSpeed);
  }
  clearOutput();
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Set output pins for stepper
//------------------------------------------------------------------
void setOutput(int out)
{
  for (int i=0; i<4; i++) {
    digitalWrite(motorPins[i], bitRead(stepPattern[out], i));
  }
}
//------------------------------------------------------------------

//------------------------------------------------------------------
// Clear output pins for stepper
// To ensure they are not left in an on state after movement
//------------------------------------------------------------------
void clearOutput()
{
  for (int i=0; i<4; i++) {
    digitalWrite(motorPins[i], 0);
  }
}

and see if you can find the similarity in the other examples.

Your objective at the moment should be nothing more than learning how to drive your stepper motor
"n" number of steps at "x" speed and then back to the same point.

I have managed to resolve this problem by simply changing the order of wires from L293D to the stepper motor. In that way, Arduino is sending step commands in the correct order for a bipolar motor to turn.