User Tools

Site Tools


projects:cnc:4th_axis

A manual controller for a spindle motor

This page describes hardware and software for controlling a stepper motor based lathe spindle manually, Such lathes are produced in China and can be commonly found on ebay.

The control is done using a potentiometer and a button. With the potentiometer the speed and direction can be set, while the button allows to rotate only a certain angle and then stops.

After powering up, it is required to either rotate the potentiometer to the center position (speed: 0) or press the button to rotate exactly 90 degrees. Afterwards the button can be pressed at any moment to rotate another 90 degrees angle further. While the stepper motor is in pause state, to commence moving at a certain speed, it is necessary to first move the potentiometer to the center position from which the direction and speed can than be set afterwards.

Project summary:

  • Code runs on an arduino (nano).
  • With a 4k7 potentiometer on A0 direction and speed can be set.
  • When using a button on D8 (to GND), the axis rotates 90 degrees in the direction it was rotating previously and stops afterwards, regardless of the potentiometer setting.
  • To start motor movement, either press the switch, or move the potentiometer to the center position, after which the motor will rotate continuously when moving the potentiometer any further.
  • If the indicator led is blinking fast, it will indicate that the potentiometer need to be positioned at the center first. This is to prevent any unwanted motor rotation.

The documentation of the stepper controller can be found here: hpsteppro_doku_2016_04.pdf

The wiring between the HP_Step.pro board and the Arduino nano controller is the following:

PIN FUNCTION DESCRIPTION ARDUINO PIN
CN4,1 RXD Unspecified 12
CN4,2 /Clock Clocksignal, rising edge causes step 9
CN4,3 /Error During low state 11
CN4,4 /CCW Rotation direction 4
CN4,5 /Off Power off stepper drivers 5
CN4,6 /Sleep Reduces motor current to 25% from nominal during low state 6
CN4,7 + CN4,8 +5v This pin is not connected but rewired to the internal 5v supply of the stepper controller and used to power the Arduino nano standalone +5v
CN4,9 + CN4,10 GND Ground GND
CN7.5 /Reset Reset 13

Arduino software using the FastAccelStepper library

//----------------------------------
// https://github.com/gin66/FastAccelStepper/blob/master/src/FastAccelStepper.h
// https://github.com/gin66/FastAccelStepper
//
// used with an arduino nano, atmega328p
 
 
#include "FastAccelStepper.h"
#include <stdint.h>
 
#define MAXSPEED 12000 // tested with stepper->setSpeedInHz(spd);
#define MINVAL 0 // smallest value from AD input
#define MAXVAL 1023 // largest value from AD input
#define LIMIT 2 // exclude zone for potentiometer at extreme angles
#define THRHLD 3 // hysteresis for potentiometer change
#define NUM_SAMPLES 10 // number of samples to use from potentiometer
 
 
// pins numbers
const uint8_t rst_pin = 13; // rst pin for HP-Step.pro
const uint8_t rxd_pin = 12; // rx pin for some kind of tty communication
const uint8_t clk_pin =  9; // Step signal avr atmega328p: only Pin 9 and Pin 10.possible..
const uint8_t err_pin = 11; // error pin (active low)
const uint8_t ccw_pin =  4; // rotation direction
const uint8_t off_pin =  5; // switches output drivers off (active low)
const uint8_t slp_pin =  6; // reduces stepper current to 25% (active low)
 
 
const uint8_t led_pin =  7; // signal led to indicate angular mode connected between D7 and GND
 
const uint8_t adj_pin = A0; //Analog pin with 4k7 lin. potentiometer
const uint8_t rot_pin =  8; // connect a button (switch to ground) to rotate (90) degrees
 
// stepper definitions
const unsigned int stp360 = 200; // number of steps for stepper motor to rotate 360 degrees
 
// connected geometry (if stepper has a gear with another gear)
const uint8_t lg = 60; // number of teeth of large gear
const uint8_t sg = 10; // number of teeth of small gear
 
// storage for potentiometer samples, rotation direction, blink time
static uint16_t pos[NUM_SAMPLES];
static uint8_t n; // samples array index
static uint64_t prevMillis = 0;        // will store last time LED was updated
enum patterns {LED_OFF, BLINK_SLOW, BLINK_FAST, LED_ON};
static enum patterns led_ptrn = BLINK_FAST;
 
// motor control
enum directions {BACKWARD = -1, STOP = 0, FORWARD = 1};
enum rotation_modes {CONTINUOUS, ANGULAR};
static enum directions stp_direction;
static enum rotation_modes rotation_mode;
 
FastAccelStepperEngine Engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;
 
void setup()
{
  pinMode(rst_pin,OUTPUT);
  digitalWrite(rst_pin,LOW);
  delay(1000);
  digitalWrite(rst_pin,HIGH);
 
  pinMode(led_pin,OUTPUT);
  digitalWrite(led_pin,LOW);
 
  delay(6000); // allow stepper controller to fully initialize
 
  pinMode(slp_pin,OUTPUT);
  digitalWrite(slp_pin,LOW); //Use full power for the stepper motor
  pinMode(off_pin,OUTPUT);
  digitalWrite(off_pin,LOW);
  pinMode(rot_pin,INPUT_PULLUP);
 
  Engine.init();
  stepper = Engine.stepperConnectToPin(clk_pin);
  stepper->setDirectionPin(ccw_pin, false); // to invert direction, use argument "ccw_pin, false"
  stp_direction = FORWARD; // default movement for stepper
  stepper->setEnablePin(off_pin, false); // false means: active low
  stepper->setAutoEnable(true);
  stepper->setAcceleration(10000);     // Set the new acceleration value     
  stepper->stopMove();
  rotation_mode = ANGULAR; // require potentiometer to start from the center.
 
 
  /* fill array with initial values */
  init_array(analogRead(adj_pin));
}
 
void move_degrees(uint32_t angle) {
  stepper->stopMove();
  int32_t steps = 200 * 4 * (lg/sg) / (360/angle);
  int32_t final_pos = stepper->targetPos() + stp_direction * steps;
  stepper->setSpeedInHz(MAXSPEED);
  stepper->move(stp_direction * steps); // now move +/- 90 degrees
  while ( stepper->getCurrentSpeedInMilliHz()); // 0: motor is at final position
}
 
enum directions get_direction(uint16_t pos) {
  /**
   * Values between 525 and 1023 mean forward direction flag set
   */  
  if (pos > 525) {
    return FORWARD;
  } else if (pos < 499) {
    return BACKWARD;
  }
  return STOP;
}
 
uint16_t calc_speed(uint16_t pos) {
  /**
   * Values between 516 and 1023 mean forward direction flag set
   * Exclude STOP as value
   */
  if (get_direction(pos) != STOP) stp_direction = get_direction(pos);
 
  /**
   * Values between 562 and 1023 mean forward movement. Speed
   * is mapped from 0 to MAXSPEED with negative sign for forward movement
   */  
  if (pos > 562) {
    return map(pos, 563, 1023, 0, MAXSPEED);
  } else if (pos < 462) {
    return map(pos, 0, 461, MAXSPEED, 0);
  }
  return 0;
}
 
uint16_t capture_potentiometer(void) {
  pos[n++] = analogRead(adj_pin);
  if (n >= NUM_SAMPLES) n = 0;
  return calc_average();
}
 
uint16_t calc_average(void) {
  uint16_t sum = 0;
  for (uint8_t i = 0; i < NUM_SAMPLES; i++) {
   sum += pos[i];
  }
  return sum/NUM_SAMPLES;
}
 
void handle_limits(uint16_t *avg, uint16_t *avg_prev) {
  if ( *avg >= MAXVAL - LIMIT && *avg_prev != MAXVAL ) {
    *avg = MAXVAL; //  force update
    *avg_prev = MINVAL;
  } else if ( *avg <= MINVAL + LIMIT && *avg_prev != MINVAL ) {
    *avg = MINVAL; //  force update
    *avg_prev = MAXVAL;
  }
}
 
bool is_spd_change_required(uint16_t *avg, uint16_t *avg_prev) {
  /* Use threshold to prevent too many successive speed changes */
  if (*avg == *avg_prev) return false;
  if (*avg > *avg_prev) {
    if (*avg - *avg_prev > THRHLD) return true;
    return false;
  }
 
  if (*avg_prev - *avg > THRHLD) return true;
  return false;
}
 
void set_speed(uint16_t spd) {
  if (!spd) {
    /* stop movement */
    stepper->stopMove();
    digitalWrite(slp_pin,LOW); // Use less power for the stepper motor
  } else {
    /* rotate with speed */
    digitalWrite(slp_pin,HIGH); // Use full power for the stepper motor
    stepper->setSpeedInHz(spd);
    set_direction();
  }
}
 
void init_array(uint16_t pv) {
  for (uint8_t i = 0; i < NUM_SAMPLES; i++) {
    pos[i] = pv;
  }
}
 
void set_direction(void) {
  if (stp_direction == FORWARD) {
    stepper->runForward();
  } else {
    stepper->runBackward();
  }
}
 
void prevent_movement(uint16_t *avg, uint16_t *avg_prev) {
  *avg = calc_average();
  *avg_prev = *avg;
}
 
void handle_angular_rotation(uint16_t angle, uint16_t *avg, uint16_t *avg_prev) {
  led_ptrn = LED_ON;
  handle_led_ptrn();
 
  *avg_prev = *avg;
  uint16_t pos = analogRead(adj_pin);
  if (get_direction(pos) != STOP) stp_direction = get_direction(pos);
  move_degrees(90); // use direction from last movement
  rotation_mode = ANGULAR;
 
  while (!digitalRead(rot_pin)); // wait for button release, if still pressed
}
/**
 * After angular rotation it is required to operator
 * the potentiometer to go back to center to prevent
 * unexpected movement
 */
bool is_potentiometer_at_center(void) {
  return get_direction(analogRead(adj_pin)) == STOP;
}
 
void handle_led_ptrn(void)
{
  uint64_t currMillis = millis();
  uint16_t blink_interval;
 
  if (led_ptrn == LED_OFF || led_ptrn == LED_ON) {
    digitalWrite(led_pin, led_ptrn == LED_OFF ? LOW : HIGH);
    return;
  }
 
  switch (led_ptrn) {
    case BLINK_SLOW:
      blink_interval = 1000;
      break;
    case BLINK_FAST:
      blink_interval = 100;  
      break;
  }
 
  if (currMillis - prevMillis >= blink_interval) {
    prevMillis = currMillis; // save last time from LED blink
    digitalWrite(led_pin, !digitalRead(led_pin));
  }
}
 
//----------------------------------
//
//  Main loop
//
//
void loop() {
  static uint8_t n;
  static uint16_t avg = calc_average();
  static uint16_t avg_prev = avg;
 
  handle_led_ptrn(); // handle led blink patterns, based on static enum led_ptrn
  avg = capture_potentiometer(); // capture sample, add to array, return array average
  handle_limits(&avg, &avg_prev);
 
  if (rotation_mode == ANGULAR) {
    if (is_potentiometer_at_center()) rotation_mode = CONTINUOUS;
    else if (is_spd_change_required(&avg, &avg_prev)) led_ptrn = BLINK_FAST;
  }
 
  /* rotate 90 degrees if button pressed */
  if (!digitalRead(rot_pin)) handle_angular_rotation(90, &avg, &avg_prev);
 
  /* handle continuous rotation */
  if (rotation_mode == CONTINUOUS) {
    led_ptrn = is_potentiometer_at_center() ? LED_ON : LED_OFF;
    if (is_spd_change_required(&avg, &avg_prev)) {
      avg_prev = avg;
      set_speed(calc_speed(avg_prev));
    }
  }
}
projects/cnc/4th_axis.txt · Last modified: 2022/01/21 01:11 by admin