daniel23
Published © LGPL

Threading on lathe: forward and backward automatic stop

An Arduino drives a DC motor and counts the number of spindle turns forward or reverse to stop the rotation very precisely.

AdvancedFull instructions provided15 hours323
Threading on lathe: forward and backward automatic stop

Things used in this project

Hardware components

9V-30V 60A PWM DC Motor Driver Module Dual-Channel
×1
Arduino UNO
Arduino UNO
Any Atmega328p based arduino can be used eg. Nano or Pro Mini Presumably arduinos based on other processors would work as well, but I din't test.
×1
Alphanumeric LCD, 20 x 4
Alphanumeric LCD, 20 x 4
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
10 kOhms Linear panel mount
×1
Panel mount toggle switch, (On)-None-(On) - (momentary)
×1
Pushbutton Switch, Pushbutton
Pushbutton Switch, Pushbutton
×2
OH137 or US5881
OH137 and US5881 are unipolar Hall-effect switch with trigger output in a flat TO-92 case
×2
Capacitors and resistors as shown in the diagram
×1
24V power supply to fit your need
CAUTION: The motor has one pole connected to ground. The connection between ground and earth must be cut inside the power supply. I use a 24V-10A chinese power supply. My motor only need 5A but it's a chinese power supply...
×1
Windshield wiper motor
Recovered from a scrap car
×1
Linear Regulator (7805)
Linear Regulator (7805)
×1
S14K20
Used for surge protection. S10K20 could be OK. I took what I had on hand. TVS diodes such as SMBJ24CA (bidirectional) or two SMBJ24A (unidirectional) mounted head to tail could also do the trick.
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Schematic

Code

LatheCounter

Arduino
/*  Outputs wiring:
    Motor driver:
        pin D10 => PWM
        pin D11 => Forward signal
        pin D12 => Reverse signal
        Use instruction:    FWD pin   REV pin
                              1         0         : Forward
                              0         0         : Brake
                              0         1         : Reverse
                          -----------------------------------
                              1         1         : FORBIDDEN
     LCD I2C:
        pin A4 => SDA
        pin A5 => SCL

// For the motor driver a dead time is required to allow the bootstrap capacitors to recharge
// The maximum duty cycle specified is 98%, but my tests showed a maximum of about 92%

=============================================================================

How it should work:

If FWD or REV are pressed (and then released) :
      The motor continues in the desired direction until the memorized position
      and then stops.

If FWD or REV are held down:
      The motor goes beyond the memorized position.
      It stops when the button is released
     
If STOP button is pressed:
			The motor stops immediately

If pressing the MEM and STOP buttons together:
      According to the last direction before the stop:
       if FWD => the counter value is stored as forward limit
       if REV => the counter value is stored as backward limit

=============================================================================


*/
// #include <avr/pgmspace.h>
#include <LiquidCrystal_I2C.h>   // Frank De Brabander V1.12
#include "EncoderStepCounter.h"  // Manuel Reimer V1.10
#include <TimerOne.h>            // Stoyko Dimitrov, Jesse Tane, Jrme Despatis, V1.1.1

#define ENCODER_PIN1 2
#define ENCODER_INT1 digitalPinToInterrupt(ENCODER_PIN1)
#define ENCODER_PIN2 3
#define ENCODER_INT2 digitalPinToInterrupt(ENCODER_PIN2)
#define SPEEDPOT 0  // analog input for the max speed setpoint potentiometer
#define STOREBUTTON 5
#define STOPBUTTON 6
#define REVERSEBUTTON 7
#define FORWARDBUTTON 8

#define I2C_LCD 0x27
#define LCD_LINE_LENGTH 0x20
#define LCD_LINE_NUMBER 0x4

#define PWMPIN 10      // 15KHz PWM output pin for the motor driver
#define FORWARDPIN 11  // only one pin of Forward or Reverse can be high at time
#define REVERSEPIN 12  // both pins high will destroy the driver !!!
#define STARTDUTY 8    // % of clock ticks to start motor rotation (10bit resolution)
#define MAXDUTY 92     // max % to allow the bootstrap capacitors to recharge
//                     //
#define ACCELERATION 1        // increment for the accelleration ramp
#define ACCELERATION_TIME 13  // ms between each PWM increase loop (13 => ~5s for start-to-max PWM)
#define RPMTIMEOUT 5000       // (ms) duration where speed is considered zero
//                               for 5000ms the rpm is displayed "0" for speeds < 12rpm
#define BRAKETIME 2000       // give the motor time to stop
#define PotReadInterval 200  // Time interval to read the speed potentiometer


enum {
  Stop = 0,      // This values are used in "rotationDirection" and "previousRotDir"
  Forward = 1,   //
  Reverse = -1,  //
};

char rotationDir = Stop;        // see the values meanings above in the enum{}
char previousRotDir = Stop;     //
signed long position = 0;       // This is the real counter position
signed long reversePosMem = 0;  // memorized reverse stop position
signed long forwardPosMem = 0;  // memorized reverse stop position
long startValue = ((long)STARTDUTY * 1023) / 100;
long maxValue = ((long)MAXDUTY * 1023) / 100;

volatile byte flagRpm = 0;

// Create instance for one full step encoder
EncoderStepCounter encoder(ENCODER_PIN1, ENCODER_PIN2);
// Use the following for half step encoders:
// EncoderStepCounter encoder(ENCODER_PIN1, ENCODER_PIN2, HALF_STEP);

const char* initMessages[] = {  // Greeting on the LCD display
  "Up/down counter for",
  "  Lathe threading",
  "  with quadrature",
  "Hall sensors - V2.0"
};

LiquidCrystal_I2C lcd(I2C_LCD, LCD_LINE_LENGTH, LCD_LINE_NUMBER);

/*============================================================*/

void setup() {
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();
  lcd.clear();

  for (char i = 0; i < 4; i++) {
    lcd.setCursor(0, i);
    lcd.print(initMessages[i]);
  }

  encoder.begin();
  attachInterrupt(ENCODER_INT1, interrupt, CHANGE);
  attachInterrupt(ENCODER_INT2, interrupt, CHANGE);

  Timer1.initialize(66);  // eg: 40 => 25 kHz; 66 => 15kHz; 400; = 2.5 kHz
  //     You will need to choose a value that best suits your DC Motor.
  pinMode(PWMPIN, OUTPUT);
  pinMode(FORWARDPIN, OUTPUT);
  digitalWrite(FORWARDPIN, LOW);
  digitalWrite(REVERSEPIN, LOW);
  pinMode(REVERSEPIN, OUTPUT);
  //pinMode(SpeedPot, INPUT);
  pinMode(FORWARDBUTTON, INPUT_PULLUP);
  pinMode(REVERSEBUTTON, INPUT_PULLUP);
  pinMode(STOPBUTTON, INPUT_PULLUP);
  Serial.println("TEST automate pour FILETAGE au tour");

  delay(3000);  // time to read the initMessage
  lcd.clear();
  lcd.setCursor(0, 0);
  displayPosition(0);
  displayFwdPosMem(0);
  displayRevPosMem(0);
}
/*------------------------------------------------------------*/
// Call tick on every change interrupt
void interrupt() {
  encoder.tick();
  flagRpm++;
}
/*------------------------------------------------------------*/
void loop() {
  updatePosition();
  checkButton();
  setPWM();
  Rpm();
}

/*------------------------------------------------------------*/

void checkButton() {
  // if ((digitalRead(STOREBUTTON) == LOW) && (digitalRead(STOPBUTTON) == LOW)) { // alternative to the next line
  if ((digitalRead(STOREBUTTON) == LOW) && (digitalRead(STOPBUTTON) == LOW)) {
    // store the counter in proper variables according "previousRotDir"
    if (previousRotDir == Forward) {
      forwardPosMem = position;
      displayFwdPosMem(forwardPosMem);
    }
    if (previousRotDir == Reverse) {
      reversePosMem = position;
      displayRevPosMem(reversePosMem);
    }
  }

  /* ????????????????????????????????????????????????????????????????
  *  One problem remains below : 
  *  If outside the limits the motor is forced to rotate with 
  *  the button, a reversal occurs without braking pause
  */
  if ((digitalRead(FORWARDBUTTON) == LOW) && (rotationDir == Reverse)) {
    rotationDir = Stop;                           //
    setPWM();                                     // Emergency Brake
    while (digitalRead(FORWARDBUTTON) == LOW) {}  // loop until button is released
    delay(BRAKETIME);
  }
  if ((digitalRead(REVERSEBUTTON) == LOW) && (rotationDir == Forward)) {
    rotationDir = Stop;  //
    setPWM();            // Emergency Brake
    while (digitalRead(REVERSEBUTTON) == LOW) {
    }  // loop until button is released
    delay(BRAKETIME);
  }
  // ----------------------------------------------------------------

  //if ((digitalRead(FORWARDBUTTON) == LOW) && ((rotationDir == Stop) || (previousRotDir == Forward))) {

  if (digitalRead(FORWARDBUTTON) == LOW) {
    rotationDir = Forward;
    previousRotDir = rotationDir;
  } else if (digitalRead(REVERSEBUTTON) == LOW) {
    rotationDir = Reverse;
    previousRotDir = rotationDir;
  }
  if (digitalRead(STOPBUTTON) == LOW) {
    rotationDir = Stop;
  }
}
// ????????????????????????????????????????????????????????????????

void updatePosition() {
  signed char pos = encoder.getPosition();
  if (pos != 0) {
    position += pos;
    encoder.reset();
    // The sensor emits 4 ticks per revolution. Each tick gives a quarter revolution
    float turns = float(position) / 4;  // the display resolution is 0.25 turns
    displayPosition(turns);
  }
}

void Rpm() {
  /* This gives an approximate value that works best at low speeds
     precision is not required (and can be hit or miss...)
     It should work fine for speeds up to 200 rpm
  
     -Sometimes a wrong value may appear briefly-
  */
  unsigned int temp = 0;
  static unsigned long int startRpm;
  unsigned long int currentTime = millis();

  if (flagRpm == 4) {
    temp = 15000 / (currentTime - startRpm);
    displayRpm(temp);
    startRpm = currentTime;
    flagRpm = 0;
  }
  if (flagRpm > 4) {  // Overflow: start a new measure
    startRpm = currentTime;
    flagRpm = 0;
  }
  if ((currentTime - startRpm) > RPMTIMEOUT) {
    displayRpm(0);
    startRpm = currentTime;
    flagRpm = 0;
  }
}

void displayRpm(unsigned int rpm) {
  char buff[9];
  static unsigned long int previousTime = 0;
  if (millis() - previousTime >= 500) {
    lcd.setCursor(1, 0);
    lcd.print("  Speed:");
    // lcd.print("V-Broche:");     // in french
    clearLCDLine(0, 10, 10);
    lcd.setCursor(10, 0);
    dtostrf(rpm, 4, 0, buff);  // NOTE: the lathe spindle has 4 magnets per revolution
    lcd.print(buff);
    lcd.print(" RPM  ");
    previousTime = millis();
  }
}

void displayPosition(float turns) {
  lcd.setCursor(1, 1);
  lcd.print(" Act.Pos:");
  // lcd.print("Position:");     // in french
  clearLCDLine(1, 10, 10);
  lcd.setCursor(11, 1);
  disp(turns);
}

void displayFwdPosMem(float forwardPosMem) {
  lcd.setCursor(1, 2);
  lcd.print("Stop FWD:");
  clearLCDLine(2, 10, 10);
  lcd.setCursor(11, 2);
  disp(float(forwardPosMem) / 4);
}

void displayRevPosMem(float reversePosMem) {
  lcd.setCursor(1, 3);
  lcd.print("Stop REV:");
  clearLCDLine(3, 10, 10);
  lcd.setCursor(11, 3);
  disp(float(reversePosMem) / 4);
}

void disp(float f) {
  char buff[8];
  if (f < 0) {                // check if negative number
    lcd.print("-");           // print the negative sign
    dtostrf(-f, 6, 2, buff);  // print the negative number without the sign
  } else {
    dtostrf(f, 7, 2, buff);  // print the positive number, leaving the space for the sign blank
  }
  lcd.print(buff);
}

void clearLCDLine(int line, int start, int length) {
  lcd.setCursor(start, line);
  if (start + length > LCD_LINE_LENGTH) {
    length = LCD_LINE_LENGTH - start;
  }
  for (int n = start; n < length; n++)  // do not exceed line length while blanking
  {
    lcd.print(" ");
  }
}

static unsigned long lastTimePotRead = 0;
static unsigned int setpointPwm = 0;
void setPWM() {
  static unsigned int currentPwm = 0;
  unsigned long currentTimePotRead = millis();

  if (currentTimePotRead - lastTimePotRead > PotReadInterval) {  // Read potentiometer every 200ms
    setpointPwm = map(analogRead(SPEEDPOT), 0, 1023, startValue, maxValue);
    lastTimePotRead = currentTimePotRead;
  }

  switch (rotationDir) {
    case Stop:
      digitalWrite(FORWARDPIN, LOW);
      digitalWrite(REVERSEPIN, LOW);
      currentPwm = 0;
      break;

    case Forward:
      digitalWrite(FORWARDPIN, HIGH);
      digitalWrite(REVERSEPIN, LOW);
      if (position >= forwardPosMem) {
        rotationDir = Stop;
      }
      break;

    case Reverse:
      digitalWrite(FORWARDPIN, LOW);
      digitalWrite(REVERSEPIN, HIGH);
      if (position <= reversePosMem) {
        rotationDir = Stop;
      }
      break;
  }

  // Serial.println(currentPwm);
  Timer1.pwm(PWMPIN, currentPwm);  // from 0 to 1023

  /* 
  This is the motor's acceleration ramp
  This formula is the best compromise I could find for my DC Motor 
  (smooth acceleration without activating the power supply current limitation)
  Formula and iteration and/or time can be changed if you have a better option
  */

  static unsigned long previousTime = 0;
  unsigned long currentTime = millis();
  if ((currentTime - previousTime) > ACCELERATION_TIME) {
    if (currentPwm == 0) currentPwm = startValue;
    previousTime = currentTime;
    currentPwm = currentPwm + ACCELERATION * (1 + (currentPwm / 80));
  }
  if (currentPwm > setpointPwm) {
    currentPwm = setpointPwm;
  }
}

Credits

daniel23
9 projects • 12 followers
Contact
Thanks to Frank De Brabander, Manuel Reimer, and Stoyko Dimitrov.

Comments

Please log in or sign up to comment.