Close

Test Run

A project log for A Better Turret

Need a turret but the not impressed by the servo type. Perhaps a stepper motor version may be better.

agpcooperagp.cooper 12/12/2017 at 06:320 Comments

Code

I wrote some code (using Bresenham's algorithm) for the turret:

/*
  Simple 3 Axis Motion Controller
  ===============================
  Written by Alan Cooper (agp.cooper@gmail.com)
  This work is licensed under the Creative Commons Attribution-NonCommercial 2.5 License.
  This means you are free to copy and share the code (but not to sell it).
*/

// Motor controller (CNC board) pin mapping: 
#define DirX     2
#define DirY     3
#define DirZ     4
#define StepX    5
#define StepY    6
#define StepZ    7
#define Enable   8  // Active low
#define Laser   12  // Turn laser on or off

// Motor Controller direction settings
bool xReverse=false;
bool yReverse=true;
bool zReverse=false;

// Movement control parameters
bool pause=false;                // Pause movement
long rampSteps;                  // start and stop ramp steps
long stepCountDown;              // Movement steps remaining
long stepCountUp;                // Movement steps completed
long movementRate;               // Steps per second
long movementInterval;           // Step period (uS)
long ramp;                       // Ramp up/down period
long xNew,yNew,zNew;             // New co-ordinates
long xCurrent,yCurrent,zCurrent; // Current co-ordinates

void motionControl(void)
{  
  static unsigned long movementPreviousMicros=0;
  static unsigned long movementCurrentMicros=0;
  static long stepX,stepY,stepZ;
  static long dx,dy,dz,ax,ay,az,sx,sy,sz,mx,my,mz;

  // Process motion at end of current movement
  if (stepCountDown==-1) {
    stepCountDown=0;
    stepCountUp=0;

    // Determine movement parameters
    dx=xNew-xCurrent;
    dy=yNew-yCurrent;
    dz=zNew-zCurrent;
    ax=abs(dx);
    ay=abs(dy);
    az=abs(dz);
    sx=xNew<xCurrent?-1:xNew>xCurrent?1:0;
    sy=yNew<yCurrent?-1:yNew>yCurrent?1:0;
    sz=zNew<zCurrent?-1:zNew>zCurrent?1:0;
    if ((ax>=ay)&&(ax>=az)) {
      mx=0;
      my=ay-(ax>>1);
      mz=az-(ax>>1);
      stepCountDown=ax;
    } else if ((ay>=ax)&&(ay>=az)) {
      mx=ax-(ay>>1);
      my=0;
      mz=az-(ay>>1);
      stepCountDown=ay;
    } else {
      mx=ax-(az>>1);
      my=ay-(az>>1);
      mz=0;
      stepCountDown=az;
    }

    // Set counters
    if (stepCountDown>0) {
      stepCountUp=1;
    } else {
      stepCountDown=-1;
      stepCountUp=-1;        
    }

    // Set the stepper directions
    if (xReverse) {
      digitalWrite(DirX,(1-sx)>>1);
    } else {
      digitalWrite(DirX,(sx+1)>>1);
    } 
    if (yReverse) {
      digitalWrite(DirY,(1-sy)>>1);
    } else {
      digitalWrite(DirY,(sy+1)>>1);
    }
    if (zReverse) {
      digitalWrite(DirZ,(1-sz)>>1);
    } else {
      digitalWrite(DirZ,(sz+1)>>1);
    }

  }

  // Advance Steppers 
  if ((pause==false)&&(stepCountDown>0)) {
    movementCurrentMicros=micros();
    if (movementCurrentMicros-movementPreviousMicros>ramp) {
      movementPreviousMicros=movementCurrentMicros;
      
      // Start/stop ramp
      if (stepCountUp<=rampSteps) ramp-=movementInterval;
      if (stepCountDown<=rampSteps) ramp+=movementInterval;
      
      // Advance steppers
      stepX=0;
      stepY=0;
      stepZ=0;
      if ((ax>=ay)&&(ax>=az)) {
        if (my>=0) {
          my-=ax;
          stepY=sy;
        }
        if (mz>=0) {
          mz-=ax;
          stepZ=sz;
        }
        my+=ay;
        mz+=az;
        stepX=sx;
      } else if ((ay>=ax)&&(ay>=az)) {
        if (mx>=0) {
          mx-=ay;
          stepX=sx;
        }
        if (mz>=0) {
          mz-=ay;
          stepZ=sz;
        }
        mx+=ax;
        mz+=az;
        stepY=sy;
      } else {
        if (mx>=0) {
          mx-=az;
          stepX=sx;
        }
        if (my>=0) {
          my-=az;
          stepY=sy;
        }
        mx+=ax;
        my+=ay;
        stepZ=sz;
      }
      xCurrent+=stepX;
      yCurrent+=stepY;
      zCurrent+=stepZ;

      // Step pulse (high for at least 10 us)
      digitalWrite(StepX,abs(stepX));
      digitalWrite(StepY,abs(stepY));
      digitalWrite(StepZ,abs(stepZ));
      delayMicroseconds(10);
      digitalWrite(StepX,LOW);
      digitalWrite(StepY,LOW);
      digitalWrite(StepZ,LOW);
      stepCountDown--;
      stepCountUp++;

      // Flag end of movement 
      if (stepCountDown==0) {
        stepCountDown=-1;
        stepCountUp=-1;
      }
      
    }
    
  } else {
    movementPreviousMicros=micros(); // Reset movement rate clock to ensure near full cycle on start
  }
  
}

void setup()
{
  // Initialise Motor Controller
  // Note: The motor controller is using 1/16 micro-stepping
  pinMode(DirX,OUTPUT);
  pinMode(StepX,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirZ,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode(Enable,OUTPUT);
  pinMode(Laser,OUTPUT);
  digitalWrite(DirX,LOW);
  digitalWrite(StepX,LOW);
  digitalWrite(DirY,LOW);
  digitalWrite(StepY,LOW);
  digitalWrite(DirZ,LOW);
  digitalWrite(StepZ,LOW);
  digitalWrite(Enable,LOW); // Active low
  digitalWrite(Laser,LOW);  // Laser off
  
  // Reset Global Variables
  pause=false;
  xNew=0;
  yNew=0;
  zNew=0;
  xCurrent=0;
  yCurrent=0;
  zCurrent=0;
  stepCountDown=-1;
  stepCountUp=-1;
  movementRate=3200;                     // Step per second
  movementInterval=1000000/movementRate; // Micro-seconds
  rampSteps=6;                           // Number for steps for ramp up/down
  
  // Precalculate ramp constant
  ramp=movementInterval*(rampSteps+1);
  
  // LED
  pinMode(LED_BUILTIN,OUTPUT);
  digitalWrite(LED_BUILTIN,LOW);
}

void loop()
{
  // Used to keep track of motion
  static int state=0;

  // LED timer
  static unsigned long intervalMillisLED=0;
  static unsigned long currentMillisLED=0;
  static unsigned long previousMillisLED=0;

  // Use LED to indicate working
  if (stepCountDown==0) {
    intervalMillisLED=1000;
  } else {
    intervalMillisLED=100;    
  }
  currentMillisLED=millis();
  if (currentMillisLED-previousMillisLED>intervalMillisLED) {
    previousMillisLED=currentMillisLED;   
    digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
  }

  /* Motion Command */
  // Line scanner (-100 to 100 x -100 to 100 )
  if (stepCountDown==-1) {
    if ((state&2)==0) {
      // Laser off
      digitalWrite(Laser,LOW);
      xNew=-100;
      yNew=-100+(state>>1);
      zNew=0;
    } else {
      // Laser on
      digitalWrite(Laser,HIGH);
      xNew=+100;
      yNew=-100+(state>>1);
      zNew=0;
    }
    state++;
    if (state>401) state=0;
  }

  /* Process Motion Command */
  motionControl();

}

The code scans left to right, top to bottom, 201 lines high by 201 pixels wide.

I set the stepper drive up for 0.5A per phase (because the 12v wall plug power supply is a bit under rated for this application) and 1/16 micro stepping.

The CNC board has a design fault in that the micro-steeping shunts are to ground rather than to Vcc, so it is alway set to full step regardless of the shunt setting. I had to jump the shunts pins to Vcc.

The code still needs fine tuning to peak the maximum step rate. It is currently running fine at 3200 PPS (60 RPM).

Assembled and Operating

Here is the turret assembled and operating:

And here is part of the trace on the wall about 5m away:

It is only about 50% of the trace due to the camera shutter speed.

Notice the wiggle! It is about a one micro step (i.e. 1/16 of a full step) high and is quite consistent, probably vibration. If you watch each scan line, some scan lines appear to not move at 1/16 micro stepping. Suggesting the turret accuracy is about 1/8 of a step or about 0.1 degrees. For the next iteration I will drop back to 1/8 micro stepping.

Magic

Discussions